webentwicklung-frage-antwort-db.com.de

Wie können Sie eine fehlerhafte Zusammenführung reparieren und Ihre guten Commits auf einer festen Zusammenführung wiedergeben?

Ich habe versehentlich eine unerwünschte Datei (filename.orig beim Auflösen einer Zusammenführung) in mein Repository vor einigen Commits, ohne dass ich es bis jetzt bemerkt habe. Ich möchte die Datei vollständig aus dem Repository-Verlauf löschen.

Ist es möglich, die Änderungshistorie so umzuschreiben, dass filename.orig wurde überhaupt nicht zum Repository hinzugefügt?

396
Grant Limberg

Verwenden Sie dieses Rezept nicht, wenn Ihre Situation nicht der in der Frage beschriebenen entspricht. Mit diesem Rezept können Sie eine fehlerhafte Zusammenführung beheben und Ihre guten Commits auf eine feste Zusammenführung übertragen.

Obwohl filter-branch Tun wird, was Sie wollen, ist es ein ziemlich komplexer Befehl, und ich würde mich wahrscheinlich dafür entscheiden, dies mit git rebase Zu tun. Es ist wahrscheinlich eine persönliche Präferenz. filter-branch Kann dies in einem einzigen, etwas komplexeren Befehl tun, während die rebase -Lösung die entsprechenden logischen Operationen schrittweise ausführt.

Probieren Sie das folgende Rezept:

# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>

# remove the incorrectly added file
git rm somefile.orig

# commit the amended merge
git commit --amend

# go back to the master branch
git checkout master

# replant the master branch onto the corrected merge
git rebase tmpfix

# delete the temporary branch
git branch -d tmpfix

(Beachten Sie, dass Sie keine temporäre Verzweigung benötigen. Sie können dies mit einem abgetrennten HEAD tun. Sie müssen jedoch die vom Schritt git commit --amend Generierte Festschreibungs-ID notieren, um die git rebase, Anstatt den Namen des temporären Zweigs zu verwenden.)

293
CB Bailey

Einführung: Sie haben 5 Lösungen zur Verfügung

Das Originalplakat besagt:

Ich habe versehentlich eine unerwünschte Datei ... vor einigen Commits in mein Repository geschrieben ... Ich möchte die Datei vollständig aus dem Repository-Verlauf löschen.

Ist es möglich, den Änderungsverlauf so umzuschreiben, dass filename.orig Überhaupt nicht zum Repository hinzugefügt wurde?

Es gibt viele verschiedene Möglichkeiten, den Verlauf einer Datei vollständig aus git zu entfernen:

  1. Änderungszusagen.
  2. Hardresets (möglicherweise plus Rebase).
  3. Nicht interaktive Basis.
  4. Interaktive Rebases.
  5. Filtern von Zweigen.

Im Fall des Originalplakats ist die Änderung des Commits keine Option für sich, da er danach mehrere zusätzliche Commits durchgeführt hat. Der Vollständigkeit halber werde ich jedoch auch erläutern, wie dies für alle anderen getan werden soll, die dies möchten ihr bisheriges Commit zu ändern.

Beachten Sie, dass alle diese Lösungen Ändern/Umschreiben history/Commits in einer anderen Weise beinhalten, sodass jeder mit alten Kopien der Commits dies tun muss zusätzliche Arbeit, um ihre Geschichte mit der neuen Geschichte zu synchronisieren.


Lösung 1: Commits ändern

Wenn Sie in Ihrem vorherigen Commit versehentlich eine Änderung vorgenommen haben (z. B. das Hinzufügen einer Datei) und das Protokoll dieser Änderung nicht mehr vorhanden sein soll, können Sie das vorherige Commit einfach ändern, um die Datei daraus zu entfernen:

git rm <file>
git commit --amend --no-edit

Lösung 2: Hard Reset (möglicherweise plus Rebase)

Wie in Lösung 1 haben Sie auch die Möglichkeit, einen Hard-Reset für das übergeordnete Element durchzuführen, wenn Sie nur Ihr vorheriges Commit entfernen möchten:

git reset --hard HEAD^

Mit diesem Befehl wird Ihr Zweig auf den vorherigen 1 zurückgesetztst übergeordnetes Commit.

Allerdings, wenn Sie wie beim Originalposter nach dem Festschreiben mehrere Festschreibungen vorgenommen haben, die Sie rückgängig machen möchten Wenn Sie auf ändern, können Sie es weiterhin mit Hard-Resets ändern. Dies erfordert jedoch auch die Verwendung einer Rebase. Mit den folgenden Schritten können Sie einen Commit weiter hinten im Verlauf ändern:

# Create a new branch at the commit you want to amend
git checkout -b temp <commit>

# Amend the commit
git rm <file>
git commit --amend --no-edit

# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master

# Verify your changes
git diff [email protected]{1}

Lösung 3: Nicht interaktiver Neustart

Dies funktioniert, wenn Sie ein Commit nur vollständig aus dem Verlauf entfernen möchten:

# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>

# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master

# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master

# Verify your changes
git diff [email protected]{1}

Lösung 4: Interaktive Neustarts

Mit dieser Lösung können Sie dieselben Aufgaben wie mit den Lösungen 2 und 3 ausführen, d. H. Commits ändern oder entfernen, die weiter zurückliegen als Ihre unmittelbar vorherige Commit-Aktion. Welche Lösung Sie also verwenden, liegt ganz bei Ihnen. Interaktive Rebases sind aus Performancegründen nicht gut geeignet, um Hunderte von Commits neu zu gründen. Daher würde ich in solchen Situationen nicht interaktive Rebases oder die Filter-Branch-Lösung (siehe unten) verwenden.

Verwenden Sie Folgendes, um die interaktive Datenbank zu starten:

git rebase --interactive <commit-to-amend-or-remove>~

# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~

Dies führt dazu, dass git den Festschreibungsverlauf an das übergeordnete Element der Festschreibung zurückspult, die Sie ändern oder entfernen möchten. Anschließend wird eine Liste der zurückgespulten Commits in umgekehrter Reihenfolge in dem Editor angezeigt, für den die Verwendung festgelegt ist (dies ist standardmäßig Vim):

pick 00ddaac Add symlinks for executables
pick 03fa071 Set `Push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)

Das Commit, das Sie ändern oder entfernen möchten, steht oben in dieser Liste. Um es zu entfernen, löschen Sie einfach seine Zeile in der Liste. Andernfalls ersetzen Sie "pick" durch "edit" auf der 1st Zeile wie folgt:

edit 00ddaac Add symlinks for executables
pick 03fa071 Set `Push.default` to `simple`

Geben Sie als nächstes git rebase --continue Ein. Wenn Sie sich dafür entschieden haben, das Commit vollständig zu entfernen, müssen Sie nur noch das Commit ausführen (außer der Überprüfung, siehe letzter Schritt für diese Lösung). Wenn Sie andererseits das Commit ändern möchten, wendet git das Commit erneut an und pausiert dann die Rebase.

Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

Zu diesem Zeitpunkt können Sie die Datei entfernen und das Festschreiben ändern und dann die erneute Basis fortsetzen:

git rm <file>
git commit --amend --no-edit
git rebase --continue

Das ist es. Unabhängig davon, ob Sie das Commit geändert oder vollständig entfernt haben, ist es immer eine gute Idee, zu überprüfen, ob keine anderen unerwarteten Änderungen an Ihrem Zweig vorgenommen wurden.

git diff [email protected]{1}

Lösung 5: Filtern von Zweigen

Schließlich ist diese Lösung am besten geeignet, wenn Sie alle Spuren der Existenz einer Datei aus dem Verlauf entfernen möchten und keine der anderen Lösungen für diese Aufgabe geeignet ist.

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'

Dadurch wird <file> Aus allen Commits entfernt, beginnend mit dem Root-Commit. Wenn Sie stattdessen nur den Festschreibungsbereich HEAD~5..HEAD Umschreiben möchten, können Sie dies als zusätzliches Argument an filter-branch Übergeben, wie in diese Antwort :

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD

Auch nach Abschluss von filter-branch Sollten Sie in der Regel sicherstellen, dass keine anderen unerwarteten Änderungen vorliegen, indem Sie Ihren Zweig vor dem Filtern von seinem vorherigen Status unterscheiden:

git diff [email protected]{1}

Filter-Branch Alternative: BFG Repo Cleaner

Ich habe gehört, dass das Tool BFG Repo Cleaner schneller als git filter-branch Ausgeführt wird. Vielleicht möchten Sie dies auch als Option prüfen. Es wird sogar offiziell in der Filter-Branch-Dokumentation als praktikable Alternative erwähnt:

mit git-filter-branch können Sie komplexe Shell-Skript-Umschreibungen Ihres Git-Verlaufs vornehmen, aber Sie benötigen diese Flexibilität wahrscheinlich nicht, wenn Sie einfach unerwünschte Daten entfernen wie große Dateien oder Passwörter . Für diese Operationen sollten Sie The BFG Repo-Cleaner in Betracht ziehen, eine JVM-basierte Alternative zum Git-Filter-Branch, die für diese Anwendungsfälle in der Regel mindestens 10-50-mal schneller ist und ganz andere Eigenschaften aufweist Eigenschaften:

  • Jede bestimmte Version einer Datei wird genau einmal gesäubert. Die BFG bietet Ihnen im Gegensatz zur Git-Filter-Verzweigung keine Möglichkeit, eine Datei anders zu behandeln, je nachdem, wo oder wann sie in Ihrem Verlauf festgeschrieben wurde. Diese Einschränkung bietet den Hauptleistungsvorteil von The BFG und ist gut für die Bereinigung fehlerhafter Daten geeignet - es ist Ihnen egal wo die fehlerhaften Daten sind, Sie möchten sie nur vergangen.

  • Standardmäßig nutzt die BFG die Vorteile von Multi-Core-Maschinen voll aus und bereinigt Commit-Dateibäume parallel. git-filter-branch bereinigt Commits nacheinander (dh auf Singlethread-Weise), obwohl es ist möglich ist, Filter mit eigenem Parallellismus in die Skripte zu schreiben, die für jedes Commit ausgeführt werden.

  • Die Befehlsoptionen sind viel restriktiver als der Git-Filter-Zweig und nur für die Aufgaben des Entfernens unerwünschter Daten vorgesehen, z. B .: --strip-blobs-bigger-than 1M.

Zusätzliche Ressourcen

  1. Pro Git § 6.4 Git Tools - Umschreibeverlauf .
  2. Git-Filter-Zweig (1) Manual Page .
  3. git-commit (1) Manual Page .
  4. git-reset (1) Manual Page .
  5. git-rebase (1) Manual Page .
  6. The BFG Repo Cleaner (siehe auch diese Antwort vom Schöpfer selbst ).
203
user456814

Wenn Sie seitdem nichts mehr festgeschrieben haben, nur noch git rm die Datei und git commit --amend.

Wenn Sie haben

git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD

durchläuft jede Änderung von merge-point zu HEAD, lösche filename.orig und schreibe die Änderung neu. Mit --ignore-unmatch bedeutet, dass der Befehl nicht fehlschlägt, wenn dateiname.orig in einer Änderung fehlt. Dies ist die empfohlene Methode aus dem Abschnitt Beispiele in der Manpage Git-Filter-Branch .

Hinweis für Windows-Benutzer: Der Dateipfad muss Schrägstriche enthalten

118
Schwern

Das ist der beste Weg:
http://github.com/guides/completely-remove-a-file-from-all-revisions

Stellen Sie einfach sicher, dass Sie zuerst die Kopien der Dateien sichern.

[~ # ~] edit [~ # ~]

Die Bearbeitung von Neon wurde im Test leider abgelehnt.
Siehe Neons Beitrag unten, er könnte nützliche Informationen enthalten!


Z.B. um alles zu entfernen *.gz Dateien, die versehentlich in das Git-Repository geschrieben wurden:

$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git Push Origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --Prune=now
$ git gc --aggressive --Prune=now

Das hat bei mir immer noch nicht funktioniert? (Ich bin derzeit bei Git Version 1.7.6.1)

$ du -sh .git ==> e.g. 100M

Ich weiß nicht warum, da ich nur EINE Hauptniederlassung hatte. Wie auch immer, ich habe mein Git-Repository endlich wirklich aufgeräumt, indem ich es in ein neues leeres und nacktes Git-Repository geschoben habe, z.

$ git init --bare /path/to/newcleanrepo.git
$ git Push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M 

(ja!)

Dann habe ich das in ein neues Verzeichnis geklont und den .git-Ordner in dieses verschoben. z.B.

$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M 

(yeah! endlich aufgeräumt!)

Nachdem Sie überprüft haben, ob alles in Ordnung ist, können Sie das ../large_dot_git und ../tmpdir Verzeichnisse (vielleicht in ein paar Wochen oder Monaten, nur für den Fall ...)

48
Darren

Das Umschreiben des Git-Verlaufs erfordert das Ändern aller betroffenen Festschreibungs-IDs. Daher muss jeder, der an dem Projekt arbeitet, seine alten Kopien des Repos löschen und einen neuen Klon ausführen, nachdem Sie den Verlauf bereinigt haben. Je mehr Leute es stört, desto mehr brauchen Sie einen guten Grund dafür - Ihre überflüssige Datei ist nicht wirklich problematisch, aber wenn nur Sie an dem Projekt arbeiten, könnten Sie dies auch tun Nun, räume die Git-Geschichte auf, wenn du willst!

Um es so einfach wie möglich zu machen, empfehle ich die Verwendung des BFG Repo-Cleaner , einer einfacheren, schnelleren Alternative zu git-filter-branch speziell zum Entfernen von Dateien aus dem Git-Verlauf. Eine Möglichkeit, das Leben zu erleichtern, besteht darin, dass es standardmäßig all refs verarbeitet (alle Tags, Zweige usw.), aber es ist auch 10 - 50x schneller .

Sie sollten die folgenden Schritte sorgfältig ausführen: http://rtyley.github.com/bfg-repo-cleaner/#usage - aber das Kernstück ist genau das: Laden Sie das BFG-Glas) herunter (erfordert Java 6 oder höher) und führen Sie diesen Befehl aus:

$ Java -jar bfg.jar --delete-files filename.orig my-repo.git

Ihr gesamter Repository-Verlauf wird gescannt und alle Dateien mit dem Namen filename.orig (das steht nicht in deinem latest commit ) wird entfernt. Dies ist erheblich einfacher als die Verwendung von git-filter-branch dasselbe zu tun!

Vollständige Offenlegung: Ich bin der Autor des BFG Repo-Cleaners.

27
Roberto Tyley
You should probably clone your repository first.

Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all

Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD    

Lastly you should run to remove empty commits:
git filter-branch -f --Prune-empty -- --all
13
paulalexandru

Um dies zu Charles Baileys Lösung hinzuzufügen, habe ich einfach eine Git-Rebase -i verwendet, um unerwünschte Dateien aus einem früheren Commit zu entfernen, und es hat wie ein Zauber funktioniert. Die Schritte:

# Pick your commit with 'e'
$ git rebase -i

# Perform as many removes as necessary
$ git rm project/code/file.txt

# amend the commit
$ git commit --amend

# continue with rebase
$ git rebase --continue

Der einfachste Weg, den ich gefunden habe, wurde von leontalbot (als Kommentar) vorgeschlagen. Dies ist ein von Anoopjohn veröffentlichter Beitrag . Ich denke, es ist seinen Platz wert als Antwort:

(Ich habe es in ein Bash-Skript konvertiert)

#!/bin/bash
if [[ $1 == "" ]]; then
    echo "Usage: $0 FILE_OR_DIR [remote]";
    echo "FILE_OR_DIR: the file or directory you want to remove from history"
    echo "if 'remote' argument is set, it will also Push to remote repository."
    exit;
fi
FOLDERNAME_OR_FILENAME=$1;

#The important part starts here: ------------------------

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --Prune=now
git gc --aggressive --Prune=now

if [[ $2 == "remote" ]]; then
    git Push --all --force
fi
echo "Done."

Alle Credits gehen an Annopjohn und an leontalbot, um darauf hinzuweisen.

[~ # ~] note [~ # ~]

Beachten Sie, dass das Skript keine Überprüfungen enthält. Vergewissern Sie sich also, dass Sie keine Fehler machen und ein Backup haben, falls etwas schief geht. Es hat bei mir funktioniert, aber in Ihrer Situation funktioniert es möglicherweise nicht. BENUTZEN SIE ES MIT VORSICHT (folgen Sie dem Link, wenn Sie wissen möchten, was los ist).

4
lepe

Auf jeden Fall ist git filter-branch Der richtige Weg.

Leider wird dies nicht ausreichen, um filename.orig Vollständig aus Ihrem Repo zu entfernen, da es weiterhin von Tags, Reflog-Einträgen, Fernbedienungen usw. referenziert werden kann.

Ich empfehle, alle diese Verweise zu entfernen und dann den Garbage Collector aufzurufen. Sie können das Skript git forget-blob Von der this Website verwenden, um all dies in einem Schritt zu erledigen.

git forget-blob filename.orig

3
nachoparker

Wenn es das neueste Commit ist, das Sie bereinigen möchten, habe ich es mit Git Version 2.14.3 (Apple Git-98) versucht:

touch empty
git init
git add empty
git commit -m init

# 92K   .git
du -hs .git

dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake

# 5.1M  .git
du -hs .git

git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --Prune=now

# 92K   .git
du -hs .git
1
clarkttfu

Das ist was git filter-branch wurde entwickelt für.

0
CesarB