43

There are instances where I can't use a .gitignore file, otherwise, on git push, critical files are purged from the remote. In these cases, I apply git update-index --assume-unchanged <file> to the files that I want to ignore.

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 <file> commands individually at their machines?

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?

Barend
  • 17,296
  • 2
  • 61
  • 80
Jordan Arsenault
  • 7,100
  • 8
  • 53
  • 96
  • It seems like you should ask a new question (after working on it on your own first of course :-) ), something along the lines of: "in a git pre-receive or update hook, how can I reject a push that includes commit(s) that modify or remove a specific file/set-of-files/..." (define problem as well as possible here). – torek Sep 02 '13 at 20:14
  • I am in a similar situation where I have a set of files that are version controlled that means they are in the repo (required for initial setup and allowed for customization) but they do not need to be tracked after clone. I wish just like gitignore for untracked files, if GIT also had a "Forget" feature for the tacked files. The suggested alternative is hooks (that run assume unchanged) but they need to be manually delivered for each cloned repository. I feel "Forget" will be a useful feature that will allow ignoring tracked files. – MG Developer Jun 12 '17 at 15:46
  • I asked this question 7 years ago. It's gained a non-trivial upvote & bookmark following. But, as the author, 7 years in the future, I must admit, I have no idea what this even means, or why I needed this information, then. – Jordan Arsenault Jun 27 '20 at 00:15
  • @JordanArseno 2021 is here. I need exactly the same: temporary changed "template"-like file (during the build) should not be committed. I want to propagate to other users in the team the same `git update-index --assume-unchanged ...` w/o to ask 'em to run it. Maybe your idea was the same – RandomB Apr 22 '21 at 06:43

3 Answers3

59

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.
Barend
  • 17,296
  • 2
  • 61
  • 80
  • Thx. Updated my question as well, asking if there are hooking solutions whereby I can suppress specific directories from being updated by `git push`... – Jordan Arsenault Sep 02 '13 at 11:03
  • I've updated my answer based on my understanding of your updated question. It's gotten a bit bigger than I expected, I hope you find it helpful. – Barend Sep 03 '13 at 10:59
  • The third point is a great idea - this will allow you to copy a set config fiel for example, whilst still being able to keep local config changes. This is especially useful for remote bare repos where you can't use the skip-worktree command to ignore said files – JamesG May 24 '17 at 12:48
4

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.

Pierre Spring
  • 10,525
  • 13
  • 49
  • 44
1

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
Roberto
  • 11,557
  • 16
  • 54
  • 68
  • An alternative to the copy the hooks would be to symlink a directory (so you don't have to copy them again if they change). There is a thread discussing this: https://stackoverflow.com/questions/3462955/putting-git-hooks-into-repository – Maurice Müller Dec 14 '17 at 08:34