Vorwort
Diese Frage versucht, die Verwirrung bezüglich der Anwendung von .gitignore zu beseitigen rückwirkendnicht nur in die Gegenwart/Zukunft.1
Begründung
Ich habe nach einer Möglichkeit gesucht, meine aktuelle .gitignore rückwirkend durchzusetzen, als ob ich .gitignore im ersten Commit erstellt hätte.
Die Lösung, die ich suche:
- Wille nicht erfordern die manuelle Angabe von Dateien
- Wille nicht erfordern ein Commit
- Gilt rückwirkend für alle Commits aller Branches
- Wille ignorieren .gitignore-spezifizierte Dateien im Arbeitsverzeichnis, nicht löschen sie (genau wie es eine ursprünglich root-committed .gitignore-Datei tun würde)
- Wird Git verwenden, nicht BFG
- Gilt für .gitignore-Ausnahmen wie:
*.ext
!*special.ext
Nicht Lösungen
git rm --cached *.ext
git commit
Dies erfordert 1. die manuelle Angabe von Dateien und 2. einen zusätzlichen Commit, der zu einer neu ignorierten Datei führt Streichung wenn von anderen Entwicklern gezogen. (Es ist effektiv nur ein git rm
– die ein Streichung vom Git-Tracking – aber es lässt die Datei allein im lokalen (dein) Arbeitsverzeichnis. Andere wer git pull
danach erhält er die Datei Streichung begehen)
git filter-branch --index-filter 'git rm --cached *.ext'
Während dies tut Dateien nachträglich löschen, es 1. erfordert die manuelle Angabe von Dateien und 2. löscht die angegebenen Dateien aus der lokal Arbeitsverzeichnis wie normal git rm
(und so auch für andere, die git pull
)!
Fußnoten
1Es gibt viele ähnliche Posts hier auf SO, mit weniger als genau definierten Fragen und noch mehr weniger als genauen Antworten. Siehe diese Frage mit 23 Antworten, wo die akzeptierte Antwort mit ~ 4.000 Stimmen ist falsch gemäß der Standarddefinition von “vergessen”, wie durch eine größtenteils richtige Antwort angegeben, und nur 2 Antworten enthalten die erforderlich git filter-branch
Befehl.
Diese Frage mit 21 Antworten ist wurde als Duplikat der vorherigen markiert, aber die Frage ist anders definiert (ignorieren vs. vergessen), also sind die Antworten zwar angemessen, aber sie sind es nicht ein Duplikat.
Diese Frage kommt dem, was ich suche, am nächsten, aber die Antworten funktionieren nicht in allen Fällen (Pfade mit Leerzeichen …) und sind vielleicht etwas komplexer als nötig, wenn es darum geht, ein External-to zu erstellen -repository .gitignore-Datei und kopieren Sie sie in jeden Commit.
EDIT: Habe ich neulich gefunden git-filter-repo. Es kann eine bessere Wahl sein. Vielleicht eine gute Idee, das zu untersuchen Begründung und Filterzweig Fallstricke für sich selbst, aber sie hätten meinen Anwendungsfall unten nicht beeinflusst.
Diese Methode macht Git komplett vergessen ignorierte Dateien (Vergangenheit/Gegenwart/Zukunft), tut es aber nicht Löschen Sie alles aus dem Arbeitsverzeichnis (auch wenn es von der Ferne erneut abgerufen wird).
Diese Methode erfordert die Verwendung von /.git/info/exclude
(bevorzugt) ODER a bereits vorhanden .gitignore
in alle die Commits, die Dateien enthalten, die ignoriert/vergessen werden sollen. 1
Diese Methode vermeidet Entfernen der neu ignorierten Dateien von anderen Entwicklercomputern auf dem nächsten git pull
2
Alle Methoden zum Erzwingen von Git-Ignorieren des Verhaltens im Nachhinein schreiben den Verlauf effektiv neu und haben daher erhebliche Auswirkungen auf alle öffentlichen/gemeinsamen/kollaborativen Repos, die nach diesem Prozess gezogen werden könnten. 3
Allgemeine Hinweise: Beginnen Sie mit einem sauberen Repo – alles festgeschrieben, nichts im Arbeitsverzeichnis oder Index ausstehend, und ein Backup machen!
Auch die Kommentare / der Überarbeitungsverlauf dieser Antwort (und der Überarbeitungsverlauf dieser Frage) können nützlich / aufschlussreich sein.
#commit up-to-date .gitignore (if not already existing)
#these commands must be run on each branch
#these commands are not strictly necessary if you don't want/need a .gitignore file. .git/info/exclude can be used instead
git add .gitignore
git commit -m "Create .gitignore"
#apply standard git ignore behavior only to current index, not working directory (--cached)
#if this command returns nothing, ensure /.git/info/exclude AND/OR .gitignore exist
#this command must be run on each branch
#if using .git/info/exclude, it will need to be modified per branch run, if the branches have differing (per-branch) .gitignore requirements.
git ls-files -z --ignored --exclude-standard | xargs -r0 git rm --cached
#Commit to prevent working directory data loss!
#this commit will be automatically deleted by the --prune-empty flag in the following command
#this command must be run on each branch
#optionally use the --amend flag to merge this commit with the previous one instead of creating 2 commits.
git commit -m "ignored index"
#Apply standard git ignore behavior RETROACTIVELY to all commits from all branches (--all)
#This step WILL delete ignored files from working directory UNLESS they have been dereferenced from the index by the commit above
#This step will also delete any "empty" commits. If deliberate "empty" commits should be kept, remove --prune-empty and instead run git reset HEAD^ immediately after this command
git filter-branch --tree-filter 'git ls-files -z --ignored --exclude-standard | xargs -r0 git rm -f --ignore-unmatch' --prune-empty --tag-name-filter cat -- --all
#List all still-existing files that are now ignored properly
#if this command returns nothing, it's time to restore from backup and start over
#this command must be run on each branch
git ls-files --other --ignored --exclude-standard
Folgen Sie schließlich dem Rest von diese GitHub-Anleitung (ab Schritt 6) die wichtige Warnungen/Informationen zu den folgenden Befehlen enthält.
git push origin --force --all
git push origin --force --tags
git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now
Andere Entwickler, die aus dem jetzt modifizierten Remote-Repo ziehen, sollten ein Backup erstellen und dann:
#fetch modified remote
git fetch --all
#"Pull" changes WITHOUT deleting newly-ignored files from working directory
#This will overwrite local tracked files with remote - ensure any local modifications are backed-up/stashed
git reset FETCH_HEAD
Fußnoten
1 Weil /.git/info/exclude
kann auf alle historischen Commits angewendet werden, indem die obigen Anweisungen verwendet werden, vielleicht Details zum Erhalten einer .gitignore
Datei hinein Die historischen Verpflichtungen, die es benötigen, gehen über den Rahmen dieser Antwort hinaus. Ich wollte eine richtige .gitignore
im Root-Commit zu sein, als wäre es das Erste, was ich getan habe. Anderen ist es da vielleicht egal /.git/info/exclude
kann dasselbe erreichen, unabhängig davon, wo die .gitignore
existiert in der Commit-Historie, und das Umschreiben der Historie ist eindeutig a sehr heikles Thema, auch wenn man sich der Auswirkungen bewusst ist.
FWIW, mögliche Methoden können umfassen git rebase
oder ein git filter-branch
das kopiert ein extern .gitignore
in jeden Commit, wie die Antworten auf diese Frage
2 Erzwingen des Git-Ignorieren-Verhaltens im Nachhinein durch Festschreiben der Ergebnisse einer eigenständigen git rm --cached
Befehl kann zu einer neu ignorierten Datei führen Streichung in Zukunft zieht aus der gewaltsam gedrückten Fernbedienung. Die --prune-empty
Flagge im git filter-branch
Befehl (oder git reset HEAD^
danach) vermeidet dieses Problem durch automatisches Entfernen des vorherigen Nur-Index-Commits „Alle ignorierten Dateien löschen“.
3 Das Umschreiben des Git-Verlaufs ändert auch Commit-Hashes, was bei zukünftigen Pulls aus öffentlichen/geteilten/kollaborativen Repos Chaos anrichten wird. Bitte verstehen Sie die Auswirkungen vollständig, bevor Sie dies mit einem solchen Repo tun. Diese GitHub-Anleitung gibt Folgendes an:
Sagen Sie es Ihren Mitarbeitern rebasieren, nicht zusammenführen, alle Zweige, die sie aus Ihrem alten (befleckten) Repository-Verlauf erstellt haben. Ein Merge-Commit könnte einen Teil oder den gesamten verdorbenen Verlauf wieder einführen, den Sie sich gerade die Mühe gemacht haben, ihn zu löschen.
Alternative Lösungen, die unterlassen Sie Auswirkungen auf das Remote-Repo haben git update-index --assume-unchanged </path/file>
oder git update-index --skip-worktree <file>
Beispiele dafür finden Sie hier.
Dies mag nur eine Teilantwort sein, aber hier ist, wie ich das rückwirkende Entfernen von Dateien aus früheren Git-Commits basierend auf meiner aktuellen .gitignore-Datei erreicht habe:
- Erstellen Sie eine Sicherungskopie des Repo-Ordners, an dem Sie arbeiten. Ich habe gerade ein .7z-Archiv des gesamten Ordners erstellt.
- Installieren git-filter-repo
- Kopieren Sie Ihre .gitignore-Datei vorübergehend an einen anderen Ort. Da ich Windows verwende und die Eingabeaufforderung verwende, habe ich ausgeführt
copy .gitignore ..\
und habe gerade die temporäre Kopie nur auf Verzeichnisebene nach oben gemacht
- Wenn Ihre .gitignore-Datei Platzhalterfilter (wie
nbproject/Makefile-*
), müssen Sie Ihre temporär kopierte .gitignore-Datei bearbeiten, damit diese Zeilen gelesen werden glob:nbproject/Makefile-*
- Laufen
git filter-repo --invert-paths --paths-from-file ..\.gitignore
. Mein Verständnis ist, dass dies die temporäre Kopie als Liste der zu entfernenden Dateien/Verzeichnisse verwendet. Hinweis: Wenn Sie eine Fehlermeldung erhalten, dass Ihr Repo kein sauberer Klon ist, suchen Sie in der Git-Filter-Repo-Hilfe nach „FRESH CLONE SAFETY CHECK AND –FORCE“. Vorsichtig sein.
Weitere Informationen finden Sie unter:
git-filter-repo-Hilfe (Suchen Sie nach „Filtern basierend auf vielen Pfaden“)
Haftungsausschluss: Ich habe keine Ahnung, was ich tue, aber das hat bei mir funktioniert.
Manchmal ist es einfach besser, ein Skript zu schreiben, das die manuellen Dinge für Sie erledigt.
– Brianrobt
8. August 2019 um 19:35 Uhr
Ist Ihr Ziel, das Repository so umzuschreiben, wie es aussehen würde, wenn die betreffenden Dateien vorhanden wären wurden nie begangen (was alle vorhandenen Commit-IDs ungültig machen und wahrscheinlich Dinge für jeden vorhandenen Klon/Auschecken des Repos kaputt machen würde), oder um Ihr lokales Arbeitsverzeichnis so zu konfigurieren, dass Git vorgibt Diese Dateien sind in einem alten Commit nicht vorhanden, wenn Sie es auschecken?
– hmakholm hat Monica übrig gelassen
11. August 2019 um 21:53 Uhr
Ziel ist ersteres, „als ob ich .gitignore am Anfang erstellt hätte“. Ich verstehe die Auswirkungen, aber mein Repo ist lokal/privat und ich habe nichts gegen einen Force-Push. Obwohl Sie gerne angeben, wie mit letzterem umgegangen werden soll, wenn Sie antworten, scheinen dies nützliche Informationen zu sein.
– Goofologie
11. August 2019 um 22:23 Uhr
Es wird wie eine Fünfzeiler sein
filter-branch
, Spitzen. Setzen Sie Ihre Ausschlüsse ein.git/info/exclude
mach eingit ls-files --exclude-standard -ci
und rm –zwischengespeichert.– jthill
12. August 2019 um 5:36 Uhr
Danke dir. Ich stimme zu, dass vergiss = rückwirkend, und müsste es nicht explizit spezifizieren, wenn nicht die andere unglaublich positiv bewertete Frage „vollständig vergessen“ mit einer akzeptierten Antwort wäre, die nur für die Gegenwart / Zukunft gilt. Vielleicht sollte diese Frage auch expliziter bearbeitet werden (nur Gegenwart / Zukunft)?
– Goofologie
6. Januar 2020 um 22:37 Uhr