A collection of computer systems and programming tips that you may find useful.
Brought to you by Craic Computing LLC, a bioinformatics consulting company.

Wednesday, March 3, 2010

Git - push and pull between repositories

I've been trying to improve my git skills beyond the basics. But I ran into real confusion when trying to clone one repo into another and then trying to keep them in sync using git pull and git push.

In principle this should work fine but when I pushed changes back to the origin and then ran git status in the origin I would see the 'old' versions of files in the origin marked as having changed - no merge conflicts, just that they were not up to date.

Turns out that you can't (easily) keep two repos in sync where the origin is a regular working copy. What you need to do is create a third repo that is a so called 'bare' repository. Bare repos only contain the contents of the .git directory and their role is simply to track changes.

So here is a simple example of how to set this up:

(I'm just using repos on my local machine to keep things simple)

1: Create your original working directory under git and check your code in. I'll call this repo_original.

2: Clone this into the bare repo with 'git clone --bare repo_original repo_bare'
(Take a look at the contents of repo_bare - it's just like a .git directory)

3: Clone the bare repo into a working copy with 'git clone repo_bare repo_1'

4: Make a second working copy with 'git clone repo_bare repo_2'

5: Try making some changes in repo_1, commit them and push back to the origin (repo_bare) with 'git push'

6: Now go to repo_2 and pull from repo_bare with 'git pull'. Look at your files and you should see the changes you made in repo_1. Try making changes in repo_2, push them, go to repo_1, pull changes and see that they came across.

So far so good.

7: Make conflicting changes in the same file in repo_1 and repo_2 and commit in both repos.

8: Push the changes from repo_1

9: Push the changes from repo_2 and you should be rejected with something like this
$ git push
To /Users/jones/tips/repo_bare
! [rejected] master -> master (non-fast forward)
error: failed to push some refs to '/Users/jones/tips/repo_bare'

That's OK - there is a conflict but you can't resolve those on a bare repo, so it won't let you proceed.

10: Instead, on repo_2, pull the current origin and you will see something like this:
$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/jones/tips/repo_bare
0d97e7a..494885c master -> origin/master
Auto-merged README
CONFLICT (content): Merge conflict in README
Automatic merge failed; fix conflicts and then commit the result.

That's what we want to see - there is a real conflict (we created it) and we can resolve it by editing the file and committing in the normal way.

So it's not difficult once you figure it out but it is not well explained in the guides that I've seen. Hope this helps.

Bonus Section - Branches

There are two ways to handle branching in repository setups like this - not sure of the official terms but I think of them as private and shared.

A 'private' branch is something I setup in my clone of a repo which will not get passed back to the origin repo. I create it, work in it and eventually merge it back into my repo's master. That branch will never get copied back to the origin and therefore any collaborators with their own repos will never know about it.

A 'shared' branch is one that I want to share with collaborators, so I need a way for them to access it. To do this, go to the origin repo, 'repo_bare' in the above example and create the branch there: 'git branch dev'. When you go to a cloned repo and do a pull you will see that new branch pulled over and 'git branch -a' will how it listed as 'origin/dev' but when you try to check it out you'll get an error.
$ git co dev
error: pathspec 'dev' did not match any file(s) known to git.
Did you forget to 'git add'?
What you need to do is track this branch in each of the cloned repos. To do this, in the cloned repo, run 'git branch --track dev origin/dev'. Now you can see a local branch called dev and you can checkout the branch. push and pull will now keep the tracked branch in sync as well as the master. You need to setup the tracked branch on each of the cloned repos in order to work with it.

What I haven't figured out yet is how to 'promote' a private branch in a cloned repo up to a shared branch. I suspect you have to use 'git stash' to stash the contents of that branch, delete the branch and then create a shared branch, track it and then put the stash back into it.


No comments:

Archive of Tips