Luigi D.
Luigi D.

Reputation: 315

Amend a Git Commit from which another branch spawns

Let's say I have commit A, on branch mainline, which is currently not pushed (e.g., because it's being code-reviewed).

In the meantime, I want to start working on a new feature B, based on commit A, so I create another branch and start working on it. This is the current situation:

--- A
     \
      -- B

In the meantime (e.g., because from the code review it has emerged I need to change something), I make some changes to commit A. I now want to amend commit A to include the new changes. What happens to commit B, at least in my head, is something like this:

   ----- A
  /
---- ?
      \
       ---- B

Where ? is the old, not-yet-amended commit A, which now doesn't exist anymore. I assume this is not a good idea.

So what would be the correct way to do this?

I thought about not amending A, and instead create a new commit A':

--- A ----- A'
     \
      -- B

Then, when I'm ready to push feature A, cherry-pick A' on B and then rebase B on A':

--- A -- A'
          \
           -- B

Finally, squash A and A', and push A.

This seems awfully convoluted and error-prone. Also, I'm not at all sure that it's the right way to go.

Is there a better way?

Upvotes: 3

Views: 1149

Answers (2)

One idea is to directly modify commit A which as you mentioned results in

   ----- A
  /
---- ?
      \
       ---- B

Then, while being at the branch of commit B you can remove commit ? by git rebase -i B~2 and deleting the line corresponding to commit ?.

Finally, you can do git rebase A to get the final result.

Upvotes: 0

torek
torek

Reputation: 488103

This is why I keep telling people that it is literally impossible to change a commit.

A commit is an entity that is denoted and found by its raw hash ID. Your A and B above are just short, human-useful substitutions for the actual hash IDs, which are long and unweildy.

If you draw the graph out a bit further, it might look like:

...--H--A   <-- mainline
         \
          B   <-- another

When you go to "amend" commit A (presumably using git commit --amend), it's quite literally impossible to change it. What you end up doing is making a new commit A' and commit A still exists, it's just that mainline no longer points to it, nor includes it in the set of commits reachable by starting from the tip and working backwards:

       A'  <-- mainline
      /
...--H--A
         \
          B   <-- another

The set of commits that is unique to another, or equivalently, the set of commits reachable from another but not from mainline, has grown, without anything in the commit graph itself changing. That's because the set of commits reachable from mainline has changed because mainline itself has changed.

The labels move. The commits do not!

Note that, like git commit --amend, git rebase also does not change any existing commits. Instead, it (typically) copies commits, then moves the labels. (The "typically" is here only because there are a few cases where rebase decides no work needs to be done and leaves existing commits in place. You can force copies anyway, if there's some reason you want all-new commits with new hash IDs, but the default is to detect this situation and preserve as much as possible.)

Hence as evolutionxbox and phd both said in comments, your situation is the same either way: having yanked the name mainline off commit A, you will want to copy commit B to some new B' whose parent is A':

       A'  <-- mainline
      / \
...--H   B'   <-- another
      \
       A--B   [abandoned, but remembered for some time]

Eventually—typically some time 30 days or more in the future, after abandoning one or more commits like this—Git will garbage collect the unwanted commits.

The tricky part of using git rebase to copy B to B' is that if there are substantive differences in A vs A', your Git will by default think that commit A should be copied again, producing—or trying to produce:

       A'  <-- mainline
      / \
...--H   A"-B'   <-- another
      \
       A--B   [abandoned, but remembered for some time]

(with, probably, a lot of merge conflicts between A' and A"). There are multiple ways to go about preventing this, such as using git rebase -i or git rebase --onto.

Upvotes: 2

Related Questions