How to do a "local-only commit" in git?

So, it sounds like you want two things:

  • Some commits should be kept private (on a local branch, for example) and never pushed or merged when you pull; they should remain "after" the shared commits.

  • This should be mostly transparent to you; you want to work on master, and have the local branch maintained automatically. You just decide which commits should be local, and commands that interact with remote repositories will ignore those.

So you want to write a script (name it git-something and put it on your path, so it's an extra git command) to identify and deal with these commits. You need some trigger for the script to recognize the local commits by. The easy way to do this is to put a magic word in the commit description -- one you'll never use in a real/shared commit -- for the script to recognize. (If that's too flaky-sounding for you, you could also use a special file in the commit's tree, like .THIS_COMMIT_IS_LOCAL_ONLY; I haven't done that in the examples because it's a little harder.)

You'll need a command to make a local commit from the current index/workdir; this is easy, it just calls git commit $@ -m "__LOCAL_COMMIT_ONLY__" (that's an example; the point is it does something to mark the commit being created as local-only, and then defers to git commit). You'll also need a command to temporarily pop all the local commits, do some other git command (pull, push, fetch, merge, whatever), and then reapply the local commits. You will also use this command for creating local commits that you do intend to share, so that they always appear "under" the local-only commits in your history.

Here's one example script that gives you both in one:

#!/bin/sh
if [[ $1 eq 'new' ]]; then
  shift
  exec git commit $@ -m "__LOCAL_COMMIT_ONLY__"
elif [[ $1 eq

OLD_HEAD=$(git rev-parse HEAD)
OLD_REAL_HEAD="$(git rev-list HEAD --grep=__LOCAL_COMMIT_ONLY__ | tail -n1)^"
git reset --soft $OLD_REAL_HEAD
git $@
git rebase --onto HEAD $OLD_REAL_HEAD $OLD_HEAD

Now, assuming you call the script git-local, use git local new to create a new local-only commit from the index (or git local new -a to create on from modified files in the workdir), git local commit (the name is imperfect, sadly) to create a new "real" commit, git local push to push, git local pull to pull, etc.

The primary downside of this is that it requires you to remember that most commands now get prefixed with local. If you forget to do this once, you're a little bit hosed, but not too badly -- a quick git rebase -i will allow you to easily move your local commits back to the top and you're off and running again. The biggest risk is that you accidentally use git push instead of git local push and send all your private changes upstream, which will annoy everyone. For that, you might want to actually write a little wrapper script to invoke instead of git itself (call it ~/bin/git and make sure ~/bin is on your path):

#!/bin/sh
if [[ $1 = 'push' ]]; then
  if /usr/bin/git rev-list HEAD --grep=__LOCAL_COMMIT_ONLY__ | grep -q .; then
    echo "Can't push with local changes still active!"
    echo "Try using `git local push' instead."
    exit 1
  fi
fi
exec /usr/bin/git "$@"

You could also make a pre-receive hook on the server that automatically rejects any commit containing __LOCAL_COMMIT_ONLY__ in its message.


You could use a patch file, which can be generated from Git.

# git diff > local.patch

When you want to commit, reverse patch:

# git apply -R local.patch

After the commit, restore patch:

# git apply local.patch

You just have to make sure only your local changes are on the working tree before creating the patch file. After you create the patch file, you could add it to .gitignore so you don't commit it.


How about putting those changes into a local branch which you rebase/merge regularly from your main dev branch? That way there wouldn't be any danger of committing them upstream.

Tags:

Git