Sean Anderson
Sean Anderson

Reputation: 29301

How can I mark a committed file as read-only in Git?

I have a file which is checked into Git. The file needs to hold an API key, but I do not wish to commit the API key to Git for security reasons. Instead of an API key, I put an explanation of how to generate an API key for each developer to use.

I don't want any developer to accidentally commit their API key and overwrite the basic file.

I have:

However, I just moved to my laptop, forgot to run the command, and accidentally committed a key to the repo. I'm looking for a more fail-safe method. In essence, I want to commit an initial version of the file to GitHub and then disallow modifications of that file.

Is this possible?

For reference, the file looks something like:

define(function () {
    //  The Simple API Access API key is used here.
    //  Please note that I've specifically omitted this key from GitHub for security. 
    //  Feel free to generate your own in order to use YouTube's API: https://code.google.com/apis/console/
    //  A valid key will look something like:
    //  Key for browser apps (with referers)
    //  API key: -------------------------------
    //  Referers: Any referer allowed
    //  Activated on:   Apr 6, 2014 2:46 PM
    //  Activated by:   ------------ – you
    //  NOTE: Please do not commit changes to this file once downloaded. CommandS:
    //  - Disable tracking: "git update-index --assume-unchanged src/js/background/key/youTubeAPI.js"
    //  - Enable tracking: "git update-index --no-assume-unchanged src/js/background/key/youTubeAPI.js"
    var key = 'API_KEY_MISSING';

    if (key === 'API_KEY_MISSING') {
        console.error('YouTube API key is not present.');
    }

    return key;
});

Upvotes: 33

Views: 29849

Answers (4)

Matt
Matt

Reputation: 4550

Another solution is to:

  1. Add your file to the repo by first renaming it (as a template), e.g. myFile_template.js
  2. Instruct developers to copy the template to myFile.js and edit the details
  3. Add myFile.js to .gitignore

Still not a very robust solution, as the developers can still commit the information you are trying to keep hidden, but at least the file won't appear in the status list.

Upvotes: 4

Kaz
Kaz

Reputation: 58598

If you don't want developers to put certain changes into the repository, a good solution is to lock the repository and have instead a review system in place like Gerrit. Individual changes are pushed into Gerrit instead and have to pass peer review.

Any commit which adds a file that ought to be local, such as an API key file or a compiled object file, can be rejected.

The developer can then revise the commit, rewriting it not to include the offending file, re-submit for review. When it passes review, it is cherry-picked into the appropriate target branch.

Upvotes: 0

jub0bs
jub0bs

Reputation: 66284

One possible approach would be to install some server-side hook (e.g. update) that would

  1. check, in pushed references, that the hash of the blob in question remains the same, and
  2. reject any push that contains references in which the hash of the blob in question changes.

Of course, such a server-side hook would not stop contributors from creating commits (locally, on their machines) that contain a different version of the file. However, after seeing a few of their pushes rejected, they would surely learn the lesson. :p

Upvotes: 11

torek
torek

Reputation: 488453

The answer to the implied subject line question ("can I make git check out a particular file as read-only") is "no, at least not directly", since git only stores a single permissions bit per file: executable, or not-executable. All other bits are set the same for each file.

That said, though, there are a few tricks using hooks. As some commenters suggested, you can test something in a server-side hook to prevent pushes. You can use smudge and clean filters. You can have a post-checkout hook set the file read-only.

The drawback to any and all hooks is that they must be set up for each repository, and users can override them (except for a server-side hook, presuming the user has no direct access to the server).1 This is also the advantage to a hook, although for naive users it's probably more drawback than advantage, since git itself won't set up the hooks automatically.

The post-checkout hook is probably the most obvious place to set file permissions, since git's documentation includes this bit:

This hook can be used to ... set working dir metadata properties.

Conveniently, the hook appears to always be run in the top level directory, regardless of where the user is, as long as the user is actually inside the git work tree.2 So this very simple hook suffices to change one file to read-only:

#! /bin/sh
# post-checkout hook to make one file read-only
chmod -w path/to/file

(on any system with chmod anyway, and remember to set the hook up as an executable file).

The user must put this hook into his/her repository, in .git/hooks/post-checkout (though you can commit the file itself into the repository, and then have the user copy or link it into place, perhaps through an auxiliary setup script).


1Thus, a server-side hook is the place to go if you want to strictly enforce a policy (this is true in general).

2That is, the following defeats the hook:

$ pwd
/home/user/dir/example
$ ls -l .git/hooks/post-checkout
-rwxr-xr-x  1 user  group  27 Dec 18 11:10 .git/hooks/post-checkout
$ cd /tmp
$ GIT_DIR=/home/user/dir/example/.git git checkout master

Here, the current working directory is just /tmp, and there is no way for the hook to figure out what it should be (you can read $GIT_DIR, but that's not necessarily helpful since the .git directory need not be directly connected to the work-tree in the first place, and that's what setting GIT_DIR is meant for in the first place).

Note that being in the work tree, in a sub-directory, does not defeat the hook; that's what I mean by "appears to always be run in the top level directory".

Upvotes: 15

Related Questions