Read more

Git: Splitting up changes into several commits

Felix Eschey
July 26, 2023Software engineer at makandra GmbH

Splitting up commits makes the process of reviewing often easier, since you can create several merge requests or review every commit one by one.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

So when you find out that you have portions of the code that you initially didn't intend to change or when you do some refactoring along the current changes, you can use one of the following processes to split up the changes into several commits in a logical order:

#1 Splitting up the last n commits into m commits
#2 Adding changes to a previous commit
2.1 While adding new changes
2.2 Selectively reverting changes
#3 Splitting up previous commits (by moving changes or creating new commits)
3.1 Splitting up
3.2 Adding to later commits
#4 Reordering commits


#1 Splitting up the last n commits into m commits

If you decide to split up the n commits after you have finished working on a feature.

  • This process first reset all changes to the index and then removes changes from the index.
  • See the box below if you want to add from the working directory to the index.

Step by step:

  1. Use git reset --soft HEAD~n (or git reset --soft <commit hash>)
  2. Then remove file selectively from the index by using git reset -- <file path>.
    2.1 If you need to add specific changes selectively again you can use git add -p
  3. Then only commit the staged files
  4. Afterwards add the unstaged files to next commit
  5. Continue step 4 until you are finished

Only adding changes to the index instead of removing from the index

  • Depending on the number of commits and the amount of changes, it might be easier to only add changes from the working directory.
  • Then use git reset HEAD~n in step 1 and then only add the desired changes for every commit in step 2 instead of removing selectively.

#2 Adding changes to a previous commit

2.1 While adding new changes

  • In this scenario git fixup comes in handy.
    • This is mostly used when you are working on a branch and make a new commits which belongs to a previous commit.
  • If the previous commit is the last commit, you can also use git commit --amend
    • This is not recommended when the commit has already been pushed to a remote branch where other developers might have pulled in the meantime.

2.2 Selectively reverting changes from a previous commit

You can also use git fixup to selectively revert previous changes and add them to another commit

  • You can also use this process reset changes from a specific commit of a file and then add them into a fixup commit by using git reset <commit hash> <file path>.
  • Note that you have to reset to a commit before the commit were the change was introduced.
  • Also note that you can selectively undo commits by using git reset --patch <commit hash> <pathspec>...
  • If you use reset, you will need two fixup commits:
    • one that reverts the version of the reset commit
    • one which adds the changes to another commit

Let's say you have three commits a -> b -> c and want to reset changes of commit b and apply them to commit c

  1. Use git log --oneline to get the required <hash of commit a>
  2. Use git git reset:
git reset <hash of commit a> app/assets/stylesheets/pagination.sass

git status

  Changes to be committed:
    (use "git restore --staged <file>..." to unstage)
  	deleted:    app/assets/stylesheets/pagination.sass
  
  Untracked files:
    (use "git add <file>..." to include in what will be committed)
  	app/assets/stylesheets/pagination.sass
  1. Now use fixup to add the commit to delete the file to b
  2. Now add the untracked files (or unstaged changes) to the index with git add
  3. Now use fixup to add the the changes to commit c
  4. git rebase -i --autosquash master

#3 Splitting up previous commits (by moving changes or creating new commits)

Even though the process in #1 could be used for this it can become quite tedious if you have a large commit and might have already done this and just want to refine some changes afterwards.

  • Then this process is useful if you want to go through several commits and might want to apply changes from a previous commit to a later commit or ad d a new commit
  • Note that you could also use this to reorder the commits at the same time with #4.

3.1 Splitting up

If you just want to split up the last commit into several commits just follow the process in "How to split up a git commit"

3.2 Adding to later commits

  1. Use git rebase -i
  2. Mark all commits you want to edit with the flag e
  3. Follow the process in #1
  4. Redo the commit with the same commit message as before
  5. Add the unstaged files now to the stash with git stash
  6. Use git rebase --continue to edit the next commit
  7. Follow #1 again
    7.1 Optionally pop the stashed changes to the commit where you want to apply them with git stash pop
    7.2 You can also do this at the end if you want to add them into a new commit

#4 Reordering commits

If you are finished with splitting up, reordering the commits may give them a more logical appearance, especially if the changes depend on each other. Then:

Use git rebase -i HEAD~n for n being the number of commits to reorder.

  1. This will open the editor of your choice with the oldest commits being at the top and the newest at the bottom
  2. You can now reorder the lines as you wish, save and exit the editor.

Vim shortcuts to reorder

If you use vim these shortcuts will come in handy

  • ddp will move current line down
  • dd, moving the cursor, p will paste the line at the current cursor position
Felix Eschey
July 26, 2023Software engineer at makandra GmbH
Posted by Felix Eschey to makandra dev (2023-07-26 11:40)