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


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 to help distinguish server-based changes in project history.

$ git config --global "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


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.


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

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.


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

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.


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.


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]


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, also to Top9Rated for creating this list on the top desks right here.


These didn’t fit in anywhere else:

156 Responses to “A web-focused Git workflow” Comments Feed for A web-focused Git workflow

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

  • @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.

  • 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.

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

  • 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.

  • 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

  • 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!

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

  • Very Interesting. I have a similar workflow for Oddmuse wiki engine to/from git : .

  • 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.

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

  • 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 (

  • Great write-up. Here’s my method of accomplishing the same thing:

  • 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.

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

  • 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?

  • @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.

  • 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

    • @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.

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

  • 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
    | |
    / | \
    dev team

    Cheers, D

  • 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 :)

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

  • @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.

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

  • 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:// 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).

  • 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?


    • @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.

  • 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!

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

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

  • 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.

    • 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.

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

  • 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”.

  • 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 '*)

    exec git-update-server-info

    The is similar to your hook’s body.

    • 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.”

      • 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.

  • 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:


    overflow: hidden;


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

  • 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?



  • 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?

    • @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.

  • 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.

    • 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?

      • 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.

      • 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)

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


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

        case " $1 " in
                cd /var/www/vhosts/ || exit
                unset GIT_DIR
                git pull hub development
                echo "Dev was pulled"

        case " $1 " in
                cd /var/www/vhosts/ || exit
                unset GIT_DIR
                git pull hub master
                echo "Master was pulled"

        exec git-update-server-info

  • 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?

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

  • Great post. Exactly what I needed. Thank you!

  • 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!

  • 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

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

  • In the rule “RewriteRule ^.git – [F,L]”, “.” is a wildcard. This will block access to in addition to 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 and one for, 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]”).

    • Good catch, I’ve updated the post.

  • 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:

  • 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?

    • 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.

      • 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.

        • 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.

          • 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.

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

    • 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.”

  • 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 ~/ whereas any updates to the live branch will trigger an update in ~/

    • 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:

      • Hi Jeff, I was able to implement this for staging. However, after I push to Hub, I realize that I want to revert changes, is there an easy way to do it? I reverted changes in my dev master and did a force push to remote, but it didn’t work very well.

        • Mythri, you shouldn’t need to do any force-pushing if you’ve reverted. A git revert is a forward-moving change, and adds history to your repository.

          However, if you git reset to a previous commit… that would cause you some serious pain. You cannot use reset with this workflow.

          You’ll need to SSH to the Hub server, and git pull there to get things sorted out. Might be easier to just re-clone the Hub repo.

          Frankly, I’d recommend different mechanisms for deploying code at this point; you might check out something like DeployHQ.

          • Thanks for responding Jeff. I think all I need to do is to revert all the commits in the most recent merge.

          • Actually, you can just revert the merge commit (if there is one), and that will accomplish the same thing.

          • Yes, that’s right! Thanks Jeff.

      •  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

        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.

        •  According to, and not according two; Sorry.

        • 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à!

          • 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. :(

          • 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. :)

          • 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

          • Here are the commands again separated…

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

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

  • Nice.
    I have a slightly different set up described here:

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

    My web Address is :

  • 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.

  • 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.


    • 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:


      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.

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

    • 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.

  • 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:


    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?

    • 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!

  • How are you dealing with your database changes?

    • nightly cron backup.

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

        • There are SQL commands to read the schema…

  • 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

    Thanks again!

  • 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?


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

  • 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?

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

  • 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).

  • Joe, thank you so much for this article!

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

  • 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!)

    • 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…

      • 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.

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

          • 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. 

  • 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.

  • This is very clever, thanks!

  • 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***

    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?

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

  • 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?

    • 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.

      • Is post-commit.sample still there?

        • 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.

          • 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)?

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

      • This is what I get when I create a new repo with 1.8.1:

    • 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


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

  • 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.

  • unset GIT_DIR never worked for me but i found a solution

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

  • 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…

    • my thoughts exactly… :o

    • You might check out James Doyle’s script to automate things & simplify:

      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.

    • 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

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

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

    • 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?

      • 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.

        • got it, thanks for the link!

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

    • 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.

    • 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.

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

  • 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: 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.


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

  • 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.

  • 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.

  • what the hell is this magic? lol

  • 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

  • Has anyone used this method in conjunction with Gitolite?

  • This model has quite a few flaws. I’m writing a ruby gitolite provision software which will allow people to set it all up correctly automatically. It will address all the issues present here, so when I publish it, I will post a link here. Now for some problems (not-exhaustive) with this system:

    – you should clone your hub instead of initing, that way you can use hard links for you object files. (otherwise you have at least 3 copies of all your data sitting around)
    – as a default option, don’t init your Prime inside your www docroot. The webserver has no business with this folder. There is no need in git to have it inside the worktree. Put all you website repos in your gitolite user directory for example.
    – you don’t take care of permission problems. git checks out always with the same permissions. Websites often need different permissions for different files. If your webserver needs to change certain files but they are owned by your gitolite user, you will have to take care of that.
    – your hooks are a bit to simple. If data can change on the server, you will get merge conflicts which will need human intervention. The way I do it, from your hub, before (gitolite PRE_GIT) and after (post-receive-hook) every action, trigger a script on your Prime which does something like:
    a) git add . && git commit
    b) git pull –ff-only
    c) if success -> checkout
    d) else git push –force hub
    This way, your prime will only get in changes that don’t cause merge conflicts, and it will reset the hub if something went wrong. That means if the dev does pull/fetch, they always have the latest version and if they push, the conflict will be on the hub, which will refuse their push. The developer will then have to pull, merge the conflict and push again. The added benefit is the dev will always get to see any changes that happened on the server before putting a new version over.
    Btw it is possible to do a single master setup, but the reason this is better is because it makes two-way communication possible. Since git/ssh/filesystem pushes are not atomic, you’d have a very hard time implementing that with only a Hub.

  • Hi Jeff.

    I found this article very usefull, but how can i adapt this to the following architecture:
    i have a local repo -> here i work
    I have a bitbucket master repo -> here i keep my code
    I have a repository on a web server -> here is where i keep the version that my users have to see.

    I want to be able to update the repo on the webserver only if there’s a new version on my bitbucket master branch. and i want to do this automatically

    • I get the impression you’re asking me this question, since the author’s name is Joe.

      You could set up a webhook on your Bitbucket repo that, whenever you push, it makes a request to a script on your web server (perhaps the PHP one above, or something similar) that does a git pull origin master on the repo that your web server is serving.

      I’d encourage, however, using something like Capistrano to handle your deployments, instead of Git. It’s become quite a lot easier to set up with Capistrano 3, and it will give you the ability to not only deploy, but roll back a deployment if things go awry. Not always possible with database changes, but for a file-based site, totally awesome.

  • This helped me a lot, thanks!

    I dug a bit more and replaced the hook with the one in the first technique here:

    That way you don’t even need the live production folder to be a .git repo at all.

  • Is there a follow-up to this article on actually connecting from a local machine to the server in order to push or pull from the hub? Haven’t been able to figure this part out.

    • did you try `git push hub master` and `git pull hub/master`?

    • You need to get ssh working first. If you can’t connect to your server with ssh, none of this is going to work for you.

  • This may be a dumb question, but how are changes made to the live server then committed to the Prime repo?

  • I am getting error: src refspec master does not match any.

    error: failed to push some refs to ‘/home/godevteam/site_hub.git’ when git push hub master, also how do I connect this to github?

  • I’m going to have to study this a bit more before implementing it, but I found this while looking for a solution that will allow “real” developers to use a Git Workflow, while still allowing a “designer” to make changes directly to FTP without me having to be hyper vigilant about watching for updated files anymore less I overwrite the designer’s changes. If it works out, I owe you a beer at the very least for time and headaches it’ll save me.

  • This is great but how do I add a remote that gets feed from the Prime. And can I have this remote do other commands different from the standard.

  • Question…in the following section:
    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.

    should the phrase “pull from Prime” read “pull from Hub”?

  • workflow git

  • The lovely overview drawing is now missing. Was it not included in your git repo? Haha. Fortunately it is archived:

    Thanks for keeping this very useful article published all these years!

  • Would you say that the hub is something like a staging environment?

    • No. The hub doesn’t have a work tree at all. Read again. By the way, I found those post very helpful and I’m using it in production in 2024.Thank you

Leave a Reply