67

I have 2 commits, A then B, ready to be pushed. I realize I forgot to add something in A.

How can I add this change to A using Magit? I don't even know which part of the Git documentation I should look at.

Mathieu Marques
  • 1,953
  • 1
  • 13
  • 30

4 Answers4

120

Let's pretend for a moment that you want to add something to the HEAD commit, i.e. "the second commit B" in your example.

The commit popup on c features a binding "a Amend". Pressing that key will "amend" the staged changes to the HEAD commit. Since commits are not mutable in Git, this will actually replace the old commit with a new commit. A buffer with the old commit message will pop up, so that you can modify it in case the added change also requires that you adjust the message. As always, press C-c C-c when you are done editing the message. This is equivalent to running git commit --amend on the command line.

  • a Amend -- add the staged changes to HEAD and edit its commit message

Because it often happens that you only have to adjust the change or the message, Magit provides two additional variants:

  • e Extend -- add the staged changes to HEAD without editing the commit message
  • w Reword-- change the message of HEAD without adding the staged changes to it

When you want to edit a commit that isn't HEAD, then the above won't work. These commands always "modify" (i.e. replace) the HEAD commit. Git doesn't provide a single command for modifying a commit other than HEAD so this is a bit more involved.

Magit does provide such a command, but because there are situations in which it is preferable to do this in multiple steps, we will discuss that first.

Modifying a commit other than HEAD can be broken down into three steps:

  1. Temporarily make that other commit (A) the HEAD.
  2. Modify the HEAD (as described above), resulting in commit A'.
  3. Tell Git to reapply the commits that followed A, but on top of A'.

This can be done using an interactive rebase. Type r to show the rebase popup. Then type m to invoke the "edit a commit" rebase variant. A buffer with recent commits appears. Move to the commit you want to modify and type C-c C-c to select it. Git then rewinds history to that commit and shows information about the ongoing rebase in the status buffer.

Modify HEAD as described above. Then tell Git that you are done by typing r r. If A' and B conflict then rebase will stop at B and you have to resolve the conflict. After you have done so press r r to continue.

If you know that your changes to A will result in conflicts with B, then proceed as describe above, otherwise use the following approach.


Git allows creating "fixup commits" using git commit --fixup A. This creates a new commit, which records changes which "should have been made in another commit". That commit becomes the new HEAD. There also exists a --squash variant. For information about the differences see the git-commit man page.

To actually combine the A commit and the new commit A' and then reapply B on top of that you have to use rebase. Magit provides a convenient command for doing so on r f.

The main difference to the above approach is that here we first create a new commit and then we rebase to combine that with the "target" and reapply B. Above we began with rebasing instead of committing.

In Magit both the --fixup and the --squash variants are available from the commit popup, on f and s. But Magit also provides "instant" variants of the fixup and squash commands on F and S. These variants create a new commit like the "non-instant" variants, but then they instantly combine the fixup commit with the target commit using rebase, without you having to invoke another command.

"Instant fixup" (c F) is essentially the same thing as "extend HEAD" (c e), except that it works for any commit, not just HEAD.


Further reading:

tarsius
  • 25,298
  • 4
  • 69
  • 109
  • 2
    Crystal clear! Thank you, awesome package BTW. – Mathieu Marques May 26 '16 at 20:14
  • 2
    Well, I think there are some mushy parts in the second half of my answer. But to avoid those I would have to double the length of this already long answer, so I am glad this works for you ;-) – tarsius May 26 '16 at 22:06
  • Thanks for this answer tarsius, this really works for me. – anquegi Jan 29 '17 at 16:05
  • The clarity of the first half of this explanation makes reading the second half, which is much harder to follow, quite frustrating! – Lyn Headley Jun 06 '18 at 18:45
  • `git-commit` man page redirects to `git-rebase(1)` which has these lines: _The suggested commit message for the folded commit is the concatenation of the commit messages of the first commit and of those with the "squash" command, but omits the commit messages of commits with the "fixup" command._ IOW, use **fixup** if you just want to fix the code in the previous commit, use **squash** if you also want to fix the commit message. – Yasushi Shoji Jun 08 '18 at 12:59
  • I just read the last two paragraphs on Instant Fixup and Instant Squash - I feel that is sufficient and clearest really. – Jens Petersen Feb 06 '20 at 01:41
  • Why is magit insisting I pull the now deleted commit after the amend commit? eg modify code, magit, stage, c a, C-c C-c. Now it says I have unmerged changes form remote. OK, I can pull that but then its back in my local git repo. – RichieHH Mar 18 '20 at 05:41
  • You have only removed the commit locally. To also remove the commit on the remote you have to push to the remote. Because you are changing history you have to force that, i.e. `git push --force`. – tarsius Mar 18 '20 at 08:25
3

git commit --amend –C HEAD is the Git command you want to be looking for, and you can do amends in Magit with C-c C-a.

Ryan
  • 3,989
  • 1
  • 26
  • 49
  • I'm using Magit latest, `C-c C-a` is from an older version (I think). Furthermore, I see no trace of "amend" in the help buffer (`?`). – Mathieu Marques May 26 '16 at 14:57
  • See [Rémi's answer](http://emacs.stackexchange.com/a/22563/5296) for the magit 2.x equivalent. – npostavs May 26 '16 at 19:54
3

So one workflow is:

  • make your change
  • c (commit) f (fixup - select commit your fixing)

Then

  • r (rebase) -a (autosquash, can be defaulted) i (interactive)

The autosquash will automatically move all !fixup commits to the right place and set them to be squashed on the re-base.

stsquad
  • 4,626
  • 28
  • 45
  • The only thing I did that you didn't say was to stage between your first bullet and the second. Hitting `i` yields me `Cannot rebase: Your index contains uncommitted changes. Please commit or stash them.`. Except I don't have any uncommitted changes. : / – Mathieu Marques May 26 '16 at 15:28
  • Tried again after pulling, `Proceed despite merge in rebase range? [c]ontinue, [s]elect other, [a]bort`. Is it trying to tell me that my fixup might poop on the upcoming merge? – Mathieu Marques May 26 '16 at 15:35
  • @MathieuMarques: "Except I don't have any uncommitted changes" - git thinks you do. Note the message suggests staged changes, not unstaged ones. Re: `merge in rebase`, see BUGS under `git help rebase`. I suggest doing the fixup before pulling upstream. – npostavs May 26 '16 at 19:52
1

For amending the last commit, it's "c a". Fixup is for amending some older commit.

NickD
  • 27,023
  • 3
  • 23
  • 42
Rémi
  • 1,607
  • 11
  • 13