822 Stimmen

Ist es möglich, Dateien in Git zu verschieben/umzubenennen und ihren Verlauf beizubehalten?

Ich möchte einen Projektunterbaum in Git umbenennen/verschieben, indem ich ihn von

/project/xyz

zu

/components/xyz

Wenn ich eine einfache git mv project components dann wird die gesamte Commit-Historie für die xyz project verloren geht. Gibt es eine Möglichkeit, dies so zu verschieben, dass die Historie erhalten bleibt?

14 Stimmen

5 Stimmen

Ich möchte nur anmerken, dass ich gerade das Verschieben von Dateien über das Dateisystem getestet habe, und nach dem Übertragen (über intellij) kann ich dann den gesamten Verlauf sehen (einschließlich des Verlaufs, wenn er sich an einem anderen Ort befand), wenn ich den Verlauf (wieder in intellij) ansehe. Ich gehe davon aus, dass intellij nichts Besonderes tut, um das zu tun, also ist es schön zu wissen, dass zumindest die Historie nachvollzogen werden kann.

0 Stimmen

Für die Regeln, denen Git bei der Erkennung einer Verzeichnisumbenennung folgt, siehe meine Antwort unten

24voto

VonC Punkte 1117238

Ich möchte einen Projektunterbaum in Git umbenennen/verschieben, indem ich ihn von

/project/xyz

zu

/Komponenten/xyz

Wenn ich eine einfache git mv project components dann wird die gesamte Commit-Historie für die xyz Das Projekt geht verloren.

Nein (8 Jahre später, Git 2.19, Q3 2018), denn Git wird die Umbenennung des Verzeichnisses erkennen und dies ist jetzt besser dokumentiert.

Voir b00bf1c übergeben , Commit 1634688 , 0661e49 festlegen , Übergabe 4d34dff , 983f464 übertragen , c840e1a übergeben , Commit 9929430 (27. Juni 2018), und Übergabe d4e8062 , Übergabe 5dacd4a (25. Juni 2018) von Elijah Newren ( newren ) .
(Zusammengefasst von Junio C. Hamano -- gitster -- in 0ce5a69 übergeben , 24. Juli 2018)

Das wird jetzt erklärt in Documentation/technical/directory-rename-detection.txt :

Exemple :

Wenn alle x/a , x/b y x/c sind umgezogen nach z/a , z/b y z/c ist es wahrscheinlich, dass x/d die in der Zwischenzeit hinzugekommen sind, wollen auch nach z/d von den Hinweis aufnimmt, dass das gesamte Verzeichnis ' x ' verschoben nach ' z '.

Aber es gibt noch viele andere Fälle, zum Beispiel:

eine Seite der Geschichte wird umbenannt x -> z und die andere benennt eine Datei um in x/e was dazu führt, dass die Zusammenführung eine transitive Umbenennung vornehmen muss.

Um die Erkennung von Verzeichnisumbenennungen zu vereinfachen, werden diese Regeln von Git erzwungen:

ein paar Grundregeln begrenzen, wann die Erkennung von Verzeichnisumbenennungen gilt:

  1. Wenn ein bestimmtes Verzeichnis auf beiden Seiten einer Zusammenführung noch existiert, betrachten wir es nicht als umbenannt.
  2. Wenn bei einer Teilmenge der umzubenennenden Dateien eine Datei oder ein Verzeichnis im Weg ist (oder sich gegenseitig behindern würde), schalten Sie die Verzeichnisumbenennung für diese spezifischen Unterpfade aus und melden Sie dem Benutzer den Konflikt.
  3. Wenn die andere Seite der Historie ein Verzeichnis in einen Pfad umbenannt hat, den Ihre Seite der Historie umbenannt hat, dann ignorieren Sie diese spezielle Umbenennung der anderen Seite der Historie für alle impliziten Verzeichnisumbenennungen (aber warnen Sie den Benutzer).

Sie können sehen viel von Tests in t/t6043-merge-rename-directories.sh die auch darauf hinweisen, dass:

  • a) Wenn Umbenennungen ein Verzeichnis in zwei oder mehrere andere aufteilen, "gewinnt" das Verzeichnis mit den meisten Umbenennungen.
  • b) Vermeiden Sie die Erkennung von Verzeichnisumbenennungen für einen Pfad, wenn dieser Pfad die Quelle einer Umbenennung auf beiden Seiten einer Zusammenführung ist.
  • c) Wenden Sie implizite Verzeichnisumbenennungen nur auf Verzeichnisse an, wenn die andere Seite der Geschichte die Umbenennung vornimmt.

18voto

oHo Punkte 45335

Ja

  1. Sie konvertieren die Commit-Historie von Dateien in E-Mail-Patches mit git log --pretty=email
  2. Sie ordnen diese Dateien in neuen Verzeichnissen an und benennen sie um
  3. Sie konvertieren diese Dateien (E-Mails) in Git-Commits zurück, um die Historie zu erhalten, indem Sie git am .

Begrenzung

  • Tags und Äste werden nicht aufbewahrt
  • Der Verlauf wird beim Umbenennen von Pfaddateien (Verzeichnisumbenennung) abgeschnitten

Schritt-für-Schritt-Erklärung mit Beispielen

1. Verlauf im E-Mail-Format extrahieren

Beispiel: Auszug Geschichte von file3 , file4 y file5

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

Einstellen/Reinigen des Ziels

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

Extrahieren des Verlaufs 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 --follow o --find-copies-harder kann nicht kombiniert werden mit --reverse . Aus diesem Grund wird der Verlauf beim Umbenennen einer Datei (oder eines übergeordneten Verzeichnisses) unterbrochen.

Vorläufiger Verlauf im E-Mail-Format:

/tmp/mail/dir
     subdir
        file3
        file4
     file5

Dan Bonachea schlägt vor, die Schleifen des Befehls zur Erstellung des Git-Protokolls in diesem ersten Schritt umzukehren: Anstatt git log einmal pro Datei auszuführen, führen Sie es genau einmal mit einer Liste von Dateien auf der Befehlszeile aus und erstellen Sie ein einziges, einheitliches Protokoll. Auf diese Weise bleiben Commits, die mehrere Dateien ändern, im Ergebnis ein einziger Commit, und alle neuen Commits behalten ihre ursprüngliche relative Reihenfolge bei. Beachten Sie, dass dies auch Änderungen im zweiten Schritt unten erfordert, wenn die Dateinamen im (nun vereinheitlichten) Protokoll umgeschrieben werden.


2. Dateibaum reorganisieren und Dateinamen aktualisieren

Angenommen, Sie möchten diese drei Dateien in ein anderes Projektarchiv verschieben (es kann dasselbe Projektarchiv sein).

my_other_repo
 dirF
    file55
    file56
 dirB              # New tree
    dirB1         # from subdir
       file33    # from file3
       file44    # from file4
    dirB2         # new dir
         file5    # from file5
 dirH
     file77

Reorganisieren Sie deshalb Ihre Dateien:

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

Ihre vorläufige Geschichte ist jetzt:

/tmp/mail/dir
     dirB
         dirB1
            file33
            file44
         dirB2
              file5

Ändern Sie auch Dateinamen in der Historie:

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

3. Neue Geschichte anwenden

Ihr anderes Repo ist:

my_other_repo
 dirF
    file55
    file56
 dirH
     file77

Übernehmen Sie Commits aus temporären History-Dateien:

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

--committer-date-is-author-date behält die ursprünglichen Commit-Zeitstempel bei ( Dan Bonachea Kommentar).

Ihr anderes Repo ist jetzt:

my_other_repo
 dirF
    file55
    file56
 dirB
    dirB1
       file33
       file44
    dirB2
         file5
 dirH
     file77

Verwenden Sie git status um die Anzahl der Commits zu sehen, die bereit sind, gepusht zu werden :-)


Extra-Trick: Überprüfen Sie umbenannte/verschobene Dateien innerhalb Ihres Repo

Zur Auflistung der umbenannten Dateien:

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

Weitere Anpassungen: Sie können den Befehl git log Nutzung von Optionen --find-copies-harder o --reverse . Sie können auch die ersten beiden Spalten entfernen, indem Sie cut -f3- und das Greifen vollständiger Muster '{.* => .*}' .

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

4 Stimmen

VORSICHT: Diese Technik teilt Commits, die 2 oder mehr Dateien ändern, in separate fragmentierte Commits auf und bringt außerdem deren Reihenfolge durch Sortierung nach Dateinamen durcheinander (so dass die Fragmente eines ursprünglichen Commits in der linearen Historie nicht nebeneinander erscheinen). Der resultierende Verlauf ist daher nur für jede einzelne Datei "korrekt". Wenn Sie mehr als eine Datei verschieben, dann stellt KEINER der neuen Commits in der resultierenden Historie einen konsistenten Schnappschuss der verschobenen Dateien dar, der jemals in der Historie des ursprünglichen Repos existiert hat.

3 Stimmen

Hallo @DanBonachea. Vielen Dank für Ihr interessantes Feedback. Ich habe einige Repos mit mehreren Dateien erfolgreich mit dieser Technik migriert (sogar mit umbenannten Dateien und Dateien, die über Verzeichnisse hinweg verschoben wurden). Was schlagen Sie vor, an dieser Antwort zu ändern? Meinen Sie, wir sollten einen WARNING-Banner am Anfang dieser Antwort hinzufügen, der die Grenzen dieser Technik erklärt? Vielen Dank

2 Stimmen

Ich habe diese Technik angepasst, um das Problem zu vermeiden, indem ich die Schleifen des Befehls zur Erstellung des Git-Protokolls in Schritt 1 umkehrte. D.h. anstatt git log einmal pro Datei auszuführen, führen Sie es genau einmal mit einer Liste von Dateien auf der Befehlszeile aus und erzeugen ein einziges, einheitliches Protokoll. Auf diese Weise bleiben Commits, die 2 oder mehr Dateien ändern, im Ergebnis ein einziger Commit, und alle neuen Commits behalten ihre ursprüngliche relative Reihenfolge bei. Beachten Sie, dass dies auch Änderungen in Schritt 2 erfordert, wenn die Dateinamen im (nun vereinheitlichten) Protokoll umgeschrieben werden. Ich habe auch git am --committer-date-is-author-date verwendet, um die ursprünglichen Commit-Zeitstempel zu erhalten.

15voto

Harish Kumar Punkte 141

Ich habe das Problem erkannt "Umbenennen des Ordners ohne Verlust der Historie" . Um das Problem zu beheben, führen Sie aus:

$ git mv oldfolder temp && git mv temp newfolder
$ git commit
$ git push

3 Stimmen

Dies sollte als richtige Antwort markiert werden. Bei mir hat das Verschieben einer Datei von einem Ordner in einen anderen innerhalb desselben Projektarchivs völlig funktioniert. Ich musste nicht einmal die "temp"-Sache machen. git mv olddir/file newdir/file hat bei mir funktioniert.

0 Stimmen

Und die ganze Geschichte ist gerettet.

26 Stimmen

Warum ist das besser als git mv oldfolder newfolder ?

12voto

Parag Bangad Punkte 141

Ich habe diesen mehrstufigen Prozess befolgt, um den Code in das übergeordnete Verzeichnis zu verschieben und den Verlauf beizubehalten.

Schritt 0: Erstellung eines Zweigs 'history' von 'master' zur sicheren Aufbewahrung

Schritt 1: Gebraucht git-filter-repo Werkzeug, um die Geschichte umzuschreiben. Mit dem folgenden Befehl wird der Ordner "FolderwithContentOfInterest" eine Ebene nach oben verschoben und der entsprechende Übertragungsverlauf geändert

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force

Schritt 2: Zu diesem Zeitpunkt hat das GitHub-Repository seinen Remote-Repository-Pfad verloren. Remote-Referenz hinzugefügt

git remote add origin git@github.com:MyCompany/MyRepo.git

Schritt 3: Informationen über das Repository abrufen

git pull

Schritt 4: Verbinden Sie den lokalen verlorenen Zweig mit dem Ursprungszweig

git branch --set-upstream-to=origin/history history

Schritt 5: Adressieren Sie den Zusammenführungskonflikt für die Ordnerstruktur, wenn Sie dazu aufgefordert werden.

Schritt 6: Schieben Sie !!

git push

Hinweis: Der geänderte Verlauf und der verschobene Ordner scheinen bereits festgeschrieben zu sein. enter code here

Erledigt. Der Code wird in das übergeordnete/gewünschte Verzeichnis verschoben, wobei der Verlauf intakt bleibt!

0 Stimmen

Dies sollte viel höher in der Liste der Antworten, ab 2020 Filter-Repo ist der Weg zu gehen für diese Art von Operationen.

10voto

Pugsley Punkte 837

Umbenennen eines Verzeichnisses oder einer Datei (ich kenne mich mit komplexen Fällen nicht aus, daher kann es einige Einschränkungen geben):

git filter-repo --path-rename OLD_NAME:NEW_NAME

Umbenennen eines Verzeichnisses in Dateien, die es erwähnen (es ist möglich, Rückrufe zu verwenden, aber ich weiß nicht, wie):

git filter-repo --replace-text expressions.txt

expressions.txt ist eine Datei, die mit Zeilen wie literal:OLD_NAME==>NEW_NAME (es ist möglich, die RE von Python mit regex: oder Glob mit glob: ).

Um ein Verzeichnis in Nachrichten von Commits umzubenennen:

git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'

Die regulären Ausdrücke von Python werden ebenfalls unterstützt, sie müssen jedoch manuell in Python geschrieben werden.

Wenn es sich um ein Original-Repository ohne Remote handelt, müssen Sie Folgendes hinzufügen --force um eine Neufassung zu erzwingen. (Möglicherweise sollten Sie vorher ein Backup Ihres Repositorys erstellen).

Wenn Sie die Verweise nicht beibehalten wollen (sie werden in der Branch History der Git GUI angezeigt), müssen Sie Folgendes hinzufügen --replace-refs delete-no-add .

0 Stimmen

git: 'filter-repo' is not a git command. See 'git --help'

0 Stimmen

@alper Dieser Befehl funktioniert! Aber filter-repo ist kein Standardbefehl in Git. Sie müssen ihn erst installieren, bevor Sie ihn verwenden können. Eine Anleitung zum Download und zur Installation finden Sie hier github.com/newren/git-filter-repo

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X