0

I'm new to git and I made a boo boo. I started a new branch (poly_attr), checked it out, made some commits. While I was still in that branch I worked on a couple other things that should've gone to a different branch. So I committed everything that should be in poly_attr, then created and checked out a new branch (issue1), committed changes, and pushed both branches. Guess what I forgot to do? Check out the master before creating issue1. So my issue1 branch went off of poly_attr. Ok, easy fix, I think, check out master and rebase issue1 to master. Nope, now I have an issue1 branch off of poly_attr (remote), and one off master (local). How can I get the issue1 branch off of poly_attr?

1 Answer 1

2

To paraphrase, you started with this:

... - M5 - M6               <-- master, origin/master

That is, there were some series of commits (which I've numbered, Mn). You made a new branch name poly_attr pointing to the same commit as the tip of master and made more commits, which I'll label Pn:

... - M5 - M6                   <-- master, origin/master
             \
              P0 - P1           <-- poly_attr

Then you made a new branch issue1 starting at the end of poly_attr (not where you had intended to start), and made more commits:

... - M5 - M6                   <-- master, origin/master
             \
              P0 - P1           <-- poly_attr
                     \
                      I0 - I1   <-- issue1

Then you did a git push that pushed both poly_attr and issue1 to your remote (the second goof).

The remote now has commits I0 and I1 (with the same branch labels on the remote as you have on your local), and you can't erase them from there unless you can log in there and do stuff including git gc. Worse, assuming the repo on origin is shared, other people may have them by now. This in turn means, how you fix this depends on how sure you are no one else has them, how much influence you have over anyone who does have them, and/or how desperate you are to have the ultimate branch history "look simple".

It's easy, in your own repo, to run git rebase -i master issue1 and choose to carry over only commits I0 and I1, or even git rebase --onto master poly_attr issue1 to carry over I0 and I1 without having to edit the interactive rebase stuff. In either case your end result is:

... - M5 - M6                   <-- master, origin/master
            |\
            | P0 - P1           <-- poly_attr, origin/poly_attr
            |        \
            |         I0 - I1   <-- origin/issue1
             \
              I0' - I1'         <-- issue1

Note that the I chain of commits are still there, and the remaining label they have is the "remote branch" origin/issue1.

If you're sure no one else has grabbed issue1 from the remote, and if you have sufficient permission, you can git push -f origin issue1:issue1 after doing your rebase. That will send over the new commits I0' and I1' and make the remote move his issue1 label to point to I1', the tip of your new issue1 branch. The old commits (I0 and I1) will still be around for a while, but no one will see them, they won't be in the way, and eventually they'll be garbage-collected:

... - M5 - M6                   <-- master, origin/master
            |\
            | P0 - P1           <-- poly_attr, origin/poly_attr
            |        \
            |         I0 - I1       [invisible and eventually gc'd]
             \
              I0' - I1'         <-- issue1, origin/issue1

Even if others have grabbed it, if you have sufficient permission and power, you can perhaps force them to take your change. ("Sorry guys, if you cloned and got issue1, please note I've forcibly rebased it and you'll have to figure out how to fix any stuff you did that depended on it.")

If you're not sure or don't have permission or whatever, you will have to live with something messier. Instead of making new commits (the In' ones), you will simply have to add, on to the end of the issue1 chain, commits that undo the effect of each Pn commit. You can do that fairly simply, starting with the original (non-rebased) chain:

... - M5 - M6                   <-- master, origin/master
             \
              P0 - P1           <-- poly_attr
                     \
                      I0 - I1   <-- issue1

Just use git revert to apply the inverse of changes made in the P series:

$ git checkout issue1
$ git revert master..poly_attr

This gives you the new chain:

... - M5 - M6                   <-- master, origin/master
             \
              P0 - P1           <-- poly_attr
                     \
                      I0 - I1 - RP1 - RP0   <-- issue1

where RP1 is a commit that reverts (reverses) P1, and RP0 is a commit that reverts P0. If you added a file in P1, it gets removed in RP1. If you deleted a line of code in P0, it gets put back in RP0. This means that the end result, the files you get when checking out RP0, look the same as if you had made the changes in I0 and I1 on top of the commit M6. The history, of course, looks quite different, and it's a good idea to explain (in the revert commits) why this happened. But the end result is the same, and you are only adding new commits on to the end of the In chain, which means other people who have the commits can pick up the new stuff with little fuss.

Sign up to request clarification or add additional context in comments.

1 Comment

Wow, torek! That is the best and most descriptive answer I think I've seen. Thank you so much. You got it exactly right. Luckily I'm not worried about anyone else pulling down the branches as yet, which is I why I just went ahead and pushed the changes without a thorough look over. I think I've got it under control now. Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.