Git post-receive hook doesn't remove deleted files from master
Asked Answered
I

2

3

I created a bare git repo on my server and set up the following post-receive hook from this blog:

#!/bin/bash

while read oldrev newrev ref
do
  branch=`echo $ref | cut -d/ -f3`

  if [ "master" == "$branch" ]; then
    git --work-tree=/path/to/my/project/live/ checkout -f $branch
    echo 'Changes pushed live.'
  fi

  if [ "develop" == "$branch" ]; then
    git --work-tree=/path/to/my/project/dev/ checkout -f $branch
    echo 'Changes pushed to dev.'
  fi
done

So that whenever I push locally to my server, the changes would automatically be published on each branch's folder without the need to manually pull.

I set the right permissions to both live an dev folder:

drwxrwsr-x 2 git git 4096 Sep 29 12:10 live/
drwxrwsr-x 2 git git 4096 Sep 29 12:09 dev/

And pushing from the develop branch works as expected. The problem occurs when I checkout the master branch and do a merge. When I push the master, the new files get copied to the live folder on my server, but the files I removed locally are not being deleted.

How can I make post-receive properly update the live folder? Thanks!

Intracutaneous answered 29/9, 2013 at 9:37 Comment(0)
W
3

The problem is that git doesn't know what to remove (it does not have an index over in the work tree, keeping track of such things). It should be possible to solve this with an index for each work tree, but I think it's simpler to just git checkout -f into a new, empty directory, then rename the new directory and the old one to make the new version "go live". This also shrinks the race condition window: now there's just one brief moment (between mv operations) when there is no version, instead of a slightly longer window (during checkout) when there is a mix of old and new versions.

Note: The script you show will go awry if there is a tag named master or develop as the reference names for those two are refs/tags/master and refs/tags/develop respectively. I'd recommend fixing this (if you care :-) ) via shell function and case statements to cut down on process spawning in the non-deploy cases, e.g.:

die() {
    echo "$@" >&2
    exit 1
}

# deploy (to given path, $1) the version named by $2
# if the target is /some/path/there we use a temp version
# named /some/path/tmp.<pid> to hold the new one until we
# can swap it out, and $1.old.<pid> while we remove the old.
deploy() {
    local path=$1 branch=$2
    local tmpdir=${path%/*}/tmp.$$        # tune this as needed

    echo "deploying $branch to $path via $tmpdir..."
    trap "rm -rf $tmpdir" 0 1 2 3 15
    mkdir $tmpdir || die "can't create work dir $tempdir"
    git --work-tree=$tmpdir/ checkout -f $branch
    mv $path $path.old.$$ ||
        die "unable to move live version out of the way"
    mv $tmpdir $path ||
        die "unable to set new version live"
    trap - 0 1 2 3 15
    echo "done, cleaning up old version"
    rm -rf $path.old.$$
}

while read oldrev newrev ref; do
    case $ref in
    refs/heads/master) deploy /path/to/my/project/live master;;
    refs/heads/develop) deploy /path/to/my/project/dev develop;;
    esac
done

(note: totally untested).

Waist answered 29/9, 2013 at 10:6 Comment(6)
will git-work-tree work as expected, if the repository is bare repository??Cavell
Yes, or at least, it definitely works with the env variable, it should work with the command line option too.Waist
I got an error: remote: mv: cannot move '/path/to/my/project/dev/' to a subdirectory of itself, '/path/to/my/project/dev/.old.847' and remote: unable to move live version out of the wayIntracutaneous
Looks like you left a trailing slash in the argument to "deploy". It requires that you not include them there (it adds them itself as needed).Waist
Now it seems to be working. Thank you! Should I be worried about that window in case the project becomes larger in size?Intracutaneous
The nice thing about the temp dir approach is that the transition (old-live to new-live) window is constant, regardless of project size. The bad thing about it is that the total "deploy time" grows with the project size, rather than the change size. If you make an index file work (I have never tried this), then the deploy time (and window) depends on change-size. (There's a third way, in between as it were, but it's even more complex: keep an index for the "next" and "prev" deploy versions and swap back and forth...)Waist
D
4

I ran into the same issue. Was looking into rsync to copy tmp folder files to the live folder but then I figured why not just use git glean on the working tree.

I'm not sure if this would be bad but it should only remove untracked files from the folder and use your .gitignore settings to not remove files not in your repo.

It seems to accomplish what I want which is to clear out left over files that were not deleted by the push.

#!/bin/sh
while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [ "master" == "$branch" ]; then
        GIT_WORK_TREE=/home/store/public_html git checkout -f master
        GIT_WORK_TREE=/home/store/public_html git clean -fd
    fi
    if [ "test" == "$branch" ]; then
        GIT_WORK_TREE=/home/store/test_html git checkout -f test
        GIT_WORK_TREE=/home/store/test_html git clean -fd
    fi
    if [ "dev" == "$branch" ]; then
        GIT_WORK_TREE=/home/store/dev_html git checkout -f dev
        GIT_WORK_TREE=/home/store/dev_html git clean -fd
    fi
done
Desecrate answered 27/11, 2015 at 17:2 Comment(0)
W
3

The problem is that git doesn't know what to remove (it does not have an index over in the work tree, keeping track of such things). It should be possible to solve this with an index for each work tree, but I think it's simpler to just git checkout -f into a new, empty directory, then rename the new directory and the old one to make the new version "go live". This also shrinks the race condition window: now there's just one brief moment (between mv operations) when there is no version, instead of a slightly longer window (during checkout) when there is a mix of old and new versions.

Note: The script you show will go awry if there is a tag named master or develop as the reference names for those two are refs/tags/master and refs/tags/develop respectively. I'd recommend fixing this (if you care :-) ) via shell function and case statements to cut down on process spawning in the non-deploy cases, e.g.:

die() {
    echo "$@" >&2
    exit 1
}

# deploy (to given path, $1) the version named by $2
# if the target is /some/path/there we use a temp version
# named /some/path/tmp.<pid> to hold the new one until we
# can swap it out, and $1.old.<pid> while we remove the old.
deploy() {
    local path=$1 branch=$2
    local tmpdir=${path%/*}/tmp.$$        # tune this as needed

    echo "deploying $branch to $path via $tmpdir..."
    trap "rm -rf $tmpdir" 0 1 2 3 15
    mkdir $tmpdir || die "can't create work dir $tempdir"
    git --work-tree=$tmpdir/ checkout -f $branch
    mv $path $path.old.$$ ||
        die "unable to move live version out of the way"
    mv $tmpdir $path ||
        die "unable to set new version live"
    trap - 0 1 2 3 15
    echo "done, cleaning up old version"
    rm -rf $path.old.$$
}

while read oldrev newrev ref; do
    case $ref in
    refs/heads/master) deploy /path/to/my/project/live master;;
    refs/heads/develop) deploy /path/to/my/project/dev develop;;
    esac
done

(note: totally untested).

Waist answered 29/9, 2013 at 10:6 Comment(6)
will git-work-tree work as expected, if the repository is bare repository??Cavell
Yes, or at least, it definitely works with the env variable, it should work with the command line option too.Waist
I got an error: remote: mv: cannot move '/path/to/my/project/dev/' to a subdirectory of itself, '/path/to/my/project/dev/.old.847' and remote: unable to move live version out of the wayIntracutaneous
Looks like you left a trailing slash in the argument to "deploy". It requires that you not include them there (it adds them itself as needed).Waist
Now it seems to be working. Thank you! Should I be worried about that window in case the project becomes larger in size?Intracutaneous
The nice thing about the temp dir approach is that the transition (old-live to new-live) window is constant, regardless of project size. The bad thing about it is that the total "deploy time" grows with the project size, rather than the change size. If you make an index file work (I have never tried this), then the deploy time (and window) depends on change-size. (There's a third way, in between as it were, but it's even more complex: keep an index for the "next" and "prev" deploy versions and swap back and forth...)Waist

© 2022 - 2024 — McMap. All rights reserved.