0

Suppose I worked on:

  • feature 1: file1 + file3
  • feature 2: file2

and I have the following commits

  • Commit 2: feature 2 completed.
  • Commit 1: Feature 1 completed.

The wrong thing I made is I forget to add the file3 to commit 1. How can add the changes of file3 to commit 1?

One way of this is:

git reset --soft Commit 1
git add file3
git commit --amend 
git add file2
git commit -m "feature 2 completed"

I don't want this way is because in fact I may not only have commit 2 but also have commit 3,4,5,6...

is there other easy way to do this?

Thanks

zhihuifan
  • 1,093
  • 2
  • 16
  • 30
  • 1
    [`git rebase --interactive`](https://git-scm.com/docs/git-rebase#git-rebase--i) is the command you need. But it will produce the results you expect only if the history line you want to change is linear (i.e. no merges). – axiac Jul 17 '18 at 08:12
  • Thanks @axias, I did found git rebase -i before but don't know how to choose the p/r/e/s/f/x/d stuff in this case. do you mind to provide an example of this? – zhihuifan Jul 17 '18 at 08:36
  • @axiac how can you change commit contents when interactively rebasing? – evolutionxbox Jul 17 '18 at 08:36
  • 2
    It is explained in the documentation of [`git rebase`](https://git-scm.com/docs/git-rebase#_splitting_commits). You can also read [this answer](https://stackoverflow.com/a/27399649/4265352) that explains how to follow the recipe provided in the documentation using a GUI Git client. It explains how to split a commit but the concept is the same: you can use `git rebase -i` to ***modify*** a commit created in the past. – axiac Jul 17 '18 at 09:14

1 Answers1

0

Technically, you cannot modify any commit. Even git commit --amend does not do that. Remember that each commit is uniquely identified by its hash ID.

What you can do is take some existing commit, extract it, and use it to build a new, different commit that is almost the same as the original commit, but with whatever it is that you consider flawed, fixed. You must then convince everyone—yourself, and anyone who has cloned the commit—to stop using the old, flawed commit, using the shiny new one instead. The new commit has a new, different hash ID.

If the shiny new commit is not at the tip of a branch—if it's two steps back, as in this case, for instance—then you have an additional problem. Remember, branch names like master and develop identify one particular commit, which Git calls the tip commit of the branch. Each commit also contains the hash ID of its parent (previous) commit, and this is how Git finds the earlier commits from the latest:

...--D--E--F--G   <-- master
         \
          H--I--J--K   <-- develop

Here, the uppercase letters stand in for the big ugly hash IDs. The name master identifies commit G: the tip of master. The name develop identifies commit K, the tip of develop. From commit G, Git can work backwards to F, then E, and so on. From K, Git can work backwards to J, and eventually to E and so on.

If there is a problem in commit I, what you can do is have Git extract commit I, make some change to the index-and-work-tree pair (just like you would do for any other commit), and then make a new commit—let's call it I' to indicate that it's the replacement for I—whose parent is the same as I's parent:

...--D--E--F--G   <-- master
         \
          H--I--J--K   <-- develop
           \
            I'  <-- temporary

Having done that, we now just need to have Git copy J to a shiny new J' whose parent is I' and whose effect on I' is the same as J's effect on I:

...--D--E--F--G   <-- master
         \
          H--I--J--K   <-- develop
           \
            I'-J'  <-- temporary

Now we repeat the trick for K to make K':

...--D--E--F--G   <-- master
         \
          H--I--J--K   <-- develop
           \
            I'-J'-K'   <-- temporary

Finally, we tell Git: Drop the temporary name. Make the name develop identify shiny new commit K' instead of icky old K. This gives us:

...--D--E--F--G   <-- master
         \
          H--I--J--K   [abandoned]
           \
            I'-J'-K'   <-- develop

and if we stop drawing the abandoned branch it looks as though we have somehow changed three commits. But we have not: the new commits have new, different hash IDs.

...--D--E--F--G   <-- master
         \
          H--I'-J'-K'  <-- develop

This is what git rebase -i does. It sounds like a lot of work, and in some cases it can be, but usually git rebase -i makes it easy and painless for you.

Note that if you have pushed develop elsewhere, you must now force-push this new develop, to tell the other Git that received commits I, J, and K that it should forget those commits (and any later ones as well!) and instead use the shiny new replacements.

If other users have picked up the original I-J-K sequence, you must convince them to discard their I-J-K copies too. If they have built their own commits atop K, you have to get them to copy their old commits atop your shiny new K'. This can be noticeable amounts of work for all the other users, so be sure you really want to do this to them.

torek
  • 448,244
  • 59
  • 642
  • 775