Searching for Signal

the n01se blog

“git along little dogies…”

At my place of work we're moving our repo management from gitosis to an evaluation installation of  GitHub:FI. After spending a while searching around for moving, backing up and transferring  git repos I was unable to find a good example. I had to dig through the git manpages for quite a while to figure out what I wanted. The solution is quite simple but it was not obvious what combination of git clone/fetch/push/pull or other commands was appropriate. Now, this may be due to my own stupidity but even when I had the solution in hand I was still unable to find pages which showed how fully copy a repo from one remote location to a new remote location. I'm posting what I found here for posterity.

Movin' right along...

The basic process of moving a repo including all branches and tags is as follows:

git clone --mirror git@gitosis:my-repo.git
git --bare --git-dir my-repo.git push --mirror git@githubfi:my-user/my-repo.git

The above assumes that the git@githubfi:my-user/my-repo.git repo was created as an empty repo some point before the last command was run.

With the default config settings, git clone will create remote tracking branches for all branches found in the remote repo but will only create a local branch for the repository's currently active branch. In order to fully mirror the repository and all references (branches and tags) locally, you need to use the --mirror option which will also create the repo as a bare repository.


A normal working copy has, at the top level, your checked out files and a .git/ sub-directory. A bare repository omits the working copy and has the contents of the .git/ directory at the top level. Bare repositories are intended to be used remotely (such as by a repository management system) and, by default, are named with a .git suffix to distinguish them as seen in the repo URLs above. (e.g. my-repo.git)

Since a bare repo has no working copy to sit in, we need to tell git where and how to find it when we call git commands against the repository, hence the --bare --git-dir my-repo.git options. The git push --mirror assures that all refs (branches/tags) will be pushed to the new location instead the default behaviors of only pushing refs (branches/tags) specified on the command line or pushing the branches that match by name between the local and remote repositories if none are specified on the command line.

More cowbell...

There's one further thing to mention that may be helpful. While transitioning from gitosis to GitHub:FI I took the opportunity to make our pre-receive hook a bit more stringent in its checking of emails, requiring that the committer email be from our corporate domain on non-upstream branches. In order to do this I had to use git filter-branch to do a little cleanup. Because we had a bare repo, the invocation looked a bit different. I'm showing a simplified version below:

git clone --mirror git@gitosis:my-repo.git
git --bare --git-dir my-repo.git filter-branch --commit-filter \
    '[ "$GIT_COMMITTER_EMAIL" = ""] && export GIT_COMMITER_EMAIL=""; \
        git commit-tree "$@"' \
    -- master devel staging v2.{30..45}
git --bare --git-dir my-repo.git push --mirror git@githubfi:my-user/my-repo.git

Note that history rewriting such as done by git filter-branch has implications for any repos that have been cloned out in the wild. Be sure you understand history rewriting before you use this command or there will be pain and sadness among the other developers. In our case, the above changes were done in a manner that was coordinated among the developers involved and was quite pain free. Note also that I did not rewrite the "upstream" branch which did not need to be purged of non-work addresses so I omitted it from the list of refs to be filtered. Further note that tags, since they are refs, also need to be rewritten so I passed our version tags to git filter-branch as well.