Gargoyle
Gargoyle

Reputation: 10375

git rebase leaves extra "branch" in the commits

I was trying to squash some old commits back together, that have already been pushed to master. I'm the only developer though so it's fine to force update the repository. I started with this:

% git log --oneline --decorate --graph --all
* 4b2fec5 (HEAD -> master, tag: v6.2.5, origin/master, origin/HEAD) Version 6.2.5 - Fixed MegaMillions payout bug
* b0ae8a9 (tag: v6.2.4) Version 6.2.4 - Handles large size text
* f4763bd (tag: v6.2.3) 6.2.3
* 3f02a27 (tag: v6.2.1) Version 6.2.1
* 7703d55 (tag: v6.2.0) Updated revision
* a2c6366 Removed legacy NSNotification stuff.
* 1e3b359 Changes UITextFieldTextDidChange to use an onPost  handler.
* 35b910b Turns off the request for app store rating.
* 5c20d46 Put the detail segue back in.
* f90722d Got rid of stack view from MasterViewController
* eb34f07 Removed unnecessary null coalesce operator.
* c969126 Changed for Megamillions new payout structure.
* 2a32838 Version 6.2.0
* efb33b0 Initial commit
* 13a5b0e (tag: v6.0.1) 6.0.1 for app store
* 45ab2ba Downloads new numbers after creating a new ticket now.
* 2d60f30 Cancel button didn't have an exit segue on the master.
* 2185112 Changed to static size text for loser
* 872f408 Initial Commit

And now I want to squish everything between the v6.2.0 tag and the Version 6.2.0 commit into a single commit, so I did a git rebase -i efb33b0 which said it was successful. But now I'm left with the following:

% git log --oneline --decorate --graph --all
* 0fb2623 (HEAD -> master) Version 6.2.5 - Fixed MegaMillions payout bug
* 81d3553 Version 6.2.4 - Handles large size text
* d7d7578 6.2.3
* eae8973 Version 6.2.1
* ded0fe9 Version 6.2.0
| * 4b2fec5 (tag: v6.2.5, origin/master, origin/HEAD) Version 6.2.5 - Fixed MegaMillions payout bug
| * b0ae8a9 (tag: v6.2.4) Version 6.2.4 - Handles large size text
| * f4763bd (tag: v6.2.3) 6.2.3
| * 3f02a27 (tag: v6.2.1) Version 6.2.1
| * 7703d55 (tag: v6.2.0) Updated revision
| * a2c6366 Removed legacy NSNotification stuff.
| * 1e3b359 Changes UITextFieldTextDidChange to use an onPost  handler.
| * 35b910b Turns off the request for app store rating.
| * 5c20d46 Put the detail segue back in.
| * f90722d Got rid of stack view from MasterViewController
| * eb34f07 Removed unnecessary null coalesce operator.
| * c969126 Changed for Megamillions new payout structure.
| * 2a32838 Version 6.2.0
|/
* efb33b0 Initial commit
* 13a5b0e (tag: v6.0.1) 6.0.1 for app store
* 45ab2ba Downloads new numbers after creating a new ticket now.
* 2d60f30 Cancel button didn't have an exit segue on the master.
* 2185112 Changed to static size text for loser
* 872f408 Initial Commit

How do I get rid of that extra "branch" that's in there? Basically it looks like that entire branch to the right just needs to go away, so that it only follows the main path...and somehow change the tags over too.

Upvotes: 0

Views: 395

Answers (1)

torek
torek

Reputation: 489848

The short answer is that you don't / can't. (But see exception below.)

What git rebase does is copy some commits to make new, different commits, and then abandon the original set of commits in favor of the new-and-improved commits.

The problem that comes in at this point is that abandoning those commits works fine if and only if the branch you are "on" (as in git status says on branch master) is the only reference to those particular commits. If there are additional reference(s) to some or all of the original commits, those references don't change!

This is true even, or maybe especially, if those references are in some other repository. The latter can occur if you have git push-ed the commits to some other repository and had that other repository set some of its references to remember those commits. This is why it's generally not great form to amend or rebase commits that you have already git pushed.

(Small side note: a reference is Git's generalization of branch and tag names. There are more kinds, but these two are the most familiar ones. A branch name is just a reference whose full name starts with refs/heads/: the rest of the name is the branch name. So refs/heads/master is the full reference name of branch name master.)

It can be done, if it's OK with everyone

If:

  • you're sure no one else has these commit hashes stored away in some other clones, or
  • you have pre-arranged with everyone else who does have these commit hashes stored away, that everyone will switch all their references as needed,

then (and only then) it is perfectly fine to rebase shared commits like this. To do that, after copying the old, slightly-defective commits to shiny new commits, you must find all references (in all repository copies!) that use the old commits, and move them so that they refer instead to the shiny new commits.

In your case, I count at least six (6) such references:

  1. tag v6.2.0 (refs/tags/v6.2.0);
  2. tag v6.2.1;
  3. tag v6.2.3;
  4. tag v6.2.4;
  5. tag v6.2.5;
  6. branch master on another Git over at origin, which your Git is remembering for you as your origin/master (that's their refs/heads/master and your refs/remotes/origin/master).

If you convince them to move their master (by force-push for instance), your own Git's memory in your refs/remotes/origin/master reference will change automatically. If you convince them to move all five of the other tags, and you move all five of your tags, that will eliminate all six of these names-for-the-old commits. Git will then stop showing you the old commits, and eventually they will truly expire and be garbage-collected away.

To force-push your new master you can just git push --force origin master (but hold off on it for a moment). That still leaves you to re-adjust each of your tags, one by one, to point to the shiny new copied commits—each one has a different new, shiny hash ID—and then force-push those tags, with git push --force --tags, and hope that they (whoever "they" are on origin) allow all these force-pushes. You can push them all at once with git push --force --tags master.

The problem with changing other people's references lies in getting all of them. Otherwise the old references, and hence the old commits, can come back to haunt you. But if your Git and the Git on origin are the only copies of this repository, and you control both of those, you can do what you want.

Upvotes: 1

Related Questions