tags:

views:

1134

answers:

4

I need to recover two Git branches that I somehow deleted during a push.

These two branches were created on a different system and then pushed to my "shared" (github) repository.

On my system, I (apparently) retrieved the branches during a fetch:

~/myfolder> git fetch
remote: Counting objects: 105, done.
remote: Compressing objects: 100% (58/58), done.
remote: Total 62 (delta 29), reused 0 (delta 0)
Unpacking objects: 100% (62/62), done.
From github.com:mygiturl
 * [new branch]      contact_page -> origin/contact_page
   731d1bb..e8b68cc  homepage   -> origin/homepage
 * [new branch]      new_pictures -> origin/new_pictures

Right after that I did a push to send my local changes up to the central repo. For some reason, these branches were deleted from both my local system and the central repo:

~/myfolder> git push
Counting objects: 71, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (43/43), done.
Writing objects: 100% (49/49), 4.99 KiB, done.
Total 49 (delta 33), reused 0 (delta 0)
To [email protected]:mygiturl.git
 - [deleted]         contact_page
 + e8b68cc...731d1bb homepage -> homepage (forced update)
   bb7e9f2..e0d061c  master -> master
 - [deleted]         new_pictures
   e38ac2e..bb7e9f2  origin/HEAD -> origin/HEAD
   731d1bb..e8b68cc  origin/homepage -> origin/homepage
   e38ac2e..bb7e9f2  origin/master -> origin/master
 * [new branch]      origin/contact_page -> origin/contact_page
 * [new branch]      origin/new_pictures -> origin/new_pictures

It's not terribly easy to get the branches off of their birthplace machine, so I'd like to try and recover them from my local if possible.

All of the git "undo" information I've googled has to with recovering lost commits. I don't think that applies here, since I don't have commit UIDs for these branches.

I'd like to know how I can get these back. I'd also like to know how they were deleted in the first place and how I can avoid this in the future.

EDIT: by request, here's my repo configuration

user.name=Craig Walker
[email protected]
alias.unadd=reset HEAD
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
[email protected]:MyGitURL.git
remote.origin.mirror=true
branch.master.remote=origin
branch.master.merge=refs/heads/master
alias.undo=reset --hard
alias.test=push -f ci HEAD:master
alias.st=status
alias.ci=commit
alias.br=branch
alias.co=checkout
alias.ch=checkout
alias.df=diff
alias.lg=log -p
alias.who=shortlog -s --
remote.ci.url=ContinuousIntegrationGitURL
remote.ci.fetch=+refs/heads/*:refs/remotes/ci/*
branch.photo.remote=origin
branch.photo.merge=refs/heads/photos
remote.foo.url=FooGitURL
remote.foo.fetch=+refs/heads/*:refs/remotes/cynthia/*
branch.homepage.remote=origin
branch.homepage.merge=refs/heads/homepage
+1  A: 

I'm not an expert. But you can try

git fsck --full --no-reflogs | grep commit

to find the HEAD commit of deleted branch and get them back.

Iamamac
I tried fsck earlier; do you know how to find out which commit is the correct one? I've got 20 to try.
Craig Walker
@Craig: You have to `git show` each commit.
Iamamac
This did it; once I had the commit messages, `git branch <uid>` got them back. Thanks!
Craig Walker
Good to hear. Be sure to also resolve the conflict between your `remotes.origin.mirror` and `remotes.origin.fetch` settings, otherwise you are bound to run into the problem again (or unintentionally clobber commits pushed from other repos).
Chris Johnsen
@Craig: Glad to be helpful :)
Iamamac
+2  A: 

Your deleted branches are not lost, they were copied into origin/contact_page and origin/new_pictures “remote tracking branches” by the fetch you showed (they were also pushed back out by the push you showed, but they were pushed into refs/remotes/origin/ instead of refs/heads/). Check git log origin/contact_page and git log origin/new_pictures to see if your local copies are “up to date” with whatever you think should be there. If any new commits were pushed onto those branches (from some other repo) between the fetch and push that you showed, you may have “lost” those (but probably you could probably find them in the other repo that most recently pushed those branches).

Fetch/Push Conflict

It looks like you are fetching in a normal, ‘remote mode’ (remote refs/heads/ are stored locally in refs/remotes/origin/), but pushing in ‘mirror mode’ (local refs/ are pushed onto remote refs/). Check your .git/config and reconcile the remote.origin.fetch and remote.origin.push settings.

Make a Backup

Before trying any changes, make a simple tar or zip archive or your whole local repo. That way, if you do not like what happens, you can try again from a restored repo.

Option A: Reconfigure as a Mirror

If you intend to use your remote repo as a mirror of your local one, do this:

git branch contact_page origin/contact_page &&
git branch new_pictures origin/new_pictures &&
git config remote.origin.fetch '+refs/*:refs/*' &&
git config --unset remote.origin.push &&
git config remote.origin.mirror true

You might also eventually want to do delete all your refs/remotes/origin/ refs, since they are not useful if you are operating in mirror mode (your normal branches take the place of the usual remote tracking branches).

Option B: Reconfigure as a Normal Remote

But since it seems that you are using this remote repo with multiple “work” repos, you probably do not want to use mirror mode. You might try this:

git config push.default tracking &&
git config --unset remote.origin.push
git config --unset remote.origin.mirror

Then, you will eventually want to delete the bogus refs/remotes/origin refs in your remote repo: git push origin :refs/remotes/origin/contact_page :refs/remotes/origin/new_pictures ….

Test Push

Try git push --dry-run to see what it git push would do without having it make any changes on the remote repo. If you do not like what it says it is going to do, recover from your backup (tar/zip) and try the other option.

Chris Johnsen
I don't think the remote tracking branches were kept, if they were copied at all. 'git branch -a' doesn't show them, and I can't find any files with those names in the .git dir either. Lastly, the "git log" commands you recommended return "fatal: ambiguous argument 'origin/contact_page': unknown revision or path not in the working tree" :-\ Thanks though.
Craig Walker
Well, those branches were there, your push log shows it. When looking for refs in the `.git` dir, be sure to check `.git/packed_refs` in addition to `.git/refs/`. `git show-ref` will dump out all your local refs (packed or ‘loose’). You should still be able to find the refs in the repo that originally pushed them to your GitHub repo (on a different machine? someone else's repo?). Failing that, as long as you have not done a gc or prune, you should be able to the `git fsck` output to examine the dangling commits and reattach them: `git branch contact_page-recovered <SHA-1-of-dangling-commit>`.
Chris Johnsen
packed_refs didn't have it either. The commits were definitely dangling; no idea how that happened. Thanks for your help though!
Craig Walker
+1  A: 

I think that you have a mismatched config for 'fetch' and 'push' so this has caused default fetch/push to not round trip properly. Fortunately you have fetched the branches that you subsequently deleted so you should be able to recreate them with an explicit push.

git push origin origin/contact_page:contact_page origin/new_pictures:new_pictures
Charles Bailey
As with my comment to @Chris Johnson, it appears that the branches no longer (never?) exist locally. When I `git push origin origin/contact_page:contact_page` I get this:`error: src refspec origin/contact_page does not match any`
Craig Walker
OK, I think I see what's happened, (although the full error would be helpful). push has updated the deleted branch and removed the ref locally as well as it's a tracking ref. What does `git rev-parse refs/remotes/origin/origin/contact_page` say ? Because of the bogus 'mirror' config, the branch my now be referenced here in the local repository.
Charles Bailey
Hi Charles; Since I wrote this I've munged (and fixed) my config so I can't get the (meaninful) rev-parse output any more. However, I don't think there was a double-nested "origin" directory in remotes.
Craig Walker
A: 

Sorry to post this here, but i can't make comments on comments yet..

This did it; once I had the commit messages, git branch <uid> got them back. Thanks! – Craig Walker Jan 3 at 5:23

Is it really git branch <uid> or git merge <uid>? I did solve my problem with merge, not with branch, but maybe i've solved the issue in a different way..

Gonçalo Queirós
I'm pretty sure it was `git branch <uid>` (though it was a long time ago). I wanted to "get the branch back", and so IIRC I created a branch based on the last commit of the deleted branch.
Craig Walker
I think that I/you could also solve the problem with your `git merge <uid>` technique. The trick with the merge is that (I think) you have to have something to merge into. For me, that would mean creating a new branch at the same ancestor that created the old (deleted) branch. If you didn't do this, then you would get a multiple-parent new branch, and that would mean that the branch content wouldn't be identical. This may be useful in some circumstances, but for what I wanted it wouldn't be desirable.
Craig Walker
Ok. Tanks Craig
Gonçalo Queirós