web-dev-qa-db-fra.com

Fusionner deux référentiels Git sans rompre l'historique des fichiers

Je dois fusionner deux référentiels Git dans un tout nouveau troisième référentiel. J'ai trouvé de nombreuses descriptions sur la manière de procéder en utilisant une fusion de sous-arbres (par exemple réponse de Jakub Narębski sur comment fusionnez-vous deux référentiels Git? ) et suivez-vous ces instructions principalement fonctionne, sauf que lorsque je valide la fusion de la sous-arborescence, tous les fichiers des anciens référentiels sont enregistrés en tant que nouveaux fichiers ajoutés. Je peux voir l'historique des validations à partir des anciens référentiels lorsque je fais git log, mais si je git log <file>, il affiche une seule validation pour ce fichier - la fusion de la sous-arborescence. À en juger par les commentaires sur la réponse ci-dessus, je ne suis pas le seul à avoir vu ce problème, mais je n'ai trouvé aucune solution publiée à ce problème.

Existe-t-il un moyen de fusionner des référentiels et de laisser l’historique des fichiers individuels intact?

201
Eric Lee

Il s'avère que la réponse est beaucoup plus simple si vous essayez simplement de coller deux référentiels ensemble et de donner l’impression qu’il en a été ainsi au lieu de gérer une dépendance externe. Vous devez simplement ajouter des télécommandes à votre ancien dépôt, les fusionner avec votre nouveau maître, déplacer les fichiers et les dossiers dans un sous-répertoire, valider le déplacement et répéter toutes les autres mises en pension. Les sous-modules, les fusions de sous-arbres et les rebases sophistiqués sont destinés à résoudre un problème légèrement différent et ne conviennent pas à ce que j'essayais de faire.

Voici un exemple de script Powershell permettant de coller deux référentiels:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir –exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Évidemment, vous pouvez plutôt fusionner old_b avec old_a (qui devient le nouveau référentiel combiné) si vous préférez le faire - modifiez le script à votre guise.

Si vous souhaitez également importer des branches de fonctionnalités en cours, utilisez ceci:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

C'est la seule partie non évidente du processus - il ne s'agit pas d'une fusion de sous-arborescence, mais d'un argument en faveur de la fusion récursive normale qui indique à Git que nous avons renommé la cible et qui aide Git à tout aligner correctement.

J'ai écrit une explication un peu plus détaillée ici .

251
Eric Lee

Voici un moyen de ne pas réécrire d’historique. Tous les ID de validation resteront donc valides. Le résultat final est que les fichiers du deuxième référentiel se retrouveront dans un sous-répertoire.

  1. Ajoutez le deuxième repo en tant que télécommande:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Assurez-vous d'avoir téléchargé tous les commits de secondrepo:

    git fetch secondrepo
    
  3. Créez une branche locale à partir de la branche du deuxième référentiel:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Déplacez tous ses fichiers dans un sous-répertoire:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Fusionner la deuxième branche dans la branche principale du premier référentiel:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

Votre référentiel aura plus d'un commit root, mais cela ne devrait pas poser de problème.

135
Flimm

s'il vous plaît jeter un oeil à l'aide

git rebase --root --preserve-merges --onto

pour relier deux histoires tôt dans leur vie.

Si vous avez des chemins qui se chevauchent, corrigez-les avec

git filter-branch --index-filter

lorsque vous utilisez le journal, assurez-vous de "trouver des copies plus difficiles" avec

git log -CC

de cette façon, vous trouverez tous les mouvements de fichiers dans le chemin.

8
Adam Dymitruk

J'ai transformé le solution de @Flimm ceci en un git alias comme ceci (ajouté à mon ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"
5
Fredrik Erlandsson

Cette fonction clone le repo distant dans le repo local:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Comment utiliser:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Profit!

3
Andrey Izman

Quelques années ont passé et des solutions bien fondées ont été votées, mais je souhaite partager la mienne car elle était un peu différente car je souhaitais fusionner deux référentiels distants dans un nouveau sans supprimer. l'historique des référentiels précédents.

  1. Créez un nouveau référentiel dans Github.

    enter image description here

  2. Téléchargez le référentiel nouvellement créé et ajoutez l'ancien référentiel distant.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Récupérez tous les fichiers de l'ancien dépôt pour créer une nouvelle branche.

    git fetch OldRepo
    git branch -a
    

    enter image description here

  4. Dans la branche principale, effectuez une fusion pour combiner l'ancien référentiel avec le nouveau créé.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    enter image description here

  5. Créez un nouveau dossier pour stocker tout le nouveau contenu créé qui a été ajouté à partir de OldRepo et déplacez ses fichiers dans ce nouveau dossier.

  6. Enfin, vous pouvez télécharger les fichiers à partir des pensions combinées et supprimer en toute sécurité le OldRepo de GitHub.

J'espère que cela peut être utile à quiconque est impliqué dans la fusion de référentiels distants.

2
abautista

Suivez les étapes pour intégrer un référentiel dans un autre référentiel, en ayant un seul historique git en fusionnant les deux historiques git.

  1. Clonez les deux dépôts que vous souhaitez fusionner.

git clone [email protected]: utilisateur/parent-repo.git

git clone [email protected]: user/child-repo.git

  1. Aller au repo enfant

cd enfant-repo /

  1. exécutez la commande ci-dessous, remplacez le chemin my/new/subdir (3 occurrences) par la structure de répertoire dans laquelle vous souhaitez obtenir le référentiel enfant.

git filter-branch --Prune-empty --tree-filter 'si [! -e mon/nouveau/sous-répertoire]; then mkdir -p my/new/subdir git ls-tree --nom uniquement $ GIT_COMMIT | xargs -I files mv files my/new/subdir fi '

  1. Aller au repo parent

cd ../parent-repo/

  1. Ajouter un référent distant au parent, pointant le chemin du référentiel enfant

git remote ajoute enfant-distant ../child-repo/

  1. Chercher le repo enfant

git chercher une télécommande enfant

  1. Fusionner les histoires

fusion git --allow-unrelated-histories enfant-distant/maître

Si vous consultez le journal git dans le référent parent maintenant, les validations de référant enfant devraient être fusionnées. Vous pouvez également voir la balise indiquant à partir de la source de validation.

L’article ci-dessous m’a aidé à Incorporer un dépôt dans un autre, ayant un seul historique git en fusionnant les deux historiques.

http://ericlathrop.com/2014/01/combining-git-repositories/

J'espère que cela t'aides. Bon codage!

0
AnoopGoudar