it-swarm.com.de

Wie führen Sie zwei Git-Repositories zusammen?

Stellen Sie sich das folgende Szenario vor:

Ich habe ein kleines experimentelles Projekt A in einem eigenen Git-Repo entwickelt. Es ist inzwischen ausgereift, und ich möchte, dass A Teil des größeren Projekts B ist, das über ein eigenes großes Repository verfügt. Ich möchte jetzt A als Unterverzeichnis von B hinzufügen.

Wie verschmelze ich A mit B, ohne auf irgendeiner Seite Geschichte zu verlieren?

1352
static_rtti

Ein einzelner Zweig eines anderen Repositorys kann leicht in ein Unterverzeichnis eingefügt werden, in dem der Verlauf gespeichert wird. Zum Beispiel:

git subtree add --prefix=Rails git://github.com/Rails/rails.git master

Dies wird als ein einzelner Commit angezeigt, bei dem alle Dateien des Rails-Master-Zweigs zum "Rails" -Verzeichnis hinzugefügt werden. Der Titel des Commits enthält jedoch einen Verweis auf den alten Historienbaum:

Füge 'Rails /' von commit <rev> hinzu

Dabei ist <rev> ein SHA-1-Commit-Hash. Sie können immer noch den Verlauf sehen, einige Änderungen tadeln.

git log <rev>
git blame <rev> -- README.md

Beachten Sie, dass Sie das Verzeichnispräfix von hier aus nicht sehen können, da dies ein tatsächlicher alter Zweig ist, der intakt geblieben ist .. Sie sollten dies wie ein gewöhnliches File-Move-Commit behandeln: Sie benötigen einen zusätzlichen Sprung, wenn Sie es erreichen.

# finishes with all files added at once commit
git log Rails/README.md

# then continue from original tree
git log <rev> -- README.md

Es gibt komplexere Lösungen, wie das manuell oder das Schreiben der Historie, wie in anderen Antworten beschrieben.

Der Befehl git-subtree ist Teil des offiziellen git-contrib, einige Paketmanager installieren ihn standardmäßig (OS X Homebrew) .. __ Sie müssen ihn möglicherweise zusätzlich zu git selbst installieren.

361

Wenn Sie project-a mit project-b zusammenführen möchten:

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

Entnommen aus: git verschiedene Repositories zusammenführen?

Diese Methode hat für mich ziemlich gut funktioniert, sie ist kürzer und meiner Meinung nach viel sauberer.

Hinweis: Der --allow-unrelated-histories-Parameter existiert nur seit git> = 2.9. Siehe Git-git-Zusammenführungsdokumentation/--allow-unzusammenhängende Historien

Update: --tags wurde hinzugefügt, wie von @jstadler vorgeschlagen, um Tags zu behalten.

1487
Andresch Serj

Hier sind zwei mögliche Lösungen:

Submodule

Kopieren Sie entweder Repository A in ein separates Verzeichnis in größerem Projekt B oder klonen Sie Repository A (vielleicht besser) in ein Unterverzeichnis in Projekt B. Verwenden Sie dann Git-Submodul , um dieses Repository zu einem Submodul eines Repository B.

Dies ist eine gute Lösung für lose gekoppelte Repositorys, bei denen die Entwicklung in Repository A fortgesetzt wird und der größte Teil der Entwicklung eine separate eigenständige Entwicklung in A ist. Siehe auch SubmoduleSupport und GitSubmoduleTutorial) Seiten im Git Wiki.

Subtree-Zusammenführung

Sie können Repository A mit der Strategie subtree merge in ein Unterverzeichnis eines Projekts B zusammenführen. Dies ist in Subtree Merging and You von Markus Prinz beschrieben.

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

(Option --allow-unrelated-histories wird für Git> = 2.9.0 benötigt.)

Oder Sie können git subtree tool ( Repository auf GitHub ) von apenwarr (Avery Pennarun) verwenden, wie zum Beispiel in seinem Blogbeitrag Eine neue Alternative zu Git-Submodulen) angekündigt : Git-Teilbaum .


Ich denke, in Ihrem Fall (A soll Teil eines größeren Projekts B sein) wäre die richtige Lösung, subtree merge zu verwenden.

599
Jakub Narębski

Der Ansatz der Submodule ist gut, wenn Sie das Projekt separat verwalten möchten. Wenn Sie jedoch beide Projekte wirklich in demselben Repository zusammenführen möchten, müssen Sie etwas mehr Arbeit erledigen.

Die erste Sache wäre, git filter-branch zu verwenden, um die Namen aller Elemente im zweiten Repository in das Unterverzeichnis zu schreiben, in dem sie gespeichert werden sollen. Anstelle von foo.c, bar.html hätten Sie projb/foo.c und projb/bar.html.

Dann sollten Sie etwa Folgendes tun können:

git remote add projb [wherever]
git pull projb

Der git pull führt einen git fetch aus, gefolgt von einem git merge. Es sollte keine Konflikte geben, wenn das Repository, in das Sie ziehen, noch kein Verzeichnis projb/ enthält.

Eine weitere Suche zeigt, dass etwas Ähnliches durchgeführt wurde, um gitk mit git zusammenzuführen. Junio ​​C Hamano schreibt darüber hier: http://www.mail-archive.com/[email protected]/msg03395.html

191
Greg Hewgill

git-subtree ist Nizza, aber es ist wahrscheinlich nicht der, den Sie wollen.

Wenn zum Beispiel projectA das in B erstellte Verzeichnis nach git subtree ist,

git log projectA

listet nur einen commit: die Zusammenführung. Die Commits des zusammengeführten Projekts beziehen sich auf verschiedene Pfade, daher werden sie nicht angezeigt.

Die Antwort von Greg Hewgill kommt am ehesten, obwohl sie eigentlich nicht sagt, wie man die Pfade neu schreibt.


Die Lösung ist überraschend einfach.

(1) In 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

Hinweis: Dadurch wird der Verlauf neu geschrieben. Wenn Sie dieses Repo A weiterhin verwenden möchten, können Sie zuerst eine wegwerfende Kopie davon kopieren (kopieren).

(2) Dann in B ausführen

git pull path/to/A

Voila! Sie haben ein projectA-Verzeichnis in B. Wenn Sie git log projectA ausführen, werden alle Commits von A angezeigt.


In meinem Fall wollte ich zwei Unterverzeichnisse, projectA und projectB. In diesem Fall habe ich auch Schritt (1) bis B ausgeführt.

64
Paul Draper

Wenn beide Repositorys über dieselbe Art von Dateien verfügen (wie zwei Rails-Repositorys für verschiedene Projekte), können Sie Daten des sekundären Repositorys in Ihr aktuelles Repository abrufen:

git fetch git://repository.url/repo.git master:branch_name

und dann mit dem aktuellen Repository zusammenführen:

git merge --allow-unrelated-histories branch_name

Wenn Ihre Git-Version kleiner als 2.9 ist, entfernen Sie --allow-unrelated-histories.

Danach können Konflikte auftreten. Sie können sie beispielsweise mit git mergetool auflösen. kdiff3 kann nur mit der Tastatur verwendet werden, so dass 5 Konfliktdateien beim Lesen des Codes nur wenige Minuten dauern. 

Denken Sie daran, die Zusammenführung abzuschließen:

git commit
43
Smar

Ich habe immer wieder Geschichte verloren, als ich Merge verwendet habe, also habe ich rebase benutzt, da sich die beiden Repositories in meinem Fall so unterscheiden, dass sie nicht bei jedem Commit verschmelzen:

git clone [email protected]/projA.git projA
git clone [email protected]/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> Konflikte lösen, dann beliebig oft fortfahren ...

git rebase --continue

Dies führt dazu, dass ein Projekt alle Commits von ProJA gefolgt von Commits von ProJB hat

22
Calahad

In meinem Fall hatte ich ein my-plugin-Repository und ein main-project-Repository, und ich wollte vorgeben, dass my-plugin immer im plugins-Unterverzeichnis von main-project entwickelt wurde.

Grundsätzlich habe ich die Historie des my-plugin-Repositorys so umgeschrieben, dass die Entwicklung im plugins/my-plugin-Unterverzeichnis stattgefunden hat. Dann habe ich die Entwicklungsgeschichte von my-plugin in die main-project-Geschichte eingefügt und die beiden Bäume zusammengefügt. Da es im plugins/my-plugin-Repository kein main-project-Verzeichnis gab, war dies eine triviale Zusammenführung ohne Konflikte. Das resultierende Repository enthielt alle Historien beider Originalprojekte und hatte zwei Wurzeln.

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

Lange Version

Erstellen Sie zuerst eine Kopie des my-plugin-Repositorys, da wir den Verlauf dieses Repositorys neu schreiben werden.

Navigieren Sie nun zum Stammverzeichnis des my-plugin-Repositorys, überprüfen Sie Ihren Hauptzweig (wahrscheinlich master), und führen Sie den folgenden Befehl aus. Natürlich sollten Sie my-plugin und plugins unabhängig von Ihren tatsächlichen Namen verwenden.

$ 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

Nun zur Erklärung. git filter-branch --tree-filter (...) HEAD führt den Befehl (...) bei jedem Commit aus, das von HEAD aus erreichbar ist. Beachten Sie, dass dies direkt für die Daten gilt, die für jedes Commit gespeichert werden. Daher müssen wir uns keine Gedanken über "Arbeitsverzeichnis", "Index", "Staging" usw. machen.

Wenn Sie einen filter-branch-Befehl ausführen, der fehlschlägt, werden einige Dateien im .git-Verzeichnis zurückgelassen. Wenn Sie filter-branch das nächste Mal versuchen, werden Sie sich darüber beschweren, es sei denn, Sie geben -f die Option filter-branch an.

Was den eigentlichen Befehl angeht, hatte ich nicht viel Glück, bash zu tun, was ich wollte, also benutze ich zsh -c, um zsh einen Befehl ausführen zu lassen. Zuerst stelle ich die extended_glob-Option ein, die die ^(...)-Syntax im mv-Befehl aktiviert, sowie die glob_dots-Option, mit der ich Dotfiles (wie .gitignore) mit einem Glob (^(...)) auswählen kann.

Als Nächstes verwende ich den Befehl mkdir -p, um gleichzeitig plugins und plugins/my-plugin zu erstellen.

Schließlich verwende ich die zsh "negative glob" -Funktion ^(.git|plugins), um alle Dateien im Stammverzeichnis des Repositorys mit Ausnahme von .git und dem neu erstellten my-plugin-Ordner abzugleichen. (Das Ausschließen von .git ist hier möglicherweise nicht erforderlich, aber der Versuch, ein Verzeichnis in sich selbst zu verschieben, ist ein Fehler.)

In meinem Repository enthielt das ursprüngliche Commit keine Dateien. Daher gab der Befehl mv einen Fehler beim ersten Commit zurück (da zum Verschieben keine Daten verfügbar waren). Daher habe ich einen || true hinzugefügt, damit git filter-branch nicht abgebrochen wird.

Die --all-Option weist filter-branch an, den Verlauf für all -Zweige im Repository umzuschreiben, und der zusätzliche -- ist erforderlich, um git zu sagen, dass er als Teil der Optionsliste für zu überschreibende Zweige interpretiert werden soll Option für filter-branch selbst.

Navigieren Sie nun zu Ihrem main-project-Repository und prüfen Sie, in welchem ​​Zweig Sie zusammenführen möchten. Fügen Sie Ihre lokale Kopie des my-plugin-Repositorys (mit geändertem Verlauf) als Remote von main-project hinzu:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Sie haben nun zwei nicht zusammenhängende Bäume in Ihrem Commit-Verlauf, die Sie mit Hilfe von:

$ git log --color --graph --decorate --all

Um sie zusammenzuführen, verwenden Sie:

$ git merge my-plugin/master --allow-unrelated-histories

Beachten Sie, dass in Git vor 2.9.0 die Option --allow-unrelated-histories nicht vorhanden ist. Wenn Sie eine dieser Versionen verwenden, lassen Sie einfach die Option aus: Die Fehlermeldung, die --allow-unrelated-histories verhindert hat auch hinzugefügt in 2.9.0.

Sie sollten keine Zusammenführungskonflikte haben. Wenn Sie dies tun, bedeutet dies wahrscheinlich, dass entweder der Befehl filter-branch nicht ordnungsgemäß funktioniert hat oder dass bereits ein Verzeichnis plugins/my-plugin in main-project vorhanden ist.

Stellen Sie sicher, dass Sie eine erklärende Commit-Nachricht für alle zukünftigen Mitwirkenden eingeben, die sich fragen, welcher Hacker gerade ein Repository mit zwei Wurzeln erstellt hat.

Sie können das neue Commit-Diagramm, das zwei Root-Commits enthalten sollte, mit dem obigen git log-Befehl visualisieren. Beachten Sie, dass nur der Zweig master zusammengeführt wird. Das bedeutet, wenn Sie an anderen my-plugin-Zweigen, die Sie in den main-project-Baum einbinden möchten, wichtige Arbeit haben, sollten Sie die my-plugin-Fernbedienung so lange nicht löschen, bis Sie diese Zusammenführungen vorgenommen haben. Wenn Sie dies nicht tun, befinden sich die Commits dieser Zweige immer noch im main-project-Repository, einige sind jedoch nicht erreichbar und für eine eventuelle Garbage Collection anfällig. (Außerdem müssen Sie von SHA darauf verweisen, da durch das Löschen einer Fernbedienung die Fernverfolgungszweige entfernt werden.)

Nachdem Sie alles, was Sie von my-plugin beibehalten möchten, zusammengeführt haben, können Sie my-plugin remote mit folgendem Befehl entfernen:

$ git remote remove my-plugin

Sie können jetzt die Kopie des my-plugin-Repositorys, dessen Verlauf Sie geändert haben, sicher löschen. In meinem Fall fügte ich dem echten my-plugin-Repository auch eine Verfallserklärung hinzu, nachdem die Zusammenführung abgeschlossen und gepusht wurde.


Getestet unter Mac OS X El Capitan mit git --version 2.9.0 und zsh --version 5.2. Ihre Laufleistung kann variieren.Verweise:.

16

Ich habe seit Tagen versucht, dasselbe zu tun, ich verwende git 2.7.2. Der Teilbaum behält die Historie nicht bei. 

Sie können diese Methode verwenden, wenn Sie das alte Projekt nicht mehr verwenden.

Ich würde vorschlagen, dass Sie zuerst B verzweigen und in der Branche arbeiten.

Hier sind die Schritte ohne Verzweigung:

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

Wenn Sie jetzt eine der Dateien im Unterverzeichnis A protokollieren, wird der vollständige Verlauf angezeigt

git log --follow A/<file>

Dies war der Beitrag, der mir dabei hilft:

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

8
Rian

Ich habe hier viele Informationen zu Stack OverFlow usw. gesammelt und habe es geschafft, ein Skript zusammenzustellen, das das Problem für mich löst.

Der Nachteil ist, dass nur der 'Develop'-Zweig jedes Repository berücksichtigt und in einem separaten Verzeichnis in einem völlig neuen Repository zusammengeführt wird.

Tags und andere Zweige werden ignoriert - dies ist möglicherweise nicht das, was Sie möchten.

Das Skript behandelt sogar Verzweigungen und Tags von Features - und benennt sie im neuen Projekt um, damit Sie wissen, woher sie stammen.

#!/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

Sie können es auch von http://paste.ubuntu.com/11732805 erhalten.

Erstellen Sie zuerst eine Datei mit der URL zu jedem Repository, z. B .: 

[email protected]:eitchnet/ch.eitchnet.parent.git
[email protected]:eitchnet/ch.eitchnet.utils.git
[email protected]:eitchnet/ch.eitchnet.privilege.git

Rufen Sie dann das Skript mit einem Namen des Projekts und dem Pfad zum Skript auf:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

Das Skript selbst enthält viele Kommentare, die erklären sollten, was es tut.

6
eitch

Ich weiß, es ist lange nach der Tatsache, aber ich war mit den anderen Antworten, die ich hier fand, nicht zufrieden, also schrieb ich Folgendes:

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

Wenn Sie einfach zwei Repositorys miteinander verkleben möchten, sind Submodule und Teilbaumzusammenführungen das falsche Werkzeug, da sie nicht den gesamten Dateiverlauf beibehalten (wie andere Benutzer dies bemerken). Siehe diese Antwort hier für die einfache und korrekte Vorgehensweise.

5
Eric Lee

Wenn Sie die Dateien eines Zweiges in Repo B in einen Teilbaum von Repo A und einfügen möchten, behalten Sie auch den Verlauf bei. (In dem folgenden Beispiel gehe ich davon aus, dass der Master-Zweig von Repo B mit dem Master-Zweig von Repo A zusammengeführt werden soll.)

Führen Sie in Repo A zunächst Folgendes aus, um Repo B verfügbar zu machen:

git remote add B ../B # Add repo B as a new remote.
git fetch B

Jetzt erstellen wir einen neuen Zweig (mit nur einem Commit) in Repo A, den wir new_b_root nennen. Das resultierende Commit enthält die Dateien, die beim ersten Commit des Hauptzweiges von Repo B festgeschrieben wurden, sich jedoch in einem Unterverzeichnis mit dem Namen path/to/b-files/ befinden.

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))"

Erläuterung: Mit der Option --Orphan des Befehls checkout werden die Dateien aus dem Hauptzweig von A ausgecheckt, es wird jedoch kein Commit erstellt. Wir hätten ein Commit auswählen können, weil wir als nächstes alle Dateien löschen. Dann, ohne sich zu verpflichten (-n), wählen wir das erste Commit aus dem Master-Zweig von B aus. (Der Kirschpick bewahrt die ursprüngliche Festschreibungsnachricht, die bei einem direkten Checkout nicht scheint.) Dann erstellen wir den Unterbaum, in dem alle Dateien aus Repo B abgelegt werden sollen. Wir müssen dann alle Dateien verschieben, die in der Kirschpick zum Teilbaum. Im obigen Beispiel müssen Sie nur eine README-Datei verschieben. Dann legen wir unser B-Repo-Root-Commit fest und behalten gleichzeitig den Zeitstempel des ursprünglichen Commits bei.

Jetzt erstellen wir einen neuen B/master-Zweig über dem neu erstellten new_b_root. Wir nennen den neuen Zweig b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

Nun fügen wir unseren Zweig b in A/master zusammen:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

Schließlich können Sie die entfernten und temporären Zweige B entfernen:

git remote remove B
git branch -D new_b_root b

Der endgültige Graph hat eine Struktur wie folgt:

 enter image description here

5
Finn Haakansson

Ich hatte eine ähnliche Herausforderung, aber in meinem Fall hatten wir eine Version der Codebase in Repo A entwickelt und diese dann in ein neues Repo, Repo B, für die neue Version des Produkts geklont. Nachdem wir einige Fehler in Repo A behoben hatten, mussten wir die Änderungen in Repo B überprüfen. Am Ende haben wir Folgendes getan:

  1. Hinzufügen einer Fernbedienung zu Repo B, die auf Repo A verweist (git remote add ...)
  2. Ziehen Sie den aktuellen Zweig (wir haben Master nicht für Bugfixes verwendet) (git pull remoteForRepoA bugFixBranch)
  3. Pushing verschmilzt mit Github

Arbeitete ein Vergnügen :) 

4
David Lemphers

Zusammenführen von 2 Repos

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

Ein A innerhalb von B zusammenführen:

1) Im Projekt A

git fast-export --all --date-order > /tmp/ProjectAExport

2) Im Projekt B

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

In diesem Zweig führen Sie alle Operationen aus, die Sie ausführen müssen, und legen Sie sie fest.

C) Dann zurück zum Meister und eine klassische Verschmelzung zwischen den beiden Zweigen:

git checkout master
git merge projectA
3

Ähnlich wie @Smar, verwendet jedoch Dateisystempfade, die in PRIMARY und SECONDARY festgelegt sind:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

Dann mischen Sie manuell zusammen.

(von Beitrag von Anar Manafov )

3
Turadg

Wenn Sie drei oder mehr Projekte in einem single - Commit zusammenführen möchten, führen Sie die in den anderen Antworten beschriebenen Schritte aus (remote add -f, merge). Setzen Sie dann (weich) den Index auf den alten Kopf zurück (wo keine Zusammenführung stattgefunden hat). Fügen Sie alle Dateien (git add -A) hinzu, und legen Sie sie fest (Nachricht "Zusammenführen der Projekte A, B, C und D in einem Projekt). Dies ist jetzt die Festschreibungs-ID des Masters.

Erstellen Sie nun .git/info/grafts mit folgendem Inhalt:

<commit-id of master> <list of commit ids of all parents>

Führen Sie git filter-branch -- head^..head head^2..head head^3..head aus. Wenn Sie mehr als drei Zweige haben, fügen Sie einfach so viel head^n..head hinzu, wie Sie Zweige haben. Um Tags zu aktualisieren, hängen Sie --tag-name-filter cat an. Fügen Sie das nicht immer hinzu, da dies dazu führen kann, dass einige Commits neu geschrieben werden. Für Details siehe man-Seite von filter-branch , suche nach "Grafts".

Nun hat Ihr letztes Commit die richtigen Eltern zugeordnet.

3
koppor

Ich wollte ein kleines Projekt in ein größeres Unterverzeichnis verschieben. Da mein kleines Projekt nicht viele Commits hatte, habe ich git format-patch --output-directory /path/to/patch-dir verwendet. Bei dem größeren Projekt habe ich dann git am --directory=dir/in/project /path/to/patch-dir/* verwendet.

Das fühlt sich Weg weniger gruselig und viel sauberer als ein Filterzweig. Zugegeben, es kann nicht in allen Fällen zutreffen.

0
Mike

Ich füge Projekte leicht manuell zusammen, so dass ich vermeiden muss, dass Konflikte bei der Zusammenführung entstehen.

kopieren Sie zunächst die Dateien aus dem anderen Projekt, wie Sie möchten.

cp -R myotherproject newdirectory
git add newdirectory

als nächstes in die Geschichte ziehen

git fetch path_or_url_to_other_repo

sag git, in der Geschichte des zuletzt abgerufenen Dinges zu verschmelzen

echo 'FETCH_HEAD' > .git/MERGE_HEAD

nun verpflichten Sie sich jedoch normalerweise

git commit
0
Collin Anderson

Mit dieser Funktion wird das Remote-Repo in das lokale Repo-Verzeichnis geklont. Nach dem Zusammenführen aller Commits werden git log die ursprünglichen Commits und die richtigen Pfade angezeigt:

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"
}

Wie benutzt man:

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

Wenn Sie ein paar Änderungen vornehmen, können Sie sogar Dateien/Verzeichnisse des zusammengeführten Repos in verschiedene Pfade verschieben, zum Beispiel:

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"

Hinweise
Pfade werden durch sed ersetzt. Stellen Sie daher sicher, dass die Pfade nach dem Zusammenführen in die richtigen Pfade verschoben wurden.
Der Parameter --allow-unrelated-histories existiert nur seit git> = 2.9.

0
Andrey Izman

Der gegebene Befehl ist die bestmögliche Lösung, die ich vorschlage.

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
0
Praveen Kumar