Git Post-Receive Hook for Website Staging

GitGit Post-Receive

Git Problem Overview


I'm trying to set up Git for staging my website so that I can git pull to get the current version to work on locally and then git push to push the changes to the remote server. I've got it set up so that it works the way I want it to, but after I push, I have to manually run git checkout -f or git reset --hard HEAD on the remote server.

I've tried putting these into a shell script as the post-receive hook on the server, but it just doesn't seem to have any effect. I know that the script is running because I'm seeing "Changes pushed to server" after I push. Here's the post-receive hook:

#!/bin/sh
git reset --hard HEAD
echo "Changes pushed to server."

Git Solutions


Solution 1 - Git

The answer to your question is here: http://toroid.org/ams/git-website-howto

In short, what you want to do is add a "detached work tree" to the bare repository. Normally you think of your work tree as containing the .git directory. Bare repositories do not have a work tree by definition, but you can create one as long as it's in a different directory than the bare repo.

The post-receive hook is just a simple git checkout -f to replicate the repository's HEAD into the work directory. Apache uses that as its document root, and you're all set. Any time you push to the bare repository, Apache will immediately start serving it.

I generally use this to automatically push to a staging server to see if the "real" environment will puke on my changes. Deploying to the live server is a completely different story. :-)

Solution 2 - Git

Update March 2015

As I mentioned in "What is this Git warning message when pushing changes to a remote repository?", you actually can push directly to a non-bare repo now (Git 2.3.0+, February 2015) with:

git config receive.denyCurrentBranch updateInstead

> Update the working tree accordingly, but refuse to do so if there are any uncommitted changes.

That would allow you to avoid any post-receive hook.


(Original answer: Oct 2010)

The GitFAQ recommends for non-bare repo this post-update hook:
(it might give you more clue as to what is actually going on in the hook execution. Note this is a post-update hook, not a post-receive)

#!/bin/sh
#
# This hook does two things:
#
#  1. update the "info" files that allow the list of references to be
#     queries over dumb transports such as http
#
#  2. if this repository looks like it is a non-bare repository, and
#     the checked-out branch is pushed to, then update the working copy.
#     This makes "push" function somewhat similarly to darcs and bzr.
#
# To enable this hook, make this file executable by "chmod +x post-update".

git-update-server-info

is_bare=$(git-config --get --bool core.bare)

if [ -z "$is_bare" ]
then
	# for compatibility's sake, guess
	git_dir_full=$(cd $GIT_DIR; pwd)
	case $git_dir_full in */.git) is_bare=false;; *) is_bare=true;; esac
fi

update_wc() {
	ref=$1
	echo "Push to checked out branch $ref" >&2
	if [ ! -f $GIT_DIR/logs/HEAD ]
	then
		echo "E:push to non-bare repository requires a HEAD reflog" >&2
		exit 1
	fi
	if (cd $GIT_WORK_TREE; git-diff-files -q --exit-code >/dev/null)
	then
		wc_dirty=0
	else
		echo "W:unstaged changes found in working copy" >&2
		wc_dirty=1
		desc="working copy"
	fi
	if git diff-index --cached HEAD@{1} >/dev/null
	then
		index_dirty=0
	else
		echo "W:uncommitted, staged changes found" >&2
		index_dirty=1
		if [ -n "$desc" ]
		then
			desc="$desc and index"
		else
			desc="index"
		fi
	fi
	if [ "$wc_dirty" -ne 0 -o "$index_dirty" -ne 0 ]
	then
		new=$(git rev-parse HEAD)
		echo "W:stashing dirty $desc - see git-stash(1)" >&2
		( trap 'echo trapped $$; git symbolic-ref HEAD "'"$ref"'"' 2 3 13 15 ERR EXIT
		git-update-ref --no-deref HEAD HEAD@{1}
		cd $GIT_WORK_TREE
		git stash save "dirty $desc before update to $new";
		git-symbolic-ref HEAD "$ref"
		)
	fi

	# eye candy - show the WC updates :)
	echo "Updating working copy" >&2
	(cd $GIT_WORK_TREE
	git-diff-index -R --name-status HEAD >&2
	git-reset --hard HEAD)
}

if [ "$is_bare" = "false" ]
then
	active_branch=`git-symbolic-ref HEAD`
	export GIT_DIR=$(cd $GIT_DIR; pwd)
	GIT_WORK_TREE=${GIT_WORK_TREE-..}
	for ref
	do
		if [ "$ref" = "$active_branch" ]
		then
			update_wc $ref
		fi
	done
fi

For this to work, you would still need to specifically allow pushing changes to the current branch by using either one of these configuration settings:

git config receive.denyCurrentBranch ignore

or

git config receive.denyCurrentBranch warn

Solution 3 - Git

I had the exact same issue. In a reply to this link: http://toroid.org/ams/git-website-howto - The following command has done it:

sudo chmod +x hooks/post-receive

We missed a sudo permission first configured the stuff.

Solution 4 - Git

Fixed version of VonC's script, works for me (absolutely no guarantees).

#!/bin/sh
#
# This hook does two things:
#
#  1. update the "info" files that allow the list of references to be
#     queries over dumb transports such as http
#
#  2. if this repository looks like it is a non-bare repository, and
#     the checked-out branch is pushed to, then update the working copy.
#     This makes "push" function somewhat similarly to darcs and bzr.
#
# To enable this hook, make this file executable by "chmod +x post-update".

set -e

git update-server-info

is_bare=$(git config --get --bool core.bare)

if [ -z "${is_bare}" ]
then
    # for compatibility's sake, guess
    git_dir_full=$(cd $GIT_DIR; pwd)
    case $git_dir_full in */.git) is_bare=false;; *) is_bare=true;; esac
fi

update_wc() {
    ref=$1
    echo "Push to checked out branch $ref" >&2
    if [ ! -f ${GIT_DIR}/logs/HEAD ]
    then
        echo "E:push to non-bare repository requires a HEAD reflog" >&2
        exit 1
    fi
    if (cd ${GIT_WORK_TREE}; git diff-files -q --exit-code >/dev/null)
    then
        wc_dirty=0
    else
        echo "W:unstaged changes found in working copy" >&2
        wc_dirty=1
        desc="working copy"
    fi
    if git diff-index --cached HEAD@{1} >/dev/null
    then
        index_dirty=0
    else
        echo "W:uncommitted, staged changes found" >&2
        index_dirty=1
        if [ -n "$desc" ]
        then
            desc="$desc and index"
        else
            desc="index"
        fi
    fi
    if [ "$wc_dirty" -ne 0 -o "$index_dirty" -ne 0 ]
    then
        new=$(git rev-parse HEAD)
        echo "W:stashing dirty $desc - see git-stash(1)" >&2
        ( trap 'echo trapped $$; git symbolic-ref HEAD "'"$ref"'"' 2 3 13 15 ERR EXIT
        git update-ref --no-deref HEAD HEAD@{1}
        cd ${GIT_WORK_TREE}
        git stash save "dirty $desc before update to $new";
        git symbolic-ref HEAD "$ref"
        )
    fi

    # eye candy - show the WC updates :)
    echo "Updating working copy" >&2
    (cd ${GIT_WORK_TREE}
    git diff-index -R --name-status HEAD >&2
    git reset --hard HEAD
    # need to touch some files or restart the application? do that here:
    # touch *.wsgi
    )

}

if [ x"${is_bare}" = x"false" ]
then
    active_branch=$(git symbolic-ref HEAD)
    export GIT_DIR=$(cd ${GIT_DIR}; pwd)
    GIT_WORK_TREE="${GIT_DIR}/.."
    for ref in $(cat)
    do
        if [ x"$ref" = x"${active_branch}" ]
        then
            update_wc $ref
        fi
    done
fi

Solution 5 - Git

Simple script for setting this git deployment:

Preparing post-receive hook:

echo '#!/bin/sh'        >  .git/hooks/post-receive
echo 'git checkout -f'  >> .git/hooks/post-receive
echo 'git reset --hard' >> .git/hooks/post-receive
chmod +x .git/hooks/post-receive

Allowing push into this repository, though it is not bare:

git config receive.denycurrentbranch false

Solution 6 - Git

I'm just guessing, but this may be some permission issue (full path needed? cd?). Check what is really happening in the log files.

However publishing the files via git is always just one tasks of the publishing process. You usually need to copy some files, delete other, setup, update permissions, generate docs etc.

For a complex solution a build script might be better than any git hook. Tools which can handle those tasks very well:

(I realize this is not the answer you are expecting, but it's too long to post as a comment)

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
QuestionMattView Question on Stackoverflow
Solution 1 - GitPaulView Answer on Stackoverflow
Solution 2 - GitVonCView Answer on Stackoverflow
Solution 3 - GitidoView Answer on Stackoverflow
Solution 4 - GitTronicView Answer on Stackoverflow
Solution 5 - GitHonzaView Answer on Stackoverflow
Solution 6 - GittakeshinView Answer on Stackoverflow