96

I'd like to add an automatically generated file to the same commit using a pre- or post-commit hook in Git, dependent on the files that were modified in that commit. How would I go about this?

I've tried this as a pre-commit hook, but no luck:

#!/bin/sh
files=`git diff --cached --name-status`
re="<files of importance>"
if [[ $files =~ $re ]]
then
  echo "Creating files"
  exec bundle exec create_my_files
  exec git add my_files
  exec git commit --amend -C HEAD
fi

This successfully adds them to the repository, but does not add them to the commit. I've also tried using the last two exec lines in a post-commit hook along with the pre-commit inspection, but no good either.

Josh Lee
  • 171,072
  • 38
  • 269
  • 275
Ian Terrell
  • 10,667
  • 11
  • 45
  • 66
  • 2
    You need to remove the `exec`s from this code (see `man sh`). It's impossible for any shell command to be executed after an `exec`, since `exec` clobbers the current process, i.e. the shell that is used to interpret commands. – Martin Jambon Mar 19 '18 at 20:15

10 Answers10

74

Since git add was also not working for me in a pre commit, I followed mark's idea of using a .commit file and splitting the process into pre- and post-commit.

Here is some code that should be easy to understand

In the pre-commit:

  • Touch a file .commit or something. (be sure to add this to .gitignore)
#!/bin/sh 
echo 
touch .commit 
exit

In the post-commit:

if .commit exists you know a commit has just taken place but a post-commit hasn't run yet. So, you can do your code generation here. Additionally, test for .commit and if it exists:

  • add the files
  • commit --amend -C HEAD --no-verify (avoid looping)
  • delete .commit file
#!/bin/sh
echo
if [ -e .commit ]
    then
    rm .commit
    git add yourfile
    git commit --amend -C HEAD --no-verify
fi
exit

Hope this makes it easier for people with few bash knowledge to follow mark's idea.

Martin Schneider
  • 14,263
  • 7
  • 55
  • 58
bitluck
  • 749
  • 5
  • 2
  • 5
    Shouldn't `-a` be `-e` to test if `.commit` exist? – Albert Netymk Aug 17 '15 at 10:08
  • 4
    @AlbertNetymk - there is practically no difference; the `-a` primary was introduced as a KornShell proposal and made its way to POSIX, it's only supported today for backwards-compatibility. later `-e` was added to avoid confusing the `-a` *primary* with the `-a` *binary* operator. – Eliran Malka Mar 28 '17 at 12:38
  • 1
    @EliranMalka Could you provide a ref? I have trouble locating `-a`. https://linux.die.net/man/1/test The only use I found is for logical `and`. – Albert Netymk Mar 29 '17 at 14:36
  • 1
    @AlbertNetymk, sure, here is the [POSIX `test` command man page on unix.com](http://www.unix.com/man-page/posix/1p/test/). search the page for the term *"An early proposal used the KornShell -a primary"* – Eliran Malka Mar 29 '17 at 15:02
  • 1
    @EliranMalka I see. Thank you very much. `-a` should probably never be used other than as a boolean operator just to avoid the confusion. – Albert Netymk Mar 30 '17 at 13:46
  • I don't get the purpose of .commit here -- from messing with this it seems like post-commit will only run after a commit is complete making the .commit file irrelevant. Maybe you meant the .commit file to indicate a special state like "cleanup needed"? – jcollum Mar 09 '20 at 21:39
  • 3
    @jcollum: Apparently, the `.commit` file prevents an infinite loop. Specifically, `--no-verify` doesn't prevent the `post-commit` hook from running, so the post-commit runs twice: once when the `.commit` file is present, which then runs `git commit --amend …` which triggers the post-commit hook again, but this time with the `.commit` file being gone, which ends the loop. – Gabriella Gonzalez Mar 21 '20 at 16:36
  • 1
    This scheme works for me, with the critical modification that I had to move the `git add` to the `pre-commit` hook instead of the `post-commit` hook. I'm using git version 2.25.1 on macOS. – mhucka May 05 '20 at 00:48
40

It's possible to do what you want using pre-commit hooks. We do something similar for a heroku deployment (compiling coffeescript to javascript). The reason your script isn't working is because you used the exec command improperly.

From the man page:

The exec builtin is used to replace the currently running shells process image with a new command. On successful completion, exec never returns. exec can not be used inside a pipeline.

Only your first exec command is running. After that your script is basically terminated.

Give something like this a try (as a pre-commit hook):

#!/bin/sh
files=`git diff --cached --name-status`
re="<files of importance>"
if [[ $files =~ $re ]]
then
  echo "Creating files"
  bundle exec create_my_files
  git add my_files
fi
Jim Garvin
  • 4,876
  • 2
  • 23
  • 18
  • This is not working in recent git (I use 1.7.9), git add in pre-commit adds the files for future commit, not the one you just engaged. – Rubycut May 11 '12 at 07:26
  • 4
    Works for me in git 1.7.10. The files are not added to the commit message, but they are commited. It seems that the 'git status' in the commit message is generated before pre-commit. This seems like a bug to me, but I suspect it was done on purpose for some reason. What you can do is add a line to the end of pre-commit that looks something like 'git status; echo -n Hit enter to continue...; read'. There is likely a better way to solve this, but that was a quick fix for me. – ben May 14 '12 at 17:53
  • I forgot mention, I have git version 1.7.7. This feature is available only version 1.7.10+ according to previous comments. – inf3rno Mar 09 '13 at 15:08
  • 1
    I have git 1.8.3.4 and whatever is added in pre-commit hook doesn't appear to be staged until the next commit. – jbasko Feb 11 '14 at 09:45
  • 4
    Actually, I can confirm this **works** in git 2.7.4 I'm using right now. It doesn't *appear* that way when editing the commit message, but when you actually commit you will see the files you added during the pre-commit hook appear in the commit. – Yi Jiang Jul 02 '16 at 02:58
  • Not work in `2.11.0` either. Changes are added but not committed. – superarts.org Sep 07 '17 at 19:02
  • I believe this needs a final `exit` command at the end of the `pre-commit` script – Gabriella Gonzalez Mar 21 '20 at 16:42
  • @GabriellaGonzalez I hoped so but that didn't fix it for me. Even worse: I know it works because I had it working before I re-cloned my repo from source (without the hooks :/ ) So I can confirm that this CAN work in 2.31.1.windows.1 – Hobbamok Sep 22 '22 at 10:12
17
#!/bin/sh
#
#  .git/hooks/pre-commit
#

git add file.xyz

This worked just fine for me. It will be part of the current commit.

git version 1.7.12.4 (Apple Git-37)

calimarkus
  • 9,955
  • 2
  • 28
  • 48
  • 2
    This worked for me, too, except I needed to change the working directory of my script using `cd $(git rev-parse --show-toplevel)` – IanS Nov 09 '16 at 14:52
  • 3
    Worked for me too. If doing just `git commit`, the added files won't appear in the automatically generated commit message, but they will be added anyway. – laurent Dec 04 '17 at 11:15
13

You could use a combination of a pre and post commit script.

In the pre-commit:

  • Touch a file .commit or something. (be sure to add this to .gitignore)

In the post-commit:

if .commit exists you know a commit has just taken place but a post-commit hasn't run yet. So, you can do your code generation here. Additionally, test for .commit and if it exists:

  • add the files
  • commit --amend -C HEAD --no-verify (avoid looping)
  • delete .commit file

This is roughly the process I use to store a .metadata file in the repository generated from metastore.

If anyone knows a better way I'm all ears but it seems to work for now.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Mark
  • 348
  • 4
  • 15
12

You can use update-index:

git update-index --add my_files

rfunduk
  • 30,053
  • 5
  • 59
  • 54
  • I updated the question with the information. There are no errors. The files are created. Git status shows they've been added but not committed. – Ian Terrell Jul 19 '10 at 19:37
  • It's possible that another option to `update-index` will be what you need, too: http://ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/git-update-index.html – rfunduk Jul 19 '10 at 20:10
  • I will definitely look at that as an option. Thanks! – Ian Terrell Jul 19 '10 at 21:23
  • 5
    I don't know about @IanTerrell, but I'm still stuck on this. I tried both `git add` and `git update-index --add`. In both cases the files get added to the repository (so they'd be in the *next* commit), but not to the current commit. – kojiro Oct 20 '11 at 19:59
  • rfunduk's response worked perfectly for me on git 2.6.2, i.e. git update-index --add my_files – erichlf Nov 19 '15 at 09:18
  • This also doesn't work if you have another file in the index which you don't want to add to the commit. – Iulian Onofrei Sep 13 '17 at 22:02
2

How about writing a post-commit script instead which generates your files, and then have that do (something along the lines of) git add my_files; git commit --amend.

rfunduk
  • 30,053
  • 5
  • 59
  • 54
  • https://git-scm.com/docs/githooks post-commit runs after the commit is complete -- you can't amend it at that point afaics – jcollum Mar 09 '20 at 21:32
  • `git commit --amend` changes the previous commit (which as you said has just happened) – rfunduk Mar 10 '20 at 23:36
1

I had the same need and this approach worked pretty well for me:

#!/bin/sh
files='git diff --cached --name-only'
re="<files of importance>"
if [[ $files =~ $re ]]
then
   echo "Creating files"
   create_my_files && git add my_files
fi

where "create_my_files" should be executable, for example if it is a python file you could execute it as "python create_my_files && git add my_files"

and is true you don't need a pre-commit to commit again (that would create a infinite nasty loop :p)

Hassek
  • 8,715
  • 6
  • 47
  • 59
0

If the files are automatically generated, and they can be generated anywhere (implicit in your desire to build them in the Git pre-commit hook) then you shouldn't be putting them under source control in the first place. You should only control source files -- generated files should be generated as part of the build scripts.

The only reason to put a generated file under source control is when it requires unique/privileged resources to generate (such as a licensed program) or it requires a significant amount of time to generate.

Added

From http://git-scm.com/docs/githooks :

pre-commit This hook is invoked by git commit, and can be bypassed with --no-verify option. It takes no parameter, and is invoked before obtaining the proposed commit log message and making a commit. Exiting with non-zero status from this script causes the git commit to abort.

The default pre-commit hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when such a line is found.

All the git commit hooks are invoked with the environment variable GIT_EDITOR=: if the command will not bring up an editor to modify the commit message.

The intent of the pre-commit hook is to be a pass-fail check on the state of the workspace and the contents of the commit, prior to making the commit. Attempting to change the contents of the commit won't work.

My recommendation would be add two steps to your build scripts: (1) a step that will build all of the out-of-date files that needs to be generated (and adds them to the workspace), and (2) a step that will check to ensure that all of the generated files are up-to-date, and return a non-zero status code. Your Git pre-commit hook should run the second step. Your developers should be trained to run the first step as necessary.

Craig Trader
  • 15,507
  • 6
  • 37
  • 55
  • 10
    True, but doesn't answer the question. He might have a very good reason for putting the generated file under source control, that's not for us to decide :) – rfunduk Jul 19 '10 at 19:35
  • 1
    They can't be generated anywhere: they're being deployed from source control to a read-only filesystem. – Ian Terrell Jul 19 '10 at 19:36
  • 1
    There you have it! :) You might try to put the generation step into your deploy script, but that might also be impractical. – rfunduk Jul 19 '10 at 20:02
  • 1
    The deployment is automated with a git push (it's a Rails app on Heroku), so it's not terribly practical to put it there. Pre-commit is really the place for it, as I can test to see if any dependent files have changed and only rebuild the generated files if they have. – Ian Terrell Jul 19 '10 at 20:03
  • 1
    @Ian, it really sounds like this is something for your build scripts, before you commit, instead of trying to use Git to automate the generation. If anything should be put into the pre-commit step, it should be a check to ensure that the files are up-to-date before committing (and fail the commit if they are out of sync). – Craig Trader Jul 19 '10 at 20:09
  • 1
    Looks like you're right about pre-commit... unfortunately for Ian's developers, there are likely no 'build scripts' involved in his project -- unless you count tests :) – rfunduk Jul 19 '10 at 20:29
0

Yes, you can add generated files automatically on the commit using git hooks! But it requires a tricky script.

Here you can find the problem solved. There, it is updating the file version on every commit, adding a new modified file and amending the commit as you need it to. It is fully working: https://github.com/evandrocoan/.versioning

Then you just replace the 'Version File Replacement' algorithm on the file 'updateVersion.sh', by your algorithm. Maybe you need to change a few things like, remove the branch limitation, because there, the script only runs if you are on the 'develop' branch.

Also, it will only change the specified file, if is staged. If the file is not staged, then it will do nothing than the normal/usual commit. More precisely, it print out what it is doing on every step.

I am going to explain, that trick. It is quite tricky. On the prepare-commit-msg-hook, it detects whether the desired file is being staged and committed. After that, it creates a flag file, and stops the prepare-commit-msg-hook. Later on the post-commit-hook, it checks whether the flag file exists. If yes, it amends the files on the commit.

Attention, it would create a infinity loop because it would call again the prepare-commit-msg-hook (as we are amending). But it does not happen because of the flag file. When the prepare-commit-msg-hook runs and find the flag file, it "knows" what is happening. Then is just deletes the flag file and do not create it again. Doing it, it will block the post-commit-hook from amending again the commits, allowing the commit to finish for good.

Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
-2

I was facing same problem in pre-commit hook also. I was modifying one file and committing but it was taking previous file not updated file so by adding git command(as below) in pre-commit hook, it solved.

git add $file

note: $file is your file to be added.

ggorlen
  • 44,755
  • 7
  • 76
  • 106