web-dev-qa-db-fra.com

Comment fusionnez-vous deux référentiels Git?

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?

1352
static_rtti

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>

<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.

361
Simon Perepelitsa

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.

1487
Andresch Serj

Voici deux solutions possibles:

Sous-modules

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.

Fusion de sous-arbres

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.

587
Jakub Narębski

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

191
Greg Hewgill

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.

64
Paul Draper

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
43
Smar

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

22
Calahad

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.

TL; DR

$ 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

Version longue

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:.

16
Radon Rosborough

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:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-file-history/

8
Rian

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.

6
eitch

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
6
jettero

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.

5
Eric Lee

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:

 enter image description here

5
Finn Haakansson

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:

  1. Ajouter une télécommande au référentiel B pointant sur le référentiel A (git remote add ...)
  2. Extraction de la branche actuelle (nous n’utilisions pas le maître pour les corrections de bugs) (git pull remoteForRepoA bugFixBranch)
  3. Pousser la fusion avec github

Travaillé un régal :) 

4
David Lemphers

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
3

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
3
user123568943685

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 )

3
Turadg

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.

3
koppor

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.

0
Mike

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
0
Collin Anderson

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.

0
Andrey Izman

É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
0
Praveen Kumar