Force git to run post-receive hook, even if everything is "up-to-date"

Git

Git Problem Overview


How do I force git to run a post-receive hook on a server even if I don't have a new commit to push?

Background

I use git to automatically deploy a website to a server. I have a bare repo in a protected area of the server and a post-receive hook that checks out the contents and systematically copies over certain files into a public_html folder. (Inspired by this tutorial)

I got tired of modifying the post-receive hook manually on the server, so my post-receive hook now actually copies over a new version of itself from the repo:

#!/bin/sh

rm -rf ~/../protected/*
GIT_WORK_TREE=~/../protected git checkout -f

# Rewrite over this file with any updates from the post-receive file
cp ~/../protected/post-receive hooks/post-receive

# Delete public_html
# Copy stuff public_html

The problem, of course, is that the new post-receive hook never gets run. A seemingly simple solution would be merely to push again, but now everything is already up to date. This is annoying, because it requires me to fake a new commit every time I update the post-receive hook. Is there a way to invoke the post-receive hook without faking a commit or sshing in?

What I tried

git push
git push -f

Git Solutions


Solution 1 - Git

Use '--allow-empty'

After the initial push replacing the script, you can do this :

git commit --allow-empty -m 'push to execute post-receive'

The --allow-empty flag overrides git's default behavior of preventing you from making a commit when there are no changes.

Use an alias and make your life even easier

Add the following to ~/.gitconfig

[alias]
    pushpr = "!f() { git push origin master;git commit --allow-empty -m 'push to execute post-receive';git push origin master; }; f"

Now Just do git pushpr

git pushpr

This will push any changes to master, which in your case will trigger your post receive replacement script, then it will push again (using the --allow-empty flag) which will then execute your updated post-receive script.

Solution 2 - Git

I know this probably going to be considered "dangerous" but I like to live on the edge.

I just delete the remote branch and then push it again. Make sure your local branch is up-to-date first to limit the chance of losing stuff.

So if I want to trigger post-receive, in my case to get the testing branch to provision, all I do is:

$ git push origin :testing
$ git push origin testing

Don't accept this as the answer though. It's more of a just FYI thing.

Solution 3 - Git

If you want to avoid making a fake new commit, you can simply use

git commit --amend --no-edit

This will modify the last commit record and you will be able to use git push -f (assuming from your answer that you're fine with the overwrite).

  • convenient: it doesn't create an extra commit
  • good: it doesn't use explicit path for the hook in the remote repo
  • bad: you overwrite history, which is fine in your use case, but shouldn't be used in a shared repository

I use this command relatively often to fix something in the last commit before pushing, so I made an alias for it:

git config --global alias.amend 'commit --amend --no-edit'

Now I can use it directly for the use case as yours (git amend), or

  • to add (all) modified files to the last commit: git amend -a
  • to modify the message: git amend -m 'better message'

Solution 4 - Git

I'm afraid you have to ssh to the server and run the hook script manually. git push doesn't make the server run the pre-push, pre-receive and post-receive hooks if there was nothing added (i.e. when git prints Everything up-to-date).

The rest of the answer is about version-tracking the post-receive hook, so you can modify it without sshing to the server.

Add a shell script named do-post-receive to the local repository:

$ ls -ld .git
$ echo 'echo "Hello, World!"' >do-post-receive
$ git add do-post-receive
$ git commit do-post-receive -m 'added do-post-receive'

Replace your hooks/post-receive hook on the server with:

#! /bin/sh
while read OLDID NEWID BRANCH; do
  test "$BRANCH" = refs/heads/master && eval "$(git show master:do-post-receive)"
done

(Make sure to chmod 755 hooks/post-receive on the server.)

Push your changes from the local repository to the server, and watch your do-post-receive code run:

$ git push origin master
...
remote: Hello, World!
...

Solution 5 - Git

I tried empty commits and delete upload branch.

But what about a direct ssh command:

ssh your_host /path/to/your_repo/hooks/post-receive

Solution 6 - Git

In my case i login into remote and run:

$ sh project.git/hooks/post-receive

works fine!

Solution 7 - Git

I made a bash function to do this. It assumes that you have ssh access can ~/.ssh/config set accordingly. The default remote is origin

kick-git() {
    remote="${1:-origin}"
    ssh $(echo $(git remote get-url "$remote")/hooks/post-receive | tr ':' ' ')
}

Source and run kick-git [remote]

Solution 8 - Git

Post-receive is the wrong place for artificial command responses.

You want your server-side goodies in the pre-receive exit, dependent on the updated ref -- do e.g. git update-ref refs/commands/update-hooks @ on the server, then you can e.g. git push server +@:commands/update-hooks, and in the server's pre-receive you can

while read old new ref; do case $ref in
commands/update-hooks)
        maybe check the incoming commit for authorization
        update hooks here
        echo the results from the update
        denypush=1
        ;;
refs/heads/master)
        extreme vetting on master-branch updates here
        ;;
esac; done

((denypush)) && exit $denypush

Solution 9 - Git

I like Jamie Carl's suggestion but it doesn't work, and I got the error:

> remote: error: By default, deleting the current branch is denied, because the next error: 'git clone' won't result in any file checked out, causing confusion.

In my case I'm testing post-receive hooks on my localhost against a bare repository. Warning/super important, run this command only on the remote server location!

git update-ref -d refs/heads/develop 

It'll delete the reference for the develop branch (you may also need to delete any of the files you deployed for that branch too), then you can go ahead and do the git push deploy_localtest develop or whatever push command you want for that branch.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndyLView Question on Stackoverflow
Solution 1 - GitAndrewDView Answer on Stackoverflow
Solution 2 - GitJamie CarlView Answer on Stackoverflow
Solution 3 - GitlaughedelicView Answer on Stackoverflow
Solution 4 - GitptsView Answer on Stackoverflow
Solution 5 - GitsitesView Answer on Stackoverflow
Solution 6 - GitCosme MarinsView Answer on Stackoverflow
Solution 7 - GitBryce GuintaView Answer on Stackoverflow
Solution 8 - GitjthillView Answer on Stackoverflow
Solution 9 - GitClayView Answer on Stackoverflow