230

Suppose I have the following commit history on my local-only branch:

A -- B -- C

How do I insert a new commit between A and B?

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • I have a very similar question on [how to insert a new version in the past](https://stackoverflow.com/questions/46678238/how-to-inject-a-version-between-some-two-arbitrary-versions-in-the-past) instead of a commit. – Pavel P Oct 11 '17 at 01:14
  • To create a new commit node, just double-click in the visual graph between `A` and `B` – Oh, wait, no, it's 2023, we don't have that yet. – Nils Lindemann May 18 '23 at 11:40

7 Answers7

320

It's even easier than in OP's answer.

  1. git rebase -i <any earlier commit>. This displays a list of commits in your configured text editor.
  2. Find the commit you want to insert after (let's assume it's a1b2c3d). In your editor, for that line, change pick to edit.
  3. Begin the rebase by closing your text editor (save your changes). This leaves you at a command prompt with the commit you chose earlier (a1b2c3d) as if it has just been committed.
  4. Make your changes and git commit (NOT amending, unlike most edits). This creates new a commit after the one you chose.
  5. git rebase --continue. This replays the successive commits, leaving your new commit inserted in the correct place.

Beware that this will rewrite history, and break anyone else who tries to pull.

o11c
  • 15,265
  • 4
  • 50
  • 75
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 3
    This added the new commit after the commit, which is after the one that I was rebasing onto (also the last commit), instead of right after the one I was rebasing onto. The result was same as if I had simply made a new commit at the end with the changes I wanted to insert. My history became `A -- B -- C -- D` instead of desired `A -- D -- B -- C`. – XedinUnknown Oct 17 '16 at 06:42
  • 3
    @XedinUnknown: Then you didn't use Rebase properly. – SLaks Oct 19 '16 at 00:11
  • Apparently. I guess I misread, and rebased _onto_ the commit before the one I needed, marking the commit I wanted to add _before_ as "edit". – XedinUnknown Oct 19 '16 at 14:36
  • What does the `after to edit` mean in "Change the commit you want to insert after to edit" ? Also, `Begin the rebase` is also not clear what does it mean - make the changes or run some other command? – Aleks Jan 20 '17 at 10:48
  • @Aleks: I mean change the prefix in its line in the rebase editor. And "begin" means close the editor – SLaks Jan 20 '17 at 15:09
  • 4
    To use this answer, first you have to identify the new commit. In one case, this could just be at the tip; let's call it `D`, so we have `A - B - C - D` and the goal is `A - D - B - C`. `git rebase -i HEAD~4`, and reorder the commits to ADBC order in the editor, save, exit. – Kaz Aug 29 '17 at 01:04
  • 11
    Now `D` could be a commit anywhere. Suppose we have `A - B - C` and we have some commit `D` which is not even in this branch. We know its SHA however, We can do `git rebase -i HEAD~3`. Now between the `A` and `B` `pick` lines, we insert a **new** `pick` line which says `pick SHA`, giving the hash of the desired `D`. It need not be the full hash, just the shortened one. **`git rebase -i` just cherry picks whatever commits are listed by `pick` lines in the buffer; they don't have to be the original ones that it listed for you.** – Kaz Aug 29 '17 at 01:06
  • Using this method, is there a way to introduce a new commit _before_ A? I know that I can always just checkout the current parent of A, then create a new commit X and then rebase the whole of A--B--C on top of X. It would be simpler however if I could just use interactive rebase and specify "edit" for the parent of A. – eekboom Sep 21 '17 at 08:11
  • @StephenFriedrich Parent of A is `A~1`, so you can pass this as an argument for `rebase`. – BartoszKP Jan 16 '18 at 17:34
  • If you want to insert a new commit right after a *merge commit* `a1b2c3d`, the described answer will get rid of the merge commit, which is likely not what you want. Use the `--preserve-merges` (`-p`) option to prevent this from happening (e.g., `git rebase -i a1b2c3d~`, then continue as described) – Tomas Jun 13 '18 at 07:18
  • 1
    @Kaz This looks like a different, valid answer. – BartoszKP Nov 15 '18 at 14:39
  • @Kaz I would encourage you to submit this as a separate answer, and I would support it as the accepted answer. It's insightful, simple, to-the-point, and appears correct, and is exactly on target. These other answers are good, but more hacky imho. – jaredscheib Apr 03 '19 at 18:43
  • 14
    Even easier, you can use the `break` keyword in the editor on its own line in between two commits (or on the first line, to insert a commit before your specified commit). – SimonT Jun 13 '19 at 18:02
  • I agree with @XedinUnknown ; this created the commit after where OP wants it. Will try `break` as mentioned by @SimonT – polynomial_donut Apr 11 '23 at 15:13
  • This works great! For I had to pick the commit *two* positions before the insertion position though (since rebase first line with `pick` will show the commit just after that, and then I need to insert yet another commit after that). Not sure how to make it work if you need to insert a commit right after initial commit. There's probably a trick to do something before the first rebase action, but not sure how. – hsandt Aug 28 '23 at 15:13
53

Turns out to be quite simple, the answer found here. Suppose you're on a branch branch. Perform these steps:

  • create a temporary branch from the commit after you want to insert the new commit (in this case commit A):

    git checkout -b temp A
    
  • perform the changes and commit them, creating a the commit, let's call it N:

    git commit -a -m "Message"
    

    (or git add followed by git commit)

  • rebase the commits you want to have after the new commit (in this case commits B and C) onto the new commit:

    git rebase temp branch
    

(possibly you need to use -p to preserve merges, if there were any - thanks to a no longer existing comment by ciekawy)

  • delete the temporary branch:

    git branch -d temp
    

After this, the history looks as follows:

A -- N -- B -- C

It is of course possible that some conflicts will appear while rebasing.

In case your branch is not local-only this will introduce rewriting history, so might cause serious problems.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • 5
    I couldn't follow the accepted answer by SLaks, but this worked for me. After I got the commit history I wanted, I had to `git push --force` to change the remote repo. – escapecharacter Jun 10 '17 at 22:07
  • 1
    When using rebase using the -Xtheirs option auto resolves conflicts correctly, so `git rebase temp branch -Xtheirs`. Helpful answer for injecting in a script! – David C Dec 12 '19 at 15:12
  • 1
    For noobs like myself I'd like to add that after `git rebase temp branch`, but before `git branch -d temp`, all you have to do is fix and stage merging conflicts and issue `git rebase --continue`, i.e. no need to commit anything, etc. – Pugsley Mar 04 '20 at 07:11
43

Even easier solution:

  1. Create your new commit at the end, D. Now you have:

    A -- B -- C -- D
    
  2. Then run:

    $ git rebase -i hash-of-A
    
  3. Git will open your editor and it will look like this:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Just move D to the top like this, then save and quit

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Now you will have:

    A -- D -- B -- C
    
Matthew
  • 6,351
  • 8
  • 40
  • 53
  • 11
    Nice idea, however it may be hard to introduce D on C, when you intend these changes being wrt. to A. – BartoszKP Oct 14 '17 at 10:33
  • 2
    I have a situation where I have 3 commits that I want to rebase together and a commit in the middle that is unrelated. This is super nice to be able to just move that commit earlier or later down the line of commits. – unflores Sep 07 '18 at 10:21
  • @BartoszKP I'm not quite following. Can you explain that case in more detail? I think you mean that if D & C both edit fileX, and the edits to fileX in D essentially depend on edits to fileX in C, it'll be hard to move D up, but it feels like that situation would preclude asking the original question... Would another answer here prevent the issue you're referring to? – ruffin Feb 09 '23 at 16:57
  • 1
    @ruffin No, I meant that edits in D are easier to be made in state A. I'm not saying this invalidates the answer or precludes anything - I said "may be hard", so only referring to a (possibly small) subset of scenarios :) – BartoszKP Feb 09 '23 at 17:57
42

Here's a strategy that avoids doing an "edit hack" during the rebase seen in the other answers I've read.

By using git rebase -i you obtain a list of commits since that commit. Just add a "break" at the top of the file, this will cause the rebase to break at that point.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Once launched, git rebase will now stop at the point of the "break". You can now edit your files and create your commit normally. You can then continue the rebase with git rebase --continue. This may cause conflicts you'll have to fix. If you get lost, don't forget you can always abort using git rebase --abort.

This strategy can be generalised to insert a commit anywhere, just put the "break" at the spot where you want to insert a commit.

After rewriting history, don't forget to git push -f. The usual warnings about other people fetching your branch apply.

axerologementy
  • 421
  • 4
  • 5
15

Many good answers here already. I just wanted to add a "no rebase" solution, in 4 easy steps.


Summary

git checkout A
# <<< modify your files at this point
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Explanation

(Note : one advantage of this solution is that you don't touch your branch until the final step, when you're 100% sure you're OK with the end result, so you have a very handy "pre-confirmation" step allowing for AB testing.)


Initial state (I've assumed master for your branch name)

A -- B -- C <<< master <<< HEAD

1) Start by pointing HEAD at the right place

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Optionnally here, rather than detaching HEAD, we could have created a temporary branch with git checkout -b temp A, which we would need to delete at the end of the process. Both variants work, do as you prefer since everything else remains the same)


2) Create the new commit D to be inserted

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Then bring copies of the last missing commits B and C (the command would be the same even if there were more commits in between, since this is picking a range of commits)

git cherry-pick A..C

# (if any, resolve potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

( comfortable AB Testing here if needed )

Now is the moment to inspect your code, test anything that needs to be tested, and you can also diff / compare / inspect what you had and what you would get after the operations, simply by checking alternatively C or C'.


4) Depending on your tests between C and C', either it is OK or it is KO.

(EITHER) 4-OK) Finally, move the ref of master

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(OR) 4-KO) Just leave master unchanged

If you created a temporary branch, just delete it with git branch -d <name>, but if you went for the detached HEAD route, no action needed at all at this point, the new commits will be eligible for garbage collection just after you reattach HEAD with a git checkout master

In both those cases (OK or KO), at this point just checkout master again to reattach HEAD.

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
  • Great answer. What will happen if there is `A -- B -- C -- E -- F` and, `B` and `E` are merge commits? – Umair Khan Jul 26 '23 at 10:23
  • 1
    My answer (like most here) is addressing non-merge commits. If merges are involved, it's more complicated. Depending on the context, it could be better to cherry-pick the (non-merge) commits these merge commits brought, rather than cherry-picking the merge commits, which may or may not be tricky. If you really consider cherry-picking merge commits, take a look at the [`-m`](https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt--mltparent-numbergt) option. – Romain Valeri Jul 26 '23 at 10:25
14

Assuming that the commit history is preA -- A -- B -- C, if you want to insert a commit between A and B, the steps are as follows:

  1. git rebase -i hash-of-preA

  2. Git will open your editor. The content may like this:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Change the first pick to edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Save and Exit.

  3. Modify your code and then git add . && git commit -m "I"

  4. git rebase --continue

Now your Git commit history is preA -- A -- I -- B -- C


If you encounter a conflict, Git will stop at this commit. You can use git diff to locate conflict markers and resolve them. After resolving all conflicts, you need to use git add <filename> to tell Git that the conflict has been resolved and then rerun git rebase --continue.

If you want to undo the rebase, use git rebase --abort.

haolee
  • 892
  • 9
  • 19
1

Given that the commit you want to insert is identified by D:

# Temporarily append the commit you want to insert to the end 
git cherry-pick D
# Results in D -- A -- B -- C

# Start interactive rebase
git rebase -i B^
# Let's imagine that this is what the rebase prompt looks like:
# pick B Third commit
# pick A Second commit
# pick D First commit
# Then reorder the commits:
# pick B Third commit
# pick D First commit
# pick A Second commit
# Save and exit
# After completing the rebase you will find
# A -- D -- B -- C
Mateja Petrovic
  • 3,799
  • 4
  • 25
  • 40
  • Your answer is extremely similar to [Matthew's one](https://stackoverflow.com/a/46413513/1057485), albeit being less explanative. I guess it's the reason someone downvoted? – Romain Valeri Jan 30 '22 at 11:49