So verschieben Sie Dateien von einem Git-Repo in ein anderes (kein Klon), wobei der Verlauf erhalten bleibt


483

Unsere Git-Repositories begannen als Teile eines einzelnen Monster-SVN-Repositorys, in dem die einzelnen Projekte jeweils einen eigenen Baum hatten:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Offensichtlich war es ziemlich einfach, Dateien mit einem von einem zum anderen zu verschieben svn mv. Aber in Git befindet sich jedes Projekt in einem eigenen Repository, und heute wurde ich gebeten, ein Unterverzeichnis von project2nach zu verschieben project1. Ich habe so etwas gemacht:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

Aber das scheint ziemlich verworren zu sein. Gibt es einen besseren Weg, um so etwas im Allgemeinen zu tun? Oder habe ich den richtigen Ansatz gewählt?

Beachten Sie, dass dies das Zusammenführen des Verlaufs in ein vorhandenes Repository umfasst, anstatt einfach ein neues eigenständiges Repository aus einem Teil eines anderen Repositorys zu erstellen ( wie in einer früheren Frage ).


1
Das klingt nach einem vernünftigen Ansatz für mich. Ich kann mir keinen offensichtlichen Weg vorstellen, um Ihre Methode signifikant zu verbessern. Es ist schön, dass Git dies tatsächlich einfach macht (ich möchte beispielsweise nicht versuchen, ein Verzeichnis von Dateien zwischen verschiedenen Repositorys in Subversion zu verschieben ).
Greg Hewgill

1
@ebneter - Ich habe dies manuell mithilfe von Shell-Skripten durchgeführt (Verlauf von einem SVN-Repo in ein anderes verschoben). Grundsätzlich habe ich den Verlauf (Diffs, Commit-Protokollnachrichten) von bestimmten Dateien / Verzeichnissen in ein zweites Repository abgespielt.
Adam Monsen

1
Ich frage mich , warum Sie nicht tun , git fetch p2 && git merge p2statt git fetch p2 && git branch .. && git merge p2? Bearbeiten: Okay, es sieht so aus, als ob Sie die Änderungen in einem neuen Zweig namens p2 erhalten möchten, nicht in dem aktuellen Zweig.
Lekensteyn

1
Gibt es keine Möglichkeit zu verhindern, dass --filter-branch die Verzeichnisstruktur zerstört? Dieser "git mv" -Schritt führt zu einem massiven Commit voller Dateilöschungen und Dateierstellungen.
Edward Falk

1
Beachten Sie, dass ab Git 2.9 das Zusammenführen nicht verwandter Historien standardmäßig nicht zulässig ist. Fügen Sie --allow-unrelated-historieszum Letzten hinzu git merge, damit es funktioniert.
Scott Berrevoets

Antworten:


55

Ja, auf das --subdirectory-filtervon zu schlagen filter-branchwar der Schlüssel. Die Tatsache, dass Sie es verwendet haben, beweist im Wesentlichen, dass es keinen einfacheren Weg gibt - Sie hatten keine andere Wahl, als den Verlauf neu zu schreiben, da Sie nur eine (umbenannte) Teilmenge der Dateien haben wollten, und dies ändert per Definition die Hashes. Da keiner der Standardbefehle (z. B. pull) den Verlauf neu schreibt, können Sie sie auf keinen Fall verwenden, um dies zu erreichen.

Sie könnten natürlich die Details verfeinern - ein Teil Ihres Klonens und Verzweigens war nicht unbedingt erforderlich -, aber der Gesamtansatz ist gut! Es ist eine Schande, dass es kompliziert ist, aber es geht natürlich nicht darum, es einfach zu machen, die Geschichte neu zu schreiben.


1
Was ist, wenn Ihre Datei mehrere Verzeichnisse durchlaufen hat und sich jetzt in einem befindet? Funktioniert der Unterverzeichnisfilter weiterhin? (dh ich gehe davon aus, dass ich, wenn ich nur eine Datei verschieben möchte, sie in ein eigenes Unterverzeichnis verschieben kann und dies funktioniert?)
Rogerdpack

1
@rogerdpack: Nein, dies folgt der Datei nicht durch Umbenennen. Ich glaube, es scheint an dem Punkt erstellt worden zu sein, an dem es in das ausgewählte Unterverzeichnis verschoben wurde. Wenn Sie nur eine Datei auswählen möchten, schauen Sie --index-filterin der filter-branchManpage nach.
Cascabel

8
Gibt es ein Rezept, wie ich Umbenennungen folgen kann?
Night Warrier

Ich denke, die Geschichte zu pflegen und zu kuratieren ist einer der Hauptpunkte von git.
artburkart

287

Wenn Ihr Verlauf korrekt ist, können Sie die Commits als Patch entfernen und im neuen Repository anwenden:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Oder in einer Zeile

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(Entnommen aus Exherbos Dokumenten )


21
Für die drei oder vier Dateien, die ich verschieben musste, war dies eine viel einfachere Lösung als die akzeptierte Antwort. Am Ende habe ich die Pfade in der Patch-Datei mit find-replace abgeschnitten, damit sie in die Verzeichnisstruktur meines neuen Repos passen.
Rian Sanderson

8
Ich habe Optionen hinzugefügt, damit auch Binärdateien (wie Bilder) ordnungsgemäß migriert werden : git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Funktioniert ohne Probleme AFAICT.
Emmanuel Touzery

35
Beim Anwenden habe ich die --committer-date-is-author-dateOption verwendet, um das ursprüngliche Festschreibungsdatum anstelle des Datums, an dem die Dateien verschoben wurden, beizubehalten.
Darrenmc

6
Merge-Commits im Verlauf unterbrechen den Befehl "am". Sie können dem obigen Befehl git log "-m --first-parent" hinzufügen, dann hat es bei mir funktioniert.
Gábor Lipták

6
@ Daniel Golden Ich habe es geschafft, das Problem mit Dateien zu beheben, die verschoben wurden (was eine Folge eines Fehlers ist git log, so dass es nicht mit beiden --followund --reversekorrekt funktioniert ). Ich habe diese Antwort verwendet , und hier ist ein vollständiges Skript, das ich jetzt zum Verschieben von Dateien verwende
tsayen

75

Nachdem Sie verschiedene Ansätze ausprobiert haben, um eine Datei oder einen Ordner von einem Git-Repository in ein anderes zu verschieben, wird im Folgenden der einzige beschrieben, der zuverlässig zu funktionieren scheint.

Dazu gehört das Klonen des Repositorys, aus dem Sie die Datei oder den Ordner verschieben möchten, das Verschieben dieser Datei oder des Ordners in das Stammverzeichnis, das Umschreiben des Git-Verlaufs, das Klonen des Ziel-Repositorys und das direkte Abrufen der Datei oder des Ordners mit Verlauf in dieses Ziel-Repository.

Bühne eins

  1. Erstellen Sie eine Kopie von Repository A, da die folgenden Schritte wesentliche Änderungen an dieser Kopie vornehmen, die Sie nicht verschieben sollten!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. CD hinein

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Löschen Sie den Link zum ursprünglichen Repository, um zu vermeiden, dass versehentlich Remote-Änderungen vorgenommen werden (z. B. durch Drücken von).

    git remote rm origin
    
  4. Durchsuchen Sie Ihren Verlauf und Ihre Dateien und entfernen Sie alles, was sich nicht in Verzeichnis 1 befindet. Das Ergebnis ist der Inhalt von Verzeichnis 1, der in die Basis von Repository A gespuckt wird.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Nur für das Verschieben einzelner Dateien: Gehen Sie die verbleibenden Dateien durch und entfernen Sie alles außer der gewünschten Datei. (Möglicherweise müssen Sie nicht gewünschte Dateien mit demselben Namen löschen und festschreiben.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

Stufe zwei

  1. Bereinigungsschritt

    git reset --hard
    
  2. Bereinigungsschritt

    git gc --aggressive
    
  3. Bereinigungsschritt

    git prune
    

Möglicherweise möchten Sie diese Dateien in das Repository B in einem Verzeichnis importieren, das sich nicht im Stammverzeichnis befindet:

  1. Machen Sie dieses Verzeichnis

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Verschieben Sie Dateien in dieses Verzeichnis

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Fügen Sie diesem Verzeichnis Dateien hinzu

    git add .
    
  4. Übernehmen Sie Ihre Änderungen und wir sind bereit, diese Dateien in das neue Repository zusammenzuführen

    git commit
    

Stufe drei

  1. Erstellen Sie eine Kopie von Repository B, falls Sie noch keine haben

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (Angenommen, FOLDER_TO_KEEP ist der Name des neuen Repositorys, in das Sie kopieren.)

  2. CD hinein

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Erstellen Sie eine Remoteverbindung zu Repository A als Zweig in Repository B.

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. Ziehen Sie aus diesem Zweig (der nur das Verzeichnis enthält, das Sie verschieben möchten) in das Repository B.

    git pull repo-A-branch master --allow-unrelated-histories
    

    Der Pull kopiert sowohl Dateien als auch den Verlauf. Hinweis: Sie können eine Zusammenführung anstelle eines Pulls verwenden, aber Pull funktioniert besser.

  5. Schließlich möchten Sie wahrscheinlich ein wenig aufräumen, indem Sie die Remoteverbindung zum Repository A entfernen

    git remote rm repo-A-branch
    
  6. Drücken Sie und Sie sind fertig.

    git push
    

Ich habe die meisten hier beschriebenen Schritte durchlaufen, es scheint jedoch nur den Commit-Verlauf der Datei oder des Verzeichnisses vom Master (und nicht von anderen Zweigen) zu kopieren. Ist das richtig?
Bao-Long Nguyen-Trong

Ich denke, das ist richtig und Sie müssten ähnliche Schritte für alle Zweige ausführen, aus denen Sie Dateien oder Ordner verschieben möchten, z. Wechseln Sie in die Filiale, z. MyBranch in Repository A, Filter-Zweig usw. Sie würden dann "git Pull Repo-A-Zweig MyBranch" in Repository B.
mcarans

Danke für die Antwort. Wissen Sie, ob Tags in den Zweigen ebenfalls migriert werden?
Bao-Long Nguyen-Trong

Ich fürchte, ich weiß es nicht, würde es aber vermuten.
Mcarans

1
@mcarans Leider ist dies kein zuverlässiger Weg, obwohl es zu sein scheint. Es hat das gleiche Problem wie alle anderen Lösungen - es behält den Verlauf der Umbenennung nicht bei. In meinem Fall ist das allererste Commit, wenn ich das Verzeichnis / die Datei umbenannt habe. Alles darüber hinaus ist verloren.
xZero

20

Ich fand das sehr nützlich. Es ist ein sehr einfacher Ansatz, bei dem Sie Patches erstellen, die auf das neue Repo angewendet werden. Weitere Informationen finden Sie auf der verlinkten Seite.

Es enthält nur drei Schritte (aus dem Blog kopiert):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

Das einzige Problem, das ich hatte, war, dass ich nicht alle Patches gleichzeitig anwenden konnte

git am --3way <patch-directory>/*.patch

Unter Windows wurde ein InvalidArgument-Fehler angezeigt. Also musste ich alle Patches nacheinander anwenden.


Hat bei mir nicht funktioniert, da irgendwann Sha-Hashes fehlten. Dies hat mir geholfen: stackoverflow.com/questions/17371150/…
dr0i

Im Gegensatz zum "Git Log" -Ansatz hat diese Option für mich perfekt funktioniert! Vielen Dank!
AlejandroVD

1
Versuchte verschiedene Ansätze für die Verlagerung von Projekten in ein neues Repo. Dies ist der einzige, der für mich gearbeitet hat. Ich kann nicht glauben, dass eine so gemeinsame Aufgabe so kompliziert sein muss.
Chris_D_Turk

Vielen Dank, dass Sie Ross Hendricksons Blog geteilt haben . Dieser Ansatz hat bei mir funktioniert.
Kaushik Acharya

1
Dies ist eine sehr elegante Lösung, die jedoch erneut unter dem gleichen Problem wie alle anderen Lösungen leidet. Die Umbenennung in der Vergangenheit wird NICHT beibehalten.
xZero

6

BEHALTEN DES VERZEICHNISNAMENS

Der Unterverzeichnisfilter (oder der kürzere Befehl git-Teilbaum) funktioniert gut, hat aber bei mir nicht funktioniert, da sie den Verzeichnisnamen aus den Festschreibungsinformationen entfernen. In meinem Szenario möchte ich nur Teile eines Repositorys in ein anderes zusammenführen und den Verlauf MIT dem vollständigen Pfadnamen beibehalten.

Meine Lösung bestand darin, den Baumfilter zu verwenden und einfach die unerwünschten Dateien und Verzeichnisse aus einem temporären Klon des Quell-Repositorys zu entfernen und dann in 5 einfachen Schritten von diesem Klon in mein Ziel-Repository zu ziehen.

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

Dieses Skript nimmt keine Änderungen an Ihrem ursprünglichen Repo vor. Wenn das in der Map-Datei angegebene Ziel-Repo nicht vorhanden ist, versucht dieses Skript, es zu erstellen.
Chetabahana

1
Ich denke auch, dass es enorm wichtig ist, die Verzeichnisnamen intakt zu halten. Andernfalls erhalten Sie zusätzliche Umbenennungs-Commits für das Ziel-Repository.
Ipuustin


5

Diese Antwort enthält interessante Befehle, die auf git amBeispielen basieren und anhand von Beispielen Schritt für Schritt dargestellt werden.

Zielsetzung

  • Sie möchten einige oder alle Dateien von einem Repository in ein anderes verschieben.
  • Sie wollen ihre Geschichte behalten.
  • Es ist Ihnen jedoch egal, ob Sie Tags und Zweige behalten.
  • Sie akzeptieren einen begrenzten Verlauf für umbenannte Dateien (und Dateien in umbenannten Verzeichnissen).

Verfahren

  1. Extrahieren Sie den Verlauf im E-Mail-Format mit
    git log --pretty=email -p --reverse --full-index --binary
  2. Dateibaum neu organisieren und Dateinamenänderung im Verlauf aktualisieren [optional]
  3. Wenden Sie einen neuen Verlauf mit an git am

1. Extrahieren Sie den Verlauf im E-Mail-Format

Beispiel: Extrahieren Sie den Verlauf von file3, file4undfile5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Reinigen Sie das temporäre Verzeichnis Ziel

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

Reinigen Sie die Repo- Quelle

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

Extrahieren Sie den Verlauf jeder Datei im E-Mail-Format

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Leider Option --followoder --find-copies-harderkann nicht mit kombiniert werden --reverse. Aus diesem Grund wird der Verlauf beim Umbenennen der Datei (oder beim Umbenennen eines übergeordneten Verzeichnisses) abgeschnitten.

Nachher: ​​Temporärer Verlauf im E-Mail-Format

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2. Reorganisieren Sie den Dateibaum und aktualisieren Sie die Änderung des Dateinamens im Verlauf [optional]

Angenommen, Sie möchten diese drei Dateien in diesem anderen Repo verschieben (kann dasselbe Repo sein).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

Organisieren Sie daher Ihre Dateien neu:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

Ihre vorübergehende Geschichte ist jetzt:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Ändern Sie auch Dateinamen innerhalb des Verlaufs:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

Hinweis: Dadurch wird der Verlauf neu geschrieben, um die Änderung von Pfad und Dateiname widerzuspiegeln.
      (dh die Änderung des neuen Ortes / Namens innerhalb des neuen Repos)


3. Wenden Sie einen neuen Verlauf an

Dein anderes Repo ist:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Übernehmen Sie Commits aus temporären Verlaufsdateien:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

Dein anderes Repo ist jetzt:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

Verwenden Sie git statusdiese Option, um die Anzahl der Commits anzuzeigen, die zum Pushing bereit sind :-)

Hinweis: Da der Verlauf neu geschrieben wurde, um die Änderung von Pfad und Dateiname widerzuspiegeln:
      (dh verglichen mit dem Ort / Namen im vorherigen Repo)

  • Sie müssen git mvden Speicherort / Dateinamen nicht ändern.
  • Sie müssen nicht auf git log --followden vollständigen Verlauf zugreifen.

Zusätzlicher Trick: Erkennen Sie umbenannte / verschobene Dateien in Ihrem Repo

So listen Sie die umbenannten Dateien auf:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Weitere Anpassungen: Sie können den Befehl git logmit den Optionen --find-copies-harderoder ausführen --reverse. Sie können die ersten beiden Spalten auch mit cut -f3-dem vollständigen Muster '{. * =>. *}' Entfernen und erfassen.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

Dieses Skript hatte einen ähnlichen Juckreiz (obwohl nur für einige Dateien eines bestimmten Repositorys) und erwies sich als sehr hilfreich: Git-Import

Die Kurzversion besteht darin, dass Patch-Dateien der angegebenen Datei oder des angegebenen Verzeichnisses ( $object) aus dem vorhandenen Repository erstellt werden:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

die dann auf ein neues Repository angewendet werden:

cd new_repo
git am "$temp"/*.patch 

Für Details schauen Sie bitte nach:


2

Versuche dies

cd repo1

Dadurch werden alle Verzeichnisse außer den genannten entfernt, und der Verlauf wird nur für diese Verzeichnisse beibehalten

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

Jetzt können Sie Ihr neues Repo in Ihre Git-Fernbedienung einfügen und darauf verschieben

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

-fzum Überschreiben hinzufügen


WARNUNG: Git-Filter-Branch hat eine Flut von Fallstricken, die verstümmelte Verlaufsumschreibungen erzeugen. Drücken Sie Strg-C, bevor Sie mit dem Abbruch fortfahren, und verwenden Sie stattdessen ein alternatives Filterwerkzeug wie 'git filter-repo' ( github.com/newren/git-filter-repo ). Weitere Informationen finden Sie auf der Handbuchseite zum Filterzweig. Um diese Warnung zu unterdrücken, setzen Sie FILTER_BRANCH_SQUELCH_WARNING = 1.
Colin

1

Unter Inspiration von http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ habe ich diese Powershell-Funktion erstellt, um dasselbe zu tun hat bisher großartig für mich funktioniert:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

Verwendung für dieses Beispiel:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

Nachdem Sie dies getan haben, können Sie die Dateien in der migrate-from-project2Verzweigung neu organisieren, bevor Sie sie zusammenführen.


1

Ich wollte etwas Robustes und Wiederverwendbares (One-Command-and-Go + Undo-Funktion), also schrieb ich das folgende Bash-Skript. Ich habe mehrmals für mich gearbeitet, also dachte ich, ich würde es hier teilen.

Es ist möglich, einen beliebigen Ordner /path/to/foovon repo1in /some/other/folder/barnach zu verschieben repo2(Ordnerpfade können gleich oder unterschiedlich sein, der Abstand zum Stammordner kann unterschiedlich sein).

Da nur die Commits durchlaufen werden, die die Dateien im Eingabeordner berühren (nicht alle Commits des Quell-Repos), sollte es auch bei großen Quell-Repos recht schnell sein, wenn Sie nur einen tief verschachtelten Unterordner extrahieren, der nicht in jedem Ordner berührt wurde verpflichten.

Da dies dazu führt, dass ein verwaister Zweig mit dem gesamten Verlauf des alten Repos erstellt und dann mit dem HEAD zusammengeführt wird, funktioniert dies sogar bei Dateinamenkonflikten (dann müssten Sie am Ende natürlich eine Zusammenführung auflösen). .

Wenn keine Dateinamenkonflikte auftreten, müssen Sie nur git commitam Ende die Zusammenführung abschließen.

Der Nachteil ist, dass es wahrscheinlich nicht nach dem Umbenennen von Dateien (außerhalb von REWRITE_FROM Ordners) im Quell-Repo - Pull-Anfragen, die auf GitHub willkommen sind, um dies zu berücksichtigen.

GitHub-Link: Git-Move-Ordner-zwischen-Repos-Keep-Verlauf

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

In meinem Fall musste ich das Repo, aus dem ich migriert habe, nicht beibehalten oder einen früheren Verlauf beibehalten. Ich hatte einen Patch des gleichen Zweigs von einer anderen Fernbedienung

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

In diesen beiden Schritten konnte ich den Zweig des anderen Repos im selben Repo anzeigen lassen.

Schließlich habe ich diesen Zweig (den ich aus dem anderen Repo importiert habe) so eingestellt, dass er der Hauptlinie des Ziel-Repos folgt (damit ich sie genau unterscheiden kann).

git br --set-upstream-to=origin/mainline

Jetzt verhielt es sich so, als wäre es nur ein weiterer Zweig, den ich gegen dasselbe Repo gedrückt hatte.


0

Wenn die Pfade für die betreffenden Dateien in den beiden Repos identisch sind und Sie nur eine Datei oder einen kleinen Satz verwandter Dateien übertragen möchten, können Sie dies auf einfache Weise verwenden git cherry-pick .

Der erste Schritt besteht darin, die Commits aus dem anderen Repo mit in Ihr eigenes lokales Repo zu bringen git fetch <remote-url>. Dadurch wird FETCH_HEADauf das Head Commit des anderen Repos verwiesen. Wenn Sie einen Verweis auf dieses Commit beibehalten möchten, nachdem Sie andere Abrufe durchgeführt haben, möchten Sie ihn möglicherweise mit einem Tag versehen git tag other-head FETCH_HEAD.

Sie müssen dann ein erstes Commit für diese Datei erstellen (falls nicht vorhanden) oder ein Commit, um die Datei in einen Zustand zu versetzen, der mit dem ersten Commit des anderen Repos, das Sie einbringen möchten, gepatcht werden kann Sie können dies mit einem tun, git cherry-pick <commit-0>wenn commit-0die gewünschten Dateien eingeführt wurden, oder Sie müssen das Commit möglicherweise "von Hand" erstellen. Fügen Sie -ndie Cherry-Pick-Optionen hinzu, wenn Sie das anfängliche Commit ändern müssen, um beispielsweise Dateien aus diesem Commit zu löschen, die Sie nicht einbringen möchten.

Danach können Sie mit git cherry-picknachfolgenden Commits fortfahren und diese -nbei Bedarf erneut verwenden . Im einfachsten Fall (alle Commits sind genau das, was Sie wollen und sauber anwenden) können Sie die vollständige Liste der Commits in der Cherry-Pick-Befehlszeile angeben : git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

Dies wird durch die Verwendung von Git-Filter-Repo einfacher.

Um zu bewegen project2/sub/dirzu project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

So installieren Sie das Tool einfach: pip3 install git-filter-repo ( Weitere Details und Optionen in README )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

Die folgende Methode zum Migrieren meines GIT-Stashs zu GitLab durch Verwalten aller Zweige und Beibehalten des Verlaufs.

Klonen Sie das alte Repository auf lokal.

git clone --bare <STASH-URL>

Erstellen Sie ein leeres Repository in GitLab.

git push --mirror <GitLab-URL>

Das obige habe ich durchgeführt, als wir unseren Code von Stash auf GitLab migriert haben und es hat sehr gut funktioniert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.