99 votes

Fusionner le dépôt git dans un sous-répertoire

Je voudrais fusionner un dépôt git distant dans mon dépôt git de travail comme un sous-répertoire de celui-ci. J'aimerais que le dépôt résultant contienne l'historique fusionné des deux dépôts et aussi que chaque fichier du dépôt fusionné conserve son historique tel qu'il était dans le dépôt distant. J'ai essayé d'utiliser la stratégie de sous-arborescence telle que mentionnée dans le document Comment utiliser la stratégie de fusion de sous-arbres ? Mais après avoir suivi cette procédure, bien que le dépôt résultant contienne effectivement l'historique fusionné des deux dépôts, les fichiers individuels provenant du dépôt distant n'ont pas conservé leur historique (`git log' sur l'un d'entre eux montre juste un message "Merged branch...").

Je ne veux pas non plus utiliser de submodules car je ne veux plus que les deux dépôts git combinés soient séparés.

Est-il possible de fusionner un dépôt git distant dans un autre en tant que sous-répertoire, les fichiers individuels provenant du dépôt distant conservant leur historique ?

Merci beaucoup pour toute aide.

EDIT : J'essaie actuellement une solution qui utilise git filter-branch pour réécrire l'historique du dépôt fusionné. Cela semble fonctionner, mais je dois la tester un peu plus. Je reviendrai pour faire un rapport sur mes découvertes.

EDIT 2 : Dans l'espoir d'être plus clair, je donne les commandes exactes que j'ai utilisées avec la stratégie de sous-arbres de git, qui ont pour résultat une perte apparente de l'historique des fichiers du dépôt distant. Soit A le dépôt git dans lequel je travaille actuellement et B le dépôt git que je voudrais incorporer dans A en tant que sous-répertoire de celui-ci. Il a fait ce qui suit :

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

Après ces commandes et en allant dans le répertoire subdir/Iwant/to/put/B/in, je vois tous les fichiers de B, mais git log sur l'un d'entre eux ne montre que le message de livraison "Merge B as subdirectory in subdir/Iwant/to/put/B/in". L'historique de leurs fichiers tel qu'il est dans B est perdu.

Quoi semble pour fonctionner (comme je suis un débutant sur git, je peux me tromper) est la suivante :

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

La commande ci-dessus pour filter-branch est tirée de git help filter-branch dans lequel je n'ai changé que le chemin du sous-répertoire.

0 votes

Qu'est-ce que gitk dire sur l'histoire ? J'ai utilisé git subtree merge avec succès dans le passé. Peut-être pouvez-vous révéler vos commandes exactes ? Je ne suis pas sûr que git-filter-branch soit la bonne approche. Je pourrais recommander d'essayer git-fast-export et git-fast-import pour synthétiser un nouvel historique.

0 votes

Après avoir effectué la procédure de sous-arbre gitk montre les deux dépôts fusionnés sur leurs pointes et non liés dans leurs commits initiaux. (Cela aiderait-il si je postais des captures d'écran de la vue historique de gitk ? Puis-je ?) Malheureusement, les fichiers individuels du dépôt distant n'ont pas conservé leur historique si je fais dans le terminal git log <file-from-remote-repo> . Je regarde dans git-fast-export y git-fast-import Je suis très novice en matière de git. Je vais modifier ma question pour montrer exactement les commandes que j'ai utilisées avec git subtree. Merci beaucoup pour votre réponse.

0 votes

@christosc : votre deuxième méthode a fonctionné à merveille et très simplement, merci beaucoup ! J'ai juste dû changer subdirir/Iwant/en/put/B/in/ et en faire un oneliner (parce que msysgit sous Windows ne semble pas supporter les retours à la ligne dans les commandes avec ) : git filter-branch --index-filter 'git ls-files -s | sed "s- \t\ "*-&subdir/Iwant/to/put/B/in/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD

81voto

kynan Points 2334

git-subtree est un script conçu exactement pour ce cas d'utilisation de fusionner plusieurs dépôts en un seul tout en préservant l'historique (et/ou en divisant l'historique des sous-arbres, bien que cela semble être sans rapport avec cette question). Il est distribué comme partie de l'arbre git. depuis la version 1.7.11 .

Pour fusionner un référentiel <repo> à la révision <rev> comme sous-répertoire <prefix> utiliser git subtree add comme suit :

git subtree add -P <prefix> <repo> <rev>

git-subtree implémente la fonction stratégie de fusion de sous-arbres d'une manière plus conviviale.

Le site inconvénient est que dans l'historique fusionné, les fichiers ne sont pas fixés (pas dans un sous-répertoire). Disons que vous fusionnez le dépôt a en b . En conséquence git log a/f1 vous montrera tous les changements (s'il y en a) sauf ceux dans l'historique de la fusion. Vous pouvez le faire :

git log --follow -- f1

mais cela ne montrera pas les changements autrement que dans l'historique de la fusion.

En d'autres termes, si vous ne changez pas a dans le référentiel b alors vous devez spécifier --follow et un chemin non fixé. Si vous les changez dans les deux dépôts, alors vous avez 2 commandes, dont aucune ne montre tous les changements.

En savoir plus ici .

48voto

Seth Robertson Points 13276

Après avoir reçu l'explication plus complète de ce qui se passe, je pense avoir compris et, en tout cas, j'ai une solution de rechange. Plus précisément, je pense que la détection des renommages est trompée par la fusion de sous-arbres avec --prefix. Voici mon cas de test :

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

Nous créons les répertoires git a et b avec plusieurs commits chacun. Nous faisons une fusion de sous-arborescence, et ensuite nous faisons un commit final dans la nouvelle sous-arborescence.

Running gitk (en z/a) montre que l'histoire apparaît, on peut la voir. Exécution de git log montre que l'histoire apparaît. Cependant, l'examen d'un fichier spécifique pose un problème : git log bdir/B

Eh bien, il y a un truc qu'on peut jouer. Nous pouvons regarder l'historique des prénoms d'un fichier spécifique en utilisant --follow. git log --follow -- B . C'est bien, mais ce n'est pas génial, car cela ne permet pas de relier l'histoire de la pré-fusion à celle de la post-fusion.

J'ai essayé de jouer avec -M et -C, mais je n'ai pas réussi à le faire suivre un fichier spécifique.

La solution, à mon avis, consiste donc à informer git du renommage qui aura lieu dans le cadre de la fusion des sous-arbres. Malheureusement, git-read-tree est assez tatillon sur les fusions de sous-arbres et nous devons donc travailler avec un répertoire temporaire, mais cela peut disparaître avant la validation. Ensuite, nous pouvons voir l'historique complet.

D'abord, créez un dépôt "A" et faites quelques commits :

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

Ensuite, créez un dépôt "B" et faites quelques commits :

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

Et le truc pour que ça marche : force Git à reconnaître le renommage en créant un sous-répertoire et en y déplaçant le contenu.

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

Retournez au dépôt "A" et récupérez et fusionnez le contenu de "B" :

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

Pour montrer qu'ils sont maintenant fusionnés :

cd bdir
echo BBB>>B
git commit -a -m BBB

Pour prouver que l'histoire complète est préservée dans une chaîne connectée :

git log --follow B

Nous obtenons l'historique après avoir fait cela, mais le problème est que si vous conservez l'ancien dépôt "b" et fusionnez occasionnellement à partir de celui-ci (disons qu'il s'agit en fait d'un dépôt tiers maintenu séparément), vous avez des problèmes puisque ce tiers n'aura pas fait le renommage. Vous devez essayer de fusionner les nouveaux changements dans votre version de b avec le renommage et je crains que cela ne se passe pas sans heurts. Mais si b disparaît, vous gagnez.

0 votes

En effet, ça marche @Seth ! Et je n'ai pas eu à recourir à la réécriture de l'historique comme avec filter-branch, ce qui donne un historique quelque peu trompeur (par exemple, lors de la visualisation de git log --stat ). Je n'avais pas non plus remarqué le --follow dans la documentation de git log ; cela semble très pratique pour les renommages. Merci beaucoup pour votre réponse si détaillée et instructive !

2 votes

Cette réponse serait beaucoup plus utile si l'exemple de code était divisé en lignes lisibles au lieu d'une seule ligne séparée par un point-virgule ;)

0 votes

J'aimerais fusionner "b" avec "a" en conservant son historique complet. Comment puis-je le faire ?

17voto

hfs Points 777

Je voulais

  1. conserver une histoire linéaire sans fusion explicite, et
  2. donne l'impression que les fichiers du dépôt fusionné ont toujours existé dans le sous-répertoire, et par effet secondaire, fait en sorte que git log -- file travailler sans --follow .

Étape 1 : Réécrire l'historique dans le référentiel des sources pour faire croire que tous les fichiers ont toujours existé sous le sous-répertoire.

Créer une branche temporaire pour l'historique réécrit.

git checkout -b tmp_subdir

Ensuite, utilisez git filter-branch comme décrit dans Comment puis-je réécrire l'historique pour que tous les fichiers, sauf ceux que j'ai déjà déplacés, se trouvent dans un sous-répertoire ? :

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

Étape 2 : Passer au référentiel cible. Ajoutez le référentiel source comme distant dans le référentiel cible et récupérez son contenu.

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

Étape 3 : Utiliser merge --onto pour ajouter les commits du référentiel source réécrit au dessus du référentiel cible.

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

Vous pouvez vérifier le journal pour voir si vous avez vraiment obtenu ce que vous vouliez.

git log --stat

Étape 4 : Après le rebasement, vous êtes dans l'état "detached HEAD". Vous pouvez faire une avance rapide du master vers la nouvelle tête.

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

Étape 5 : Enfin un peu de nettoyage : Enlever la télécommande temporaire.

git remote rm sourcerepo

5voto

Adam Dymitruk Points 34999

Si vous voulez vraiment coudre des choses ensemble, consultez la rubrique "greffage". Vous devriez également utiliser git rebase --preserve-merges --onto . Il existe également une option permettant de conserver la date de l'auteur pour les informations relatives au committer.

0 votes

@adymitruk Merci, pour votre réponse. Je suis vraiment novice en matière de git, je vais donc étudier la solution que vous proposez. J'ai essayé git filter-branch et cela semble fonctionner, mais peut-être que le vôtre est meilleur. Je vais l'essayer.

0 votes

@adymitruk Puis-je utiliser rebase avec deux dépôts qui ne sont pas liés entre eux en tant que branches ? Je veux dire que les deux dépôts que je veux fusionner n'ont pas de commits initiaux communs...

0 votes

Merci @adymitruk. Je n'étais pas sûr que le rebasement puisse être fait avec deux dépôts non liés. Cela sera certainement utile

4voto

x-yuri Points 616

Disons que vous voulez fusionner le référentiel a en b (Je suppose qu'ils sont situés l'un à côté de l'autre) :

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Pour cela, il vous faut git-filter-repo installé ( filter-branch es découragé ).

Un exemple de fusion de deux grands dépôts, en plaçant l'un d'eux dans un sous-répertoire : https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

En savoir plus ici .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X