Search code examples
gitrebasegit-rebaseundo

git squash changes for one file


I have several commits that do ineffective changes to a file but some of them are combined with relevant changes to another file. So I would like to just squash the changes to the one file. How can I do that?

Example: The following should symbolize the commits and the [......] the content of the file.

This is what I have:

  • commit 5 file A: [.....] file B: [......]
  • commit 4 file A: [ ] file B: [.... ]
  • commit 3 file A: [.....] file B: [... ]
  • commit 2 file A: [ ] file B: [... ]
  • commit 1 file A: [.....] file B: [.. ]

The diff would be:

  • commit 5 file A: [+++++] file B: [....++]
  • commit 4 file A: [-----] file B: [...+ ]
  • commit 3 file A: [+++++] file B: [... ]
  • commit 2 file A: [-----] file B: [..+ ]
  • commit 1 file A: [+++++] file B: [++ ]

In this case file A at commits 5, 3 and 1 is the same.

This is what I want: So I would like to squash it to have.

  • commit 5 file A: [.....] file B: [......]
  • commit 4 file A: [.....] file B: [.... ]
  • commit 2 file A: [.....] file B: [... ]
  • commit 1 file A: [.....] file B: [.. ]

The diff would be:

  • commit 5 file A: [.....] file B: [....++]
  • commit 4 file A: [.....] file B: [...+ ]
  • commit 2 file A: [.....] file B: [..+ ]
  • commit 1 file A: [+++++] file B: [.. ]

Is there any way to do that.

Edit: Maybe I was not very clear in my question. I know how to do interactive rebasing and squash in general. It is more about the fact, that I want to only "squash" the ineffective changes of file A, while I preserve the changes on file B. (Doing this would result in commit 3 doing nothing, and thus it is removed.)

The problem I'm facing with "normal" rebasing would be, if I just squash all commits from 1-5, I would end up with the result I want for file A, but all intermediate changes to file B are lost.

I have added a representation of diff to maybe better depict the situation.


Solution

  • There will be multiple interactive rebases. To sum up, you should edit commit 4 and commit 5 while interactive rebasing. Let me explain:

    You already found that squashing commit 2 and commit 3 is the first step. When we just do that, the beginning of the commit history will be like after first interactive rebase:

    • commit 2 file A: [.....] file B: [... ]
    • commit 1 file A: [.....] file B: [.. ]

    Well, let's analyze the scenario. Your file B changes will be kept, the same as commit 4 and commit 5. However, we need to get rid of file A changes. This is where we will get help from edit option in interactive rebase.

    While interactive rebasing, select edit option for commit 4 and commit 5. While editing state for both commits, seperate file A changes from the commit. So a sample history will be look like after second interactive rebase:

    1--2--4A--4B--5A--5B
    

    that 4A only includes changes in file A at commit 4. Then final rebase; move 5A commit next to 4A commit and squash two commits into commit 2. And magic, because they are opposite commits, they will be disappeared. And commit history will look like after third interactive rebase:

    • commit 5B file A: [.....] file B: [......]
    • commit 4B file A: [.....] file B: [.... ]
    • commit 2 file A: [.....] file B: [... ]
    • commit 1 file A: [.....] file B: [.. ]

    as you wanted.