78

I've looked at the githooks manpage but unless I'm missing something I don't see an option for local, post-push git hooks. I'd like to have one that updates the api docs on my web server (for which I already have a script) after I push the master branch to the GitHub repo. Of course I could just write my own script that combines the git push and the api docs run, but that feels somewhat inelegant.

1
  • the post-update hooks can be triggered for this case right? Commented Jan 30, 2019 at 8:21

8 Answers 8

58

Another solution to this problem is to have a wrapper for git push that executes .git/hooks/pre-push and .git/hooks/post-push scripts before and after the git push call. A possible wrapper could look like this:

#!/bin/sh GIT_DIR_="$(git rev-parse --git-dir)" BRANCH="$(git rev-parse --symbolic --abbrev-ref $(git symbolic-ref HEAD))" PRE_PUSH="$GIT_DIR_/hooks/pre-push" POST_PUSH="$GIT_DIR_/hooks/post-push" test -x "$PRE_PUSH" && exec "$PRE_PUSH" "$BRANCH" "$@" git push "$@" test $? -eq 0 && test -x "$POST_PUSH" && exec "$POST_PUSH" "$BRANCH" "$@" 

Saved as git-push-wh somewhere in your PATH, it can then be called as git push-wh if you want to push with hooks.

Sign up to request clarification or add additional context in comments.

2 Comments

This is brilliant... I use separate remotes: one where I host my code, a bare repo in my production server so I added this code to my ~/.functions.sh and use ** push origin ** ** push production ** it automatically launches the website (since that’s defined in push-post) in my browser once the push completes. Thanks!
Current code does not work in git worktrees, to fix it, just change 3 lines: ` GIT_DIR_="$(git rev-parse --git-path hooks)" PRE_PUSH="${GIT_DIR_}/pre-push" POST_PUSH="${GIT_DIR_}/post-push" `
41

I recently came across the same issue. I wanted a hook so that a push from my git submodule would commit the new submodule reference in the 'superproject'.

As Chris mentioned, the best way is to just use a git alias, like this:

$ git config alias.xpush '!git push $1 $2 && update-server.sh' # (remember the backslash before the ! if your shell requires it) 

This adds the following to your .git/config file:

[alias] xpush = !git push $1 $2 && update-server.sh 

And so now, if you type:

$ git xpush 

your changes will be pushed, and then update-server.sh will be executed.

2 Comments

Umm what about if you are in a branch? and you do that xpush? shouldn't be something like !git push $1 $2 && update-server.sh so you can specify origin branch?
Note that overriding an existing command is not possible with a git alias, see stackoverflow.com/questions/3538774/…
38

This type of hook is not supported by Git. It falls outside the valid reasons for a Git hook as given by Git's maintainer.

The introductory remark in the above linked message speaks almost directly to your case:

I do not particularly like hooks that act after an operation is initiated locally and act solely on local data. This is maybe because I still consider git tools building blocks suitable for higher level scripting more than other people do.

P.S. A “Single Push” Hint

  • There are too many caveats for a full explanation, but if you can figure it all out you should be able to deal with the details.

An extra pushurl to a local repo with an “alternates” objects store could give you a low overhead way to locally execute a push hook. But really, the effort is much more than git push upstream && update-web-server (maybe in a shell alias, git alias, or script).

2 Comments

Please, notice that from 1.8.2 Git added a pre-push hook. Check my answer to find more information.
9

Implementing a post-push hook is in fact possible by using the reference-transaction hook. After a push is done, git will locally update the remote tracking branch, triggering a reference transaction on refs/remotes/REMOTE/BRANCH.

Here's an example for a hook that uses this technique:

#!/bin/bash set -eu while read oldvalue newvalue refname do if [ $1 = committed -a $refname = refs/remotes/origin/main ] then exec .git/hooks/post-push fi done 

This script must have the executable bit and be placed in .git/hooks/reference-transaction. The hook runs after pushing the main branch to origin. Put your actual hook script in .git/hooks/post-push.

1 Comment

Perfect! To me, this should be be the ✅ accepted answer! I just put my own command on the line where it says exec .git/hooks/post-push instead of creating another file. Push to deploy via remote ssh command: Achieved. Thanks!
6

From Git 1.8.2 there is a new hook invoked before the push operation: pre-push If the script returns other than 0 the push operation will be cancelled.

Mention in the release notes: https://github.com/git/git/blob/master/Documentation/RelNotes/1.8.2.txt#L167

Sample: https://github.com/git/git/blob/87c86dd14abe8db7d00b0df5661ef8cf147a72a3/templates/hooks--pre-push.sample

2 Comments

I don't thing this answer is the good one, it tells about the pre-push hook, and the original question was about post-push hook.
Levente Holló,the pre-push hook is executed after the user runs git-push and before git actually pushes the commit/s.There is no other push related hook integrated in Git.
3

I'm using a function for this:

current_branch() { local ref=$(git symbolic-ref HEAD 2> /dev/null) || return echo ${ref#refs/heads/} } gp() { local post_push="$(git rev-parse --git-dir)/hooks/post-push" git push "$@" && { [[ -x "$post_push" ]] && "$post_push" "$(current_branch)" "$@" } } compdef _git gp=git-push 

The compdef part is for ZSH.

Comments

2

incron to the rescue.

my incrontab has this:

/path/to/.git/logs/refs/remotes/origin IN_CLOSE_WRITE /usr/local/bin/git-post-push $@/$# 

The script checks whether the remote changed by a push and if yes extracts a few helpful things with GNU grep:

# The script is called with a filename like /path/to/.git/logs/refs/remotes/origin/main # Each lines of the file is, as per https://git-scm.com/docs/git-update-ref#_logging_updates: # oldsha1 SP newsha1 SP committer TAB message LF # "committer" is the committer’s name, email address, the Unix timestamp and # the timezone, all separated by spaces. # git push uses the literal text "update by push" without quotes as message. push=$(tail -1 "$1"| grep -oP '\d{10}(?=\s.{1,5}\supdate by push$)') if [ -n "$push" ]; then gitdir=$(echo "$1"|grep -oP '^.*/.git/') remote=$(echo "$1"|grep -oP '.git/logs/refs/remotes/\K[^/]+') branch=$(echo "$1"|grep -oP '.git/logs/refs/remotes/[^/]+/\K.*$') # your code here # git --git-dir="$gitdir".... fi 

The push variable contains the timestamp of the push, the rest is self explanatory.

1 Comment

Incron sounds like a very interesting tool in general...
1

Just create a pre-push hook with a sleep at the top. Ensure sleep is long enough for the commits to be uploaded to upstream server based on your network connection speed. Ideally add a & to run your script in the background:

(sleep 30 && .git/hooks/post-push) & 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.