Wie können Sie eine fehlerhafte Zusammenführung beheben und Ihre guten Commits auf einer festen Zusammenführung wiedergeben?
-
08-07-2019 - |
Frage
Ich habe versehentlich eine unerwünschte Datei übertragen (filename.orig
beim Auflösen einer Zusammenführung) vor einigen Commits in mein Repository kopiert, 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, den Änderungsverlauf so umzuschreiben? filename.orig
wurde überhaupt nicht zum Repository hinzugefügt?
Lösung
Bitte nicht dieses Rezept verwenden, wenn Ihre Situation nicht die in der Frage beschrieben ist. Dieses Rezept ist für eine schlechte merge Fixierung und Ihre guten Commits auf einem festen merge Wiedergabe.
Obwohl filter-branch
wird tun, was Sie wollen, ist es ein ziemlich komplexer Befehl und ich würde wahrscheinlich wählt diese mit git rebase
zu tun. Es ist wahrscheinlich eine persönliche Präferenz. filter-branch
kann es in einem einzigen, etwas komplexen Befehl, während die rebase
Lösung wird zu einer Zeit, den entsprechenden logische Operationen eines Schritt durchgeführt wird.
Versuchen 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 nicht wirklich benötigen ein temporärer Zweig, kann Sie dies mit einem ‚abgelöst HEAD‘, aber Sie müssen eine Notiz von der Commit-ID durch den git commit --amend
Schritt erzeugt ergreifen, um den git rebase
Befehl liefern eher als die temporären Zweignamen verwenden.)
Andere Tipps
Einführung:Ihnen stehen 5 Lösungen zur Verfügung
Auf dem Originalplakat heißt es:
Ich habe versehentlich eine unerwünschte Datei begangen ... in meinem Repository vor einigen Commits ... Ich möchte die Datei vollständig aus dem Repository -Verlauf löschen.
Ist es möglich, die Veränderungsgeschichte so umzuschreiben, dass
filename.orig
Wurde in erster Linie nie zum Repository hinzugefügt?
Es gibt viele verschiedene Möglichkeiten, die Geschichte einer Datei vollständig aus Git zu entfernen:
- Commits ändern.
- Hard-Resets (ggf. plus Rebase).
- Nicht interaktive Rebase.
- Interaktive Rebases.
- Zweige filtern.
Im Falle des ursprünglichen Plakats ist die Änderung des Commits für sich nicht wirklich eine Option, da er danach mehrere zusätzliche Commits gemacht hat, aber aus Gründen der Vollständigkeit werde ich auch erklären, wie es geht, für alle anderen, die Gerechte wollen ihr früheres Commit zu ändern.
Beachten Sie, dass alle diese Lösungen Folgendes beinhalten ändern/neu schreiben Geschichte/Commits in gewisser Weise auf eine andere Weise, also muss jeder mit alten Kopien der Commits zusätzliche Arbeit leisten, um seine Geschichte mit der neuen Geschichte neu zu synchronisieren.
Lösung 1:Änderungsverpflichtungen
Wenn Sie in Ihrem vorherigen Commit versehentlich eine Änderung vorgenommen haben (z. B. eine Datei), und Sie möchten nicht, dass die Geschichte dieser Änderung mehr existiert, können Sie einfach das vorherige Commit ä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 Lösung Nr. 1, wenn Sie nur Ihr vorheriges Komitee loswerden möchten, haben Sie auch die Möglichkeit, einfach einen harten Reset für seinen Elternteil durchzuführen:
git reset --hard HEAD^
Dieser Befehl setzt Ihren Zweig hart auf den vorherigen 1 zurückst Elternbefestigung.
Jedoch, Wenn Sie wie das ursprüngliche Poster nach dem Commit, wie Sie die Änderung rückgängig machen möchten, mehrere Commits gemacht haben, können Sie weiterhin harte Resets verwenden, um sie zu ändern.Hier sind die Schritte, mit denen Sie einen Commit weiter in die Geschichte ändern können:
# 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 master@{1}
Lösung 3:Nicht interaktives Rebase
Dies funktioniert, wenn Sie einen 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 master@{1}
Lösung 4:Interaktive Rebases
Mit dieser Lösung können Sie die gleichen Dinge wie die Lösungen Nr. 2 und Nr. 3 erledigen, dhÄndern oder entfernen Sie die Commits weiter zurück in die Geschichte als Ihr unmittelbares Vorgänger. Welche Lösung Sie verwenden, ist eine Art bis zu Ihnen.Interaktive Neubasen sind nicht gut geeignet, um Hunderte von Commits aus Leistungsgründen neu zu reservieren. Daher würde ich in solchen Situationen nicht-interaktive Rebasen oder die Filterzweiglösung (siehe unten) verwenden.
Um mit der interaktiven Rebase zu beginnen, verwenden Sie Folgendes:
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 die Verschingungsgeschichte an den Elternteil des Commits zurückspulen, das Sie ändern oder entfernen möchten.Anschließend werden Sie eine Liste der Rewound -Commits in umgekehrter Reihenfolge in dem verwendet, in dem Git verwendet wird, in dem die Verwendung von Editor eingestellt 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)
Der Commit, den Sie ändern oder entfernen möchten, steht ganz oben in dieser Liste.Um es zu entfernen, löschen Sie einfach seine Zeile in der Liste.Andernfalls ersetzen Sie "Auswahl" durch "Bearbeiten" auf der 1st Zeile, etwa so:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Als nächstes geben Sie ein git rebase --continue
.Wenn Sie sich für die vollständige Entfernung entschieden haben, müssen Sie alles, was Sie tun müssen (außer Überprüfung, den letzten Schritt für diese Lösung).Wenn Sie andererseits das Komitee ändern wollten, wird Git das Commit erneut anwenden und dann die Rebase pausieren.
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 Commit ändern und dann die Rebase fortsetzen:
git rm <file>
git commit --amend --no-edit
git rebase --continue
Das ist es.Unabhängig davon, ob Sie das Komitee 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, indem Sie ihn vor der Rebase mit seinem Zustand verbreiten:
git diff master@{1}
Lösung 5:Zweige filtern
Schließlich ist diese Lösung am besten, wenn Sie alle Spuren der Existenz einer Datei aus der Geschichte vollständig auslöschen möchten, und keine der anderen Lösungen ist der Aufgabe ziemlich hoch.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
Das wird entfernt <file>
von allen Commits, beginnend mit dem Root-Commit.Wenn Sie stattdessen nur den Commit -Bereich umschreiben möchten HEAD~5..HEAD
, dann können Sie das als zusätzliches Argument an übergeben filter-branch
, wie in ausgeführtdiese Antwort:
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Nochmals nach dem filter-branch
Es ist vollständig, es ist normalerweise eine gute Idee zu überprüfen, ob es keine anderen unerwarteten Änderungen gibt, indem Sie Ihren Zweig mit seinem vorherigen Zustand vor dem Filterbetrieb verzichten:
git diff master@{1}
Filterzweig-Alternative:BFG Repo Cleaner
Ich habe gehört, dass das BFG Repo Cleaner Werkzeug läuft schneller als git filter-branch
, also sollten Sie das vielleicht auch als Option ausprobieren. Es wird sogar offiziell im erwähnt Filterzweigdokumentation als sinnvolle Alternative:
Mit Git-Filter-Branch können Sie komplexe Shell-Script-Umschreibungen Ihrer Git-Geschichte vornehmen, aber Sie brauchen diese Flexibilität wahrscheinlich nicht, wenn Sie einfach sind Entfernen unerwünschter Daten wie große Dateien oder Passwörter.Für diese Vorgänge sollten Sie vielleicht in Betracht ziehen Der BFG Repo-Cleaner, Eine JVM-basierte Alternative zur Git-Filter-Branch, die für diese Anwendungsfälle in der Regel mindestens 10-50x schneller und mit ganz unterschiedlichen Eigenschaften schneller ist:
Jede bestimmte Version einer Datei wird genau bereinigt einmal.Im Gegensatz zu Git-Filter-Branch gibt Ihnen das BFG nicht die Möglichkeit, eine Datei unterschiedlich zu verarbeiten, basierend darauf, wo oder wann sie in Ihrer Geschichte begangen wurde.Diese Einschränkung bietet den Kernleistung des BFG und ist für die Aufgabe, schlechte Daten zu reinigen, gut geeignet - Sie kümmern sich nicht darum Wo Die schlechten Daten sind, dass Sie es einfach wollen gegangen.
Standardmäßig nutzt die BFG die Vorteile von Multi-Core-Maschinen voll aus und bereinigt parallel Commit-Dateibäume.Git-Filter-Branch-Reinigungen verpflichtet sich nacheinander (dh in einer einzigen Thread-Weise), obwohl es es Istmöglich, Filter zu schreiben, die ihren eigenen Parallellismus in die Skripte enthalten, die gegen jeden Commit ausgeführt werden.
Der Befehlsoptionen sind viel restriktiver als Git-Filter-Zweig und nur den Aufgaben, unerwünschte Daten zu entfernen- z. B.:
--strip-blobs-bigger-than 1M
.
Zusätzliche Ressourcen
Wenn Sie haben nichts begangen, da git rm
einfach die Datei und git commit --amend
.
Wenn Sie
git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD
wird von merge-point
durch jede Änderung gehen HEAD
, löschen filename.orig und die Änderung neu schreiben. --ignore-unmatch
Mit der Benutzung wird der Befehl nicht fehlschlagen, wenn aus irgendeinem Grunde filename.orig aus einer Änderung fehlt. Das ist die empfohlene Art und Weise aus dem Beispielabschnitt in dem git-filter-branch Mann .
Hinweis für Windows-Benutzer: Der Dateipfad muss verwenden Schrägstriche
Dies ist die beste Art und Weise:
http://github.com/guides/completely-remove-a -file-von-allen-Revisionen
Seien Sie sicher, dass die Kopien der Dateien zu sichern zuerst.
Bearbeiten
Das Bearbeiten von Neon leider während der Überprüfung abgelehnt wurde.
Siehe Neon unten schreiben, könnte es nützliche Informationen enthält!
z. Alle *.gz
Dateien versehentlich in Git Repository begangen zu entfernen:
$ 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 noch nicht für mich arbeiten? (Ich bin derzeit bei git Version 1.7.6.1)
$ du -sh .git ==> e.g. 100M
Nicht sicher, warum, da ich nur einen Master-Zweig hatte. Wie auch immer, ich habe endlich mein git Repo truely durch Drücken in eine neue leere und kahle git-Repository bereinigt, 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 klonen ich, dass in ein neues Verzeichnis und bewegt über sie .git Ordner, in diesem ist. z.
$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M
(ja endlich aufgeräumt!)
Nach Überprüfung, ob alles in Ordnung ist, dann können Sie die ../large_dot_git
und ../tmpdir
Verzeichnisse löschen (vielleicht in ein paar Wochen oder Monaten ab jetzt, nur für den Fall ...)
Umschreiben Git Geschichte Anforderungen verändern sich ids verpflichten betroffen, und so jeder, der an dem Projekt arbeitet, müssen ihre alte Kopien des Repo löschen und machen einen frischen Klon, nachdem Sie die Geschichte gereinigt haben. Je mehr Menschen es Unannehmlichkeiten, desto mehr müssen Sie einen guten Grund, es zu tun - Ihre überflüssige Datei ist nicht wirklich ein Problem verursacht, aber wenn nur Sie sind an dem Projekt arbeiten, könnte man genauso gut sauber up der Git Geschichte, wenn Sie wollen!
es so einfach wie möglich zu machen, würde ich empfehlen, die BFG Repo-Cleaner mit , eine einfachere, schnellere Alternative zum Entfernen von Dateien von Git Geschichte speziell entwickelt, um git-filter-branch
. Eine Möglichkeit, wie es Ihr Leben macht hier leichter ist, dass es tatsächlich Griffe alle Refs standardmäßig (alle Tags, Zweige, usw.), aber es ist auch 10 -?. 50x schneller
Sie sollten sorgfältig die Schritte folgen hier: http://rtyley.github.com / bfg-Repo-Reiniger / # Nutzung - aber das Kern-Bit ist nur dies: Download der BFG jar (erfordert Java 6 oder höher) und diesen Befehl ausführen:
$ java -jar bfg.jar --delete-files filename.orig my-repo.git
Ihre gesamte Repository-Historie wird gescannt, und jede Datei mit dem Namen filename.orig
(das ist nicht in den letzte verpflichten ) werden entfernt. Dies ist wesentlich einfacher als git-filter-branch
mit der gleichen Sache zu tun!
Vollständige Offenlegung:. Ich bin der Autor des BFG Repo-Cleaner
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
nur hinzufügen, dass Charles Bailey-Lösung, habe ich nur ein git rebase -i unerwünschte Dateien aus einem früheren entfernen begehen und es funktionierte wie ein Charme. 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, fand ich von leontalbot
vorgeschlagen wurde (als Kommentar), das ist ein Beitrag von Anoopjohn veröffentlicht. Ich denke, es lohnt seinen eigenen Raum als Antwort:
(I umgewandelt es zu einem Bash-Skript)
#!/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."
geht Alle Kredite an Annopjohn
, und es leontalbot
für den Hinweis auf.
Hinweis
Beachten Sie, dass das Skript nicht Validierungen enthält, so sicher, dass Sie keine Fehler machen und dass Sie ein Backup, falls etwas schief geht. Er arbeitete für mich, aber es kann in Ihrer Situation nicht funktionieren. Verwenden Sie es mit VORSICHT (folgen Sie dem Link, wenn Sie wollen wissen, was los ist).
Auf jeden Fall, git filter-branch
ist der Weg zu gehen.
Leider wird dies nicht ausreicht, um vollständig filename.orig
aus dem Repo zu entfernen, da es noch von Tags, reflog Einträgen, Fernbedienungen usw. Bezug genommen werden wird.
Ich empfehle all diese Referenzen auch, zu entfernen und dann den Garbage Collector aufrufen. Sie können die git forget-blob
Skript von dieser Webseite all dies in einem Schritt zu tun.
git forget-blob filename.orig
Wenn es das letzte verpflichten Sie bereinigen möchten, habe ich versucht, mit git Version 2.14.3 (Apple-Git-98):
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
Dies ist, was git filter-branch
wurde konzipiert.
Sie können auch verwendet werden:
git reset HEAD file/path