bstar55
bstar55

Reputation: 3624

Does .gitignore have an equivalent for version controlled files?

This question is related to the following questions: https://stackoverflow.com/search?q=[git]+%2Bassume-unchanged, however, none of the provided answers to these questions seem to meet my needs.

I have a header file (key_info.h) that gets generated by a python script. To be clever, I version controlled a placeholder version of the header (key_info.h) with a single line of code:

int i = you_need_to_run_the_key_info_gen_python_script_to_generate_this_file();

Prior to running the python script, compiling this code will yield an error telling the developer exactly what to do; run the script.

Naturally, the developer will proceed by running the python script to generate the latest version of key_info.h and the code will successfully compile. The developer will then go on to make a series of changes and then 'git commit' their code.

The problem we run into is the fact that key_info.h has now been modified. As such, git will pick it up as a modification and will allow it to be committed. However, the generated version of this header should never be version controlled. We need the placeholder version to remain unchanged to continue to provide the compile error hint to developers.

My first instinct was to use .gitignore to prevent git from picking up key_info.h in a commit. .gitignore is then easily shared between developers since .gitignore itself is version controlled. Unfortunately, .gitignore only works for files that aren't version controlled.

My next thought was to use 'git update-index --assume-unchanged /path/to/file'. This works great locally, but I have no way of enforcing this rule upon other developers. It's only a matter of time before a dev accidentally commits the generated header.

So my question is this: is there a way to version control a file, while silently enforcing a rule that the file is to be ignored when it comes time to commit changes? What I really want is .gitignore for version controlled files.

Upvotes: 8

Views: 421

Answers (3)

jthill
jthill

Reputation: 60275

Some alternatives, ranked and tl;dr'd by some guy you've never met:

  • The best way to do this is to simply not track the file at all, because what you have is a makefile target. If you can't get or don't want a make ... well, any IDE can configure build recipes, right?

    @hvd has the best version of this one, I missed it at first: put generated headers in a .gitignored generated-headers directory you get your compiler to search first, and keep the default as backup.

  • If your build process isn't amenable to that, next best is the filter method @VonC describes, because it's robust against temporarily checking out a branch that doesn't have that file. The tl;dr on the filters is "preserve what's there":

    git config filter.pin-content.clean  'git show HEAD:%f 2>&- || cat'
    git config filter.pin-content.smudge 'cat %f           2>&- || cat' 
    
    echo anypatternyouwant filter=pin-content >>.gitattributes  # tracked, global
    echo anypatternyouwant filter=pin-content >>.git/info/attributes # local-only
    

    Note well: git will silently ignore filters that aren't configured, so giving a file a tracked attribute will work, it will have no effect on others until they want it to.

  • Next best is the canonical method, much the easiest to implement and works a treat so long as the file is tracked on all branches:

    git update-index --assume-unchanged key_info.h
    

    but when you temporarily switch to a branch that doesn't track that file it will lose this protection on return.

Upvotes: 3

VonC
VonC

Reputation: 1324268

This works great locally, but I have no way of enforcing this rule upon other developers

The other way is to declare a content filter driver which would, on git add, automatically restore your file to the appropriate content.

http://git-scm.com/figures/18333fig0703-tn.png

(From Pro Git book 7.2 Customizing Git - Git Attributes)

You can store and share:

  • the declaration of the filter in a .gitattributes file.
  • the 'clean' script which will restore the content

But: each user would still have to register that filter in their local config:

git config filter.<filtername>.clean ./<filterscript>

So there is no universal way to "ignore" a versioned file, only local solution to be applied in each repo.


Those same filter can help you:

  • version a key_info.h.tpl
  • generate on checkout (smudge script) a key_info.h which remains private

You don't always have to store a script, since it can be in the filter declaration itself, as jthill comments below:

for this, since 1.8.5 the filters get the pathname and you can make the filter setup self-contained.

For instance:

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

That will preserve the destination content if any else used what's supplied.
Then:

  • echo anypatternyoulike filter=pin-content >>.git/info/attributes to make the filter local, or
  • ...>>.gitattributes to make it global.

Upvotes: 1

user743382
user743382

Reputation:

but I have no way of enforcing this rule upon other developers

You do, almost: you could make your key-info-gen script run git update-index --assume-unchanged /some/path.

But I don't think this is a good idea.

I would probably opt for not overwriting the header file at all. Let your Python script generate the correct header file and write it into a different directory, and pass -I compiler options for both of those directories, in the right order. If the script has not been run, the version-controlled header will be found. If the script has been run, the user-generated header will be found. And you can then put the path of the user-generated header in .gitignore.

Upvotes: 3

Related Questions