1

Assume I have this history log:

* hash8 (HEAD -> branch_4, origin/branch_4) Message 8
|
* hash7 Message 7
|
* hash6 (origin/branch_3, branch_3) Message 6
|
* hash5 Message 5
|
* hash4 (origin/branch_2, branch_2) Message 4
|
* hash3 Message 3
|
* hash2 (origin/branch_1, branch_1) Message 2
|
* hash1 Message 1

And I want to do some code changes to commit with hash4 and history to look the same. Please note that I have branches in between too, not only commits. How would you do that?

Damia Fuentes
  • 5,308
  • 6
  • 33
  • 65
  • Does this answer your question? [How to modify a specified commit?](https://stackoverflow.com/questions/1186535/how-to-modify-a-specified-commit) – LLSv2.0 Mar 24 '20 at 15:29
  • 1
    I already tried that. But the branches are not preserved. Instead, by doing exactly that you end up with two paths: one with the original commits and branches, and another one with only one branch and all the commits with the new changes on hash4. – Damia Fuentes Mar 24 '20 at 15:44

1 Answers1

0

You cannot do this at all. No part of any commit can ever be modified.

You can do something different, that may—or may not—be good enough.

Given any Git commit, you can:

  • extract that commit, so that you can work on it
  • do some work
  • make a new commit, which will have a different hash ID

and having made that new commit, you can then repeat this. So, suppose you have this series of commits:

A--B   <-- branch_1, origin/branch_1
    \
     C--D   <-- branch_2, origin/branch_2
         \
          E--F   <-- branch_3, origin/branch_3
              \
               G--H   <-- branch_4, origin/branch_4

(this is the same graph you drew, except that I've used uppercase letters to stand in for the hash IDs, and put newer commits towards the right, rather than towards the top). It is completely impossible to change any of the existing commits, but we can extract D to a work area, then make changes, then make a new and improved D':

A--B   <-- branch_1, origin/branch_1
    \
     C--D   <-- branch_2, origin/branch_2
      \  \
       D' \  <-- [remember this hash]
           \
            E--F   <-- branch_3, origin/branch_3
                \
                 G--H   <-- branch_4, origin/branch_4

Now that we have D' we copy E atop it, changing one thing—its parent—so as to make E', the same way we copied but changed D to make D':

A--B   <-- branch_1, origin/branch_1
    \
     C--D   <-- branch_2, origin/branch_2
      \  \
       D' \  <-- [remember this hash]
        \  \
         E' E--F   <-- branch_3, origin/branch_3
                \
                 G--H   <-- branch_4, origin/branch_4

We'll need to copy F to F' and G to G' and H to H' as well, for a total of five commits copied to new-and-improved commits:

A--B   <-- branch_1, origin/branch_1
    \
     C--D   <-- branch_2, origin/branch_2
      \  \
       \  \  <-- [remember this hash]
        \  \
         \  E--F   <-- branch_3, origin/branch_3
          \     \
           \     G--H   <-- branch_4, origin/branch_4
            \
             D'  <-- new_branch_2
              \
               E'-F'  <-- new_branch_3
                   \
                    G'--H'  <-- new_branch_4

We now have new branches, which are "just as good" as the old ones (because they have commits that are as good or better, but with different hash IDs). Now we must discard the old branch names in favor of the new ones, perhaps by renaming the old branches to old_* and the new ones to take out the new_.

(Our old_* names are no longer very useful so we can delete them entirely, sooner or later.)

Finally, our remote-tracking names origin/* are remembering branch names as stored in some other Git, so now we must convince that other Git repository to jettison its copies of those commits and start using our new copies. To do that, we will need to use git push --force or something similar to send our new commits to that other Git and tell it abandon all the work you used to have, in favor of these new and improved commits. For instance:

git push --force-with-lease branch_2 branch_3 branch_4

would do the trick (and the --force-with-lease would make sure that their hash IDs, which are in our origin/* names, match up with what we think they do).

To achieve this in realistic situations, use git rebase or git filter-branch or similar

When the number of commits to "improve" like this is small, you can do it one commit at a time, using git cherry-pick, leaving yourself markers: here is where new_branch_3 goes for instance. When it's large, this is painful. You will want to automate the process.

Unfortunately, this is tricky. Fortunately, there are several power tools for this, including git replace. This lets you insert a graft. However, grafts do not transfer on clones: whoever clones your repository sees the original, un-grafted history. Fortunately, git filter-branch or the new git filter-repo can then transform the grafted clone into a clone with a new series of commits, updating branch names en-masse. See other StackOverflow answers for details.

torek
  • 448,244
  • 59
  • 642
  • 775