How to sync a local dir to server using Git?

How can I use Git to simply keep in sync a local dir with a server dir?

I'm used to FTP but it's slow and prone to errors.

I have SSH access to the server. I can run Git on the server.

I also want to avoid a third-party Git hosting service like Github or Bitbucket. I want to be able to directly upload to my server.


Solution 1:

On the local machine:

  1. Initialize the directory to be synced as a Git repository

    git init
    

    Commit any existing code

    git add -A
    git commit -am "initial commit"
    
  2. Set its remote to the server

    git remote set-url origin user@server:/path/to/dir/on/server
    

    Where

    • user is the username you use to SSH to your server. Eg. [email protected]

    • /path/to/dir/on/server is the directory on the server you wish to sync your changes to

    Side-note: It's advisable to go password-less for SSH logins

On the remote machine:

  1. Initialize an empty directory on the server

    mkdir /path/to/dir/on/server
    cd /path/to/dir/on/server
    git init
    
  2. Set its config to ignore updates to checked out branch

    git config receive.denyCurrentBranch ignore
    

    This is needed because (quoting the exact error you'll get if you don't do this) "updating the current branch in a non-bare repository is denied, because it will make the index and work tree inconsistent with what you pushed, and will require 'git reset --hard' to match the work tree to HEAD." (we'll actually do just that in the next step).

  3. Set up a post-receive git hook:

    Create a file post-receive in the .git/hooks directory, and open it in an editor:

    vim .git/hooks/post-receive
    

    Put the following in it:

    #!/bin/sh
    git --git-dir=. --work-tree=.. checkout -f
    

    Set its mode to executable:

    chmod +x .git/hooks/post-receive
    

    This will checkout your latest changes on the server dir whenever you push any.

  • One-liner for all of above:

    dir=<dir>; mkdir $dir && cd $dir && git init && git config receive.denyCurrentBranch ignore && printf '#!/bin/sh\ngit --git-dir=. --work-tree=.. checkout -f' > .git/hooks/post-receive && chmod +x .git/hooks/post-receive
    

Now on the local machine you can do this:

git commit -am "Some changes"
git push

and your local dir will be synced to the server dir.


An additional recipe that I use often: On local machine, I keep the origin remote name for Github/Bitbucket which is the "home" of the project, and a special server remote name which is where it gets deployed. I also create a git branch (which is also named) server and configure it to always push to the server remote:

git config branch.server.remote server
git config remote.server.push server:master

This way whenever I'm on master branch it pushes to the origin (Github/Bitbucket etc) and when I'm on the server branch it pushes to the server.