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 -sor
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 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.
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
gitwdirectly 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 put
alias git='gitw'in your shell initialization file. Also you probably need to give
gitwthe same command line completion as original git, so put
compdef gitw=gitin shell init file too.
- Add still-want-ignore-even-tracked files to
.gitignoreper git working directory, most likely you need to add .gitignore to
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
.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 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
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.
-  http://git-scm.com/book/en/Git-Internals-Plumbing-and-Porcelain
-  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.