Considérez le scénario suivant:
J'ai développé un petit projet expérimental A dans son propre dépôt Git. Il a maintenant mûri et j'aimerais que A fasse partie du plus grand projet B, qui a son propre grand référentiel. J'aimerais maintenant ajouter A comme sous-répertoire de B.
Comment fusionner A en B sans perdre l’histoire de quelque côté que ce soit?
Une seule branche d'un autre référentiel peut facilement être placée dans un sous-répertoire en conservant son historique. Par exemple:
git subtree add --prefix=Rails git://github.com/Rails/rails.git master
Cela apparaîtra comme une seule validation dans laquelle tous les fichiers de la branche principale de Rails sont ajoutés dans le répertoire "Rails" . Toutefois, le titre de la validation contient une référence à l'ancien arbre de l'historique:
Ajoutez 'Rails /' à partir du commit
<rev>
Où <rev>
est un hachage de validation SHA-1. Vous pouvez toujours voir l'historique, blâmer certains changements.
git log <rev>
git blame <rev> -- README.md
Notez que vous ne pouvez pas voir le préfixe de répertoire à partir d’ici car il s’agit d’une ancienne branche laissée intacte . Vous devriez le traiter comme un commit de déplacement de fichier habituel: vous aurez besoin d’un saut supplémentaire pour l’atteindre.
# finishes with all files added at once commit
git log Rails/README.md
# then continue from original tree
git log <rev> -- README.md
Il existe des solutions plus complexes telles que le faire manuellement ou la réécriture de l'historique comme décrit dans d'autres réponses.
La commande git-subtree fait partie de git-contrib officielle, certains gestionnaires de paquets l'installent par défaut (OS X Homebrew) . Mais vous devrez peut-être l'installer vous-même en plus de git.
Si vous souhaitez fusionner project-a
dans project-b
:
cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a
Extrait de: git fusionne différents référentiels?
Cette méthode a très bien fonctionné pour moi, elle est plus courte et, à mon avis, beaucoup plus propre.
Remarque: Le paramètre --allow-unrelated-histories
n'existe que depuis git> = 2.9. Voir Documentation Git - git merge/--allow-unrelated-histories }
Update: Ajout de --tags
comme suggéré par @jstadler afin de conserver les tags.
Voici deux solutions possibles:
Copiez le référentiel A dans un répertoire distinct du grand projet B ou (peut-être mieux) clonez le référentiel A dans un sous-répertoire du projet B. Ensuite, utilisez git submodule pour transformer ce référentiel en sous-module d'un référentiel B.
C'est une bonne solution pour les référentiels à couplage faible, où le développement dans le référentiel A se poursuit, et la majeure partie du développement est un développement autonome distinct en A. Voir aussi SubmoduleSupport et GitSubmoduleTutorial pages sur Git Wiki.
Vous pouvez fusionner le référentiel A dans un sous-répertoire d'un projet B à l'aide de la stratégie fusion d'arborescence. Ceci est décrit dans Subtree Merging and You de Markus Prinz.
git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master
(L'option --allow-unrelated-histories
est nécessaire pour Git> = 2.9.0.)
Vous pouvez également utiliser l'outil git subtree ( repository sur GitHub ) de apenwarr (Avery Pennarun), annoncé par exemple dans son article de blog Une nouvelle alternative aux sous-modules Git: git subtree .
Je pense que dans votre cas (A doit faire partie d'un projet plus vaste B), la solution correcte consisterait à utiliser subtree fusion.
L'approche des sous-modules est utile si vous souhaitez gérer le projet séparément. Cependant, si vous voulez vraiment fusionner les deux projets dans le même référentiel, vous avez encore un peu de travail à faire.
La première chose à faire serait d’utiliser git filter-branch
pour réécrire les noms de tous les éléments du deuxième référentiel afin de les placer dans le sous-répertoire où vous souhaitez qu’ils se retrouvent. Ainsi, au lieu de foo.c
, bar.html
, vous auriez projb/foo.c
et projb/bar.html
.
Ensuite, vous devriez pouvoir faire quelque chose comme ce qui suit:
git remote add projb [wherever]
git pull projb
Le git pull
fera un git fetch
suivi d'un git merge
. Il ne devrait y avoir aucun conflit, si le référentiel que vous extrayez n'a pas encore de répertoire projb/
.
Une recherche plus poussée indique que quelque chose de similaire a été fait pour fusionner gitk
en git
. Junio C Hamano écrit à ce sujet ici: http://www.mail-archive.com/[email protected]/msg03395.html
git-subtree
est agréable, mais ce n’est probablement pas celui que vous voulez.
Par exemple, si projectA
est le répertoire créé dans B, après git subtree
,
git log projectA
listes un seul commit: la fusion. Les commits du projet fusionné sont pour des chemins différents, ils ne sont donc pas affichés.
La réponse de Greg Hewgill est la plus proche, bien qu'elle ne dise pas réellement comment réécrire les chemins.
La solution est étonnamment simple.
(1) En A,
PREFIX=projectA #adjust this
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$PREFIX"'/," |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD
Note: Ceci réécrit l'historique, donc si vous avez l'intention de continuer à utiliser ce dépôt A, vous voudrez peut-être tout d'abord copier (copier) une copie jetable.
(2) Puis en B, lancez
git pull path/to/A
Voila! Vous avez un répertoire projectA
dans B. Si vous exécutez git log projectA
, vous verrez tous les commits de A.
Dans mon cas, je voulais deux sous-répertoires, projectA
et projectB
. Dans ce cas, j’ai également fait l’étape (1) à B.
Si les deux référentiels ont le même type de fichiers (comme deux référentiels Rails pour différents projets), vous pouvez récupérer les données du référentiel secondaire dans votre référentiel actuel:
git fetch git://repository.url/repo.git master:branch_name
puis fusionnez-le dans le référentiel actuel:
git merge --allow-unrelated-histories branch_name
Si votre version de Git est inférieure à 2.9, supprimez --allow-unrelated-histories
.
Après cela, des conflits peuvent survenir. Vous pouvez les résoudre par exemple avec git mergetool
. kdiff3
peut être utilisé uniquement avec le clavier, donc 5 fichiers de conflit prennent lors de la lecture du code juste quelques minutes.
N'oubliez pas de terminer la fusion:
git commit
Je perdais sans cesse mon historique lors de l'utilisation de la fusion, je me suis donc retrouvé à utiliser rebase car dans mon cas, les deux référentiels sont suffisamment différents pour ne pas fusionner à chaque validation:
git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB
cd projB
git remote add projA ../projA/
git fetch projA
git rebase projA/master HEAD
=> résolvez les conflits, puis continuez autant de fois que nécessaire ...
git rebase --continue
Faire cela conduit à un projet ayant tous les commits de projA suivi de commits de projB
Dans mon cas, j'avais un référentiel my-plugin
et un référentiel main-project
et je voulais prétendre que my-plugin
avait toujours été développé dans le sous-répertoire plugins
de main-project
.
En gros, j'ai réécrit l'historique du référentiel my-plugin
pour qu'il apparaisse que tout le développement a eu lieu dans le sous-répertoire plugins/my-plugin
. Ensuite, j'ai ajouté l'historique de développement de my-plugin
dans l'historique main-project
et fusionné les deux arbres. Puisqu'il n'y avait pas de répertoire plugins/my-plugin
déjà présent dans le référentiel main-project
, il s'agissait d'une fusion triviale sans conflit. Le référentiel résultant contenait tout l'historique des deux projets d'origine et avait deux racines.
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
Commencez par créer une copie du référentiel my-plugin
, car nous allons réécrire l'historique de ce référentiel.
Maintenant, accédez à la racine du référentiel my-plugin
, extrayez votre branche principale (probablement master
) et exécutez la commande suivante. Bien sûr, vous devriez remplacer my-plugin
et plugins
quels que soient vos noms.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Maintenant pour une explication. git filter-branch --tree-filter (...) HEAD
exécute la commande (...)
sur chaque commit accessible depuis HEAD
. Notez que ceci fonctionne directement sur les données stockées pour chaque commit, nous n'avons donc pas à nous soucier des notions de "répertoire de travail", "index", "staging", etc.
Si vous exécutez une commande filter-branch
qui échoue, elle laissera des fichiers dans le répertoire .git
et la prochaine fois que vous essaierez filter-branch
, elle s'en plaindra, à moins que vous ne fournissiez l'option -f
à filter-branch
.
En ce qui concerne la commande réelle, je n’ai pas eu beaucoup de chance d’obtenir bash
de faire ce que je voulais, alors j’utilise plutôt zsh -c
pour que zsh
exécute une commande. J'ai d'abord défini l'option extended_glob
, qui active la syntaxe ^(...)
dans la commande mv
, ainsi que l'option glob_dots
, qui me permet de sélectionner des fichiers point (tels que .gitignore
) avec un glob (^(...)
).
Ensuite, j'utilise la commande mkdir -p
pour créer à la fois plugins
et plugins/my-plugin
en même temps.
Enfin, j'utilise la fonction zsh
"negative glob" ^(.git|plugins)
pour faire correspondre tous les fichiers du répertoire racine du référentiel, à l'exception de .git
et du dossier my-plugin
nouvellement créé. (Exclure .git
n'est peut-être pas nécessaire ici, mais essayer de déplacer un répertoire vers lui-même est une erreur.)
Dans mon référentiel, la validation initiale n'incluait aucun fichier. Par conséquent, la commande mv
a renvoyé une erreur lors de la validation initiale (car rien ne pouvait être déplacé). Par conséquent, j'ai ajouté un || true
pour que git filter-branch
n'abandonne pas.
L'option --all
indique à filter-branch
de réécrire l'historique de toutes branches dans le référentiel, et le --
supplémentaire est nécessaire pour indiquer à git
de l'interpréter comme faisant partie de la liste d'options permettant aux branches de réécrire option à filter-branch
lui-même.
Maintenant, accédez à votre référentiel main-project
et recherchez la branche dans laquelle vous souhaitez fusionner. Ajoutez votre copie locale du référentiel my-plugin
(avec son historique modifié) en tant que télécommande de main-project
avec:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Vous allez maintenant avoir deux arbres non liés dans votre historique de commit, que vous pouvez visualiser facilement en utilisant:
$ git log --color --graph --decorate --all
Pour les fusionner, utilisez:
$ git merge my-plugin/master --allow-unrelated-histories
Notez que dans les versions antérieures à la version 2.9.0 de Git, l'option --allow-unrelated-histories
n'existait pas. Si vous utilisez l'une de ces versions, omettez simplement l'option: le message d'erreur que --allow-unrelated-histories
empêche était aussi ajouté dans 2.9.0.
Vous ne devriez pas avoir de conflits de fusion. Si vous le faites, cela signifie probablement que la commande filter-branch
n'a pas fonctionné correctement ou qu'il y avait déjà un répertoire plugins/my-plugin
dans main-project
.
Assurez-vous de saisir un message de validation explicatif pour tout contributeur futur qui se demanderait ce qui se passait dans le piratage pour créer un référentiel avec deux racines.
Vous pouvez visualiser le nouveau graphique de validation, qui devrait comporter deux validations racine, à l'aide de la commande ci-dessus git log
. Notez que seule la branche master
sera fusionnée. Cela signifie que si vous avez un travail important sur d'autres branches my-plugin
que vous souhaitez fusionner dans l'arborescence main-project
, vous devez vous abstenir de supprimer la my-plugin
distante tant que vous n'avez pas effectué ces fusions. Si vous ne le faites pas, les commits de ces branches seront toujours dans le référentiel main-project
, mais certains seront inaccessibles et sujets à un éventuel nettoyage de mémoire. (En outre, vous devrez y faire référence par SHA, car la suppression d'une télécommande supprime ses branches de suivi à distance.)
Une fois que vous avez fusionné tout ce que vous souhaitez conserver depuis my-plugin
, vous pouvez supprimer le my-plugin
distant en utilisant:
$ git remote remove my-plugin
Vous pouvez maintenant supprimer en toute sécurité la copie du référentiel my-plugin
dont vous avez modifié l'historique. Dans mon cas, j'ai également ajouté un avis de désapprobation au référentiel réel my-plugin
une fois la fusion terminée et poussée.
Testé sur Mac OS X El Capitan avec git --version 2.9.0
et zsh --version 5.2
. Votre kilométrage peut varier.Références:.
J'essaie de faire la même chose depuis des jours, j'utilise git 2.7.2. Le sous-arbre ne conserve pas l'historique.
Vous pouvez utiliser cette méthode si vous n'utiliserez plus l'ancien projet.
Je suggèrerais que vous ayez d'abord la branche B et que vous travailliez dans la branche.
Voici les étapes sans branchement:
cd B
# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B
# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B
git add .
git commit -m "Moving content of project B in preparation for merge from A"
# Now merge A into B
git remote add -f A <A repo url>
git merge A/<branch>
mkdir A
# move all the files into subdir A, excluding .git
git mv <files> A
git commit -m "Moved A into subdir"
# Move B's files back to root
git mv B/* ./
rm -rf B
git commit -m "Reset B to original state"
git Push
Si vous enregistrez maintenant l’un des fichiers dans le sous-répertoire A, vous obtiendrez l’historique complet.
git log --follow A/<file>
C'est le post qui m'aide à faire ça:
J'ai rassemblé beaucoup d'informations ici sur Stack OverFlow, etc., et j'ai réussi à mettre en place un script qui résout le problème pour moi.
La mise en garde est qu'il ne prend en compte que la branche "develop" de chaque référentiel et le fusionne dans un répertoire séparé dans un tout nouveau référentiel.
Les balises et autres branches sont ignorées - ce n'est peut-être pas ce que vous voulez.
Le script gère même les branches et les balises des fonctionnalités, en les renommant dans le nouveau projet afin que vous sachiez d'où elles viennent.
#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
## and tag. These are renamed to have the Origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
## which are to be merged on separate lines.
##
## Author: Robert von Burg
## [email protected]
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#
# disallow using undefined variables
shopt -s -o nounset
# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'
# Detect proper usage
if [ "$#" -ne "2" ] ; then
echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
exit 1
fi
## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"
# Script functions
function failed() {
echo -e "ERROR: Merging of projects failed:"
echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
echo -e "$1"
exit 1
}
function commit_merge() {
current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
if [[ ! -f ".git/MERGE_HEAD" ]] ; then
echo -e "INFO: No commit required."
echo -e "INFO: No commit required." >>${LOG_FILE} 2>&1
else
echo -e "INFO: Committing ${sub_project}..."
echo -e "INFO: Committing ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
fi
fi
}
# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
exit 1
fi
# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
exit 1
fi
# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo
# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do
if [[ "${url:0:1}" == '#' ]] ; then
continue
fi
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"
echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
echo -e "----------------------------------------------------"
echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1
# Fetch the project
echo -e "INFO: Fetching ${sub_project}..."
echo -e "INFO: Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
git remote add "${sub_project}" "${url}"
if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
failed "Failed to fetch project ${sub_project}"
fi
# add remote branches
echo -e "INFO: Creating local branches for ${sub_project}..."
echo -e "INFO: Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
while read branch ; do
branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)
echo -e "INFO: Creating branch ${branch_name}..."
echo -e "INFO: Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1
# create and checkout new merge branch off of master
if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
# Merge the project
echo -e "INFO: Merging ${sub_project}..."
echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
fi
# And now see if we need to commit (maybe there was a merge)
commit_merge "${sub_project}/${branch_name}"
# relocate projects files into own directory
if [ "$(ls)" == "${sub_project}" ] ; then
echo -e "WARN: Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
echo -e "WARN: Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
else
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
mkdir ${sub_project}
for f in $(ls -a) ; do
if [[ "$f" == "${sub_project}" ]] ||
[[ "$f" == "." ]] ||
[[ "$f" == ".." ]] ; then
continue
fi
git mv -k "$f" "${sub_project}/"
done
# commit the moving
if ! git commit --quiet -m "[Project] Move ${sub_project} files into sub directory" ; then
failed "Failed to commit moving of ${sub_project} files into sub directory"
fi
fi
echo
done < <(git ls-remote --heads ${sub_project})
# checkout master of sub probject
if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
failed "sub_project ${sub_project} is missing master branch!"
fi
# copy remote tags
echo -e "INFO: Copying tags for ${sub_project}..."
echo -e "INFO: Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
while read tag ; do
tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)
# hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
tag_name="${tag_name_unfixed%%^*}"
tag_new_name="${sub_project}/${tag_name}"
echo -e "INFO: Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
echo -e "INFO: Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
echo -e "WARN: Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
echo -e "WARN: Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
fi
done < <(git ls-remote --tags --refs ${sub_project})
# Remove the remote to the old project
echo -e "INFO: Removing remote ${sub_project}..."
echo -e "INFO: Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
git remote rm ${sub_project}
echo
done
# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do
if [[ ${url:0:1} == '#' ]] ; then
continue
fi
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."
echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
failed "Failed to merge branch ${sub_project}/master into master"
fi
# And now see if we need to commit (maybe there was a merge)
commit_merge "${sub_project}/master"
echo
done
# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo
exit 0
Vous pouvez également l'obtenir de http://paste.ubuntu.com/11732805
Commencez par créer un fichier avec l’URL de chaque référentiel, par exemple:
[email protected]:eitchnet/ch.eitchnet.parent.git
[email protected]:eitchnet/ch.eitchnet.utils.git
[email protected]:eitchnet/ch.eitchnet.privilege.git
Appelez ensuite le script en lui donnant le nom du projet et le chemin d'accès au script:
./mergeGitRepositories.sh eitchnet_test eitchnet.lst
Le script lui-même a beaucoup de commentaires qui devraient expliquer ce qu’il fait.
Je sais que cela fait longtemps après les faits, mais je n’étais pas satisfait des autres réponses que j’ai trouvées ici.
me=$(basename $0)
TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo
echo "building new repo in $TMP"
echo
sleep 1
set -e
cd $TMP
mkdir new-repo
cd new-repo
git init
cd ..
x=0
while [ -n "$1" ]; do
repo="$1"; shift
git clone "$repo"
dirname=$(basename $repo | sed -e 's/\s/-/g')
if [[ $dirname =~ ^git:.*\.git$ ]]; then
dirname=$(echo $dirname | sed s/.git$//)
fi
cd $dirname
git remote rm Origin
git filter-branch --tree-filter \
"(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
cd ..
cd new-repo
git pull --no-commit ../$dirname
[ $x -gt 0 ] && git commit -m "merge made by $me"
cd ..
x=$(( x + 1 ))
done
Si vous essayez simplement de coller deux référentiels ensemble, les sous-modules et les fusions de sous-arborescences ne sont pas le bon outil à utiliser car ils ne conservent pas tout l'historique des fichiers (comme les gens l'ont noté dans d'autres réponses). Voir cette réponse ici pour la manière simple et correcte de le faire.
Si vous souhaitez placer les fichiers d'une branche dans le référentiel B dans un sous-arbre du référentiel A et conserve également l'historique, continuez à lire. (Dans l'exemple ci-dessous, je suppose que nous souhaitons que la branche principale du référentiel B soit fusionnée dans la branche principale du référentiel A.)
Dans le référentiel A, procédez comme suit pour le rendre disponible:
git remote add B ../B # Add repo B as a new remote.
git fetch B
Maintenant, nous créons une nouvelle branche (avec un seul commit) dans le dépôt A que nous appelons new_b_root
. La validation résultante aura les fichiers qui ont été validés dans la première validation de la branche principale du référentiel B mais placés dans un sous-répertoire appelé path/to/b-files/
.
git checkout --Orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"
Explication: L'option --Orphan
de la commande d'extraction extrait les fichiers de la branche principale de A, mais ne crée aucune validation. Nous aurions pu sélectionner n'importe quel commit car nous effacerions ensuite tous les fichiers. Ensuite, sans encore valider (-n
), nous sélectionnons le premier commit à partir de la branche principale de B. (Le choix de recherche conserve le message de validation d'origine, ce qu'une commande simple ne semble pas faire.) Nous créons ensuite la sous-arborescence dans laquelle nous voulons placer tous les fichiers du référentiel B. cherry-pick à la sous-arbre. Dans l'exemple ci-dessus, il n'y a qu'un fichier README
à déplacer. Ensuite, nous validons notre commit racine B-repo tout en préservant l’horodatage du commit initial.
Nous allons maintenant créer une nouvelle branche B/master
par-dessus le new_b_root
nouvellement créé. Nous appelons la nouvelle branche b
:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
Maintenant, nous fusionnons notre branche b
en A/master
:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
Enfin, vous pouvez supprimer les branches B
à distance et temporaires:
git remote remove B
git branch -D new_b_root b
Le graphique final aura une structure comme celle-ci:
J'avais un défi similaire, mais dans mon cas, nous avions développé une version de la base de code dans le référentiel A, puis cloné le tout dans un nouveau référentiel, repo B, pour la nouvelle version du produit. Après avoir corrigé quelques bogues dans le référentiel A, nous devions effectuer les modifications dans le référentiel B. Nous avons finalement procédé comme suit:
Travaillé un régal :)
Fusion de 2 pensions
git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2
delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master
Pour fusionner un A dans B:
1) Dans le projet A
git fast-export --all --date-order > /tmp/ProjectAExport
2) Dans le projet B
git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport
Dans cette branche, faites toutes les opérations que vous devez faire et engagez-les.
C) Puis retour au maître et une fusion classique entre les deux branches:
git checkout master
git merge projectA
Identique à @Smar mais utilise des chemins de système de fichiers, définis dans PRIMARY et SECONDARY:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
Ensuite, vous fusionnez manuellement.
(adapté de article d'Anar Manafov )
Lorsque vous souhaitez fusionner trois projets ou plus dans un commit single, suivez les étapes décrites dans les autres réponses (remote add -f
, merge
). Ensuite, (soft) réinitialiser l'index à old head (où aucune fusion n'a eu lieu). Ajoutez tous les fichiers (git add -A
) et validez-les (message "Fusion des projets A, B, C et D dans un projet). C’est maintenant le commit-id du maître.
Maintenant, créez .git/info/grafts
avec le contenu suivant:
<commit-id of master> <list of commit ids of all parents>
Exécutez git filter-branch -- head^..head head^2..head head^3..head
. Si vous avez plus de trois branches, ajoutez autant de head^n..head
que vous avez de branches. Pour mettre à jour les balises, ajoutez --tag-name-filter cat
. N'ajoutez pas toujours cela, car cela pourrait provoquer une réécriture de certains commits. Pour plus de détails, voir page de manuel de filter-branch , recherchez "greffes".
Maintenant, votre dernier commit a les bons parents associés.
Je voulais déplacer un petit projet dans un sous-répertoire d'un plus grand. Comme mon petit projet n’avait pas beaucoup de commits, j’ai utilisé git format-patch --output-directory /path/to/patch-dir
. Ensuite, sur le projet plus grand, j’ai utilisé git am --directory=dir/in/project /path/to/patch-dir/*
.
Cela semble chemin moins effrayant et beaucoup plus propre qu'un filtre à branche. Certes, cela peut ne pas être applicable à tous les cas.
Je fusionne légèrement les projets manuellement, ce qui me permet d'éviter d'avoir à gérer des conflits de fusion.
tout d'abord, copiez les fichiers de l'autre projet comme vous le souhaitez.
cp -R myotherproject newdirectory
git add newdirectory
prochain pull dans l'histoire
git fetch path_or_url_to_other_repo
dites à Git de se fondre dans l'histoire de la dernière chose recherchée
echo 'FETCH_HEAD' > .git/MERGE_HEAD
maintenant commettez comme vous le feriez normalement
git commit
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.
Étant donné le commandement est la meilleure solution possible que je suggère.
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master