-1

Here's my commit stack:

E (current -- contains error)
D (contains error)
C (contains error)
B (contains error)
A (oldest -- contains error)

There's an error in A, so I fix it, and I want that fix to apply to all the following commits above it in the stack.

I've read about fixup + autosquash, but that seems to only apply to a specific commit, and I'd like it to apply to all subsequent commits.

What's the common practice for this type of situation?

h.and.h
  • 690
  • 1
  • 8
  • 26
  • 2
    Does this answer your question? [How to modify a specified commit?](https://stackoverflow.com/questions/1186535/how-to-modify-a-specified-commit) – luk2302 Mar 15 '22 at 19:11
  • 1
    I'd rather ask why you want to change commit history? It's not the best approach. I think fixup & rebase should procuce the result you're expecting. – michal Mar 15 '22 at 19:16
  • @luk2302 No, I want to modify the specific commit and have the changes be reflected in the subsequent ones. That post only talks about modifying the commit. – h.and.h Mar 15 '22 at 19:17
  • @michal Fixing forward isn't an option, sorry to say it. If commits A - E contain an error, they might merge days apart, so they need to be fixed to remove the error. – h.and.h Mar 15 '22 at 19:19
  • What does "reflect the change" mean? Do you understand how git works? If you fix A all commits after it are also "fixed" (as long as they are properly rebased) – luk2302 Mar 15 '22 at 19:19
  • @luk2302 hmmm... if I fix commit A, I'm not seeing that fix applied to B-E. I suspect the "properly rebased" is the part I'm not understanding. – h.and.h Mar 15 '22 at 19:20
  • ...Unless they modify the same line(s) of code, in which case a manual merge is required, but offers you an opportunity to fix these commits too. – tripleee Mar 15 '22 at 19:21
  • The right thing to do is to fix the error in commit F, and *not merge* one of the previous commits. – chepner Mar 15 '22 at 19:21
  • The commits A-E will no longer exist but new commits with the same content but a different hash will have replaced them. – luk2302 Mar 15 '22 at 19:22
  • 2
    Commits are immutable; "fixing in A" really means creating a new commit A' and basing all future work off that commit instead (which at this point means creating B', C', D', E' as well. `git rebase -i` and `git rerere` can help with that, but should only be used if you haven't shared commits A-E anywhere yet). – chepner Mar 15 '22 at 19:22
  • 2
    first thing you need to take into account is if this is a private or public branch, you should never change history on a public brunch (brunch that someone else could be working on. if it is a private brunch, you could do an interactive rebase fix the A commit, when finished the rebase push force to change the remote – Ofer Skulsky Mar 15 '22 at 19:29
  • h.and.h I'm afraid to ask ;) you merge specific commits separately? you don't keep features/change sets as separate branches? Also, as @Ofer Skulsky pointed out - if you already pushed the changes to remote and somebody else is working on this branch you'll mess it up for them if you rebase. – michal Mar 15 '22 at 19:39
  • @michal yeah, it's a big company with a proprietary "merge" system. It's git on my local, but it's not on remote, so I'm just trying to get my local to work. So "yes" is the answer to your question. I could have a commit "merge" on Monday and the next one not merge until Wednesday. Yeah, docs at the company would be helpful, but... there aren't any. – h.and.h Mar 15 '22 at 20:02
  • 1
    @h.and.h OK - since it really is what you're looking for - I've posted a short 'how-to' rebase a fixup. Best if you try it out on sample repository (just git init in an empty dir) and commit couple of changes into a single file & then try to fixup & rebase. Good luck! – michal Mar 15 '22 at 20:30

2 Answers2

2

Git fixup and rebase autosquash is your way to go. First commit with fixup pointing to the commit that introduced the error (as per your example 'A'):

git commit -a --fixup <commit A hash here>

this will produce a new fixup commit on top of existing ones:

F Fixup! (oldest -- contains error)
E (current -- contains error)
D (contains error)
C (contains error)
B (contains error)
A (oldest -- contains error)

Now to apply the fix to all the subsequent commits you need to do rebase:

git rebase --autosquash --interactive <commit hash before A >

This will call the editor you have set up with list of commits and rebase steps in order:

pick HashA
fixup HashF fixup!
pick HashB 
pick HashC 
pick HashD 
pick HashE 

Once you confirm/close the editor git will apply the changes to all commits after A. Note that all commit hashes will be changed as they are not the same commits anymore.

michal
  • 323
  • 3
  • 15
  • Thank you! Is it possible that it should be the commit hash from before A? `$ git rebase --autosquash -i ` – h.and.h Mar 15 '22 at 21:32
  • 1
    Yeah, my bad, you need to put the commit hash you want to use as 'the base' of rebase. I've edited the answer, cause you don't have the base commit hash on the rebase list & since in the example I applied fixup to A it wouldn't make sense. – michal Mar 15 '22 at 22:28
0

Keep in mind the usual caveats with editing history: don't modify public history and backup your work first (e.g. git branch backup).

Let's say your working tree is clean and your HEAD is pointing at commit E. Then you can do

git rebase -i A~

You'll get some lines like:

pick E
pick D
...
pick A

Insert a line with 'b' (for break) after pick A:

pick B
b
pick A

Git will start from the commit before A, apply A and then stop. At this point, you can apply your fixes for A, then git commit --amend, then git rebase --continue and resolve merge conflicts as needed. Of course you can also add more b lines after any other commit that needs fixing at the same time.

I think this is better than the other solution, since the other solution writes the fix commit on the branch's head, which might be too late. e.g. commit A might have an error (or private information, etc.), which commit B removes. Then there's no fix you can write at commit E; the error is already gone. This way you can apply your fix directly on commit A.

Steven Fontanella
  • 764
  • 1
  • 4
  • 16