29

I have two repository urls, and I want to synchronise them such that they both contain the same thing. In Mercurial, what I'm trying to do would be:

hg pull {repo1}
hg pull {repo2}
hg push -f {repo1}
hg push -f {repo2}

This will result in two heads in both repos (I know it's not common to have two heads, but I'm doing this for synchornisation and it needs to be non-interactive. The heads will be merged manually from one of the repos and then the sync run again).

I'd like to do the same thing in Git. Eg., with no user interaction, get all of the changes into both repos, with multiple branches/heads/whatever to be merged later. I'm trying to do this using urls in the commands, rather than adding remotes(?), as there could be a number of repos involved, and having aliases for them all will just make my script more complicated.

I'm currently cloning the repo using git clone --bar {repo1} however I'm struggling to "update" it. I've tried get fetch {repo1} but that doesn't seem to pull my changes down; git log still doesn't show the changeset that has been added in repo1.

I also tried using --mirror in my push and clone, but that seemed to remote changesets from repo2 that didn't exist locally, whereas I need to keep changes from both repos :/

What's the best way to do this?

Edit: To make it a little clearer what I'm trying to do...

I have two repositories (eg. BitBucket and GitHub) and want people to be able to push to either (ultimately, one will be Git, one will be Mercurial, but let's assume they're both Git for now to simplify things). I need to be able to run a script that will "sync" the two repos in a way that they both contain both sets of changes, and may require merging manually later.

Eventually, this means I can just interact with one of the repos (eg. the Mercurial one), and my script will periodically pull in Git changes which I can merge in, and then they'll be pushed back.

In Mercurial this is trivial! I just pull from both repos, and push with -f/--force to allow pushing multiple heads. Then anybody can clone one of the repos, merge the heads, and push back. I want to know how to do the closest similar thing in Git. It must be 100% non-interactive, and must keep both repos in a state that the process can be repeated infinitely (that means no rewriting history/changing changesets etc).

Danny Tuppeny
  • 40,147
  • 24
  • 151
  • 275

6 Answers6

30

Git branches do not have "heads" in the Mercurial sense. There is only one thing called HEAD, and it's effectively a symlink to the commit you currently have checked out. In the case of hosted repositories like GitHub, there is no commit checked out—there's just the repository history itself. (Called a "bare" repo.)

The reason for this difference is that Git branch names are completely arbitrary; they don't have to match between copies of a repository, and you can create and destroy them on a whim.[1] Git branches are like Python variable names, which can be shuffled around and stuck to any value as you like; Mercurial branches are like C variables, which refer to fixed preallocated memory locations you then fill with data.

So when you pull in Mercurial, you have two histories for the same branch, because the branch name is a fixed meaningful thing in both repositories. The leaf of each history is a "head", and you'd normally merge them to create a single head.

But in Git, fetching a remote branch doesn't actually affect your branch at all. If you fetch the master branch from origin, it just goes into a branch called origin/master.[2] git pull origin master is just thin sugar for two steps: fetching the remote branch into origin/master, and then merging that other branch into your current branch. But they don't have to have the same name; your branch could be called development or trunk or whatever else. You can pull or merge any other branch into it, and you can push it to any other branch. Git doesn't care.

Which brings me back to your problem: you can't push a "second" branch head to a remote Git repository, because the concept doesn't exist. You could push to branches with mangled names (bitbucket_master?), but as far as I'm aware, you can't update a remote's remotes remotely.

I don't think your plan makes a lot of sense, though, since with unmerged branches exposed to both repositories, you'd either have to merge them both, or you'd merge one and then mirror it on top of the other... in which case you left the second repository in a useless state for no reason.

Is there a reason you can't just do this:

  1. Pick a repository to be canonical—I assume BitBucket. Clone it. It becomes origin.

  2. Add the other repository as a remote called, say, github.

  3. Have a simple script periodically fetch both remotes and attempt to merge the github branch(es) into the origin branches. If the merge fails, abort and send you an email or whatever. If the merge is trivial, push the result to both remotes.

Of course, if you just do all your work on feature branches, this all becomes much less of a problem. :)


[1] It gets even better: you can merge together branches from different repositories that have no history whatsoever in common. I've done this to consolidate projects that were started separatedly; they used different directory structures, so it works fine. GitHub uses a similar trick for its Pages feature: the history of your Pages is stored in a branch called gh-pages that lives in the same repository but has absolutely no history in common with the rest of your project.

[2] This is a white lie. The branch is still called master, but it belongs to the remote called origin, and the slash is syntax for referring to it. The distinction can matter because Git has no qualms about slashes in branch names, so you could have a local branch named origin/master, and that would shadow the remote branch.

Eevee
  • 47,412
  • 11
  • 95
  • 127
  • I want to keep the two repos in sync so that they can be merged at either end. The normal sync process would then deal with pushing this to the other. Imagine allow contributions via both BitBucket and GitHub. I just want some automated process to copy changes between them and they can be manually merged at either end. – Danny Tuppeny Feb 25 '13 at 08:26
  • Ok, I've re-read your post after some caffeine, and I think I understand. Seems like to do what I need, I'm going to have to have two branches (the master from each) in my combined repo... – Danny Tuppeny Feb 25 '13 at 12:18
  • If I ended yo with master and master_github in BB and master and master_bitbucket in GH, could I merge them in a way that I can push to both repos and just have a single master again, or would the different names mess this up? – Danny Tuppeny Feb 25 '13 at 12:18
  • why do they need to be mergeable at _either end_? the git clone exists so other contributors can use git, right? i doubt they want step 1 of that process to be "merge other people's arbitrary code" – Eevee Feb 25 '13 at 19:00
  • and yes, you could just merge one into the other, call the result "master", and push it to both. git doesn't care about names, so just make sure you're merging into the hg branch called "master". – Eevee Feb 25 '13 at 19:01
  • Merging at either end isn't a huge requirement, I was just trying to make sure both repos are the "same" so future merges are easy. Maybe that's not a big deal – Danny Tuppeny Feb 25 '13 at 19:06
  • Ok, if I add a remote for github and do `git fetch --all` what's the most efficient way to push this back to bitbucket as a branch named `github`? then once I've cloned from BB and merged those two branches, how do I push it back to github so there's just the merged master? – Danny Tuppeny Feb 25 '13 at 19:07
  • i don't know how your hg-git bridge works, sorry :( i'd think you could do this all in hg, treat bitbucket and github as two heads of the same branch, merge them, and then just push that to both BB and GH? once you have a merge you can just `git push origin master` to update the `master` branch on `origin` – Eevee Feb 25 '13 at 22:24
  • Sorry, my comment wasn't related to Hg, I meant if I had Git both sides. Anyway, I managed to get everything worked as I need now. I added github as a remote and did `fetch --all` then pushed the github/master to origin/github, did the merge over there, then fetch and pushed back to github/master, and all seems to work well :-) – Danny Tuppeny Feb 26 '13 at 10:53
11

For something similar I use this simple code trigerred by webhook in both repositories to sync GitLab and Bitbucket master branch:

git pull origin master
git pull gitlab master
git push origin master
git push gitlab master

It propably is not what you need in question, but it could be helpful for somebody else who needs to sync just one branch.

Bobík
  • 1,828
  • 20
  • 19
9

Here's a tested solution for the issue: http://www.tikalk.com/devops/sync-remote-repositories/

The commands to run:

#!/bin/bash

# REPO_NAME=<repo>.git
# ORIGIN_URL=git@<host>:<project>/$REPO_NAME
# REPO1_URL=git@<host>:<project>/$REPO_NAME

rm -rf $REPO_NAME
git clone --bare $ORIGIN_URL
cd $REPO_NAME
git remote add --mirror=fetch repo1 $REPO1_URL
git fetch origin --tags ; git fetch repo1 --tags
git push origin --all ; git push origin --tags
git push repo1 --all ; git push repo1 --tags
vy32
  • 28,461
  • 37
  • 122
  • 246
yorammi
  • 6,272
  • 1
  • 28
  • 34
  • 3
    It's working for first time but then while executing above script REPO1 is not updating when ever there is a change in ORIGIN. Any inputs on how to keep REPO1 is in sync with ORIGIN for every change. Thanks – rameshthoomu Oct 06 '16 at 04:21
3

git-repo-sync
It exactly synchronizes pairs of remote Git repositories and intended for constant team development work from both remote sides.
It is like you have two entry points to a single repository and your two remote Git-repositories will be behaving almost like a single repository.

I am the author of git-repo-sync, and my main idea during development was to install, auto-run periodically and forget about existence of this tool.
And it's actually doing its stuff pretty well.

The git-repo-sync has auto conflict solving strategies, different disaster protections and many features. You'd better look at the README of the project.

The only thing is, it doesn't sync Git tags, but that's intentional.

Sorry, I can't help with the Mercurial side of this SO question. But my tool could be helpful for those who are seeking solving of this problem for Git remotes only.

it3xl
  • 2,372
  • 27
  • 37
  • 1
    Please don't go seeking out every possible place you could post about your tool. Instead, see if those posts could be closed as duplicates instead. – Martijn Pieters Apr 15 '20 at 12:59
  • 1
    We picked this anwer as it was the most complete, and the other posts were mostly duplicates. I can understand you are proud of your project, but that doesn't mean it needs re-posting. Rather, consider if the questions can be closed as duplicates instead. – Martijn Pieters Apr 17 '20 at 20:24
2

You might not have seen that the fetch did in fact work when you used git clone --mirror --bare, because by default git does not list it's remote branches. You can list them with git branch -a.

I don't quite have the syntax worked out for unnamed remotes, but you could automatically add remotes based on some scheme from the url... in any case, it'll probably work best if you choose some unique and consistent name for each repo, so you can know what changes came from where

However, you could try something like this:

git clone --bare --mirror --origin thing1 {repo1} repo.git
cd repo.git
git fetch thing2 --mirror
git push thing1 --mirror
git push thing2 --mirror

After this was done, thing1 would have all of thing2's branches available to merge at any time, as remote branches. You can list the remote branches with git branch -a.

On github or bitbucket, you will not be able to see these remote branches via the web interfaces, however you can see them if you clone with --mirror, so they do exist.

derekv
  • 3,131
  • 3
  • 21
  • 30
  • I saw that, but assumed that `merge` meant it tried to merge changesets? (I don't want any merging it needs to be non-interactive). Does Git allow "multiple heads" within a single branch in the same way that Mercurial does? Or do I need to have a named branch in order to have two heads? :( – Danny Tuppeny Feb 24 '13 at 20:46
  • @Danny Tuppeny -- No a branch has one head. Looks like I misread your question though.. – derekv Feb 24 '13 at 20:46
  • In Mercurial, if we pull two repos together that both had changes in them, we'd end up with two heads. We'd usually merge these before pushing to a repo (otherwise people wouldn't know which head to base their changes on)... – Danny Tuppeny Feb 24 '13 at 20:47
  • I'm trying to pull multiple repos together and let them be merged later, so in Mercurial, I just pull both, then push with --force/-f (by default push won't push multiple heads) – Danny Tuppeny Feb 24 '13 at 20:48
  • OK in git, that is done by having two branches instead, with one being named "/remote/{repo}/branchname" in addition to your local branch. – derekv Feb 24 '13 at 20:48
  • I want to do the same thing in Git. eg. End up with a repo that has the changes from both remote repositories ready for a user to merge them later. But I want to push this into a repo, so it can be cloned and then them merged, not done in the temporary repo I'm using to "sync" them. – Danny Tuppeny Feb 24 '13 at 20:49
  • I don't really have a "remote" or "local" repo. I'm using a temporary repo just to merge these two remote repositories, and then want to push the results back to each. They should end up being identical. – Danny Tuppeny Feb 24 '13 at 20:50
  • I think I understand... however I do not think you can make them identical, especially without merges. But you should be able to get the changes from one available to merge later in the other. – derekv Feb 24 '13 at 20:55
  • They need to be reasonably identical, as this process needs to be something that can be done over and over, without human input :/ Surely Git can support something Mercurial can do with just a single extra flag passed to push? ;-) – Danny Tuppeny Feb 24 '13 at 20:58
  • I have no idea what your edit means. What is the-other-master? – Danny Tuppeny Feb 24 '13 at 21:09
  • (note: If there are branches in either repo, they all need to be preserved too!) – Danny Tuppeny Feb 24 '13 at 21:10
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/25053/discussion-between-derekv-and-danny-tuppeny) – derekv Feb 24 '13 at 21:15
1

Try git reset --hard HEAD after git fetch. However, I'm not sure I understand exactly what your goal is. You will need to cd into the separate repository directories before running the fetch, reset, and push commands.

adamdunson
  • 2,635
  • 1
  • 23
  • 27
  • 1
    Basically, I have two Git repos (eg. GitHub and BitBucket), and changes may be made at both ends. I want to push the changes into each other such that the repos are the same. If changes did exist at both ends, I should be able to clone one of the repos, merge, and then push back. – Danny Tuppeny Feb 24 '13 at 20:51
  • I don't know much about Git, but I'm fairly sure reset --hard isn't what I want - that sounds like it may throw stuff away. This is certainly not what I want :/ – Danny Tuppeny Feb 24 '13 at 20:52
  • `git merge` is your only option for merging, in that case, but you can't be guaranteed that it won't have conflicts. Why do you have the same repository located on both Github and BitBucket? This feels like a bad idea. – adamdunson Feb 24 '13 at 20:57
  • One is Git and one is Mercurial; I need to merge them :) But that's a complication I can deal with; if both repos were Mercurial I could do what I posted in the question (I could do this automatically, non-interactively, and if people push changes to both sides, we'd just have multiple heads until someone merged, then all would be good). I want to figure out how to do a similar thing in Git. Eg. combined two repos and allow merging later. – Danny Tuppeny Feb 24 '13 at 21:04
  • I've added a little more info to the question too; hope that makes it clearer. – Danny Tuppeny Feb 24 '13 at 21:08