Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git rebase, commits repeating

Tags:

git

rebase

I'm working on another branch of my repo and need to bring in changes from the master branch. I'm in the habit of doing git rebase master, and all works fine. However, in a couple of recent projects, I'm seeing commits repeating in the logs, and rebasing tends to complain (loudly):

$ git log --graph --abbrev-commit --decorate ...
*   4988d49 - (2 hours ago) Merge branch 'branchname' of ... 3
|\
| * e979be1 - (2 days ago) change verbiage 10
| *   93a1c80 - (2 days ago) Merge branch 'branchname' of ... 2
| |\
| | *   4e4e790 - (2 weeks ago) Merge branch 'branchname' of ... 1
| | |\
| | | * 87cc232 - (8 weeks ago) change verbiage 3
| | | * 3d5cf09 - (8 weeks ago) change verbiage 2
| | | * aea4cbd - (9 weeks ago) change verbiage 1
| | * | a7043ef - (2 weeks ago) change verbiage 6
| | * | fa3413b - (2 weeks ago) change verbiage 5
| | * | be038a7 - (2 weeks ago) change verbiage 4
| | * | 37cb1e6 - (8 weeks ago) change verbiage 3
| | * | 1ab71c6 - (8 weeks ago) change verbiage 2
| | * | c4560f4 - (9 weeks ago) change verbiage 1
| * | | d3211fd - (2 weeks ago) change verbiage 6
| * | | 72a2a4a - (2 weeks ago) change verbiage 5
| * | | ae1c123 - (2 weeks ago) change verbiage 4
| * | | 8328c08 - (8 weeks ago) change verbiage 3
| * | | e52588f - (8 weeks ago) change verbiage 2
| * | | 114cbec - (9 weeks ago) change verbiage 1
* | | | 38bd6ce - (2 hours ago) change verbiage 9
* | | | 5aaf360 - (2 hours ago) change verbiage 8
* | | | 2745790 - (2 days ago) change verbiage 7
* | | | 7bb613f - (2 weeks ago) change verbiage 6
* | | | 726a312 - (2 weeks ago) change verbiage 5
* | | | 771dd7f - (2 weeks ago) change verbiage 4
* | | | b451926 - (8 weeks ago) change verbiage 3
* | | | 484d5dc - (8 weeks ago) change verbiage 2
* | | | 630df34 - (9 weeks ago) change verbiage 1

(I've changed the commit messages for simplicity. There really is only one branch being displayed here, despite four "pipe lines".) I've done a few rebases on this project, and I think they can be traced rather simply based on the number of repeats of (say) change verbiage 1.

I thought I was doing a "best practices" thing with project workflow by rebasing to merge. I thought at least one of the previous rebases was done with -no-ff, but I cannot see a difference in the behavior.

When the commits are replayed, they conflict (obviously, placing one commit on top of itself is problematic), so I'm forced to do an interactive rebase, removing all duplicate commits.

Lacking access to the repo itself, is this a problem with my workflow? Is it indicative of other problems in the branch?

like image 718
r2evans Avatar asked Mar 04 '16 07:03

r2evans


1 Answers

Why?

It looks like you modified old commits with rebase that another branch was already pointing too. So what happened is:

You had a dag that looks like this:

* abc123 (master) Most recent commit
|
| * f00ba12 (foo, HEAD) Some feature you're working on
| |
|/
* 192837 Fix the things
|
* 348cc87 Remove Peter's garbage code
|
* 000a0a WOOOORRRDDDDDSSSS

So you're currently on foo doing some things but you realize you still have some of Peter's garbage code left over and want to remove it. Being the good programmer you are, you want those changes to be in commit 348cc87 along with the rest of that change. So you do a rebase to get back to that point and amend the changes in.

This seems fine right? Thing is, you've changed 348cc87 so it's not 348cc87 anymore. Now its 111222, because remember the commit hash is calculated from the project's tree, among other things. One of those other things is parent commit hash (000a0a). So since 348cc87 is now 111222, '192837' is recalculated to 999999 and f00ba12 changes to ba12f00. So now your log from HEAD looks like

* ba12f00 (foo, HEAD) Some feature you're working on
|
|
* 999999 Fix the things
|
* 111222 Remove Peter's garbage code
|
* 000a0a WOOOORRRDDDDDSSSS

This becomes an issue because abc123 is still pointing at 192837, which works because the commit object 192837 still exists in git. This is because from git's perspective 192837 and abc123 are distinctly different commits that just happen to have the same log message.

So now your git dag looks like:

* abc123 (master) Most recent commit
|
| * f00ba12 (foo, HEAD) Some feature you're working on
| |
| * 999999 Fix the things
* | 192837 Fix the things
| | 
| * 111222 Remove Peter's garbage code
* | 348cc87 Remove Peter's garbage code
|/
* 000a0a WOOOORRRDDDDDSSSS

Now when you finish working on your foo branch and are ready to merge you get

* 5f93da (master) Merge 'foo' into 'master'
|\
* | abc123 Most recent commit
| |
| * f00ba12 (foo, HEAD) Some feature you're working on
| |
| * 999999 Fix the things
* | 192837 Fix the things
| | 
| * 111222 Remove Peter's garbage code
* | 348cc87 Remove Peter's garbage code
|/
* 000a0a WOOOORRRDDDDDSSSS

Do this a lot and you end up in the situation you are currently in.

But Kyle, how do I fix this?? Hold your horses r2evans, I'm getting there.

How to Fix

There are a few ways you could fix this, all of the ones I am aware of are mildly complicated given certain circumstances.

  • You could squash lots of stuff together so that there are fewer commits, but you lose some of your history, which I don't love.

  • You can do some more rebasing magic to change your 'abc123' commit to have the newer 999999 commit as it's parent. You can use the --onto argument for this. This can be challenging depending on the number of commits in this situation.

  • You can leave it as it is. So long as the tip of master is in the correct state, this isn't horrible but makes looking back on your history pretty difficult.

Is This a Problem?

Yes. This looks like a problem with your workflow. Editing history tends to be a less than great idea for this very reason and you should typically avoid it.

For me, master tracks what is public. It is the code that both myself and my teammates base our changes off of. I never modify commits that are in master. Firstly because our internal git repo won't allow me to push changes like that (even with --force) and secondly because that would mess with my teammates' code (and put them into your position).

I create feature branches off of master for specific features so I will have branches foo, bar, fizz, and buzz all as separate branches off of master, not off each other. This allows me to work on each independently. I don't typically branch off of a feature branch, but when I do, I generally don't do any editing of commit histories. That's because I'll generally clean up the branch before merging anyway, so I don't need to.

like image 82
Kyle Avatar answered Sep 30 '22 05:09

Kyle