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

Git

Git Problem Overview


I'm using git, and I'd like to be able to create a commit which isn't synced with the remote repository. Such a commit would have to "float" atop all other commits in the local repository to avoid affecting the history. I could use such a commit to store local-specific changes (configuration changes, debugging flags, local workarounds, etc.).

Currently, I manually rebase when I commit to reorder the commit back to the top, and I push using HEAD^ to avoid pushing the local changes. I've also considered putting the changes in the stash, but that's less convenient because it precludes normal use of the stash. Another alternative is to simply leave all those local changes unstaged, and use git add -p every time I want to commit. However, with a large number of trivial local changes, that becomes a hassle.

Here's an example of my current workflow:

My repository initially looks like

A---B---C---F master

where "F" is my floating commit.

I make a commit:

A---B---C---F---D master

then git rebase -i HEAD~2 to reorder:

A---B---C---D---F master

then git push remote HEAD~1... to push everything but the local F commit.

The change F contains changes to existing versioned files, and may contain an arbitrary amount of changes. (If I can make more than one commit "floating", that would be even better, since I could then separate my local changes).

Git Solutions


Solution 1 - Git

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.

Solution 2 - 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.

Solution 3 - Git

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.

Solution 4 - Git

At a previous job, everyone had their own local settings branch on which we committed our individual settings. This branch was based off master; any topic branches were branched off of settings. When topics were ready for integration, we rebased them onto master. This seems to be what @koljaTM is suggesting.

A---B---C  master
         \
          F  settings
           \
            D  topic

rebase --onto master settings topic

A---B---C  master
        |\
        | F  settings
         \
          D  topic

When new changes hit master, we would rebase settings of master, then rebase any topics we were working on off of settings.

Admittedly, this isn't a one-step "leave this floating forever" solution, but it is clean and simple enough.

Solution 5 - Git

Assuming that you have the local-specific changes in a file that does not contain other changes that you need to commit, you can just ask git to ignore the changes in that file, via git update-index. In particular, you need either --assume-unchanged or --skip-worktree; while they basically do the same thing (mark the file as unchanged in git index), they have some very subtle peculiarities that are well described here.

Solution 6 - Git

If it is actual commits you are trying to prevent from going forward, you are on the right track - use

git rebase -i

and reorder all your "personal commits" to be the latest commits, then

git push <remotename> <latest commit SHA you want to push>:<remotebranchname>

This will push all commits up to and including the SHA you provide but not the "personal commits" that come after it.

Solution 7 - Git

You can ignore certain files in git so they dont get pushed to the remote repository. The way i do things like that is to keep those files locally and dont push them to the repository by ignoring them.

Here's some explanation of gitignore.

https://help.github.com/articles/ignoring-files

Solution 8 - Git

I've come across the problem constantly, typically with splitting IDE settings and the like off from a shared code base. It's a common enough use case that you might think that it would have been solved by now, but not yet. I don't believe that git can handle this as you want.

The issue, boiled down to its essence, is that you want two checkouts to share a single directory. In order for this to work, there would need to be some data structure, manipulable by the user, tagging which files belong to which checkouts. And this isn't a 1-1 relationship, either; it's perfectly reasonable to have some file part of more than one checkout. In all cases you'd need some way naming the different checkouts to support disambiguation. For basic sanity, you'd need a directory lister that annotates files with their checkout associations.

As a particular illustration, consider .gitignore. That's a single file that applies to "all" checkouts, meaning implicitly "the unique checkout". In order to have .gitignore work well, you'd need one per checkout, a way to distinguish between the different files in each checkout. Yet there's no hint of this kind of infrastructure in git.

My "standard" way of solving such issues is to arrange things so it's one checkout per directory. I create a directory called "custom" (or the like). That directory goes into .gitignore in the parent. In the custom directory, I put a checkout from a different repository and/or different location. That means two commits to capture all changes; that's rarely a problem in practice.

If this inspires anyone to hack on git, add the option to make a "named checkout" and leave the existing behavior as an anonymous checkout. You can have [0..1] anonymous checkouts and [0..*) named ones. From that basis most everything else you might want follows fairly naturally.

Solution 9 - Git

I know this question has already been answered but I got similar issue and end up configuring my ~/.gitconfig to add three usefull commands. It might help some people if other solutions are not ok.

[alias]
	local-export = "![ ${#} -eq 1 ] && CURRENT_BRANCH=$(git branch --show-current) && git checkout -b \"local/${1}\" && git commit --allow-empty -m ${1} && git checkout \"${CURRENT_BRANCH}\" || echo 'You must provide <local_branch_name>' #"
	local-import = "![ ${#} -eq 1 ] && CURRENT_BRANCH=$(git branch --show-current) && git merge \"local/${1}\" --no-ff --commit --no-edit && git reset HEAD^ || echo 'You must provide <local_branch_name>' #"
	local-delete = "![ ${#} -eq 1 ] && git branch -D \"local/${1}\" || echo 'You must provide <local_branch_name>' #"
Those command allow to:
  • git local-export <name> will create a branch with a single commit containing what is currently staged. The branch will be named "local/<name>" and the commit message will be "<name>"
  • git local-import <name> will merge "local/<name>" into current branch with a single commit (no fast forward, no prompt, all automatic) and reset to initial state. This allow to import the local branch modification into the working directory without doing anything on the current branch history.
  • git local-delete <name> will force delete the branch "local/<name>", in case you want to clean up your local branches.

I hope this will be usefull for someone.

Thank you.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnneonneoView Question on Stackoverflow
Solution 1 - GitkoljaTMView Answer on Stackoverflow
Solution 2 - GitJonathan Klabunde TomerView Answer on Stackoverflow
Solution 3 - GitLukeView Answer on Stackoverflow
Solution 4 - GitBeyamorView Answer on Stackoverflow
Solution 5 - GitkaesoView Answer on Stackoverflow
Solution 6 - GitMichaelView Answer on Stackoverflow
Solution 7 - GitfonZView Answer on Stackoverflow
Solution 8 - Giteh9View Answer on Stackoverflow
Solution 9 - GitncenerarView Answer on Stackoverflow