Git wrap script to ignore files that have already been committed
Motivation
As a git user, given this sceniro:
You made certain changes to some files but you don’t want to commit it, nor do you want see them under
git status -s
orgit diff
etc.
In other words:
You want git to ignore certain files but unfortunately they have already been tracked.
what you gonna do?
I googled around a lot, all I can get is remove these files entirely from repo by run git rm --cached <file>
(so .gitignore
will take effect), but it’s not always doable, because your teammates may still need these files exist in repo.
Then I checked git hooks, hoping I could write some scripts to preserve these local changes before execute git status
and git diff
. Unfortunately I failed again, what git hooks can do is pretty limited, furthermore I realized not only do I need to ‘deceive’ git status
and git diff
, git add
git checkout
git pull
and a lot other commands also require the same trick.
Up to this point I had very little choices, either I figure out a way to make .gitignore
also ignore tracked files (git porcelain command git check-ignore
has a switch to do this), or write a script wrap git entirely. I chose the latter.
Usage
The code is here, you are very welcome to enhance or report issues, I’ll illustrate this script line by line in next section, but at first let’s see the usage.
- Download the script and put under $PATH directory and make it executable, make sure can run
gitw
directly from command line - You may have aliased common git command to a shorter command (e.g.
alias gpr='git pull --rebase'
) or shell plugins have done the same thing for you, in that case you need to putalias git='gitw'
in your shell initialization file. Also you probably need to givegitw
the same command line completion as original git, so putcompdef gitw=git
in shell init file too. - Add still-want-ignore-even-tracked files to
.gitignore
per git working directory, most likely you need to add .gitignore to.gitignore
as well
Explanation
This script should not work on git clone
or git init
, so it will pass command arguments directly to original git then exit if we are not under a git working directory.
This script preserves local change to a directory named w
under .git
, which will be created here if not exists.
In following steps we need to pattern match git command to decide whether we wrap specific git command or not, but you probably already alias git command to a shorter name (eg. co = checkout
, di = diff
). We extract original git command by parsing git <command_or_alias> -h
.
This function here identifies local changed files need to be ignored. We use porcelain version[0] of git status
(to list all local changed files) cross check with git check-ignore
(to check if each file is ignored) to get a list of files with local changes but we’d like to ignore.
Pay attention to git check-ignore
has a switch --no-index
, meaning not look in the index when undertaking ignore checks. This is exactly what we need for .gitignore
but only exists in git check-ignore
[1].
This script only works on these git commands.
If the current git command lies in commands we should wrap and there are local changes need to be ignored, this script should do the job. At first we copy all the target files to .git/w
with directory structure perserved, then drop local changes for these files before execute original git, finally we recover local changes by copy target files from .git/w
to workspace.
References:
- [0] http://git-scm.com/book/en/Git-Internals-Plumbing-and-Porcelain
- [1] http://git-scm.com/docs/git-check-ignore.html
### Update 2016-01-31 22:22:22:
Thanks to teammate @SuXiaoKai, git update-index --assume-unchanged <path>
can do this job pretty well.