Git: Move commits before branches
I have a commit history shown below:
B-X / A-C \ D
I’d like to apply
X to all branches
cherry-pick does the trick, it makes history like this:
B-X / A-C-X \ D-X
X is duplicated three times, which is very inconvenient if I have many branches. Ideally, I’d like the history to look like this:
B / A-X-C \ D
X appears only once. This history is much cleaner. Which command should I use to achieve that?
4 Solutions collect form web for “Git: Move commits before branches”
The first main point is that the fix you are requesting will rewrite history, which is only something you should do if no-one else is using the branch. For instance if you move
D to be after
X, then anyone else who had previously checked out
D will get very confused when they pull. You should only do this if no-one else is using it, or they know and expect the change.
// Make a new temporary branch starting at A, and move X into it. git checkout -b tmp A git cherry-pick X // For each of B, C and D, rebase them on top of the temporary branch. git checkout <branch> git rebase tmp
The cherry-pick strategy pointed out by others works very well. Just wanted to point out you can also do this sort of thing using a set of rebases with the –onto flag, and it doesnt require a temporary branch.
Cloning your commit to its new location
git rebase --onto A B X
This means you apply commits that exist at point X, but dont exist at point B, and are applying them to the end of A. This will result in the following.
B-X / A--Y |\ D C
Read the note at the bottom of this answer regarding
Y, why it has a different name than
X, and about how git handles any changes in history.
So you’ve applied X where you want it, now you just need to rebase the other branches so that they have it in their history too.
Updating downstream branches
git checkout C git rebase Y git checkout D git rebase Y git checkout B git rebase Y
The only stipulation here is with
B. The sequence above makes sense regardless of whether or not the letters represent branch names or SHA1 commits. However if
X are actually in a branch, then you need to checkout that branch and rewind it to before
X was created. Lets call the branch “bee” for the sake of the example.
git checkout bee git reset --hard B git rebase Y
And now you have….
B / A--Y-C \ D
After this, just eat cake.
Rebasing, Cherry-Picking, Amending, Oh my!
A commit and its SHA1 are unique to the parent commit, the changes performed, and some other details. So when changing the parent or ancestor of a commit, that commit actually gets cloned and will have a new SHA1. Rebasing, cherry-picking, or anything else that modifies existing commits or the parents of a commit will result in modified commit getting a new SHA1, also, since that commits SHA1 has changed, all of that commits children think they have a new parent, so they will also get new SHA1 hashes.
The old commits will stil exist, hence the first diagram above shows Y as the clone of X with a new name and a new parent. Both still exist, in most cases that old commit can easily be retrieved and in some cases (like this one), you actually need to intentionally move the old commit out of the way. Thats why after the rebase –onto, you see the diagram shows both X and Y. The X commit was not “moved”, its more like a clone. After which X still exists, and needs to be dealt with by checking out the branch of B, and resetting it to the commit just before ‘X’.
In reality,the commits in
D would also have all their commits cloned, and they would all have new SHA1 hashes.
This is the reason why it can be troublesome to rebase or cherry-pick, or amend commits after they have been shared with other developers. If those developers pull in the clones, their local repo will think its new work, and they will get duplicate commits and because those commits do the exact same thing, they will get conflicts. Then cthulhu will rise and begin its reign over the world.
The difficulty in explaining this is caused by the fact that in these examples, we are using branch names as if they are commits. If this question were specifically about the side effects of modifying history, I would add more accurate diagrams.
One possible solution is to do this with multiple cherry-picks. Checkout A and cherry-pick X to create
B-X / A-X' \ \-C \ D
Now you should be on X’ so you can cherry pick B:
B-X / A-X'-B' \ \-C \ D
Checkout X’ and cherry pick C. Repeat one more time: checkout X’ and cherry pick D.
This might not be the most efficient solution, but it should work.
It looks like XY problem to me, i.e. is asking about your attempted solution rather than your actual problem.
Why not create bugfix commit ‘X’ on a separate bugfix-X branch starting from common ancestor, and merge it into ‘A’, ‘B’ and ‘C’. This idea is described for example in Junio C. Hamano blog post “Resolving conflicts/dependencies between topic branches early”
This mean creating the following history:
B---------b / | A-C-----c | |\ | / | D-d / / \ | / / \X/--/--/
where ‘b’, ‘c’ and ‘d’ are merge commits.
This can be done with the following commands:
First, move (rebase) commit ‘X’ onto common ancestor of branches (commits) ‘B’, ‘C’ and ‘D’.
$ git rebase --onto $(git merge-base B C D) B X
This will lead to the following situation, with bugfix commit ‘X’ on a separate bugfix branch:
B / A-C |\ | D \ \X'
Next, merge the bugfix into all branches that need it. For example merging into branch ‘D’ can be done with:
$ git checkout D
$ git merge X
This will result in:
B / A-C |\ | D---d \ / \X'/
Do the same for branches ‘C’ and ‘B’.