I create software for fun and money.

How to create temporary commits in Git

Sun, May 08 2022

We've all been there. You're working on a new feature. It's not ready yet, but you've written a substantial amount of code which you don't want to lose.

You think of pushing it to your remote repository, but the code isn't polished. Besides, the damned thing doesn't even work properly (yet).

But again, you don't want to risk losing it.

What should you do? Here's a thing you could try (or at least, this is what I do, anyway):

  • Stage all the files you wish to commit
  • Commit them with a message reading "Checkpoint"
  • Push the commit

Now you're safe. Your work is checked-in in the remote repository.

You can now continue working on that killer feature your boss told you needs to be finished yesterday.

Once your feature is finished and tested (You test your code, right? Don't look away. Look at me. You test your code, right?), you can now stage all the files you wish to commit.

But instead of regularly committing them, run:

git commit --amend

What happens with the --amend flag is that you change your last commit (the one with the "Checkpoint" message). The old commit gets replaced with a new one, with its own commit ID.

Once you've named your new commit, you can push it:

git push origin <your_branch>

However, the commit gets rejected:

 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:kormosi/temporary-commit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

This happened because the histories between your local branch and its remote counterpart are divergent.

To make the remote accept the changes, you have to force the push:

git push --force

This comes with a drawback, though. If some poor soul based their work off of your "Checkpoint" commit, their history will now be all messed up.

If your branch has been merged into main, they won't be able to simply merge their branch into main. They will need to rebase on top of main and (probably) resolve a bunch of conflicts.

But Patrik, what if one "Checkpoint" commit isn't enough for me?

Don't worry. I heed your call.

You can either amend the first "Checkpoint" commit without editing it's message:

git commit --amend --no-edit

Or, if you prefer more granularity, you can add multiple "Checkpoint" commits with the same message:

git commit -m "Checkpoint"

Then, once you've finished the feature, you can perform an interactive rebase.

Run git log. If you've created multiple "Checkpoint" commits, you'll see something like this:

commit 1a5a4c1ffc4d54b25a86f1584209074b27c13ac9 (HEAD -> feature-branch)
Author: Patrik Kormosi
Date:   Sun May 8 21:23:27 2022 +0200

    Checkpoint

commit 5bd3166ac482025ee4e20d7315424d7d2e88f8c8
Author: Patrik Kormosi
Date:   Sun May 8 21:23:18 2022 +0200

    Checkpoint

commit 9ef921da2d149576dc3cd7051f7576272ad1c668
Author: Patrik Kormosi
Date:   Sun May 8 21:23:09 2022 +0200

    Checkpoint

commit 9ba3a9b401415b5853ec2b479ac58d53c1eb7cd4
Author: Patrik Kormosi
Date:   Sun May 8 21:16:25 2022 +0200

    Base commit  

The 9ba3a9b commit is the one you've based your work off of.

Copy the hash of the Base commit. Run git rebase -i <commit_hash>. You'll be presented with this file to edit:

pick 9ef921d Checkpoint
pick 5bd3166 Checkpoint
pick 1a5a4c1 Checkpoint

# Rebase 9ba3a9b..1a5a4c1 onto 9ba3a9b (3 commands)
#
# Commands:
# p, pick  = use commit
# r, reword  = use commit, but edit the commit message
# e, edit  = use commit, but stop for amending
# s, squash  = use commit, but meld into previous commit
# f, fixup [-C | -c]  = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec  = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop  = remove commit
# l, label 

Change the pick command on the first commit to reword. Change all the other pick commands to squash:

reword 9ef921d Checkpoint
squash 5bd3166 Checkpoint
squash 1a5a4c1 Checkpoint

...

This will change the commit message of the first commit. Then it will "meld" all the subsequent commits into that first commit.

Save the file. This will open another editor. Here you can reword the first commit's message. Rename it to your liking and save the file.

You'll be presented with yet another editor window (this is the last one, I promise):

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

Fancy new feature

# This is the commit message #2:

Checkpoint

# This is the commit message #3:

Checkpoint

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun May 8 21:23:09 2022 +0200
#
# interactive rebase in progress; onto 9ba3a9b
# Last commands done (3 commands done):
#    squash 5bd3166 Checkpoint
#    squash 1a5a4c1 Checkpoint
# No commands remaining.
# You are currently rebasing branch 'feature-branch' on '9ba3a9b'.
#
# Changes to be committed:
#	modified:   file.txt
#

You can get rid of the "Checkpoint" commit messages:

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

Fancy new feature

# This is the commit message #2:

# This is the commit message #3:

...

Save the file and exit the editor.

Running git log will show you your new, beautiful history:

commit 7986dae7c2c2811f638100b47623a85a3bd02f8d (HEAD -> feature-branch)
Author: Patrik Kormosi
Date:   Sun May 8 21:23:09 2022 +0200

    Fancy new feature

commit 9ba3a9b401415b5853ec2b479ac58d53c1eb7cd4
Author: Patrik Kormosi
Date:   Sun May 8 21:16:25 2022 +0200

    Base commit

This also comes with the identical drawback as the previous (git commit --amend) approach: you change the history.

This is no bueno if somebody else based their work off of your "Checkpoint" commit (and the branch it resides on).

That's it folks. This is all I had to say. If you have a better solution for this problem, I'll be happy to hear about it! Drop me an email at patres dot kormi at gmail dot com.