Skip to content

Instantly share code, notes, and snippets.

@naviat
Created March 26, 2020 10:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save naviat/118d92de12507494cf312c5ce3ad58a9 to your computer and use it in GitHub Desktop.
Save naviat/118d92de12507494cf312c5ce3ad58a9 to your computer and use it in GitHub Desktop.

Mastering your history

Frequently rebasing from master

If you work on feature branches, like we do, that are based off a parent branch (e.g. the master branch of the main repository), you want to avoid your branch getting out of sync with the latest work in the parent branch.

As time goes by, your forked branch where you’re working can become significantly out of sync with the latest master branch. This may mean that merging your work when you’ve finished might become very tricky. You can avoid this by frequently running:

git pull --rebase upstream master

(where upstream is the name of the remote pointing to your central repository, and master is your parent branch).

This will grab any new commits from the master branch, and then add all the commits in your feature branch on top of them. This keeps everything in a clear logical order.

If you can, you should also run git pull --rebase upstream master just before your feature is merged into master. This will help the history in master stay chronological, rather than branching too often.

Commiting files explicitly

Try to avoid using git commit --all (or git commit -a). This creates a commit that automatically includes all existing changes for currently tracked files (it won’t add changes from files that aren’t yet tracked).

By doing this you miss a chance to consider how your work could be logically grouped into multiple commits. It can also easily lead to errors, because you might easily accidentally include changes you weren’t aware of, and forget to include new files that aren’t currently tracked.

Instead, try to get into the habit of checking which files are actually changed (with git status), and then adding files to your commits explicitly:

git add {file1} {file2}
git commit

Separating changes within the same file

You can explicitly choose which changes to add to a commit with the --patch command:

git add --patch {file}

This will open an interactive menu for each block of the diff on that file, so you can choose to add work to the commit bit by bit. This means, for example, that if you solved one problem at the top of the file and another problem at the bottom, you can easily add the first change to one commit and the second change to another.

You can even manually edit the diff to choose what to add line by line, or even change the diff completely.

Add changes to the previous commit

git add {file}
git commit --amend --no-edit

The --no-edit command means you don’t want to change the commit message. You can also omit this if you want to change the description of the commit.

Reordering commits with interactive rebasing

Let’s say:

  1. You do some work in users.js and you commit it with “Improve user logic”
  2. You do some work in README.md and you commit it with “Explain user logic better in README”
  3. Now you realise you need another fix to your users.js work. You want it to be added to the “Improve user logic” commit, so do this:
$ git add users.js
$ git commit -m '... to be rebased'
$ git rebase -i HEAD~3  # Interactively rebase the last 3 commits

This will open an editor with these contents:

pick 239d4c3 Improve user logic
pick adc7c21 Explain user logic better in README
pick fd4f81b ... to be rebased

Now if we reorder our commits to move line 3 to line 2, and replace “pick” with “squash” (or simply “s”), then it will “squash” the “… to be rebased” commit into the “Improve user logic” one:

pick 239d4c3 Improve user logic
squash fd4f81b ... to be rebased
pick adc7c21 Explain user logic better in README

Save and exit, and the commit to “squash” will be combined with the commit above it, to form a new commit. Another editor window will open for you can edit the commit message for the new commit:

# This is a combination of 2 commits.
# This is the 1st commit message:

Improve user logic

# This is the commit message #2:

... to be rebased

In this case we should just delete the second message, save and exit, and voila!:

$ git log
commit b87706ed594c983841857b51923e499988760d41 (HEAD -> master)
Author: Cody Pendant <cody@example.com>
Date:   Fri Dec 7 11:05:54 2018 +0000

    Explain user logic better in README

commit da1e9d47073cdc3bf86d3658b2e020c7e37292c0
Author: Cody Pendant <cody@example.com>
Date:   Fri Dec 7 11:05:40 2018 +0000

    Improve user logic

This process might seem complicated, but once you’ve done it a couple of times, you’ll see that it actually doesn’t take long. When you get used to it it will probably only take about 15-20 seconds.

There are of course some cases where commits can’t be neatly re-ordered (although fewer than you’d think). In these cases, after you perform your interactive rebase, you’ll get a conflict which you’ll have to resolve in the normal way. You’ll quickly get a feel for which commits can be reordered and which can’t, and if you think it’s going to be too much trouble, just don’t bother starting. Or git rebase --abort.

You can also use interactive rebasing to remove commits, or combine multiple commits into one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment