The difficult part is not having machine B update, but rather defining precisely how you want B to update. For instance:
What if on machine B, the branch that is checked out is deploy, but I've made some changes there and checked them in, and now I make changes on A and force-push to deploy and there would be a merge conflict if this were a real merge? (In fact, merge does not really apply on push, as I'll describe in a bit more detail below.)
These questions rarely have a single right answer. That's why git push has the receive.denyCurrentBranch option in the first place: if the answer to the first question above is assumed to be no (it usually is no), then only updating the currently-checked-out branch raises the remaining questions. If we deny the ability to do that, why then, all those questions vanish and we don't have to think hard about the answers! :-)
There's a simple way to sidestep all of this, which is to have a bare repository on the receiving machine, and then what you might call a "bare work tree" (no .git directory in it) somewhere else on that same machine. That way there's no direct notion of "current branch" in the first place (although it sneaks back in through the back door, as it were).
There's a fundamental asymmetry here in git, in that when you git fetch from a remote, you get commits and other objects from them, stuff those in your repository, and then update your remote branches. After git fetch origin you may have a new origin/master, but you do not have a new master. This gives you a stopping point, an intermediate step, during which you get to pause, rest a bit, look at what's just come in, and decide whether and how to rebase or merge the changes.
When you git push c0ffee3:master to a remote, however, you send your commits and other objects over and they (the remote) stuff the objects into their repository, and then they update their branch master to your commit (which is also now their commit) whose ID is c0ffee3. There's no pause for evaluation; there's no chance to rebase or merge; you've replaced their master with your c0ffee3. For that matter, your c0ffee3 does not have to be your master at all. Any suitable repository objectthat's any commit ID or any annotated tag IDis sufficient if you force-push (provided there's no fancy remote hook to deny you).
All that said, though, let's go back to the "bare work tree" idea. Here, on machine Blet's stop calling this "the remote" now, and just say "here on B"we'll have a bare repository so that we can take incoming pushes regardless of what git may think is the "current branch".
Next, we'll answer the "what if" questions with this: *whenever we receive anything new for some branch(es), we'll completely blow away whatever we had before, no matter what we're in the middle of doing with it, and replace it with new stuff based on what we now believe to be in that branch or those branches."
(Is that really the right answer? What if we're in the middle of compiling or testing? Well, we claimed it was the right answer; onward.)
What we'll do here on B, then, is set up our --bare repository with a hookthis can be the post-update hook or the post-receive hookthat runs after some branch(es) is/are updated. Both "post" hooks are run just once per receive (basically once for each push), and given a list of all updates. The post-update hook gets all updated ref-names as arguments, while the post-receive hook gets all updated refs, including both old and new SHA-1s, on stdin.
(The complexity here is that in one push, I can update more than one branch and/or tag. For instance, with git push c0ffee3:master badf00d:refs/tags/new-tag, I can tell you to update your master branch to make it point to commit c0ffee3, and to create a tag pointing to object badf00d. Here, your post-update hook would get refs/heads/master refs/tags/new-tag, while your post-receive hook would be able to read two lines, roughly oldsha1 c0ffee3 refs/heads/master and then 0000000 badf00d refs/tags/new-tag, from stdin. These would all be full 40-character SHA-1s of course.)
Because we've decided that we'll just blow away the "bare work tree", all we have to do in this hook is find out if an interesting branch has been updated. Let's say we care specifically (and only) about a branch named develop, i.e., the ref-name refs/heads/develop. Then in a post-receive hook written as a shell script, our stdin scan loop might look like this:
while read oldsha newsha ref; do
[ $ref = refs/heads/develop ] && do_update=true
In a post-update hook, we would just check arguments:
for ref do
[ $ref = refs/heads/develop ] && do_update=true
Either way, if we see that the interesting branch has changed, we now want to do the blow-away-and-rebuild step:
local target_dir=$1 branch=$2
rm -rf $target_dir
GIT_WORK_TREE=$target_dir git checkout -f $target_dir
if $do_update; then
blow_away_and_rebuild /home/me/develop.dir develop
exit 0 # succeed, regardless of actual success
Note that the git checkout step above populates the (removed and re-created) "bare work tree", but also has the side effect of setting the "current branch" (and fussing with git's index). This is how "current branch" manages to sneak in even though we have a nominally bare repository. We often don't need the rm -rf step, but if you have two different branches you'll "deploy" in this fashion, it sidesteps the "single current branch = single index" model git uses, which otherwise may leave old files behind.
The other trick here is that since /home/me/develop.dir has no .git directory within it (hence "bare work tree"), I won't be fooled into going into it, checking out a branch, and starting to edit there. Of course I can still be fooled into going into it and starting to work there, but at least I won't blame git if suddenly all my work gets rm -rf-ed. :-)