Joe Maller.com

A web-focused Git workflow

After months of looking, struggling through Git-SVN glitches and letting things roll around in my head, I’ve finally arrived at a web-focused Git workflow that’s simple, flexible and easy to use.

Some key advantages:

  • Pushing remote changes automatically updates the live site
  • Server-based site edits won’t break history
  • Simple, no special commit rules or requirements
  • Works with existing sites, no need to redeploy or move files

Overview

The key idea in this system is that the web site exists on the server as a pair of repositories; a bare repository alongside a conventional repository containing the live site. Two simple Git hooks link the pair, automatically pushing and pulling changes between them.

The two repositories:

  • Hub is a bare repository. All other repositories will be cloned from this.
  • Prime is a standard repository, the live web site is served from its working directory.

Using the pair of repositories is simple and flexible. Remote clones with ssh-access can update the live site with a simple git push to Hub. Any files edited directly on the server are instantly mirrored into Hub upon commit. The whole thing pretty much just works — whichever way it’s used.

Getting ready

Obviously Git is required on the server and any local machines. My shared web host doesn’t offer Git, but it’s easy enough to install Git yourself.

If this is the first time running Git on your webserver, remember to setup your global configuration info. I set a different Git user.name to help distinguish server-based changes in project history.

$ git config --global user.name "Joe, working on the server"

Getting started

The first step is to initialize a new Git repository in the live web site directory on the server, then to add and commit all the site’s files. This is the Prime repository and working copy. Even if history exists in other places, the contents of the live site will be the baseline onto which all other work is merged.

$ cd ~/www
$ git init
$ git add .
$ git commit -m"initial import of pre-existing web files"

Initializing in place also means there is no downtime or need to re-deploy the site, Git just builds a repository around everything that’s already there.

With the live site now safely in Git, create a bare repository outside the web directory, this is Hub.

$ cd; mkdir site_hub.git; cd site_hub.git
$ git --bare init
Initialized empty Git repository in /home/joe/site_hub.git

Then, from inside Prime’s working directory, add Hub as a remote and push Prime’s master branch:

$ cd ~/www
$ git remote add hub ~/site_hub.git
$ git remote show hub
* remote hub
  URL: /home/joe/site_hub.git
$ git push hub master

Hooks

Two simple Git hooks scripts keep Hub and Prime linked together.

An oft-repeated rule of Git is to never push into a repository that has a work tree attached to it. I tried it, and things do get weird fast. The hub repository exists for this reason. Instead of pushing changes to Prime from Hub, which wouldn’t affect the working copy anyway, Hub uses a hook script which tells Prime to pull changes from Hub.

post-update – Hub repository

This hook is called when Hub receives an update. The script changes directories to the Prime repository working copy then runs a pull from Prime. Pushing changes doesn’t update a repository’s working copy, so it’s necessary to execute this from inside the working copy itself.

#!/bin/sh

echo
echo "**** Pulling changes into Prime [Hub's post-update hook]"
echo

cd $HOME/www || exit
unset GIT_DIR
git pull hub master

exec git-update-server-info

post-commit – Prime repository

This hook is called after every commit to send the newly commited changes back up to Hub. Ideally, it’s not common to make changes live on the server, but automating this makes sure site history won’t diverge and create conflicts.

#!/bin/sh

echo
echo "**** pushing changes to Hub [Prime's post-commit hook]"
echo

git push hub

With this hook in place, all changes made to Prime’s master branch are immediately available from Hub. Other branches will also be cloned, but won’t affect the site. Because all remote repository access is via SSH urls, only users with shell access to the web server will be able to push and trigger a site update.

Conflicts

This repository-hook arrangement makes it very difficult to accidentally break the live site. Since every commit to Prime is automatically pushed to Hub, all conflicts will be immediately visible to the clones when pushing an update.

However there are a few situations where Prime can diverge from Hub which will require additional steps to fix. If an uncommitted edit leaves Prime in a dirty state, Hub’s post-update pull will fail with an “Entry ‘foo’ not uptodate. Cannot merge.” warning. Committing changes will clean up Prime’s working directory, and the post-update hook will then merge the un-pulled changes.

If a conflict occurs where changes to Prime can’t be merged with Hub, I’ve found the best solution is to push the current state of Prime to a new branch on Hub. The following command, issued from inside Prime, will create a remote “fixme” branch based on the current contents of Prime:

$ git push hub master:refs/heads/fixme

Once that’s in Hub, any remote clone can pull down the new branch and resolve the merge. Trying to resolve a conflict on the server would almost certainly break the site due to Git’s conflict markers.

Housekeeping

Prime’s .git folder is at the root level of the web site, and is probably publicly accessible. To protect the folder and prevent unwanted clones of the repository, add the following to your top-level .htaccess file to forbid web access:

# deny access to the top-level git repository:
RewriteEngine On
RewriteRule \.git - [F,L]

Troubleshooting

If you’re seeing this error when trying to push to a server repository:

git-receive-pack: command not found
fatal: The remote end hung up unexpectedly

Add export PATH=${PATH}:~/bin to your .bashrc file on the server. Thanks to Robert for finding and posting the fix.

Links

These didn’t fit in anywhere else:

No related posts.


  • Jakub Narębski

    Why not use some git web interfaces, like gitweb or cgit?

  • http://www.joemaller.com Joe Maller

    @Jakub Unless I’m mistaken, Gitweb, cgit and similar projects are for hosting repositories while making source code visible on the web. I’m using Git to publish and manage web sites where the codebase isn’t and often shouldn’t be publicly visible. The Git web interfaces provide access to files and history in a repository, this workflow focuses instead on the contents of files in a repository.

  • Adrian B

    This is very interesting, I manage a lot of sites (on shared hosting) and I been wanting to start using some kind of version control and I’ve been looking at git. But since I’m new to both version control in general and git there’s a lot to learn. This seems like a good starting point to experiment with.

  • http://schinckel.net Matt Schinckel

    Great post. I’m about to set up something similar using Mercurial, but most of the concepts with different DVCS are the same.

  • http://tryveganpdx.com/ Chris Emery

    This is exactly what I needed, a reasonable way to keep git synced up to the website and get changes back from the website into git, thank you very much.

  • http://greghostetler.com Greg Hostetler

    If you didn’t want to use a rewrite rule, you could also just deny access to .git folder.

    Order deny,allow
    deny from all

  • http://www.jasondavies.com/ Jason Davies

    Very useful, I currently use something like this but with SVN. I’m switching to Git now so I’ll give your tutorial a try. Thanks!

  • http://floatsolutions.com Aidan McQuay

    Interesting, i’ve used a similar setup with SVN but as my project got bigger it was too slow to be practical.

  • http://www.foo.be/ Alexandre Dulaunoy

    Very Interesting. I have a similar workflow for Oddmuse wiki engine to/from git : http://www.foo.be/cgi-bin/wiki.pl/OddmuseGit .

  • Alex Marandon

    Interesting article. Although it’s certainly easy to look it up ourselves, it could have been handy to include directions on how to set up hooks and to explain a bit what git-update-server-info does.

    • http://www.saltedlolly.com Olly S

      Did you ever discover what git-update-server-info is for? I am at a loss!

  • http://www.pluggable.co.uk Richard Harrison

    Just thought I’d mention that you can do a similar thing to this using GitHub as the “hub” element above, except that the hook is an HTTP callback (http://github.com/guides/post-receive-hooks).

  • http://dmiessler.com/ Daniel Miessler

    Great write-up. Here’s my method of accomplishing the same thing: http://dmiessler.com/blog/using-git-to-maintain-your-website

  • http://www.danklassen.com Dan Klassen

    Thanks Joe. I’m going to try this out as well. This example should get me started on getting a deploy strategy that will work for my setup.

  • http://elecompte.com Evanlec

    Kickass, this works great. Been looking for this for a while…

  • http://pixelchimp.net Drew

    Hey this is great.

    Has anyone considered committing the server DB to another repo & using hooks to version the DB in sync with the commits on the Prime repo?

  • http://www.devmoz.com othmane ouahbi

    @Drew: I don’t think versionning the DB file(s) is a good idea. There are tools ( migrations ) to accomplish this (at least for the db scheme) and they on the other hand are files you can version.

  • http://mdgrech.com Michael

    I finally got this working, thanks joe! Also I should note that I had to chmod post-commit and post-update to executable

    chmod +x /path_to/post-commit
    chmod +x /path_to/post-update

    • http://www.joemaller.com Joe

      @Michael, the activation method for hooks changed with the Git 1.6.0 release. If you’re using an older version, you won’t have to rename the script, but as you point out, you will need to make it executable.

    • Jon

      TY for sharing this 2 years ago…  just helped me out today :)

  • http://www.webcoder.eu David

    Hi, Thanks for this great article. Our websites are often talking to different systems such as DBs, LDAP etc. we cannot sync back directly to our PRIME from HUB and rather auto push changes to our TEST environment first then manually clone WWW-TEST to WWW-PROD. Even though your code might seem to be OK in HUB it doesn’t guarantee it’ll work with other systems (e.g. you forgot to reflect code changes on those systems). I stick to the golden rule “Always test before sending to production”.

    test prod
    SYSTEMS SYSTEMS
    | |
    WWW-TEST -> WWW-PROD
    \
    HUB
    / | \
    dev team

    Cheers, D

  • Fabian

    Great tutorial. I’m quite a newbie on all of this. I’m up to the Hooks section. I don’t know how to create the shell scripts and get them to automatically execute? Could anyone point me in the right direction pls?
    Cheers :)

    • Fabian

      ps: I’m using Dreamhost shared hosting (if that helps).

  • http://jkoops.com Jed Koops

    @Fabian: I had the same question, and discovered that you put the hooks files in the .git/hooks directory of the repo you want it to run on.

    • Fabian

      haha yea, I managed to find it on the git manual page *chuckle*

  • http://jkoops.com Jed Koops

    Nice tutorial. It would be great if you added one more section: how to clone the remote repo on your local machine (git clone ssh://yoursite.com/~/site_hub.git). I was getting the “fatal: The remote end hung up unexpectedly” error when trying to execute the clone command, until I realized I was not specifying the full path on the remote server filesystem (the tilde referring to my home directory was crucial).

  • http://www.kelvinluck.com Kelvin Luck

    Great tutorial – I’ve been using your method for a while and am very happy with it. I have a question though about a new way I’m trying to use it which is causing me headaches (it’s probably because I don’t quite get git completely yet). Basically I’m working on a branch locally to implement some new functionality. And I’d like for this branch to be checked out to a different directory on my server (which is served up as a seperate subdomain). How would I go about implementing this?

    Thanks!

    • http://www.joemaller.com Joe

      @Kelvin,

      What you describe sounds a bit SVN-ish. A more Git-style solution, as opposed to just flipping branches, might be a local clone of your repository where you could create your alternate branch.

  • Yann

    I’m in the process of setting up something along those line, but I was wondering what the point of the “hub” is exactly… Couldn’t just clone the live site directly? Is there any problem doing that? Thanks!

  • Yann

    nevermind… should have read the whole post first ;) thanks!

  • http://meanstudios.com Cody

    Thanks a ton!! This really helped me out a lot :).

  • http://omita.com Hays Clark

    Is there any magical way to make any change to files auto-commit? I have some artist that use FTP to upload content to the site and I was curious if there was some nifty way to have GIT auto-commit on any change in Prime.

    • http://www.joemaller.com Joe

      The watch command can keep track of directory changes, or else just use cron to add and commit the directory regularly, if there are no changes there won’t be a commit.

  • http://hkn.eecs.berkeley.edu/~ibrahima Ibrahim

    Does this script work if the Prime repository is owned by a different user than the one that owns Hub?

  • http://www.profv.de/ Volker Grabsch

    If “git push” would work properly for non-bare destinations (as it is the case with e.g. Mercurial and also Darcs), the design could be simplified by merging “hub” and “prime”.

  • fedesilva

    Nice post.
    I do something similar for work in progress previews.
    The difference is I push to a preview branch and the post update hook does:

    echo "PostUpdate Hook"
    echo $1 $2 $3

    case " $* " in
    *' refs/heads/preview '*)
    /home/client/bin/update-repo.sh
    ;;
    esac

    exec git-update-server-info

    The update-repo.sh is similar to your hook’s body.

    • http://www.joemaller.com Joe

      Somewhat embarrassingly, I had completely missed the various hook parameters until reading your comment, I’m definitely going to be experimenting with these. Thanks!

      For reference, post-update hook documentation: “[Post-update] takes a variable number of parameters, each of which is the name of ref that was actually updated.”

      • fedesilva

        Glad to be of help :)

        I find that pushing to a specific branch lets me use Hub to share with other devs and the design team without triggering deploys or setting yet another bare repo.
        I will incorporate triggering a deploy to production soon.

        Your trick to resolve conflicts in the Prime repo through a brach is neat, will be using that, too.

  • J Smith

    This page doesn’t seem to print very well. I have only tried in Firefox and Opera. I think it is down to the following in style.css:

    #main{

    overflow: hidden;

    }

    It seems to print okay when I disable this using Firebug or Dragonfly.

  • Paul

    Hey Great article!

    I have an issue that when I pull to my webserver I can no longer edit/delete those files via FTP.

    Does Git lock them somehow?

    Thanks

    Paul

  • http://www.principalwebsolutions.com darrell

    How are you testing if the changes you make are working and the site works the way you want it until pushing to Prime? Is your local working copy in a folder on a test server that you can run the site locally to test?

    • http://www.joemaller.com Joe

      @darrell Yes, my local clone lives in a functional working environment which closely mirrors the live server’s configuration. I’ve also used a variation on this to have a parallel dev server for testing where my local branches push to different remotes. It’s very, very flexible and extremely forgiving.

  • ben

    If I wanted to have a staging version of the site, could I just add a “cd ~/stage/” and “git pull hub staging_branch” to the post-update hook? As well as a “cd ~/live/” & “git pull hub production_branch” for the live site?

    I think that would enable me to have a staging branch and a production branch both updated with the required changes.

    • http://mideos.com Samin Shams

      If you keep the staging and production branch updated with every change, what’s the point of having a staging branch. I mean, there should be a way to update to stage, verify if everything is OK, and then push to production. Anyone know of a way?

      • http://www.joemaller.com Joe

        I usually keep a staging site paired with a different branch. All you need to do is repeat the `cd; unset; git pull` section of the post-update hook for the other branch and path.

        The workflow is then pretty much the same, whenever dev is at a milestone, you just merge the staging branch into master and push the changes.

      • Britt O’Halloran

        I have a very similar setup to what you’re describing. I have two branches: dev and master. dev is my development and test branch, and master is my live site. 

        On my server I have a bare git repo OUTSIDE public_html. It has the following post-receive hook:

        GIT_WORK_TREE=/home/britt/public_html/LIVESITEFOLDER/export GIT_WORK_TREEgit checkout master -fGIT_WORK_TREE=/home/britt/public_html/DEVSITEFOLDER/export GIT_WORK_TREEgit checkout dev -f

        First update the live site from the master branch (if needed), then update the dev site from the dev branch (if needed)

      • http://www.jeffbyrnes.net/ Jeff Byrnes

        I’ve got a post-update hook that conditionally updates a particular site based on the branch I’m pushing in:

        #!/bin/sh

        echo
        echo "**** Pulling changes into Dev [Hub's post-update hook]"
        echo

        case " $1 " in
        *'refs/heads/development'*)
                cd /var/www/vhosts/dev.domain.com/httpdocs || exit
                unset GIT_DIR
                git pull hub development
                echo
                echo "Dev was pulled"
                echo
                ;;
        esac

        case " $1 " in
        *'refs/heads/master'*)
                cd /var/www/vhosts/domain.com/httpdocs || exit
                unset GIT_DIR
                git pull hub master
                echo
                echo "Master was pulled"
                echo
                ;;
        esac

        exec git-update-server-info

  • http://active-thought.com/ Daniel Waterworth

    I’m having a problem with this. The prime repo (the working tree) is getting the wrong permissions, it isn’t group writeable despite being set as a shared repository. Any ideas?

    • http://www.joemaller.com Joe

      I haven’t run into that problem (yet?). Have you tried tweaking the the core.sharedRepository setting in Git Config?

  • HASSELHOF

    Great post. Exactly what I needed. Thank you!

  • http://designmuseumboston.org Steve A.

    Thank you for this tutorial! It is going to save me alot of time and headaches. I followed the tutorial and have everything working.

    Like some others have mentioned in previous comments, our organization also like to develop locally then push to a test or staging repo.

    When I start to think about how to integrate this functionality, my head starts to spin. Any chance someone could break it down for me?

    Thanks again!

  • http://www.mnemonicdictionary.com Amit Aggarwal

    In case hooks are not working for anyone, make sure you run chmod +x post-update and chmod +x post-update post-commit for your hooks

    • http://jplew.com/ JP Lew

      this is a good point, I had this problem. it should be added to the original post.

  • Zach Shepherd

    In the rule “RewriteRule ^.git – [F,L]“, “.” is a wildcard. This will block access to http://example.com/agit in addition to http://example.com/.git. If you only want to block access to the later, simply escape the period (i.e. use “RewriteRule ^\.git – [F,L]“).

    Additionally, this rule only matches documents at the root; if you have multiple nested git repositories for a single domain (e.g. one for http://example.com and one for http://example.com/blog, http://example.com/blog/.git will not be protected). To forbid access to all files containing “.git” in the path, you can remove the “^” (i.e. use “RewriteRule \.git – [F,L]“).

    • http://www.joemaller.com Joe

      Good catch, I’ve updated the post.

  • http://oli.zilla.org.uk olizilla

    Thanks for writing this up. I’m rolling out this set up today and ran into some trouble installing git on Debian Lenny; The git you get from apt by default is a gnu file viewer app, not the version control tool, and once you figure out its package name is actually git-core you end up with git 1.5! So in the form of a strange love story, here is what happened:

    http://oli.zilla.org.uk/2010/12/07/installing-git-on-debian-lenny.html

  • http://kaspersorensen.com Kasper Sørensen

    Nice article. I am thinking of replicating your setup, I just have one question. If I use this setup and change a file from within my WordPress installation. For example sya I update the theme. Will those new, updated, files be added to the hub automatically?

    • http://www.joemaller.com Joe

      Nothing is added automatically, but you can commit from your live WordPress site and everything in that commit will be automatically pushed to Hub.

      If you change live site files without committing then push from a working repository, Hub won’t overwrite the files because your live site will be in a “dirty” state.

      • http://kaspersorensen.com Kasper Sørensen

        Thanks for your reply. That’s why I’m on the fence about using Git with WordPress. WordPress often updates stuff in the wp-content folder. Especially when you run a WPMU installation.

        Even if I commit to the Hub first, then a week after, when I come to push the new theme to Prime, it will be dirty again, because new files have been upploaded, a plugin has been updated etc. etc.

        I don’t really see how you can use Git with dynamic websites. You won’t ever be able to mirror the liveserver (Prime), because it’s constantly changing.

        • http://www.joemaller.com Joe

          This system or others like it are being used on many, many sites. I’m personally using this on a bunch of dynamic sites including a few running WordPress. On some I have updating disabled in WordPress, that way I have full control over what gets updated and when. Others, I just periodically update the repository if I’m tweaking something.

          I personally dislike the idea of automated commits, but it would be trivial to put the Git commands into Cron or to build a simple WordPress admin plugin.

          • http://kaspersorensen.com Kasper Sørensen

            It makes sense to disable auto updating. But even so there are many files in the wp-content directory that will be updated. For example the uploads folder and when running wpmu the blog.dir.

  • Otto Kovarik

    I have maybe stupid question, but what does it mean GIT_DIR in post-update ?
    And why there is unset ?

    • http://joemaller.com Joe Maller

      From Git’s online docs:

      “If the GIT_DIR environment variable is set then it specifies a path to use instead of the default .git for the base of the repository.”

  • http://profiles.google.com/jannisg Jannis Gundermann

    Does anyone know how I can adapt this model (I’ve got this working as it is above) but add a separation for staging and live sites.

    So essentially have 3 separate branches, master + stage + live, updates to stage will trigger an update of ~/stage.domain.com/ whereas any updates to the live branch will trigger an update in ~/domain.com?

    • http://www.jeffbyrnes.net/ Jeff Byrnes

      You’ve got it: two main branches, one for each server. Pushing to master sends changes on to live, pushing to dev/stage (or whatever you call it) sends changes to staging. Workflow I’ve come up with is like so:

      Create branch from dev, work locally until you’re ready to test, merge back to dev, push to Hub, which sends changes to dev/staging server.

      Once you’re ready with those to go live, merge from dev to master, then push master to Hub, which sends those changes on to the live server.

      I found this site’s ideas on Git workflow to be most helpful:
      http://nvie.com/posts/a-successful-git-branching-model/

    • http://www.jeffbyrnes.net/ Jeff Byrnes
      • Talofo

         Hello Jeff, one question I have myself. According two your suggestion, where should we first place our prime repository ?

        The original author here says:

        “The first step is to initialize a new Git repository in the live web
        site directory on the server, then to add and commit all the site’s
        files.”

        We have, here, two live directories, one for staging and another for production so, where should we this first step be made ?

        Thank you all in advance.

        • Talofo

           According to, and not according two; Sorry.

        • http://www.jeffbyrnes.net/ Jeff Byrnes

          I would recommend you start with the production/master site, and recreate the staging one. To explain:

          git init --bare your Hub

          git init your production directory
          git commit your production dir
          git remote add hub ~/site_hub.git to connect production to your Hubgit push hub master to push your production code into your HubDelete everything in your staging dirgit clone ~/site_hub.git into your staging dir to recreate your production site in staginggit remote rename origin hub to keep things sanegit branch staging && git checkout staging to create a new “staging” branch & check it outgit push hub staging to push your staging branch back to HubNow set up the two hooks like I mention above, and then clone the Hub locally, and push & pull into the two branches. Et voilà!

          • Talofo

            Thank you so much Jeff, struggling to understand, because I’m quite new into all this, so I have  3 quick (I hope) questions:

            1) Why / Where should we do git remote rename origin ?

            2) Why should we clone the Hub locally ? I thought that the bare hub repository would exist only on the shared host servers not locally.

            3) On your hook mention above, is this line part of the hook (to be placed at the very end, or is it something else?)

            exec git-update-server-infoThanks again, and sorry for all those questions. :(

          • Talofo

            1)From our staging directory – so that we call the remote repo equally.

            2) To work on it locally.

            3) It’s part of the hook.

            Jeff, one question still remains, you refer “know setup the two hooks that I mention above” ….

            I’m only seeing one hook. A post-update hook.

            What am I missing?

            Thanks again. It took 3 chapters on git pro book to answer those questions. :)

          • Christian

            I really excited about setting up my projects like this. Thanks for the article! I’m fairly new to git, but I have learned a lot over the past few days.

            I’m running into a problem and I hope you can help me.

            I get to “git remote rename origin hub” and I get an error: Not a git repository
            Previously commands used…
            cd ~/domain.comgit initgit add .git commit -m “initial import of pre-existing web files”cd ~/hub.domain.comgit –bare initcd ~/domain.comgit remote add hub ~/hub.domain.comgit push hub mastercd ~/dev.domain.comgit clone ~/hub.domain.comgit remote rename origin hub

          • Christian

            Here are the commands again separated…

            cd ~/domain.com
            git init
            git add .
            git commit -m “initial import of pre-existing web files”
            cd ~/hub.domain.com
            git –bare init
            cd ~/domain.com
            git remote add hub ~/hub.domain.com
            git push hub master
            cd ~/dev.domain.com
            git clone ~/hub.domain.com
            git remote rename origin hub

          • Christian

            I think what is throwing it off is running “git clone ~/hub.domain.com” from within “~/dev.domain.com” create a “~/dev.domain.com/hub.domain.com/” directory. Not sure how to avoid this.

  • João Bruni

    Nice.
    I have a slightly different set up described here:
    http://stackoverflow.com/questions/6112852/my-git-website-update-strategy

  • Parekhpiya2002

    Thanks for your valuable information.!
    But can you please tell me how I will get basics about this..as I am completely new to this concept.
    Thanks in advance…!

    My web Address is : http://priyankaparekh.blog.co.in/

  • dixhuit

    I got a a fatal error trying the initial push from Prime to Hub:

    fatal: unable to create thread: Resource temporarily unavailable
    error: pack-objects died with strange error
    error: failed to push some refs to ‘/home/yadayadayada…’

    I managed to fix this by editing the resource limits for GIT:

    $ git config –global pack.windowMemory “100m”
    $ git config –global pack.SizeLimit “100m”
    $ git config –global pack.threads “1″

    Then I just tried to push again and it seemed to work.

    Hope that helps someone.

  • Henry Turk

    How would you go about ignoring temp/config files specific to the environment in which they are running?

    Lets say I have Prime hosted on a production server with its specific config files inside of config/. I then clone Hub locally and receive those same config files but I need to modify them in order to be able to run the web-app locally. Once modified and committed those files are pushed to Hub and subsequently pulled to Prime rendering Prime dead in the water.

    We can’t handle this by untracking the config/tmp directories completely because it is needed during cloning.

    Suggestions?

    • http://joemaller.com Joe Maller

      I solved this by writing a small python script which assembles the config files from a specifically named set of components. In my case I have four .htaccess files:

        .htaccess_base
        .htaccess_live
        .htaccess_dev
        .htaccess_localdev

      The assembly script knows where it is and combines the appropriate component file with the base file. 

      I call the script from the post-commit and post-merge hooks.

      • http://www.jeffbyrnes.net/ Jeff Byrnes

        I would love to see this script (or an example version), as I’ve got to work out this very thing :)

    • dixhuit

      You need to check out .gitgnore. I’ve found that it’s easiest to set this sort of stuff up *before* you start moving stuff around with GIT.

  • Jon

    After several days of fiddling and figuring I’ve got this all working… (I’m doing joyful backfilps).  It took some “extra” effort because I’ve got it setup on on a shared web server with only a jailed shell and (I’m uncertain of all the right terms here but) the method you describe using bashrc doesn’t work for Host Monster (as far I can tell) it’s different than …  took me forever to figure out I needed to chmod the hooks.  They worked manually “sh post-update” at the SSH’d in at command line , they just wouldn’t work when using a git client.

    One question…  With this, in your workflow, if/when you make changes on the server you still have to SSH in and do a git commit -a at the command line, is that correct?  There isn’t a way to magically trigger that post-commit hook remotely from my local git client, right?

    I wrote a little local shell script that logs in, switch to prime’s director and runs the hook then logs back out, but I’m wondering if there is an even better way…  

    The shell script looks like this:

    ssh  remote_user_name@myserver.com 

    cd /home/remote_user_name/www/website_dir/

    git commit -m “remote server commit”

    exitIt’s not ideal to always commit with description of “remote server commit” but I don’t know another way to make it quick and painless… and it’s infrequent, right?

    • joe

      Thanks Jon, I was wrestling with this the last few weeks, but then check the permissions on the directories in the hooks file. Turns out that did the trick. Thanks!

  • Gary Hussey

    How are you dealing with your database changes?

    • http://joemaller.com Joe Maller

      nightly cron backup.

      • http://bossninja.com Gary Hussey

        So if one of the developers needs to change the schema you’re handling it manually?

        • Anonymous

          There are SQL commands to read the schema…

  • Samgalope

    Thank you soooooo much!!! I have been doing back flips and calling on the gods of linux just to understand and actually implement the Git workflow. I just wish databases can also be Git-ed. Anyway, I’ll be quoting you on my blog at samgalope.org.

    Thanks again!

  • Anonymous

    Hello! What if you are dealing with user uploads from the server (like profile pics)? Would you include these files in the reposity or ignore them? if you would include them, how would you go about having the server commit these changes?

    thanks!
    david

  • Haiyang

    Thank you so much for sharing this. I find it very valuable.

  • Terranmoccasin

    Great article!  I’m trying to set this up, but I am having some trouble with the hooks.  I am using an account that is in the sudoers file.  Pushing with the hook files as you posted gives me a permission denied error, but if I add change it to something like “sudo git pull hub master” I get an error still.  Do you know what could be causing the problem?

  • Fred

    Git push says it was successful.. but when I check my public_html(the master branch) there are no files to be seen? 

  • http://twitter.com/zelob Philipp Ringli

    If you get these errors when pushing the empty repo …
    git push hub master
    error: src refspec master does not match any.
    error: failed to push some refs to ‘/home/sitedir/site_hub.git’

    add a README file or something to your ~/www (prime).

  • alias

    Joe, thank you so much for this article!

  • alias

    But if you use ispmanager i recomend to create hub in the domain folder, because one user on server can may many domains

  • Ben

    Just following this through. Clearest example I have come across although some linkrot has set in.
    Beginner question – the parts in the post that start: #!/bin/shIs this script entered in the terminal window line by line? Or do I use vim and paste the contents in and save it somewhere as something specific? Sorry – just a little unsure of what I actually do with them having never done anything like this before (just getting into Git!).

    Thanks for any pointers (feel free to explain as if talking to a child!)

    • http://joemaller.com Joe Maller

      Thanks Ben, 

      There should be sample hooks scripts in your `.git/hooks` directory. These are enabled by renaming them (just remove ‘.sample’ from the filename). You can edit the scripts with any text editor.

      I do need to clean up some of these links…

      • Ben

        Hi Joe, thanks for the reply. I’m actually further along now. Just don’t have a /hooks directory in my ‘prime’ repository for some reason. Fear not, I’ll get this working if it’s the last thing I do. And at the rate I’m going, it may well be! Thanks for this post though, certainly a lot further along having read it.

        • http://joemaller.com Joe Maller

          from the top of your repo, cd .git/hooks

          • Ben

            OK Joe, thanks. Dare say I’ve got this working. However, what if I’ve developed locally (e.g. I pulled down on a remote repo from Github, added a submodule and my own changes) and want to push this up to the empty prime/hub repository on the server? What command would I use from the local repo to ‘push’ it up there (apologies if I’m using wonky terminology!).

            Thanks again for any further thoughts. 

  • Anderson

    This doesent make any sense. You have two repos on a server? How is is this idea? Isn’t the idea of a perfect web dev workflow is to have a LOCAL repo on your working machine and to PUSH commits to a remove web server to go live? This is useless. Just like Git is.

  • Ryan

    This is very clever, thanks!

  • Bill

    Hi

    I have been able to set up the 2 repos on the server as described in the article. I also have the post update and post commit hooks in place. I am trying to test the system by pushing an update to the server. I am using netbeans 7.3 beta git support. However when give the command:

    push ( in netbeans ) to ssh://****.com/home/******/site_hub.git

    ==[IDE]== Nov 19, 2012 10:28:15 PM Pushing
    git push ssh://***.com/home/***/site_hub.git +refs/heads/master:refs/heads/master
    Repository Updates
    Branch : master
    Old Id : 0121897bdd7cf3caad9e18717fc27a7a08***
    New Id : 837c194c70fb41dc7de3be7841c946ca***
    Result : REJECTED_NONFASTFORWARD

    Local Repository Updates
    No update
    ==[IDE]== Nov 19, 2012 10:28:18 PM Pushing finished.

    I was advised to try ” git pull in order to merge both branches, and then push the result” – this did not work.

    I was also advised to try ” git fetch and then a git rebase origin/master (or whatever names you put to your remote and HEAD branch) so your commits are applied on top of the remote branch. Then you can push the result ”

    Can anyone advise me on what to do next?

  • Lutz

    I am using this for all my projects! Thanks a lot.

  • Dan Bohea

    Git v1.8.1 doesn’t seem to have a post-commit hook any more (no sample hook file in the hooks directory). Having used this workflow successfully on older versions of Git many times before (Thanks! btw) I’m unsure how to adapt it to this later version of Git. Simply creating a post-commit hook file from scratch doesn’t seem to work. Any ideas?

    • http://www.jeffbyrnes.net/ Jeff Byrnes

      Not sure what you’re seeing, but looking in a new clone I just made w/ Git 1.8.1, there’s definitely a hooks folder w/ sample hooks, so they’re still there.

      • Dan Bohea

        Is post-commit.sample still there?

        • http://www.jeffbyrnes.net/ Jeff Byrnes

          Hm, you’re right, I didn’t notice that post-commit.sample was missing. The Git docs still suggest it’s a hook, so make sure you’ve made your post-commit executable. Failing that, it seems the hook has gone.

          • Dan Bohea

            Double checked that post-commit is executable but still no dice. Do you have it working at your end? Properly scratching my head now – will I have to abandon this workflow I wonder (hope not)?

          • Noel St. John

            I just added the post-commit script and it worked. I has to be named “post-commit”.

      • Dan Bohea

        This is what I get when I create a new repo with 1.8.1: http://goo.gl/pVW23

    • Dan Bohea

      Got it working. Thanks for all your help Jeff.

      It does indeed
      still work despite the sample hook file no longer being present by
      default. I had some other errors going on that was preventing the
      post-commit from firing (errors that I would have had in any version of
      Git).

      FALSE ALARM! :D

  • joe

    Thanks for sharing Joe. This will undoubtably make my workflow much easier form now on!

  • Chandrakanta Pal

    what if I have some file in some directory above the webroot. e.g. in case of codeIgniter it is recommended to put the system dir above public_html.

  • Joe

    unset GIT_DIR never worked for me but i found a solution

    git –git-dir /path/to/hub/.git pull hub master

  • http://twitter.com/the_3 John Porter

    I apologize if this is a stupid question, but where does the post-commit script go? In the hub repo I created, there is a hooks directory with sample scripts, so it’s pretty clear that post-update goes there. But from the wording of this walk-through, it seems like post-commit is supposed to be in prime’s repo, but I’m not sure that’s the case…

    • http://www.facebook.com/hutber Jamie Hutber

      my thoughts exactly… :o

    • http://www.jeffbyrnes.net/ Jeff Byrnes

      You might check out James Doyle’s script to automate things & simplify: https://github.com/james2doyle/git-website-workflow

      To answer the question directly though, yes, the post-commit hook goes in the Prime repository; it makes sure that if you make a commit on the server (Prime), it’s immediately pushed up to Hub, so things stay in lockstep at the production end.

    • http://Coeus.cc/ Patrick Zeinert

      I know this was 8 months ago, but since I’m revisiting the article to get back up to speed, I think I can solve your problem (and anyone after you).

      Based on your issue, odds are you are using Windows. If you are, simply:
      * go to the prime repo folder in explorer
      * tap ALT to bring up the secondary menu bar
      * click Tools->Folder Options
      * select the “View” tab
      * find “Hidden files and folders” and select the “Show…” radio button

  • James Doyle

    I turned this into a script. You should run it and it will do all this for you.

    https://github.com/james2doyle/git-website-workflow

    • http://www.facebook.com/hutber Jamie Hutber

      pretty dam sweet dude… pretty…dam…sweett!!!!

    • http://jplew.com/ JP Lew

      Thanks James, I learned a bunch by looking at your script.

      You’re using `post-receive` instead of `post-update`. Any reason for that, what’s the difference between the two?

      • http://warpaintmedia.ca/ James Doyle

        In this case it probably doesn’t matter since I’m bit using/logging any of the info from the repo. I’m just using the hook to purely run an external script. I found this answer on stackoverflow and it seems to be a good explanation.

        http://stackoverflow.com/a/9653208

        • http://jplew.com/ JP Lew

          got it, thanks for the link!

  • Umair

    Can we have file filters for prod like .gitignore? I want to deploy my whole master branch excluding some files.

    • http://www.jeffbyrnes.net/ Jeff Byrnes

      Unfortunately, no, this workflow doesn’t lend itself to that. You could explore using something like Capistrano, or just a simple rsync, both of which can exclude files as part of their copy process.

    • http://joemaller.com Joe Maller

      Hooks are just shell scripts. The nature of Git is to keep things identical, but there’s no reason you couldn’t monkey around with it. Take a look at `git update-index` especially the `–assume-unchanged` and `–no-assume-unchanged` flags.

      • http://www.jeffbyrnes.net/ Jeff Byrnes

        *facepalm* or, y’know, that works too. Obviously I need more caffeine…

  • http://www.digitalhit.com ianevans

    Scratching my head here. Followed the tutorial (thanks!) and cloned the hub to my laptop. When I created a file in the main directory of the clone it would appear in the web server directory. So I thought all was well until I tried to add a file in a sub-directory and recd the following error message:

    remote: **** Pulling changes into Prime [Hub's post-update hook]
    remote:
    remote: From /home/git/site_hub
    remote: * branch master -> FETCH_HEAD
    remote: error: unable to create file academy/testthegit2.html (Permission denied)

    I’ve started Googling, but I can’t see why it’s getting permission errors for sub-directories but not the root.

    Thanks.

  • Vladislav

    Thank you, Joe! Your guide helped me a lot!

  • Matt Speakman

    This post was absolutely instrumental in my solving this workflow, after reading and researching literally dozens of others. Thanks. I’m still trying to get the hooks right, as I’m currently pulling and pushing manually.

  • Toni

    Obvious tip: ran into some file permission issues at post-update hook level due to our developpers pushing from “clones” to “hub” with a dedicated system user for git operations. Recursively chowning the “hub” directory to that user solved this first issue. Due to the post-update hook runnig as that same user we had to add the git user to the www-data group and chmod g+w the root directory of “prime” to allow for file creation. All new and modified files will belong to git:git but permissions will be set to 644 so no big issue.

  • rffaguiar

    what the hell is this magic? lol

  • http://www.digitalhit.com ianevans

    I’m currently using this setup on my site. Thx. How would you suggest migrating the hub/master to a new server as we’re changing hosts

  • Dan Z. Bonomo

    Has anyone used this method in conjunction with Gitolite?