6

We've got a situation where a file in our repo should be locked in place, unchangeable going forward.

I've added the file to .gitignore but when I modify it locally git still tracks it-- my understanding now is that adding files/folders to .gitignore after they're already in the repo doesn't work and that one possibility is to use git rm --cached file to untrack them.

If I do, will the file no longer be available in the repo for the next person who clones it? Is there a better way to freeze this file in the repo?

larryq
  • 15,713
  • 38
  • 121
  • 190
  • So, backing up a step - locking the file is the solution to a particular problem you're trying to solve. What is that problem? That is, why does it need locked in place? – Don Branson May 07 '14 at 20:48
  • It provides a baseline configuration for our users, who can then customize it (font colors, etc) to their heart's content, but don't need to share with others. – larryq May 07 '14 at 20:50

5 Answers5

5

If you want to "freeze" the file (prevent further changes being tracked in Git), you can run git update-index --assume-unchanged somefile.

From the docs:

When the "assume unchanged" bit is on, the user promises not to change the file and allows Git to assume that the working tree file matches what is recorded in the index.

Further reading: git-update-index

NOTE: This does NOT prevent other people from changing the file. Everyone who clones the repo will have to run this command as well if you wish for the file to truly never change in Git.

Duncan Smith
  • 359
  • 4
  • 6
4

It's not too hard to configure your own repos so they will never commit a change to that path, other repos' owners will have to cooperate to the extent of executing some one-time config commands.

Here's what you do for your own repo:

  • set up a content filter that will not alter existing content either in the worktree or in the repository.

    git config filter.pinned-content.clean  'git show HEAD:%f 2>&- || cat'
    git config filter.pinned-content.smudge 'cat %f 2>&- || cat'
    

    git will execute the clean commands to generate the content to be put in the repo whenever you stage (git add) such files. Here, the commands try to show what's already committed for the file, and if that doesn't work, if nothing has been committed for it, then they'll take what you're staging. Likewise, git executes the smudge commands to generate the content that should be put in the worktree on checkout, here using whatever's already there as-is, otherwise taking the content from the repository as a default.

  • advertise that you want path/to/file treated this way

    echo path/to/file filter=pinned-content >>.gitattributes
    
  • make the treatment apply to all future checkouts/adds in this repo, even of already-existing commits

    mkdir -p .git/info
    echo path/to/file filter=pinned-content >> .git/info/attributes
    

Here's one way to distribute the request and instructions for cooperation:

# commit the new .gitattributes and explain what you need by way of cooperation:

git add .gitattributes
git commit -F- -- .gitattributes <<EOD
How to set up to preserve existing file content

# set up a content filter that will not alter existing content
# either in the worktree or in the repository:

git config filter.pinned-content.clean  'git show HEAD:%f 2>&- || cat'
git config filter.pinned-content.smudge 'cat %f 2>&- || cat'

# make the treatment apply to all future checkouts/adds in this repo

mkdir -p .git/info
echo path/to/file filter=pinned-content >> .git/info/attributes

-------    
Please do the above configuration to avoid mistakenly committing local changes
to files marked with the `pinned-content` filter.  Currently path/to/file is 
marked in the committed `.gitattributes` to be treated this way, you can locally
mark files to be treated this way for even preexisting commits by adding a 
similar line to your repository's `.git/info/attributes` file.
EOD

The %f capability was included in 1.7.4, so it's been four years, it seems reasonable to expect distros have that available.

You can get a very similar effect with git's sparse checkout, but that won't handle supplying default content and can't be enabled through .gitattributes.


This has passed smoketests and several hours of banging around, it doesn't look much like my first attempt at it.

jthill
  • 55,082
  • 5
  • 77
  • 137
2

git rm --cached file will indeed remove the file from the repository, so that in only exists as an untracked file in the current working directory. Future clones will not include the file.

Git doesn't really provide a way to freeze a file so that it cannot be modified. The best you can do is to create a pre-commit hook which resets the file prior to a commit, or preferably aborts the commit until you reset the file yourself.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thanks for (you and vonbrand both) confirming that, and for the pre-commit suggestion. I was wondering as I typed the question, 'is this even possible in a git repo?' – larryq May 07 '14 at 21:35
2

This did the trick (according to this) :

# Did some change in any file in the local repo
$ git commit anyFile
$ git update-index --skip-worktree myFile
$ git ls-files -v myFile
S myFile
$ git push

And the best part is I didn't even need to type the git update-index ... command on other PCs after the git pull

SebMa
  • 4,037
  • 29
  • 39
1

What .gitignore does is to tell git which files to ignore in the commands given, unless they are already part of the repository (or you ask it to override).

E.g. I have *.png in my .gitignore for the lecture notes I'm writing (some images get made into PNGs for inclusion), but the repository contains images that are original PNGs. No problem at all, old ones are tracked OK, ones created by rebuilding are ignored. Changing old.png is just a git commit -m "Old image was changed" old.png. If I want to add a new one, it's just an git add -f some-new.png; git commit -m "Some new image" away.

You can't just exclude files already in the repository by mentioning them somewhere, and excluding them forevermore from now on won't work anyway.

vonbrand
  • 11,412
  • 8
  • 32
  • 52