Jak w przeszłości popełnić Git?

222

Konwertuję wszystko na Git na własny użytek i znalazłem kilka starych wersji pliku już w repozytorium. Jak zapisać historię w prawidłowej kolejności zgodnie z „datą modyfikacji” pliku, aby mieć dokładną historię pliku?

Powiedziano mi, że coś takiego działa:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  
nieznany
źródło
Krótkie i proste odpowiedzi: stackoverflow.com/a/34639957/2708266
Yash
33
Zastanawiam się, czy ludzie, którzy szukają odpowiedzi na to pytanie, chcą po prostu utrzymać ciągłość „
pasji
1
@ZitRo tak. I wystarczy ustawienie git commit --date="xxx day ago" -m "yyy"do tego celu, jeśli ktoś się zastanawia.
Vlas Sokolov
alexpeattie.com/blog/working-with-dates-in-git : jeśli szukasz delikatnego wyjaśnienia
thepurpleowl

Odpowiedzi:

198

Porada, którą otrzymałeś, jest wadliwa. Bezwarunkowe ustawienie GIT_AUTHOR_DATE w --env-filterspowoduje przepisanie daty każdego zatwierdzenia. Poza tym użycie git commit w środku byłoby niezwykłe --index-filter.

Masz tutaj do czynienia z wieloma niezależnymi problemami.

Określanie dat innych niż „teraz”

Każde zatwierdzenie ma dwie daty: datę autora i datę osoby zatwierdzającej. Możesz zastąpić każdą z nich, podając wartości za pomocą zmiennych środowiskowych GIT_AUTHOR_DATE i GIT_COMMITTER_DATE dla każdego polecenia, które zapisuje nowe zatwierdzenie. Zobacz „Formaty daty” w git-commit (1) lub poniżej:

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

Jedynym poleceniem, które zapisuje nowe zatwierdzenie podczas normalnego użytkowania, jest polecenie git . Posiada również --dateopcję, która pozwala bezpośrednio określić datę autora. Twoje przewidywane użycie obejmuje git filter-branch --env-filterrównież wykorzystanie wyżej wymienionych zmiennych środowiskowych (są one częścią „env”, po której nazwa jest nazwana; patrz „Opcje” w git-filter-branch (1) i leżące u jej podstaw polecenie „hydrauliczne” git-commit -drzewo (1) .

Wstawianie pliku do historii pojedynczego odwołania

Jeśli twoje repozytorium jest bardzo proste (tzn. Masz tylko jedną gałąź, bez tagów), prawdopodobnie możesz użyć git rebase do wykonania tej pracy.

W poniższych poleceniach użyj nazwy obiektu (skrót SHA-1) zatwierdzenia zamiast „A”. Nie zapomnij użyć jednej z metod „przesłonięcia daty” podczas uruchamiania git commit .

---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

Jeśli chcesz zaktualizować A, aby zawierał nowy plik (zamiast tworzenia nowego zatwierdzenia w miejscu, w którym został dodany), użyj git commit --amendzamiast git commit. Wynik wyglądałby następująco:

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

Powyższe działa tak długo, jak można nazwać zatwierdzenie, które powinno być rodzicem nowego zatwierdzenia. Jeśli naprawdę chcesz, aby nowy plik został dodany za pomocą nowego zatwierdzenia głównego (bez rodziców), potrzebujesz czegoś innego:

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 --orphanjest stosunkowo nowy (Git 1.7.2), ale istnieją inne sposoby robienia tego samego, co działa na starszych wersjach Gita.

Wstawianie pliku do historii wielu referencji

Jeśli twoje repozytorium jest bardziej złożone (tzn. Ma więcej niż jeden odnośnik (gałęzie, tagi itp.)), Prawdopodobnie będziesz musiał użyć git filter-branch . Przed użyciem git filter-branch powinieneś wykonać kopię zapasową całego repozytorium. Wystarczy proste archiwum tar całego działającego drzewa (w tym katalogu .git). git filter-branch wykonuje kopie zapasowe kopii zapasowej, ale często łatwiej jest odzyskać dane po niezupełnie właściwym filtrowaniu, po prostu usuwając .gitkatalog i przywracając go z kopii zapasowej.

Uwaga: Poniższe przykłady używają polecenia niższego poziomu git update-index --addzamiast git add. Możesz użyć git add , ale najpierw musisz skopiować plik z jakiejś lokalizacji zewnętrznej do oczekiwanej ścieżki ( --index-filteruruchamia polecenie w tymczasowym drzewie GIT_WORK_TREE, które jest puste).

Jeśli chcesz, aby nowy plik był dodawany do każdego istniejącego zatwierdzenia, możesz to zrobić:

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

Naprawdę nie widzę powodu, by zmieniać daty istniejących zatwierdzeń --env-filter 'GIT_AUTHOR_DATE=…'. Gdybyś go użył, uczyniłbyś to warunkowym, aby przepisał datę każdego zatwierdzenia.

Jeśli chcesz, aby nowy plik pojawiał się tylko w zatwierdzeniach po pewnym istniejącym zatwierdzeniu („A”), możesz to zrobić:

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

Jeśli chcesz, aby plik został dodany za pomocą nowego zatwierdzenia, które ma być wstawione w środku twojej historii, musisz wygenerować nowy zatwierdzenie przed użyciem git filter-branch i dodać --parent-filterdo git filter-branch :

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

Możesz również zaaranżować, aby plik był dodawany po raz pierwszy w nowym głównym zatwierdzeniu: utwórz nowy główny zatwierdzenie za pomocą metody „osieroconej” z sekcji git rebase (przechwyć new_commit), użyj bezwarunkowego --index-filteri --parent-filterpodobnego "sed -e \"s/^$/-p $new_commit/\"".

Chris Johnsen
źródło
W pierwszym przykładzie przypadku użycia „Wstawianie pliku do historii wielu referencji”: Czy istnieje sposób na --index-filterzastosowanie do zwróconych zatwierdzeń git rev-list? W tej chwili widzę filtr indeksu zastosowany do podzestawu rev-list. Doceń każdy wgląd.
Jeż
@ Jeż: Wszystkie przykłady „wielokrotnego ref” używają -- --alldo przetwarzania wszystkich zatwierdzeń dostępnych z dowolnego ref. Ostatni przykład pokazuje, jak zmienić tylko niektóre zatwierdzenia (wystarczy przetestować GIT_COMMIT pod kątem tego, co chcesz). Aby zmienić tylko określoną listę zatwierdzeń, możesz zapisać listę przed filtrowaniem (np. git rev-list … >/tmp/commits_to_rewrite), A następnie przetestować członkostwo w filtrze (np if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index ….). Co dokładnie próbujesz osiągnąć? Możesz zacząć nowe pytanie, jeśli jest zbyt wiele do wyjaśnienia w komentarzach.
Chris Johnsen,
W moim przypadku mam mały projekt, który chciałem poddać kontroli źródła. (Nie zrobiłem tego, ponieważ jest to projekt solowy i nie zdecydowałem, którego systemu użyć). Moja „kontrola źródła” polegała tylko na skopiowaniu całego drzewa projektu do nowego katalogu i zmianie nazwy katalogu poprzedniej wersji. Jestem nowy w Git (po wcześniejszym użyciu SCCS, RCS i brzydkiej zastrzeżonej pochodnej RCS, która przypomina CVS), więc nie martw się o rozmowę ze mną. Mam wrażenie, że odpowiedź obejmuje odmianę sekcji „Wstawianie pliku do historii pojedynczego odwołania”. Poprawny?
Steve,
1
@ Steve: Scenariusz „pojedynczego odniesienia” ma zastosowanie, jeśli twoja historia pochodzi z jednej linii rozwoju (tzn. Miałoby sens, gdyby wszystkie migawki były postrzegane jako kolejne punkty jednej, liniowej gałęzi). Scenariusz „wielu referencji” ma zastosowanie, jeśli masz (lub miałeś) wiele oddziałów w swojej historii. O ile twoja historia nie jest skomplikowana (wersje „rozwidlone” dla różnych klientów, utrzymywała gałąź „poprawki błędów”, podczas gdy nowa praca pojawiła się ponownie w „rozwoju” itp.), Prawdopodobnie patrzysz na sytuację „pojedynczego ref”. Wygląda jednak na to, że Twoja sytuacja różni się od pytania…
Chris Johnsen,
1
@ Steve: Ponieważ masz serię historycznych migawek katalogów - a nie masz jeszcze repozytorium Git - prawdopodobnie możesz po prostu użyć import-directories.perlGit contrib/(lub import-tars.perl, lub import-zips.py…), aby utworzyć nowe repozytorium Git z twoich migawek (nawet z „Stare” znaczniki czasu). W rebase/ filter-branchtechniki w mojej odpowiedzi są potrzebne tylko jeśli chcesz wstawić plik, który został „left out” historii istniejącego składowiska.
Chris Johnsen
118

Możesz utworzyć zatwierdzenie jak zwykle, ale kiedy zatwierdzasz, ustaw zmienne środowiskowe GIT_AUTHOR_DATEi GIT_COMMITTER_DATEodpowiednie czasy danych.

Oczywiście spowoduje to zatwierdzenie na końcu gałęzi (tj. Przed bieżącym zatwierdzeniem HEAD). Jeśli chcesz odsunąć go z powrotem w repozytorium, musisz się trochę rozkoszować. Powiedzmy, że masz tę historię:

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

I chcesz, aby twoje nowe zatwierdzenie (oznaczone jako „X”) pojawiło się na drugim miejscu :

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

Najłatwiejszym sposobem jest rozgałęzienie od pierwszego zatwierdzenia, dodanie nowego zatwierdzenia, a następnie zmiana bazy wszystkich innych zatwierdzeń na nowym. Tak jak:

$ 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
źródło
25
zawsze znajduję tę dobrą odpowiedź, ale potem muszę pobiegać, aby znaleźć format daty, więc tutaj jest następny raz „Pt 26 lipca 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
Jest więcej, na przykład raczej mnemoniczny 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian
3
Nawiasem mówiąc, część „-0400” określa przesunięcie strefy czasowej. Pamiętaj, aby wybrać go poprawnie ... ponieważ jeśli nie, Twój czas zmieni się na tej podstawie. Na przykład w przypadku mojej osoby mieszkającej w Chicago musiałem wybrać „-0600” (CST w Ameryce Północnej). Możesz znaleźć kody tutaj: timeanddate.com/time/zones
evaldeslacasa
2
ponieważ datę trzeba powtórzyć, łatwiej było mi stworzyć inną zmienną: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

Wiem, że to pytanie jest dość stare, ale to właśnie dla mnie zadziałało:

git commit --date="10 day ago" -m "Your commit message" 
Hyder B.
źródło
16
To działało idealnie. Jestem zaskoczony, że --dateobsługuje także format daty względnej czytelny dla człowieka.
ZitRo
@ZitRo dlaczego nie 10 dni?
Alex78191
10
Ostrzeżenie: git --datezmodyfikuje tylko $ GIT_AUTHOR_DATE ... więc w zależności od okoliczności zobaczysz aktualną datę dołączoną do zatwierdzenia ($ GIT_COMMITTER_DATE)
Guido U. Draheim
3
Tagowanie do tego, co powiedział @ GuidoU.Draheim. Możesz sprawdzić pełne szczegóły zatwierdzenia za pomocą git show <commit-hash> --format=fuller. Tam zobaczysz AuthorDatedatę, którą podałeś, ale CommitDaterzeczywistą datę zatwierdzenia.
d4nyll
Więc nie ma sposobu, aby zmienić CommitDate lub dokonać całkowicie przeszłego zatwierdzenia? @ d4nyll
Rahul
35

W moim przypadku w czasie I uratował kilka wersji myfile jak myfile_bak, myfile_old, myfile_2010, tworzenie kopii zapasowych / myfile itp chciałem umieścić historię myfile w git przy użyciu ich daty modyfikacji. Więc zmień nazwę najstarszego na myfile, git add myfilea następnie git commit --date=(modification date from ls -l) myfilezmień nazwę następnego najstarszego na myfile, kolejne polecenie git z opcją --data, powtórz ...

Aby to nieco zautomatyzować, możesz użyć shell-foo, aby uzyskać czas modyfikacji pliku. Zacząłem od ls -li cut, ale stat (1) jest bardziej bezpośredni

git commit --date = "` stat -c% y mój_plik `" mój_plik

strona dla narciarzy
źródło
1
Miły. Z pewnością miałem pliki sprzed moich dni , dla których chciałem użyć zmodyfikowanego czasu. Czy to jednak nie tylko ustawia datę zatwierdzenia (nie datę autora?)
Xeoncross,
From git-scm.com/docs/git-commit: --date„Zastąp datę autora użytą w zatwierdzeniu”. git logData wydaje się być datą autora, git log --pretty=fullerpokazuje zarówno datę ważności, jak i datę zatwierdzenia.
narciarzy
16
git commitOpcja --datebędzie modyfikować tylko GIT_AUTHOR_DATEnie GIT_COMMITTER_DATE. Jak wyjaśnia Pro Git Book : „Autorem jest osoba, która pierwotnie napisała dzieło, a osoba odpowiedzialna to osoba, która jako ostatnia zastosowała dzieło”. W kontekście dat GIT_AUTHOR_DATEjest to data, w której plik został zmieniony, a GIT_COMMITTER_DATEdata, w której został zatwierdzony. Ważne jest, aby pamiętać, że domyślnie git logwyświetla daty autora jako „Data”, ale następnie używa dat zatwierdzania do filtrowania, gdy podano --sinceopcję.
Christopher
W OS X 10.11.1 nie ma opcji -c dla statystyki
cieńszy żołnierz
Odpowiednikiem stat -c %yw macOS (i innych wariantach BSD) jest stat -f %m.
zwycięzca
21

Oto co mogę użyć, aby dokonać zmian na foodo N=1dni w przeszłości:

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

Jeśli chcesz, aby zobowiązać się do jeszcze starszej daty, powiedzmy 3 dni z powrotem, wystarczy zmienić dateargumentu date -v-3d.

Jest to bardzo przydatne, gdy na przykład zapomnisz coś popełnić.

AKTUALIZACJA : --dateakceptuje również wyrażenia takie jak --date "3 days ago"lub nawet --date "yesterday". Możemy więc zredukować to do jednego wiersza polecenia:

git add foo ; git commit --date "yesterday" -m "Update"
Vilson Vieira
źródło
6
Ostrzeżenie: git --datezmodyfikuje tylko $ GIT_AUTHOR_DATE ... więc w zależności od okoliczności zobaczysz aktualną datę dołączoną do zatwierdzenia ($ GIT_COMMITTER_DATE)
Guido U. Draheim
mieszanie dwóch podejść razem sprawia, że ​​prawie idealnie pasuje:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej
Niesamowite! To samo polecenie z datą czytelną dla człowiekagit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets
16

W moim przypadku podczas korzystania z opcji --date mój proces git się zawiesił. Być może zrobiłem coś strasznego. W rezultacie pojawił się plik index.lock. Więc ręcznie usunąłem pliki .lock z folderu .git i wykonałem, aby wszystkie zmodyfikowane pliki zostały zatwierdzone w minionych terminach i tym razem zadziałało. Dzięki za wszystkie odpowiedzi tutaj.

git commit --date="`date --date='2 day ago'`" -am "update"
JstRoRR
źródło
5
Zobacz komentarz powyżejgit commit --date . Również twój przykładowy komunikat zatwierdzenia powinien zostać zmieniony, aby zniechęcić słabe wiadomości jednowierszowe, takie jak te @JstRoRR
Christopher
4
Ostrzeżenie: git --datezmodyfikuje tylko $ GIT_AUTHOR_DATE ... więc w zależności od okoliczności zobaczysz aktualną datę dołączoną do zatwierdzenia ($ GIT_COMMITTER_DATE)
Guido U. Draheim
9

Aby dokonać zatwierdzenia, które wygląda tak, jakby było zrobione w przeszłości, musisz ustawić oba GIT_AUTHOR_DATEi GIT_COMMITTER_DATE:

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

gdzie date -d'...'może być dokładna data jak 2019-01-01 12:00:00lub względna jak 5 months ago 24 days ago.

Aby zobaczyć obie daty w dzienniku git, użyj:

git log --pretty=fuller

Działa to również w przypadku zatwierdzeń scalania:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git merge <branchname> --no-ff
mx0
źródło
2

Zawsze możesz zmienić datę na komputerze, dokonać zatwierdzenia, a następnie zmienić datę z powrotem i przesłać.

Nik
źródło
1
Myślę, że to nie jest właściwa praktyka, może powodować pewne nieznane problemy
Vino,