Wie mache ich ein Git-Commit in der Vergangenheit?

222

Ich konvertiere alles für meinen persönlichen Gebrauch in Git und habe einige alte Versionen einer Datei gefunden, die sich bereits im Repository befinden. Wie kann ich es in der richtigen Reihenfolge entsprechend dem "Änderungsdatum" der Datei in den Verlauf übernehmen, damit ich einen genauen Verlauf der Datei habe?

Mir wurde gesagt, dass so etwas funktionieren würde:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  
Unbekannt
quelle
Kurze und einfache Antworten: stackoverflow.com/a/34639957/2708266
Yash
33
Ich frage mich, ob Leute, die nach einer Antwort auf diese
Frage
1
@ ZitRo ja. Und eine einfache Einstellung git commit --date="xxx day ago" -m "yyy"reicht für diesen Zweck aus, wenn sich jemand wundert.
Vlas Sokolov
alexpeattie.com/blog/working-with-dates-in-git : Wenn Sie nach einer sanften Erklärung suchen
thepurpleowl

Antworten:

198

Der Rat, den Sie erhalten haben, ist fehlerhaft. Das bedingungslose Setzen von GIT_AUTHOR_DATE in a --env-filterwürde das Datum jedes Commits neu schreiben. Es wäre auch ungewöhnlich, Git Commit im Inneren zu verwenden--index-filter .

Sie haben es hier mit mehreren unabhängigen Problemen zu tun.

Andere Daten als "jetzt" angeben

Jedes Commit hat zwei Daten: das Autorendatum und das Committerdatum. Sie können jeden überschreiben, indem Sie Werte über die Umgebungsvariablen GIT_AUTHOR_DATE und GIT_COMMITTER_DATE für jeden Befehl angeben, der ein neues Commit schreibt. Siehe "Datumsformate" in git-commit (1) oder unten:

Git internal format = <unix timestamp> <time zone offset>, e.g.  1112926393 +0200
RFC 2822            = e.g. Thu, 07 Apr 2005 22:13:13 +0200
ISO 8601            = e.g. 2005-04-07T22:13:13

Der einzige Befehl, der während des normalen Gebrauchs ein neues Commit schreibt, ist git commit . Es gibt auch eine --dateOption, mit der Sie das Autorendatum direkt angeben können. Ihre erwartete Verwendung umfasst git filter-branch --env-filterauch die oben genannten Umgebungsvariablen (diese sind Teil der "env", nach der die Option benannt ist; siehe "Optionen" in git-filter-branch (1) und den zugrunde liegenden "plumbing" -Befehl git-commit -Baum (1) .

Einfügen einer Datei in eine Einzel ref Geschichte

Wenn Ihr Repository sehr einfach ist (dh Sie haben nur einen einzigen Zweig, keine Tags), können Sie wahrscheinlich die Git-Rebase verwenden , um die Arbeit zu erledigen.

Verwenden Sie in den folgenden Befehlen den Objektnamen (SHA-1-Hash) des Commits anstelle von "A". Vergessen Sie nicht, beim Ausführen von git commit eine der Methoden zum Überschreiben von Datumsangaben zu verwenden .

---A---B---C---o---o---o   master

git checkout master
git checkout A~0
git add path/to/file
git commit --date='whenever'
git tag ,new-commit -m'delete me later'
git checkout -
git rebase --onto ,new-commit A
git tag -d ,new-commit

---A---N                      (was ",new-commit", but we delete the tag)
        \
         B'---C'---o---o---o   master

Wenn Sie A aktualisieren möchten, um die neue Datei einzuschließen (anstatt ein neues Commit zu erstellen, in dem es hinzugefügt wurde), verwenden Sie git commit --amendstattdessen anstelle von git commit. Das Ergebnis würde folgendermaßen aussehen:

---A'---B'---C'---o---o---o   master

Das Obige funktioniert so lange, wie Sie das Commit benennen können, das das übergeordnete Element Ihres neuen Commits sein soll. Wenn Sie tatsächlich möchten, dass Ihre neue Datei über ein neues Root-Commit hinzugefügt wird (keine Eltern), benötigen Sie etwas anderes:

B---C---o---o---o   master

git checkout master
git checkout --orphan new-root
git rm -rf .
git add path/to/file
GIT_AUTHOR_DATE='whenever' git commit
git checkout -
git rebase --root --onto new-root
git branch -d new-root

N                       (was new-root, but we deleted it)
 \
  B'---C'---o---o---o   master

git checkout --orphanist relativ neu (Git 1.7.2), aber es gibt andere Möglichkeiten, dasselbe zu tun , die bei älteren Versionen von Git funktionieren.

Einfügen eine Datei in einem Multi - ref Geschichte

Wenn Ihr Repository komplexer ist (dh mehr als eine Referenz (Zweige, Tags usw.) hat), müssen Sie wahrscheinlich den Git-Filter-Zweig verwenden . Bevor Sie git filter-branch verwenden , sollten Sie eine Sicherungskopie Ihres gesamten Repositorys erstellen. Ein einfaches Tar- Archiv Ihres gesamten Arbeitsbaums (einschließlich des .git-Verzeichnisses) ist ausreichend. git filter-branch erstellt zwar Sicherungsreferenzen, aber es ist oft einfacher, sich von einer nicht ganz richtigen Filterung zu erholen, indem Sie einfach Ihre löschen.git Verzeichnis und es aus Ihrer Sicherung wiederherstellen.

Hinweis: In den folgenden Beispielen wird git update-index --addanstelle des Befehls der unteren Ebene der Befehl verwendet git add. Sie könnten git add verwenden , aber Sie müssten zuerst die Datei von einem externen Speicherort in den erwarteten Pfad kopieren ( --index-filterführt den Befehl in einem temporären GIT_WORK_TREE aus, der leer ist).

Wenn Sie möchten, dass Ihre neue Datei zu jedem vorhandenen Commit hinzugefügt wird, können Sie Folgendes tun:

new_file=$(git hash-object -w path/to/file)
git filter-branch \
  --index-filter \
    'git update-index --add --cacheinfo 100644 '"$new_file"' path/to/file' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Ich sehe keinen Grund, die Daten der bestehenden Commits mit zu ändern --env-filter 'GIT_AUTHOR_DATE=…'. Wenn Sie es verwendet hätten, hätten Sie es an Bedingungen geknüpft, damit das Datum für jedes Commit neu geschrieben wird.

Wenn Sie möchten, dass Ihre neue Datei erst nach einem vorhandenen Commit („A“) in den Commits angezeigt wird, können Sie Folgendes tun:

file_path=path/to/file
before_commit=$(git rev-parse --verify A)
file_blob=$(git hash-object -w "$file_path")
git filter-branch \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$before_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Wenn Sie möchten, dass die Datei über ein neues Commit hinzugefügt wird, das in die Mitte Ihres Verlaufs eingefügt werden soll, müssen Sie das neue Commit generieren, bevor Sie git filter-branch verwenden und --parent-filterzu git filter-branch hinzufügen :

file_path=path/to/file
before_commit=$(git rev-parse --verify A)

git checkout master
git checkout "$before_commit"
git add "$file_path"
git commit --date='whenever'
new_commit=$(git rev-parse --verify HEAD)
file_blob=$(git rev-parse --verify HEAD:"$file_path")
git checkout -

git filter-branch \
  --parent-filter "sed -e s/$before_commit/$new_commit/g" \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$new_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Sie können auch festlegen, dass die Datei zuerst in einem neuen Root-Commit hinzugefügt wird: Erstellen Sie Ihr neues Root-Commit über die "Orphan" -Methode im Abschnitt " Git-Rebase " (erfassen Sie es new_commit), verwenden Sie das Unbedingte --index-filterund --parent-filterdergleichen "sed -e \"s/^$/-p $new_commit/\"".

Chris Johnsen
quelle
Im ersten Beispiel des Anwendungsfalls "Einfügen einer Datei in einen Multi-Ref-Verlauf": Gibt es eine Möglichkeit, die --index-filtervon zurückgegebenen Commits anzuwenden git rev-list? Im Moment wird der Indexfilter auf eine Untergruppe von Rev-Listen angewendet. Schätzen Sie jeden Einblick.
Igel
@ Hedgehog: In allen Beispielen -- --allfür mehrere Refs werden alle Commits verarbeitet, die von jedem Ref aus erreichbar sind. Das letzte Beispiel zeigt, wie Sie nur bestimmte Commits ändern (testen Sie einfach GIT_COMMIT für alles, was Sie möchten). Um nur eine bestimmte Liste von Commits zu ändern, können Sie die Liste vor dem Filtern speichern (z. B. git rev-list … >/tmp/commits_to_rewrite) und dann die Mitgliedschaft im Filter testen (z if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index …. B. ). Was genau versuchst du zu erreichen? Vielleicht möchten Sie eine neue Frage beginnen, wenn es zu viel ist, um sie in Kommentaren zu erklären.
Chris Johnsen
In meinem Fall habe ich ein kleines Projekt, das ich unter Quellcodeverwaltung stellen wollte. (Ich hatte es nicht getan, weil es ein Solo-Projekt ist und ich nicht entschieden hatte, welches System ich verwenden sollte). Bei meiner "Quellcodeverwaltung" ging es lediglich darum, meinen gesamten Projektbaum in ein neues Verzeichnis zu duplizieren und das Verzeichnis der vorherigen Version umzubenennen. Ich bin neu bei Git (nachdem ich zuvor SCCS, RCS und ein hässliches proprietäres RCS-Derivat verwendet habe, das CVS ähnelt), also mach dir keine Sorgen, dass du mit mir redest. Ich habe den Eindruck, dass die Antwort eine Variation des Abschnitts "Einfügen einer Datei in einen einzelnen Referenzverlauf" beinhaltet. Richtig?
Steve
1
@Steve: Das Szenario "Einzelreferenz" gilt, wenn Ihr Verlauf aus einer einzelnen Entwicklungslinie stammt (dh es wäre sinnvoll, wenn alle Schnappschüsse als aufeinanderfolgende Punkte eines einzelnen linearen Zweigs betrachtet würden). Das Szenario „Multi-Ref“ gilt, wenn Sie mehrere Zweige in Ihrem Verlauf haben (oder hatten). Sofern Ihr Verlauf nicht kompliziert ist ("gegabelte" Versionen für verschiedene Clients, Aufrechterhaltung eines "Bugfix" -Zweigs, während neue Arbeiten bereits in "Entwicklung" durchgeführt wurden usw.), handelt es sich wahrscheinlich um eine "Single-Ref" -Situation. Es scheint jedoch , dass sich Ihre Situation von der der Frage unterscheidet…
Chris Johnsen
1
@Steve: Da Sie eine Reihe historischer Verzeichnis-Snapshots haben - und noch kein Git-Repository haben -, können Sie wahrscheinlich einfach import-directories.perlaus Git contrib/(oder import-tars.perloder import-zips.py...) ein neues Git-Repository aus Ihren Snapshots erstellen (auch mit) "Alte" Zeitstempel). Die rebase/ filter-branchTechniken in meiner Antwort werden nur benötigt, wenn Sie eine Datei einfügen möchten, die aus dem Verlauf eines vorhandenen Repositorys „weggelassen“ wurde.
Chris Johnsen
119

Sie können das Commit wie gewohnt erstellen. Wenn Sie jedoch ein Commit durchführen, legen Sie die Umgebungsvariablen GIT_AUTHOR_DATEund GIT_COMMITTER_DATEdie entsprechenden Datumszeiten fest.

Dadurch wird das Commit natürlich an der Spitze Ihres Zweigs ausgeführt (dh vor dem aktuellen HEAD-Commit). Wenn du es im Repo weiter zurückschieben willst, musst du ein bisschen ausgefallen sein. Angenommen, Sie haben diese Geschichte:

o--o--o--o--o

Und Sie möchten, dass Ihr neues Commit (markiert als "X") als zweites angezeigt wird :

o--X--o--o--o--o

Am einfachsten ist es, vom ersten Commit zu verzweigen, das neue Commit hinzuzufügen und dann alle anderen Commits über das neue zu setzen. Wie so:

$ git checkout -b new_commit $desired_parent_of_new_commit
$ git add new_file
$ GIT_AUTHOR_DATE='your date' GIT_COMMITTER_DATE='your date' git commit -m 'new (old) files'
$ git checkout master
$ git rebase new_commit
$ git branch -d new_commit
Mipadi
quelle
25
Ich finde immer diese gute Antwort, muss mich dann aber beeilen, um das Datumsformat zu finden. Hier ist es also für das nächste Mal. 'Fr 26 Jul 19:32:10 2013 -0400'
MeBigFatGuy
94
GIT_AUTHOR_DATE='Fri Jul 26 19:32:10 2013 -0400' GIT_COMMITTER_DATE='Fri Jul 26 19:32:10 2013 -0400' git commit
Xeoncross
15
Es gibt noch mehr, zum Beispiel die eher mnemonische 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian
3
Übrigens gibt der Teil '-0400' den Zeitzonenversatz an. Achten Sie darauf, dass Sie es richtig auswählen ... denn wenn nicht, ändert sich Ihre Zeit basierend darauf. Zum Beispiel musste ich bei mir, der ich in Chicago lebe, '-0600' (North America CST) wählen. Sie finden die Codes hier: timeanddate.com/time/zones
evaldeslacasa
2
Da das Datum wiederholt werden muss, fiel es mir leichter, eine weitere Variable zu erstellen:THE_TIME='2019-03-30T8:20:00 -0500' GIT_AUTHOR_DATE=$THE_TIME GIT_COMMITTER_DATE=$THE_TIME git commit -m 'commit message here'
Frank Henard
118

Ich weiß, dass diese Frage ziemlich alt ist, aber genau das hat bei mir funktioniert:

git commit --date="10 day ago" -m "Your commit message" 
Hyder B.
quelle
16
Das hat perfekt funktioniert. Ich bin überrascht, dass --dateauch das vom Menschen lesbare relative Datumsformat unterstützt wird.
ZitRo
@ ZitRo warum nicht 10 Tage?
Alex78191
10
Warnung: git --dateÄndert nur das $ GIT_AUTHOR_DATE. Abhängig von den Umständen wird das aktuelle Datum angezeigt, das mit dem Commit verbunden ist ($ GIT_COMMITTER_DATE)
Guido U. Draheim,
3
Tagging auf das, was @ GuidoU.Draheim gesagt hat. Sie können die vollständigen Details eines Commits mit überprüfen git show <commit-hash> --format=fuller. Dort sehen Sie dieAuthorDate das Datum, das Sie angegeben haben, aber CommitDatedas tatsächliche Datum des Commits.
d4nyll
Es gibt also keine Möglichkeit, das CommitDate zu ändern oder ein vollständig in der Vergangenheit datiertes Commit durchzuführen. @ d4nyll
Rahul
35

In meinem Fall hatte ich im Laufe der Zeit eine Reihe von Versionen von myfile als myfile_bak, myfile_old, myfile_2010, Backups / myfile usw. gespeichert. Ich wollte den Verlauf von myfile anhand ihrer Änderungsdaten in git einfügen. So benennen Sie die älteste bis Meinedat, git add myfiledanngit commit --date=(modification date from ls -l) myfile umbenennen nächsten älteste Meinedat, begeht eine andere git mit --date, wiederholt ...

Um dies etwas zu automatisieren, können Sie shell-foo verwenden, um die Änderungszeit der Datei abzurufen. Ich habe mit ls -lund angefangencut , aber stat (1) ist direkter

git commit --date = "` stat -c% y myfile `" myfile

Skifahrerseite
quelle
1
Nett. Ich hatte sicherlich Dateien vor meinen Git-Tagen , für die ich die Zeit für die Dateiänderung verwenden wollte.
Setzt
Von git-scm.com/docs/git-commit: --date" Überschreiben Sie das im Commit verwendete Autorendatum." git log's Date scheint das AuthorDate zu sein, git log --pretty=fullerzeigt sowohl AuthorDate als auch CommitDate an.
Skifahrerseite
16
Die git commitOption --dateändert nur die GIT_AUTHOR_DATE, nicht die GIT_COMMITTER_DATE. Wie das Pro Git Book erklärt: "Der Autor ist die Person, die das Werk ursprünglich geschrieben hat, während der Committer die Person ist, die das Werk zuletzt angewendet hat." Im Zusammenhang mit Datumsangaben GIT_AUTHOR_DATEist dies das Datum, an dem die Datei geändert wurde, während dies GIT_COMMITTER_DATEdas Datum ist, an dem sie festgeschrieben wurde. Hierbei ist zu beachten, dass git logAutorendaten standardmäßig als "Datum" angezeigt werden, dann jedoch Festschreibungsdaten zum Filtern verwendet werden, wenn eine --sinceOption angegeben wird.
Christopher
Es gibt keine -c Option für stat in OS X 10.11.1
Thinsoldier
Das Äquivalent für stat -c %yin macOS (und anderen BSD-Varianten) ist stat -f %m.
Sieger
21

Im Folgenden ist das, was ich Änderungen zu übernehmen verwenden , um auf foozu N=1Tagen in der Vergangenheit:

git add foo
git commit -m "Update foo"
git commit --amend --date="$(date -v-1d)"

Wenn Sie sich auf ein noch älteres Datum festlegen möchten, z. B. vor 3 Tagen, ändern Sie einfach das dateArgument : date -v-3d.

Das ist sehr nützlich, wenn Sie beispielsweise gestern vergessen haben, etwas zu begehen.

UPDATE : Akzeptiert--date auch Ausdrücke wie --date "3 days ago"oder sogar --date "yesterday". Wir können es also auf einen Zeilenbefehl reduzieren:

git add foo ; git commit --date "yesterday" -m "Update"
Vilson Vieira
quelle
6
Warnung: git --dateÄndert nur $ GIT_AUTHOR_DATE. Abhängig von den Umständen wird das aktuelle Datum für das Commit angezeigt ($ GIT_COMMITTER_DATE)
Guido U. Draheim,
Das Mischen von 2 Ansätzen passt fast perfekt zusammen:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej
Genial! Gleicher Befehl mit einem von Menschen lesbaren Datumgit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
Pixelbrackets
16

In meinem Fall ist mein Git-Prozess bei Verwendung der Option --date abgestürzt. Vielleicht habe ich etwas Schreckliches getan. Infolgedessen wurde eine index.lock-Datei angezeigt. Also habe ich die .lock-Dateien manuell aus dem .git-Ordner gelöscht und ausgeführt, damit alle geänderten Dateien in verstrichenen Daten festgeschrieben werden, und diesmal hat es funktioniert. Vielen Dank für alle Antworten hier.

git commit --date="`date --date='2 day ago'`" -am "update"
JstRoRR
quelle
5
Siehe Kommentar oben aufgit commit --date . Außerdem sollte Ihre Beispiel-Commit-Nachricht geändert werden, um schlechte einzeilige Nachrichten wie diese zu unterbinden. @JstRoRR
Christopher
4
Warnung: git --dateÄndert nur $ GIT_AUTHOR_DATE. Abhängig von den Umständen wird das aktuelle Datum für das Commit angezeigt ($ GIT_COMMITTER_DATE)
Guido U. Draheim,
9

Um ein , das aussieht begehen , wie es in der Vergangenheit getan wurde , um Sie setzen müssen beide GIT_AUTHOR_DATEund GIT_COMMITTER_DATE:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit -m '...'

wo date -d'...'kann genaues Datum wie 2019-01-01 12:00:00oder relativ wie sein5 months ago 24 days ago .

Um beide Daten im Git-Protokoll anzuzeigen, verwenden Sie:

git log --pretty=fuller

Dies funktioniert auch für Merge-Commits:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git merge <branchname> --no-ff
mx0
quelle
2

Sie können jederzeit ein Datum auf Ihrem Computer ändern, ein Commit durchführen, dann das Datum zurück ändern und drücken.

Nik
quelle
1
Ich denke, das ist nicht die richtige Praxis, es kann einige unbekannte Probleme verursachen
Vino
2

Oder verwenden Sie einfach einen Fake-Git-Verlauf , um ihn für einen bestimmten Datenbereich zu generieren.

rel1x
quelle