Do git update-index --assume-unchanged rules propagate to clients on pull?

When working with npm

As previous answers have explained, it is not possible to propagate git update-index --assume-unchanged through git.

This being said, when working with npm, you can add a postinstall hook which updates the index:

{
  "scripts": {
    "assume-unchanged": "git update-index --assume-unchanged file",
    "postinstall": "npm run assume-unchanged"
  }
}

This will be run on every npm install.


The index is local to your workstation, any changes you make there do not propagate to other clones of the same remote.

(updated, after question was updated)

The .gitignore file

There are instances where I can't use a .gitignore file, otherwise, on git push, critical files are purged from the remote.

This is not true. The git ignore file does not affect files that are already tracked in the repository. If you add a file to .gitignore after it has been committed, it'll stay inside the repository; it will not be purged. In fact, it behaves pretty much as if it weren't ignored at all.

You can easily check this in a temporary repository:

$ mkdir -p /tmp/repo
$ cd /tmp/repo
$ git init
Initialized empty Git repository in /tmp/repo/.git/
$ echo red > a.txt
$ git commit -am '1'
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt
$ echo a.txt > .gitignore
$ echo b.txt >> .gitignore
$ git commit -am '2'
 1 file changed, 2 insertions(+)
 create mode 100644 .gitignore

The repository now contains two files: a.txt and .gitignore. Both behave normally, which you can see when you clone it:

$ cd ..
$ git clone file://repo repo2
$ ls -A repo2
.git       .gitignore a.txt
$ cd repo

If we modify both ignored files and request git status, we'll see that a.txt is seen as modified, despite having been gitignored. We can add and commit it as normal; in fact, if you add an tracked file to gitignore, it behaves pretty much like it's not in gitignore at all.

$ echo green > a.txt
$ echo blue > b.txt
$ git status --short
 M a.txt
$ git add a.txt

The b.txt file is different, because it was ignored before git started tracking it. This file will not normally make it into the repository, but we can force it if we want to.

$ git add b.txt
The following paths are ignored by one of your .gitignore files:
b.txt
Use -f if you really want to add them.
fatal: no files added
$ git add -f b.txt

Issuing git commit now commits two files that have both been git ignored:

$ git commit -m '3'
 2 files changed, 1 insertion(+), 1 deletion(-)
 create mode 100644 b.txt

Long story short, think of git ignore rules as guidelines :-)

Propagating assume-unchanged

After applying the assume-unchanged rules and calling git push, will these rules be attached to the remote branch so that all subsequent pulls (from other clients) will inherit them? Or, must these clients also run the git update-index --assume-unchanged commands individually at their machines?

The latter. Closest you can get is add a shell script to the repository that makes the changes for you.

The server hook?

If the commands are not inherited -- has anybody written a server hook for this before? Instead of mandating that all current and future clients safeguard against it?

If your aim is to write a server hook that removes the critical files as if they weren't part of the push at all, that's not possible. Git push deals primarily with commit objects (and refs). Their dependent objects, like trees and blobs, are transferred as needed, subject to reachability from a commit. What it boils down to is that, if it's not committed, you can't push it to a remote (that's an oversimplification, but it holds true for the files in the repository). In addition, git commits are cryptographically guarded. You can't change a commit without changing its commit hash, and if you change the commit hash, you basically have a different, new commit (that may happen to have the same diff as the old one).

This means that the server can't rewrite commits; at least not without seriously confusing the client that did the push (which will still have a copy of the old commit objects).

What you can do is write a post-receive hook that refuses the commits if they contain the files you don't want to have updated. This doesn't really solve your problem, because if you have trouble explaining git commit --assume-unchanged to your coworkers, then you'll likely have even more trouble explaining how they can use interactive rebase to recreate their commits without the undesirable files in them.

Long story short, I think chasing everybody to keep using assume-unchanged (maybe combined with a post-receive hook) is your least bad choice if you're dealing with files that should be committed once and never thereafter, like you have now.

A possible work-around

Your life would become a whole lot easier if you can keep these files out of git in their entirety. One of the things I can think of:

  • move the files within the repository to a lib directory, where they don't get modified all the time
  • .gitignore the files in their definitive location, the one where they have to be but get unwanted changes all the time
  • add an "init" script to your repository that people need to run once after cloning and before starting work. This script copies the files into the right location.

One possible solution is to write a client-side hook to prevent people from committing local changes to the files you want to ignore.

These hooks are scripts that run upon certain events in git, like pre-commit, post-commit, pre-push, etc. You can see the samples in <your_repo>/.git/hooks/.

The bad news is that they are also not under version control, so you have to write your own setup script to copy the hook script to .git/hooks/.

Here is a small sample of a pre-commit hook that prevents the forbidden.txt file to be committed (it can be adapted to take a list of files):

ROOT_DIR="$(pwd)/"
LIST=$(git diff --cached --name-only --diff-filter=ACRM)

for file in $LIST
do
    if [ "$file" == “forbidden.txt” ]; then
        echo You cannot commit file $file. Please reset it and try again.
        exit 1
    fi
done
exit 0

Tags:

Git

Gitignore