gitgithooksgit-skip-worktree

Using skip-worktree on a file that is modified by git hooks?


I have a post-commit hook that edits a specific source file (specifically, writes commit hash to a string in c# class). Script is:

#!/bin/sh
hashStr=`git log --pretty=format:'%H' -n 1`
str="public static class GitHash { public static readonly string CommitHash = \"$hashStr\"; }"
echo $str > MyApp\\MyNamespace\\gitHash.cs

The same script is in my post-rebase, post merge, etc hooks. It was working fine and was showing up as modified in git until I added GitHash.cs file to skip-worktree:

git update-index --skip-worktree MyApp/MyNamespace/GitHash.cs

Now the file isn't being modified at all and stays in the state it was when I added it to skip-worktree. If I remove it from skip-worktree, hook works again (file is modified). If I am understanding skip-worktree correctly, it is ok to use it when I want to modify files locally, but don't want to accidentally commit them and don't want to see those files when i do git diff. However, it seems that somehow it's blocking the hook from editing this file?

So, if I want to have the file modified by hooks AND not showing up on git diff, what should I do?


Solution

  • Technically, the skip-worktree bit exists as a helper for sparse checkout. Using it the way you are is an abuse of the bit. However, the other bit, assume-unchanged, is a helper for slow lstat system calls, and while you can use it the same way, that's also a technical abuse. So you're kind of out of luck either way.

    It's generally best not to do this at all, if at all possible. For instance, if your build system can just create the appropriate information—writing it to a file that never gets committed—that's usually the way to go. (This is what Mark Adelsberger said.) You can, however, have your post-commit hook write to the file, yet have one or both of these bits set, which is what you are doing. It just means that the file doesn't go into a new commit: instead, the index hangs on to the old copy of that file.

    To get the updated file into a new commit, you must copy the file into Git's index before you make the new commit. But remember, every commit you make has a new, unique hash ID. This means you must store the hash ID before you know it.

    In other words, the hash ID you can store in a file that is committed is never the hash ID of the commit you can make that contains that file. You can get the one just before that. Perhaps that is sufficient: you could, before making the commit, store the hash ID of the current commit, after making sure it matches everything except the new-hash-ID-file:

    bit=skip-worktree
    # or assume-unchanged: either will work, though both are technically abuse
    
    git commit                    # make sure we commit what we have
    hash=$(git rev-parse HEAD)    # get the hash ID of the current commit
    echo "built from stuff that mostly matches $hash" > file.ext
    git update-index --no-$bit file.ext
    git add file.ext
    git update-index --$bit file.ext
    git commit -m "update build info"
    

    But you definitely can't store the hash ID of the commit that has the correct hash ID in the file, because you can't predict what it will be: storing the future hash ID in the file changes the hash ID of the future commit.1


    1The one possible exception here is if you manage to find a fixed point in the hash function.