Question

I'm pretty new to Git and I think I created a mess in my master branch.

Here's what happened:

I was working (peacefully and without messing with anybody) and I got my commits like this.

a --> b --> C --> D

a, b being commits from other people, and C and D being my commits. I pushed all to our main repository and an automatic tool we have to run tests on every push said that the build was failing. So I panicked. I created a new directory and re-cloned the repository until commit b (when that automated tool was saying that the build was ok) made some very small changes and forced a push (yeah, not very clever of me, but it was late and I was freaking out) so at that point, the remote repository looked like this:

a --> b --> E

But it turns out that my initial commits C and D were correct, and it was the automated tool who failed. It was giving false positives!! When I realized, someone had commited again to the main branch (remote repository), so at this point it looks like this.

a --> b --> E --> f

What I want to do is safely and without destroying my coworker's work insert my good commits C and D into the branch. I still have those commits in a directory within my computer, but as soon as I do a git pull, they'll be destroyed.

I would like the remote repository to look something like:

a --> b --> C --> D --> f

or

a --> b --> f --> C --> D

or even

a --> b --> E --> f --> C --> D

(keeping the super-dummy commit E, I don't mind)

Basically, I want C and D there, without destroying my coworker's f commit.

Is this even possible to do?

I'm in a panic mode so anything would help.

Was it helpful?

Solution 2

You can get C and D back [edit: if you had "lost" them in your original repo, but I see you haven't]. Use git reflog to find them by ID:

$ git reflog

or:

$ git reflog master

(if you were on master and just want to see the changes that happened there), etc.

Once you find commit D, stick a branch label on it. Let's say that the output from the above makes it clear that HEAD@{3} is the one you want. Then:

$ git branch recover_my_stuff 'HEAD@{3}'

or:

$ git branch get_d_back 0123456

to do it by number (and with a different name and so on). You can run gitk HEAD@{3} and gitk 0123456 before this, or git log HEAD@{3}, etc., to double check whether that's the one you want. Once you have a branch label on it, git branch and gitk --all will show it to you again.


Edit: since you still have them in the original repo, let's work in there instead. First, your master currently has the a - b - C - D sequence. Let's rename this branch to temp, then use git fetch to get up to date with the remote repo, and create a new master that tracks origin/master:

$ git branch -m master temp
$ git fetch origin
$ git checkout -b master --track origin/master

(Here, temp—since it's just a rename of the original master—will "track" origin/master too, so git will tell you that it's different from origin/master, being both ahead and behind by several commits. But you don't need to care, you can ignore these notices from git. If you like, you can make it stop "tracking" by editing the git config, but it will all be harmless either way.)


OK, so, let's say you've got the above done, and gitk now shows (in a more pretty graphical form) this. I'll use the branch label temp for the recovered stuff, and assume the rest is all on master (well, you said it was :-) ):

a - b - E - f         <-- HEAD=master, origin/master
      \
        C - D         <-- temp

In general it's not nice to "rewind" branch labels on other people (it's OK to do it to yourself :-) ) so let's leave E in there. You can at any point git revert it to add a commit that simply un-does whatever E did. All you need to do now is graft copies of C and D on top of f. This is what git rebase does: it copies the changes in the original commits, to new commits. Then it moves the label.

$ git checkout temp
$ git rebase master

Now you'll have this:

a - b - E - f         <-- master, origin/master
     \       \
      \       C'-D'   <-- HEAD=temp
       \
        C - D          [no branch label]

(since C and D have no label, they will disappear from gitk --all. You can put another label back on them again, if you want to see them, but you don't need them anymore now that you have the copies.)

Now all you have to do is bring master forward, which you can do with git merge. First you have to git checkout master (there's a way to do this without the intermediate step but let's keep it simple):

$ git checkout master

a - b - E - f         <-- HEAD=master, origin/master
             \
              C'-D'   <-- temp

This is the same commit diagram, we just changed where HEAD points (to master) and dropped the invisible commits.

$ git merge temp

The merge op will "fast-forward" master, as the only thing needed to achieve the merge is to "slide the label down" from commit f to commit D':

a - b - E - f         <-- origin/master
             \
              C'-D'   <-- HEAD=master, temp

Now you can git branch -d temp to delete the temporary branch, and git push origin master to push.


Side note: congrats on drawing clear examples of what the commit graph looks like now, and what you want it to look like. That's about 80% of all the work needed to figure out which git commands to use. :-)

OTHER TIPS

Just do:

git pull --rebase

Note that a 'git pull' won't destroy your local commits, it will create a merge, which is probably not want you want.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top