web-dev-qa-db-fra.com

Comment synchroniser deux référentiels Git distants?

J'ai deux URL de référentiel et je veux les synchroniser de sorte qu'elles contiennent toutes les deux la même chose. Dans Mercurial, ce que j'essaie de faire serait:

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

Cela se traduira par deux têtes dans les deux dépôts (je sais que ce n'est pas courant d'avoir deux têtes, mais je fais cela pour la synchronisation et cela doit être non interactif. Les têtes seront fusionnées manuellement à partir de l'un des dépôts, puis la synchronisation s'exécute à nouveau).

Je voudrais faire la même chose dans Git. Par exemple, sans interaction avec l'utilisateur, obtenez toutes les modifications dans les deux référentiels, avec plusieurs branches/têtes/quoi que ce soit à fusionner plus tard. J'essaie de faire cela en utilisant des URL dans les commandes, plutôt que d'ajouter des télécommandes (?), Car il pourrait y avoir un certain nombre de dépôts impliqués, et avoir des alias pour tous rendra mon script plus compliqué.

Je clone actuellement le référentiel à l'aide de git clone --bar {repo1} mais j'ai du mal à le "mettre à jour". J'ai essayé get fetch {repo1} mais cela ne semble pas faire baisser mes changements; git log n'affiche toujours pas l'ensemble de modifications qui a été ajouté dans repo1.

J'ai également essayé d'utiliser --mirror dans mes Push et clone, mais cela semblait éloigner les changesets de repo2 qui n'existaient pas localement, alors que je dois garder les changements des deux repos: /

Quelle est la meilleure façon de procéder?

Edit: Pour rendre un peu plus clair ce que j'essaie de faire ...

J'ai deux référentiels (par exemple, BitBucket et GitHub) et je veux que les gens puissent pousser vers l'un ou l'autre (en fin de compte, l'un sera Git, l'autre Mercurial, mais supposons qu'ils sont tous les deux Git pour l'instant pour simplifier les choses). Je dois être en mesure d'exécuter un script qui "synchronisera" les deux dépôts de manière à ce qu'ils contiennent tous les deux les deux ensembles de modifications, et peut nécessiter une fusion manuelle plus tard.

Finalement, cela signifie que je peux simplement interagir avec l'un des référentiels (par exemple, le Mercurial), et mon script va périodiquement intégrer des modifications Git dans lesquelles je peux fusionner, puis elles seront repoussées.

Dans Mercurial c'est trivial ! Je viens de tirer des deux repos et de pousser avec -f/--force pour permettre de pousser plusieurs têtes. Ensuite, n'importe qui peut cloner l'un des dépôts, fusionner les têtes et repousser. Je veux savoir comment faire la chose similaire la plus proche dans Git. Il doit être 100% non interactif, et doit garder les deux référentiels dans un état où le processus peut être répété à l'infini (ce qui signifie pas de réécriture de l'historique/modification des changesets, etc.).

21
Danny Tuppeny

Les branches Git n'ont pas de "têtes" au sens mercuriel. Il n'y a qu'une seule chose appelée HEAD, et c'est en fait un lien symbolique vers la validation que vous avez actuellement extraite. Dans le cas de référentiels hébergés comme GitHub, il n'y a pas de commit extrait - il n'y a que l'historique du référentiel lui-même. (Appelé un repo "nu".)

La raison de cette différence est que les noms de branche Git sont complètement arbitraires; ils n'ont pas à correspondre entre les copies d'un référentiel, et vous pouvez les créer et les détruire sur un coup de tête. [1] Les branches Git sont comme Python noms de variables, qui peuvent être mélangés et collés à n'importe quelle valeur comme vous le souhaitez; les branches Mercurial sont comme des variables C, qui se réfèrent à des emplacements de mémoire préalloués fixes que vous remplissez ensuite avec des données .

Ainsi, lorsque vous tirez dans Mercurial, vous avez deux historiques pour la même branche, car le nom de la branche est fixe et significatif dans les deux référentiels. La feuille de chaque histoire est une "tête", et vous devez normalement les fusionner pour créer une seule tête.

Mais dans Git, la récupération d'une branche distante n'affecte pas du tout votre branche. Si vous récupérez la branche master dans Origin, elle va simplement dans une branche appelée Origin/master. [2] git pull Origin master est juste du sucre mince pour deux étapes: récupérer la branche distante dans Origin/master, puis fusionnant cette autre branche dans votre branche actuelle. Mais ils n'ont pas besoin d'avoir le même nom; votre branche pourrait s'appeler development ou trunk ou autre chose. Vous pouvez y extraire ou fusionner n'importe quelle autre branche, et vous pouvez la pousser vers n'importe quelle autre branche. Git s'en fiche.

Ce qui me ramène à votre problème: vous ne pouvez pas pousser une "seconde" tête de branche vers un référentiel Git distant, car le concept n'existe pas. Vous pourriez Poussez vers les branches avec des noms mutilés (bitbucket_master?), mais pour autant que je sache, vous ne pouvez pas mettre à jour les télécommandes d'une télécommande à distance.

Je ne pense pas que votre plan ait beaucoup de sens, cependant, étant donné que les branches non fusionnées sont exposées aux deux référentiels, vous devez soit les fusionner toutes les deux, soit vous en fusionner une puis la mettre en miroir l'une sur l'autre. .. auquel cas vous avez laissé le deuxième dépôt dans un état inutile sans raison.

Y a-t-il une raison pour laquelle vous ne pouvez pas simplement faire ceci:

  1. Choisissez un référentiel pour être canonique - je suppose que BitBucket. Clonez-le. Il devient Origin.

  2. Ajoutez l'autre référentiel en tant que distant appelé, par exemple, github.

  3. Demandez à un script simple de récupérer périodiquement les deux télécommandes et de tenter de fusionner les branches github dans les branches Origin. Si la fusion échoue, abandonnez et envoyez-vous un e-mail ou autre chose. Si la fusion est triviale, envoyez le résultat aux deux télécommandes.

Bien sûr, si vous faites tout votre travail sur les branches de fonctionnalités, tout cela devient beaucoup moins problématique. :)


[1] C'est encore mieux: vous pouvez fusionner des branches de différents référentiels qui ont aucun historique en commun. J'ai fait cela pour consolider des projets qui ont été démarrés séparément; ils ont utilisé différentes structures de répertoires, donc cela fonctionne bien. GitHub utilise une astuce similaire pour sa fonctionnalité Pages: l'historique de vos pages est stocké dans une branche appelée gh-pages qui vit dans le même référentiel mais qui n'a absolument aucun historique en commun avec le reste de votre projet.

[2] Ceci est un mensonge blanc. La branche est toujours appelée master, mais elle appartient à la télécommande appelée Origin, et la barre oblique est la syntaxe pour s'y référer. La distinction peut être importante car Git n'a aucun scrupule à propos des barres obliques dans les noms de branche, vous pouvez donc avoir une branche locale nommée Origin/master, et cela masquerait la branche distante.

24
Eevee

Pour quelque chose de similaire, j'utilise ce code simple déclenché par webhook dans les deux référentiels pour synchroniser la branche principale GitLab et Bitbucket:

git pull Origin master
git pull gitlab master
git Push Origin master
git Push gitlab master

Ce n'est probablement pas ce dont vous avez besoin, mais cela pourrait être utile pour quelqu'un d'autre qui n'a besoin de synchroniser qu'une seule branche.

6
Bobík

Voici une solution testée pour le problème: http://www.tikalk.com/devops/sync-remote-repositories/

Les commandes à exécuter:

#!/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
4
yorammi

Essayez git reset --hard HEAD après git fetch. Cependant, je ne suis pas sûr de comprendre exactement quel est votre objectif. Vous devrez cd dans les répertoires séparés du référentiel avant d'exécuter les commandes fetch, reset et Push.

1
adamdunson

Vous n'avez peut-être pas vu que la récupération fonctionnait en fait lorsque vous avez utilisé git clone --mirror --bare, car par défaut, git ne répertorie pas ses branches distantes. Vous pouvez les répertorier avec git branch -a.

Je n'ai pas tout à fait la syntaxe élaborée pour les télécommandes sans nom, mais vous pouvez automatiquement ajouter des télécommandes basées sur un schéma de l'URL ... dans tous les cas, cela fonctionnera probablement mieux si vous choisissez un nom unique et cohérent pour chaque repo, afin que vous puissiez savoir quels changements sont venus d'où

Cependant, vous pouvez essayer quelque chose comme ceci:

git clone --bare --mirror --Origin thing1 {repo1} repo.git
cd repo.git
git fetch thing2 --mirror
git Push thing1 --mirror
git Push thing2 --mirror

Après cela, thing1 aurait toutes les branches de thing2 disponibles pour fusionner à tout moment, en tant que branches distantes. Vous pouvez répertorier les branches distantes avec git branch -a.

Sur github ou bitbucket, vous ne pourrez pas voir ces branches distantes via les interfaces web, mais vous pouvez les voir si vous clonez avec --mirror, donc elles existent.

1
derekv