views:

534

answers:

3

Hi all,

I'm using Git to manage my website's source code and deployment, and currently have the test and live sites running on the same box. Following this resource http://toroid.org/ams/git-website-howto originally, I came up with the following post-receive hook script to differentiate between pushes to my live site and pushes to my test site:

while read ref
do
  #echo "Ref updated:"
  #echo $ref -- would print something like example at top of file
  result=`echo $ref | gawk -F' ' '{ print $3 }'`
  if [ $result != "" ]; then
    echo "Branch found: "
    echo $result
    case $result in
      refs/heads/master )
        git --work-tree=c:/temp/BLAH checkout -f master
        echo "Updated master"
        ;;
      refs/heads/testbranch )
        git --work-tree=c:/temp/BLAH2 checkout -f testbranch
        echo "Updated testbranch"
        ;;
      * )
        echo "No update known for $result"
        ;;
    esac
  fi
done
echo "Post-receive updates complete"

However, I have doubts that this is actually safe :) I'm by no means a Git expert, but I am guessing that Git probably keeps track of the current checked-out branch head, and this approach probably has the potential to confuse it to no end.

So a few questions:

  1. IS this safe?

  2. Would a better approach be to have my base repository be the test site repository (with corresponding working directory), and then have that repository push changes to a new live site repository, which has a corresponding working directory to the live site base? This would also allow me to move the production to a different server and keep the deployment chain intact.

  3. Is there something I'm missing? Is there a different, clean way to differentiate between test and production deployments when using Git for managing websites?

As an additional note in light of Vi's answer, is there a good way to do this that would handle deletions without mucking with the file system much?

Thank you, -Walt

PS - The script I came up with for the multiple repos (and am using unless I hear better) is as follows:

sitename=`basename \`pwd\``

while read ref
do
  #echo "Ref updated:"
  #echo $ref -- would print something like example at top of file
  result=`echo $ref | gawk -F' ' '{ print $3 }'`
  if [ $result != "" ]; then
    echo "Branch found: "
    echo $result
    case $result in
      refs/heads/master )
        git checkout -q -f master
        if [ $? -eq 0 ]; then
            echo "Test Site checked out properly"
        else
            echo "Failed to checkout test site!"
        fi
        ;;
      refs/heads/live-site )
        git push -q ../Live/$sitename live-site:master
        if [ $? -eq 0 ]; then
            echo "Live Site received updates properly"
        else
            echo "Failed to push updates to Live Site"
        fi
        ;;
      * )
        echo "No update known for $result"
        ;;
    esac
  fi
done
echo "Post-receive updates complete"

And then the repo in ../Live/$sitename (these are "bare" repos with working trees added after init) has the basic post-receive:

git checkout -f
if [ $? -eq 0 ]; then
    echo "Live site `basename \`pwd\`` checked out successfully"
else
    echo "Live site failed to checkout"
fi
+2  A: 

Think that both ways will work.

You can also use "git archive master | tar -C c:/temp/BLAH -x" and "git archive live-site | ssh live-site 'tar -C /var/www -x'".

Keeping separate repositories may be useful, but "push inside another push-related hook" looks tricky and I expect it to be slow. Sort of long chain that will be slow and fragile.

May be live site updates should be manually triggered after testing the "testing" version?

Vi
The tar'ing might be a good idea; I'll definitely look at this some next week. I'm not entirely sure how well it'd work though, as I'm running the scripts through msysGit. But, they seem to come packaged with tar, so maybe.
Walt W
OK - Here's a question about this answer - I like the concept of tarring, but there's one unsatisfactory part - I'd like the updates to be as seamless as possible. One of the benefits with the multiple repo trick is that, though tricky, the file system is only changed according to each commit's deltas - the tar approach, however, would required rm -rf'ing the deploy directory, THEN extracting the tar . . . Maybe there's a tar flag I'm not aware of? The tar approach is nice for a number of reasons, but I think it's hazardous with my host to be deleting everything between updates.
Walt W
Then you probably should use separate repositories.But I still don't think that pushing to the some special ref should _automatically_ update production site. It just should not be that easy.
Vi
Updating a site using "git checkout" is not atomic itself. What will happen is some user access the site just at the time of "checkout"?You may extract/checkout the site to some other directory and point config file of site to use that other directory.
Vi
What files are stored in repository? Like some bunch of small interlinked PHP files or big independent blobs?
Vi
Vi - I agree it shouldn't be that easy, but please consider that the repository containing the branch to push to the live site could be a private repository - not necessarily the public copy. Regarding your other points - Yeah, git checkout isn't atomic, but its effects are more contained than a general tar command. As far as the files stored in the repo, it's mostly a bunch of small stuff.
Walt W
+1  A: 

Would a better approach be to have my base repository be the test site repository (with corresponding working directory), and then have that repository push changes to a new live site repository, which has a corresponding working directory to the live site base? This would also allow me to move the production to a different server and keep the deployment chain intact.

Yes definitely. It's a very rare occasion you want your test site hosted right next to your production site. It's dangerous and unprofessional in almost every regard, not to speak about database corruption, webserver lockups etc.

I do usually have a VM setup for testing purposes. Works very well and I can have it with me on my laptop while travelling.

Using git to deploy your website is a very good idea, there are a lot of other people doing so (e.g. Rob Conery). If you happen to have a live and testing site anyway, you should have seperate branches for them in your repository, set-up as remote-tracking branches on the corresponding server repositories. Your workflow becomes as easy as doing work in your test branch, push it to test, test it, merge to live and push live.

Honestly, don't make it too hard for yourself.

Johannes Rudolph
This is kind of sounding like the way to go / worked out pretty well last week.
Walt W
+1  A: 

I also followed the same guide at toroid.org, but I'd like to note that although you started with a bare repository, by adding a working directory, extra handling will most likely be needed. I found that the following hook is useful if you have content that may change dynamically or otherwise and don't want to lose data when using git checkout -f

pre-receive

#!/bin/sh
git add -A
git diff --quiet --cached
if [ $? -gt 0 ]; then
    git commit --quiet -m "autocommit"
    echo "Working Directory was out of sync. Pull to receive updated index."
    exit 1
fi

This will stop a push if there are changes in the remote working directory. Think of it as someone (the web server) making changes but forgetting to commit them. Using checkout with the -f will discard those changes. This hook is a good place to prevent this from happening, but it would be nice if there was also a hook called on the remote server prior to a pull so that you would receive these changes seamlessly.

post-receive

#!/bin/sh
git checkout -f
echo "Working directory synced."

Regarding have two branches, I thought your first solution was more elegant than having to deal with multiple repositories. If you really want to keep your production site isolated, you could use rsync locally which has similar delta patching. I would have a testing and stable branch in the repository with only the testing site as the working directory. When ready to release merge the testing into the stable branch, push, and have a hook looking for commits to stable branch make the call to rsync.

tcp
+1 for the pre-receive comment - that's a really useful tip, thanks. As far as multiple repos, it's not too bad in actuality :) rsync is definitely another viable option though. Thanks!
Walt W
`git add .` wasn't picking up all the changes (deletions), changed to `git add -A`
tcp