web-dev-qa-db-fra.com

Comment importer un référentiel Git existant dans un autre?

J'ai un référentiel Git dans un dossier appelé XXX, et j'ai un deuxième référentiel Git appelé YYY.

Je veux importer le référentiel XXX dans le référentiel YYY en tant que sous-répertoire nommé ZZZ et ajouter l'historique de tous les changements de XXX à YYY.

Structure des dossiers avant:

XXX
 |- .git
 |- (project files)
YYY
 |- .git
 |- (project files)

Structure du dossier après:

YYY
 |- .git  <-- This now contains the change history from XXX
 |-  ZZZ  <-- This was originally XXX
      |- (project files)
 |-  (project files)

Cela peut-il être fait ou dois-je recourir à des sous-modules?

416
Vijay Patel

Le moyen le plus simple serait probablement de tirer le contenu XXX dans une branche de YYY, puis de le fusionner en maître:

Dans YYY:

git remote add other /path/to/XXX
git fetch other
git checkout -b ZZZ other/master
mkdir ZZZ
git mv stuff ZZZ/stuff                      # repeat as necessary for each file/dir
git commit -m "Moved stuff to ZZZ"
git checkout master                
git merge ZZZ --allow-unrelated-histories   # should add ZZZ/ to master
git commit
git remote rm other
git branch -d ZZZ                           # to get rid of the extra branch before pushing
git Push                                    # if you have a remote, that is

En fait, je viens d’essayer cela avec deux de mes pensions et ça marche. Contrairement à la réponse de Jörg cela ne vous permettra pas de continuer à utiliser l'autre dépôt, mais je ne pense pas que vous ayez précisé cela de toute façon.

Note: Depuis que cela a été écrit en 2009, git a ajouté la fusion de sous-arborescence mentionnée dans la réponse ci-dessous. J'utiliserais probablement cette méthode aujourd'hui, bien que cette méthode fonctionne toujours bien sûr.

369
ebneter

Si vous souhaitez conserver l'historique de validation exact du deuxième référentiel et conserver ainsi la possibilité de fusionner facilement les modifications en amont à l'avenir, voici la méthode que vous souhaitez. Il en résulte que l'historique du sous-arbre non modifié est importé dans votre référentiel, ainsi qu'un commit de fusion pour déplacer le référentiel fusionné dans le sous-répertoire.

git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit --allow-unrelated-histories XXX_remote/master
git read-tree --prefix=ZZZ/ -u XXX_remote/master
git commit -m "Imported XXX as a subtree."

Vous pouvez suivre les modifications en amont comme suit:

git pull -s subtree XXX_remote master

Git détermine par lui-même où sont les racines avant de procéder à la fusion. Vous n'avez donc pas besoin de spécifier le préfixe lors des fusions suivantes.

Versions de Git antérieures à 2.9: vous n’avez pas besoin de passer l’option --allow-unrelated-histories à git merge.

La méthode de l'autre réponse qui utilise read-tree et ignore l'étape merge -s ours n'est en réalité pas différente de la copie des fichiers avec cp et de la validation du résultat.

La source originale provient de l'article d'aide de "github" "Subgee Merge" .

336
ColinM

git-subtree est un script conçu exactement pour ce cas d'utilisation consistant à fusionner plusieurs référentiels en un tout en préservant l'historique (et/ou en fractionnant l'historique des sous-arbres, bien que cela ne semble pas être pertinent pour cette question). Il est distribué dans l'arborescence de git depuis la version 1.7.11 .

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

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

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

Dans votre cas, dans le référentiel YYY, vous exécuteriez:

git subtree add -P ZZZ /path/to/XXX.git master
64
kynan

Il en existe un exemple bien connu dans le référentiel Git lui-même, qui est collectivement appelé dans la communauté Git " la fusion la plus cool jamais vue " (après la ligne de sujet que Linus Torvalds a utilisée dans le courrier électronique envoyé à Git liste de diffusion décrivant cette fusion). Dans ce cas, l'interface graphique gitk Git, qui fait maintenant partie de Git proprement dit, était en réalité un projet distinct. Linus a réussi à fusionner ce référentiel dans le référentiel Git de manière à 

  • il apparaît dans le référentiel Git comme s'il avait toujours été développé dans le cadre de Git, 
  • toute l'histoire est conservée intacte et 
  • il peut toujours être développé indépendamment dans son ancien référentiel, les modifications étant simplement git pulled.

L’e-mail contient les étapes nécessaires à la reproduction, mais ce n’est pas pour les âmes sensibles: d’abord, Linus a écrit Git, donc il en sait probablement un peu plus que vous ou moi, et ensuite, c’était Il y a presque 5 ans, et Git s'est amélioré considérablement depuis, alors peut-être que c'est maintenant beaucoup plus facile.

En particulier, je suppose qu’aujourd’hui, on utiliserait un sous-module gitk, dans ce cas précis.

46
Jörg W Mittag

Pour ce faire, utilisez simplement git format-patch.

Supposons que nous ayons 2 dépôts git foo et bar .

foo contient:

  • foo.txt
  • .git

bar contient:

  • bar.txt
  • .git

et nous voulons nous retrouver avec foo contenant le bar history et ces fichiers: 

  • foo.txt
  • .git
  • foobar/bar.txt

Alors pour faire ça:

 1. create a temporary directory eg PATH_YOU_WANT/patch-bar
 2. go in bar directory
 3. git format-patch --root HEAD --no-stat -o PATH_YOU_WANT/patch-bar --src-prefix=a/foobar/ --dst-prefix=b/foobar/
 4. go in foo directory
 5. git am PATH_YOU_WANT/patch-bar/*

Et si nous voulons réécrire tous les commits de message de barre, nous pouvons le faire, par exemple sous Linux:

git filter-branch --msg-filter 'sed "1s/^/\[bar\] /"' COMMIT_SHA1_OF_THE_PARENT_OF_THE_FIRST_BAR_COMMIT..HEAD

Cela ajoutera "[bar]" au début de chaque message de validation.

12
Damien R.

Basé sur cet article , l’utilisation de sous-arbre est ce qui a fonctionné pour moi et seul l’historique applicable a été transféré. Publier ici au cas où quelqu'un aurait besoin des étapes (assurez-vous de remplacer les espaces réservés par les valeurs qui vous sont applicables):

dans votre sous-dossier divisé du référentiel source dans une nouvelle branche

git subtree split --prefix=<source-path-to-merge> -b subtree-split-result

dans votre référentiel de destination, fusionnez dans la branche de résultat fractionnée

git remote add merge-source-repo <path-to-your-source-repository>
git fetch merge-source-repo
git merge -s ours --no-commit merge-source-repo/subtree-split-result
git read-tree --prefix=<destination-path-to-merge-into> -u merge-source-repo/subtree-split-result

vérifier vos modifications et commettre

git status
git commit

N'oublie pas de 

Nettoyer en supprimant la branche subtree-split-result

git branch -D subtree-split-result

Supprimez la télécommande que vous avez ajoutée pour extraire les données du référentiel source

git remote rm merge-source-repo

6
Alex

Cette fonction clone le repo distant dans le repo local. Après la fusion de toutes les validations, git log affichera les validations originales et les chemins appropriés:

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

Si vous apportez quelques modifications, vous pouvez même déplacer les fichiers/répertoires du référentiel fusionné dans différents chemins, par exemple:

repo="https://github.com/example/example"
path="$(pwd)"

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

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

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

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"

Avis
Les chemins remplacent via sed, alors assurez-vous qu’il a été déplacé dans les chemins appropriés après la fusion.
Le paramètre --allow-unrelated-histories n'existe que puisque git> = 2.9.

5
Andrey Izman

Ajouter une autre réponse car je pense que c'est un peu plus simple. Une extraction de repo_dest est effectuée dans repo_to_import, puis un maître Push - set-up-upstream: repo_dest est créé.

Cette méthode a fonctionné pour moi en important plusieurs pensions plus petites dans une plus grande.

Comment importer: repo1_to_import dans repo_dest

# checkout your repo1_to_import if you don't have it already 
git clone url:repo1_to_import repo1_to_import
cd repo1_to_import

# now. pull all of repo_dest
git pull url:repo_dest
ls 
git status # shows Your branch is ahead of 'Origin/master' by xx commits.
# now Push to repo_dest
git Push --set-upstream url:repo_dest master

# repeat for other repositories you want to import

Renommez ou déplacez les fichiers et les répertoires à la position souhaitée dans le référentiel d'origine avant de procéder à l'importation. par exemple. 

cd repo1_to_import
mkdir topDir
git add topDir
git mv this that and the other topDir/
git commit -m"move things into topDir in preparation for exporting into new repo"
# now do the pull and Push to import

La méthode décrite sur le lien suivant a inspiré cette réponse. Je l'ai aimé comme cela semblait plus simple. Mais méfiez-vous! Il y a des dragons! https://help.github.com/articles/importing-an-external-git-repositorygit Push --mirror url:repo_dest pousse l'historique et les états de votre repo locaux à distance (url: repo_dest). MAIS il supprime l’ancienne histoire et l’état de la télécommande. Le plaisir s'ensuit! : -E

3
gaoithe

Je voulais importer uniquement certains fichiers de l'autre référentiel (XXX) dans mon cas. Le sous-arbre était trop compliqué pour moi et les autres solutions ne fonctionnaient pas. C'est ce que j'ai fait:

ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')

Cela vous donne une liste de tous les commits séparés par des espaces qui affectent les fichiers que je voulais importer (ZZZ) dans l’ordre inverse (vous devrez peut-être ajouter --follow pour capturer également les renommés). Je suis ensuite allé dans le référentiel cible (YYY), j'ai ajouté l'autre référentiel (XXX) en tant que distant, j'en ai extrait un extrait et enfin:

git cherry-pick $ALL_COMMITS

qui ajoute tous les commits à votre branche, vous aurez ainsi tous les fichiers avec leur historique et pourrez faire ce que vous voudrez avec comme s’ils ont toujours été dans ce référentiel.

1
Sebastian Blask

Voir Exemple de base dans cet article et envisagez un tel mappage sur les référentiels:

  • A <-> YYY
  • B <-> XXX

Après toutes les activités décrites dans ce chapitre (après la fusion), supprimez la branche B-master:

$ git branch -d B-master

Ensuite, appuyez sur les modifications.

Ça marche pour moi.

0
VeLKerr

Je peux suggérer une autre solution (alternative à git-submodules ) pour votre problème - gil (git links) tool

Il permet de décrire et de gérer des dépendances complexes de référentiels git. 

En outre, il fournit une solution au problème de dépendance des sous-modules récursifs git .

Considérez que vous avez les dépendances de projet suivantes: exemple de graphe de dépendance du référentiel git

Vous pouvez ensuite définir le fichier .gitlinks avec la description de la relation de référentiels:

# Projects
CppBenchmark CppBenchmark https://github.com/chronoxor/CppBenchmark.git master
CppCommon CppCommon https://github.com/chronoxor/CppCommon.git master
CppLogging CppLogging https://github.com/chronoxor/CppLogging.git master

# Modules
Catch2 modules/Catch2 https://github.com/catchorg/Catch2.git master
cpp-optparse modules/cpp-optparse https://github.com/weisslj/cpp-optparse.git master
fmt modules/fmt https://github.com/fmtlib/fmt.git master
HdrHistogram modules/HdrHistogram https://github.com/HdrHistogram/HdrHistogram_c.git master
zlib modules/zlib https://github.com/madler/zlib.git master

# Scripts
build scripts/build https://github.com/chronoxor/CppBuildScripts.git master
cmake scripts/cmake https://github.com/chronoxor/CppCMakeScripts.git master

Chaque ligne décrit le lien git au format suivant:

  1. Nom unique du référentiel
  2. Chemin relatif du référentiel (démarré à partir du chemin du fichier .gitlinks)
  3. Le référentiel Git qui sera utilisé dans la commande git clone La branche du référentiel à récupérer
  4. Les lignes vides ou les lignes commençant par # ne sont pas analysées (traitées comme des commentaires).

Enfin, vous devez mettre à jour votre exemple de référentiel racine:

# Clone and link all git links dependencies from .gitlinks file
gil clone
gil link

# The same result with a single command
gil update

Le résultat est de cloner tous les projets requis et de les lier les uns aux autres de manière appropriée.

Si vous souhaitez valider toutes les modifications dans un référentiel avec toutes les modifications dans les référentiels liés aux enfants, vous pouvez le faire avec une seule commande:

gil commit -a -m "Some big update"

Les commandes Pull, Push fonctionnent de la même manière:

gil pull
gil Push

Gil (liens git) supporte les commandes suivantes:

usage: gil command arguments
Supported commands:
    help - show this help
    context - command will show the current git link context of the current directory
    clone - clone all repositories that are missed in the current context
    link - link all repositories that are missed in the current context
    update - clone and link in a single operation
    pull - pull all repositories in the current directory
    Push - Push all repositories in the current directory
    commit - commit all repositories in the current directory

En savoir plus sur problème de dépendance récursif des sous-modules git .

0
chronoxor

J'étais dans une situation où je cherchais -s theirs mais bien sûr, cette stratégie n'existe pas. Mon histoire était que j'avais lancé un projet sur GitHub, et maintenant pour une raison quelconque, ma variable locale master ne pouvait pas être fusionnée avec upstream/master bien que je n'avais apporté aucune modification locale à cette branche. (Vraiment, je ne sais pas ce qui s’est passé là-bas - je suppose qu’en amont, peut-être avait-il fait quelques sales coups en coulisses, peut-être?)

Ce que j'ai fini par faire était

# as per https://help.github.com/articles/syncing-a-fork/
git fetch upstream
git checkout master
git merge upstream/master
....
# Lots of conflicts, ended up just abandonging this approach
git reset --hard   # Ditch failed merge
git checkout upstream/master
# Now in detached state
git branch -d master # !
git checkout -b master   # create new master from upstream/master

Alors maintenant, ma master est à nouveau synchronisée avec upstream/master (et vous pouvez répéter ce qui précède pour toute autre branche que vous souhaitez également synchroniser de la même manière).

0
tripleee