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?
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.
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" .
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
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 à
git pull
ed.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.
Pour ce faire, utilisez simplement git format-patch.
Supposons que nous ayons 2 dépôts git foo et bar .
foo contient:
bar contient:
et nous voulons nous retrouver avec foo contenant le bar history et ces fichiers:
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.
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
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.
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
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.
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.
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:
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 .
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).