So you create a git repository (
repo) on your local machine (
foo) and begin hacking away. Then, you want to push a backup copy of that repo to another box (
bar). What's the best way to do that? There are many ways to do it, but some ways are dangerous and some are just cumbersome.
Ideally, git would have an option to push that instructs it to initialize the remote repository and just do the right thing. I hope this is in our future, but for now we have to do some dancing around.
The canonical way is
bar:~/repo$ git clone --mirror local:repo
foo:~/repo$ git push bar:repo --mirror
but that's not always feasible due to firewalls and nasty NATs.
Unwise methods include copying the working tree with rsync or scp, doing something like the above without
--mirror (which implies
--bare), and other methods that would have you pushing to a non-bare repository.
The best method I've found is this:
bar:~$ git --git-dir=repo init --bare
foo:~/repo$ git remote add --mirror bar bar:repo
foo:~/repo$ git push bar
We set up a bare repository on bar, then set up a "remote" for bar which automatically mirrors (you could do that with the canonical method described above too), so it sends the whole shebang. After that, we push as we normally would.
Let's say you are a web developer, and you do development on your laptop, then when things are nice and shiny you want to push those changes to the webserver. Seems natural enough, right?
git clone server:/var/www/foo # ... git pull # edit stuff git commit -a -m 'i edited stuff'
Now, let's say you're a (possibly former) darcs/bzr/mercurial user and this time you're using git. Git has
git-push. You read the man page like a good little code monkey, and it seems like it does the same or similar thing to
darcs push or
hg push. It seems like if you want to push your changes to the server, you'd do this:
Am I off in left field or does this not seem 100% rational? But wo be unto the code monkey that utters this unfortunate incantation. Observe:
$ mkdir foo $ cd foo $ git init Initialized empty Git repository in /private/tmp/foo/.git/ $ echo hello > foo.txt $ git add foo.txt $ git commit -m 'hello' Created initial commit bee50da: hello 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 foo.txt $ cd .. $ git clone foo bar Initialized empty Git repository in /private/tmp/bar/.git/ $ cd bar $ echo goodbye >> foo.txt $ git commit -a -m goodbye Created commit 99c13c1: goodbye 1 files changed, 1 insertions(+), 0 deletions(-) $ git push ../foo Counting objects: 5, done. Writing objects: 100% (3/3), 248 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To ../foo bee50da..99c13c1 master -> master $ cd ../foo $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: foo.txt # $ git diff $ git diff --cache error: invalid option: --cache $ git diff --cached diff --git a/foo.txt b/foo.txt index a32119c..ce01362 100644 --- a/foo.txt +++ b/foo.txt @@ -1,2 +1 @@ hello -goodbye $ git log commit 99c13c1e60888ae2c0e221898411e1cd52ad3815 Author: Hans Fugal <firstname.lastname@example.org> Date: Mon Nov 10 17:11:57 2008 -0700 goodbye commit bee50da72798edc47ddc36dbc4f559f141b1e28b Author: Hans Fugal <email@example.com> Date: Mon Nov 10 17:11:34 2008 -0700 hello
I promise I didn't fake that. Yes, you saw that correctly—git wants to undo the changes you just committed. If you happen to have a clean working directory, all you need to do to return to sanity is
git reset HEAD. If not, heaven help you.
This is totally unacceptable. It's unforgivable on so many levels. At the very least, the manpage should warn you to not push to repositories with working copies. Git should warn you before you push and screw up your repo that it has a working copy checked out. Ideally, git would behave like darcs and update the working copy. Suboptimally, it would behave like mercurial and make it a new revision that you have to manually checkout. But this is simply ridiculous.
So what is the solution? They tell you to use pull. Hello! Is anyone home? My laptop is roving. It's often behind a NATing firewall. I'm supposed to find my public IP address and figure out how to subvert the evil firewalls of the world every time I want to push my changes to the server?
A workaround, and probably the best real-world workflow, is to have a second bare repository or a second branch on the server, push into that, then ssh into the server and pull the changes. I think this page describes how to do that with a second branch, though I'm short on time to actually try it out at the moment.
More of this sickening story in this thread, where you will learn that at least one other person out there has his head screwed on properly, that the developers are more interested in how hooks work (and fail to allow you to do this even if you grok them), and that they've discussed the problem before and decided the correct response is to RTFM (M for minds this time, since the manual was completely unhelpful).
Update: Some of you have been quick to defend git and the design choice of how push behaves. I want to clarify that I don't care so much that push updates the repository but not the working directory. Mercurial works this way too. Not the way I'd do it but it's a valid approach. The problem here is that
git push seems like a natural thing to do but screws up your working directory on the remote side. Mercurial doesn't change the working directory, but neither does it silently rebase it and set you up to undo your changes if you're not careful. The problem here is a lack of safety and a lack of warning. They know it's a problem, they've fielded enough "morons". A few words of warning in the man page is all it would take to make me happy.
And now, I have had time to work out a more specific workaround. Here's what I did, and it seems to work well:
# Server setup (set up incoming branch) server$ git branch incoming server$ git branch incoming * master origin # Laptop setup (local master to remote incoming) laptop$ git config remote.origin.push master:incoming # Everyday usage laptop$ git push Counting objects: 5, done. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 279 bytes, done. Total 3 (delta 1), reused 0 (delta 0) To server:/tmp/foo b108a07..a9d3282 master -> incoming server$ git status # On branch master nothing to commit (working directory clean) server$ git pull . incoming From . * branch incoming -> FETCH_HEAD Updating b108a07..a9d3282 Fast forward foo.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
You could use hooks to automatically do that
git pull . incoming if you liked, making it more like darcs than mercurial.
Updated update: On further thought, the cleanest solution is probably to have a separate master (bare) repository, e.g.
$ mkdir master $ cd master $ git init Initialized empty Git repository in /private/tmp/foo/master/.git/ $ git commit --allow-empty -m initial Created initial commit 999755e: initial $ cd .. $ git clone master live Initialized empty Git repository in /private/tmp/foo/live/.git/ $ cd live $ git branch * master $ cd .. $ git clone master laptop Initialized empty Git repository in /private/tmp/foo/laptop/.git/ $ cd laptop $ echo hello > foo.txt $ git add foo.txt $ git commit -m hello Created commit 2297bcf: hello 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 foo.txt $ git push Counting objects: 4, done. Writing objects: 100% (3/3), 239 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /tmp/foo/master/.git 999755e..2297bcf master -> master $ cd ../live $ ls $ git pull remote: Counting objects: 4, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/foo/master/ 999755e..2297bcf master -> origin/master Updating 999755e..2297bcf Fast forward foo.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 foo.txt $ echo goodbye >> foo.txt $ git commit -a -m goodbye Created commit 04f6702: goodbye 1 files changed, 1 insertions(+), 0 deletions(-) $ git push Counting objects: 5, done. Writing objects: 100% (3/3), 248 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /tmp/foo/master/.git 2297bcf..04f6702 master -> master $ cd ../laptop $ git pull remote: Counting objects: 5, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/foo/master/ 2297bcf..04f6702 master -> origin/master Updating 2297bcf..04f6702 Fast forward foo.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)