Come posso effettuare un commit Git in passato?


222

Sto convertendo tutto in Git per uso personale e ho trovato alcune vecchie versioni di un file già nel repository. Come posso eseguire il commit nella cronologia nell'ordine corretto in base alla "data di modifica" del file in modo da avere una cronologia accurata del file?

Mi è stato detto che qualcosa del genere avrebbe funzionato:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  

Risposte brevi e semplici: stackoverflow.com/a/34639957/2708266
Yash

33
Mi chiedo se le persone che cercano la risposta vogliono solo mantenere la loro "serie di contributi" GitHub continua in questo modo
ZitRo,

1
@ZitRo sì. E semplicemente l'impostazione git commit --date="xxx day ago" -m "yyy"è sufficiente a tale scopo se qualcuno si sta chiedendo.
Vlas Sokolov

Risposte:


198

Il consiglio che ti è stato dato è imperfetto. L'impostazione incondizionata di GIT_AUTHOR_DATE in --env-filterriscriverebbe la data di ogni commit. Inoltre, sarebbe insolito usare git commit all'interno --index-filter.

Qui hai a che fare con più problemi indipendenti.

Specificare date diverse da "adesso"

Ogni commit ha due date: la data dell'autore e la data del committer. È possibile sovrascrivere ciascuno fornendo valori tramite le variabili di ambiente GIT_AUTHOR_DATE e GIT_COMMITTER_DATE per qualsiasi comando che scrive un nuovo commit. Vedi "Formati di data" in git-commit (1) o sotto:

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

L'unico comando che scrive un nuovo commit durante l'uso normale è git commit . Ha anche --dateun'opzione che ti consente di specificare direttamente la data dell'autore. L'utilizzo previsto include git filter-branch --env-filteranche l'utilizzo delle variabili di ambiente sopra menzionate (fanno parte del "env" dopo il quale l'opzione è denominata; vedere "Opzioni" in git-filter-branch (1) e il comando "idraulico" sottostante git-commit -tree (1) .

Inserimento di un file in un singolo ref Storia

Se il tuo repository è molto semplice (cioè hai un solo ramo, nessun tag), allora puoi probabilmente usare git rebase per fare il lavoro.

Nei seguenti comandi, utilizzare il nome dell'oggetto (hash SHA-1) del commit anziché "A". Non dimenticare di usare uno dei metodi di "sostituzione data" quando esegui 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

Se si desidera aggiornare A per includere il nuovo file (anziché creare un nuovo commit nel punto in cui è stato aggiunto), utilizzare git commit --amendinvece di git commit. Il risultato sarebbe simile al seguente:

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

Quanto sopra funziona finché puoi nominare il commit che dovrebbe essere il genitore del tuo nuovo commit. Se vuoi davvero che il tuo nuovo file venga aggiunto tramite un nuovo commit root (senza genitori), allora hai bisogno di qualcosa di leggermente diverso:

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 --orphanè relativamente nuovo (Git 1.7.2), ma ci sono altri modi per fare la stessa cosa che funzionano con le versioni precedenti di Git.

Inserimento di un file in un Multi- ref Storia

Se il tuo repository è più complesso (cioè ha più di un ref (rami, tag, ecc.)), Probabilmente dovrai usare git filter-branch . Prima di utilizzare git filter-branch , è necessario creare una copia di backup dell'intero repository. È sufficiente un semplice archivio tar dell'intero albero di lavoro (inclusa la directory .git). git filter-branch crea riferimenti di backup, ma spesso è più facile recuperare da un filtro non proprio corretto semplicemente cancellando la .gitdirectory e ripristinandola dal backup.

Nota: gli esempi seguenti utilizzano il comando di livello inferiore git update-index --addanziché git add. Potresti usare git add , ma prima devi copiare il file da una posizione esterna nel percorso previsto ( --index-filteresegue il suo comando in un GIT_WORK_TREE temporaneo che è vuoto).

Se vuoi che il tuo nuovo file venga aggiunto a ogni commit esistente, puoi farlo:

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

Non vedo davvero alcun motivo per cambiare le date degli commit esistenti --env-filter 'GIT_AUTHOR_DATE=…'. Se lo avessi usato, lo avresti reso condizionale in modo da riscrivere la data per ogni commit.

Se vuoi che il tuo nuovo file appaia solo nei commit dopo alcuni commit esistenti ("A"), puoi farlo:

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

Se si desidera aggiungere il file tramite un nuovo commit da inserire nel mezzo della cronologia, sarà necessario generare il nuovo commit prima di utilizzare git filter-branch e aggiungere --parent-filtera 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

Puoi anche organizzare che il file venga aggiunto per la prima volta in un nuovo commit di root: crea il tuo nuovo commit di root tramite il metodo "orfano" dalla sezione git rebase (acquisiscilo in new_commit), usa l'incondizionato --index-filtere --parent-filtersimili "sed -e \"s/^$/-p $new_commit/\"".


Nel primo esempio del caso d'uso, "Inserimento di un file in una cronologia multi-ref": esiste un modo per --index-filterapplicare i commit restituiti git rev-list? Al momento sto vedendo il filtro indice applicato a un sottoinsieme di rev-list. Apprezzo qualsiasi intuizione.
Riccio

@Hedgehog: tutti gli esempi "multi-ref" usano -- --allper elaborare tutti i commit raggiungibili da qualsiasi ref. L'ultimo esempio mostra come modificare solo alcuni commit (basta testare GIT_COMMIT per quello che ti piace). Per modificare solo un elenco specifico di commit, è possibile salvare l'elenco prima di filtrare (ad es. git rev-list … >/tmp/commits_to_rewrite), Quindi verificare l'appartenenza al filtro (ad es if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index ….). Cosa, esattamente, stai cercando di realizzare? Potresti voler iniziare una nuova domanda se è troppo da spiegare nei commenti.
Chris Johnsen,

Nel mio caso, ho un piccolo progetto che intendevo mettere sotto il controllo del codice sorgente. (Non l'avevo fatto, perché è un progetto solista e non avevo deciso quale sistema usare). Il mio "controllo del codice sorgente" era solo una questione di duplicare l'intero albero del progetto in una nuova directory e rinominare la directory della versione precedente. Sono nuovo di Git (dopo aver usato SCCS, RCS e un brutto derivato RCS proprietario che assomiglia a CVS), quindi non preoccuparti di parlarmi. Ho l'impressione che la risposta implichi una variazione della sezione "Inserimento di un file in una cronologia di riferimento singola". Corretta?
Steve,

1
@Steve: lo scenario "single-ref" si applica se la tua storia proveniva da una singola linea di sviluppo (vale a dire che avrebbe senso se tutte le istantanee fossero visualizzate come punti successivi di un singolo ramo lineare). Lo scenario "multi-ref" si applica se hai (o hai avuto) più rami nella tua cronologia. A meno che la tua storia non sia complicata (versioni "biforcate" per diversi client, mantenuto un ramo "bugfix" mentre sono avvenuti nuovi lavori nello "sviluppo", ecc.), Allora probabilmente stai osservando una situazione di "riferimento singolo". Tuttavia , sembra che la tua situazione differisca da quella della domanda ...
Chris Johnsen,

1
@Steve: Dal momento che hai una serie di snapshot di directory storiche — e non hai già un repository Git — allora probabilmente puoi semplicemente usare import-directories.perlda Git contrib/(o import-tars.perl, o import-zips.py...) per creare un nuovo repository Git dai tuoi snapshot (anche con "Vecchi" timestamp). Le tecniche rebase/ filter-branchnella mia risposta sono necessarie solo se si desidera inserire un file che è stato "lasciato fuori" dalla cronologia di un repository esistente.
Chris Johnsen,

119

È possibile creare il commit come al solito, ma quando si esegue il commit, impostare le variabili di ambiente GIT_AUTHOR_DATEe GIT_COMMITTER_DATEgli orari dei dati appropriati.

Ovviamente, questo farà il commit sulla punta del tuo ramo (cioè, di fronte all'attuale HEAD commit). Se vuoi spingerlo più indietro nel repository, devi avere un po 'di fantasia. Diciamo che hai questa storia:

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

E vuoi che il tuo nuovo commit (contrassegnato come "X") appaia secondo :

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

Il modo più semplice sarebbe quello di diramare dal primo commit, aggiungere il nuovo commit, quindi rifare tutti gli altri commit in cima a quello nuovo. Così:

$ 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

25
trovo sempre questa buona risposta, ma poi devo scappare per trovare il formato della data, quindi eccolo per la prossima volta 'Ven 26 Lug 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
C'è di più, ad esempio il mnemonico 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian,

3
A proposito, la parte '-0400' specifica l'offset del fuso orario. Sii consapevole di selezionarlo correttamente ... perché in caso contrario, il tuo tempo cambierà in base a quello. Ad esempio, nel caso di me stesso, che vive a Chicago, ho dovuto scegliere "-0600" (North America CST). Puoi trovare i codici qui: timeanddate.com/time/zones
evaldeslacasa

2
poiché la data deve essere ripetuta, ho trovato più facile creare un'altra variabile: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

So che questa domanda è piuttosto vecchia, ma è quello che ha funzionato per me:

git commit --date="10 day ago" -m "Your commit message" 

16
Questo ha funzionato perfettamente. Sono sorpreso che --datesupporti anche il formato data relativo leggibile dall'uomo.
ZitRo,

@ZitRo perché non 10 giorni?
Alex78191,

10
Attenzione: git --datemodificherà solo $ GIT_AUTHOR_DATE ... quindi a seconda delle circostanze vedrai la data corrente allegata al commit ($ GIT_COMMITTER_DATE)
Guido U. Draheim

3
Tagging su ciò che ha detto @ GuidoU.Draheim. Puoi controllare i dettagli completi di un commit usando git show <commit-hash> --format=fuller. Lì vedrai AuthorDatela data specificata, ma CommitDateè la data effettiva del commit.
d4nyll,

Quindi non c'è modo di cambiare CommitDate o fare un commit completamente datato? @ d4nyll
Rahul,

35

Nel mio caso nel corso del tempo avevo salvato un sacco di versioni di myfile come myfile_bak, myfile_old, myfile_2010, backup / myfile ecc. Volevo mettere la storia di myfile in git usando le loro date di modifica. Quindi rinominare il più vecchio in myfile, git add myfilequindi git commit --date=(modification date from ls -l) myfilerinominare il più vecchio in myfile, un altro comando git con --date, ripetere ...

Per automatizzare un po 'questo, puoi usare shell-foo per ottenere il tempo di modifica del file. Ho iniziato con ls -lecut , ma stat (1) è più diretto

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


1
Bello. Sicuramente ho avuto dei file precedenti ai miei giorni git per i quali volevo usare il tempo modificato. Tuttavia, questo non imposta solo la data di commit (non la data dell'autore?)
Xeoncross,

Da git-scm.com/docs/git-commit: --date"Sostituisci la data dell'autore utilizzata nel commit." git logLa data sembra essere la AuthorDate, git log --pretty=fullermostra sia la AuthorDate che la CommitDate.
skierpage,

16
L' git commitopzione --datemodificherà solo il GIT_AUTHOR_DATE, no GIT_COMMITTER_DATE. Come spiega Pro Git Book : "L'autore è la persona che ha originariamente scritto l'opera, mentre il committer è la persona che ha applicato per l'ultima volta l'opera". Nel contesto delle date, GIT_AUTHOR_DATEè la data in cui il file è stato modificato, mentre GIT_COMMITTER_DATEè la data in cui è stato eseguito il commit. È importante qui notare che, per impostazione predefinita, git logvisualizza le date dell'autore come "Data" ma utilizza quindi le date di commit per il filtro quando viene data --sinceun'opzione.
Christopher,

Non esiste alcuna opzione -c per stat in OS X 10.11.1
tholdoldier

L'equivalente per stat -c %yin macOS (e altre varianti di BSD) è stat -f %m.
vincitore

21

Quello che segue è ciò che utilizzo per eseguire il commit delle modifiche fooai N=1giorni passati:

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

Se si vuole impegnarsi per una data ancora più antica, dire 3 giorni indietro, basta cambiare l' dateargomento: date -v-3d.

È molto utile quando ti dimentichi di fare qualcosa ieri, per esempio.

AGGIORNAMENTO : --dateaccetta anche espressioni simili --date "3 days ago"o pari --date "yesterday". Quindi possiamo ridurlo a un comando di riga:

git add foo ; git commit --date "yesterday" -m "Update"

6
Attenzione: git --datemodificherà solo $ GIT_AUTHOR_DATE ... quindi, a seconda delle circostanze, vedrai la data corrente allegata al commit ($ GIT_COMMITTER_DATE)
Guido U. Draheim,

mescolando 2 approcci insieme si adatta quasi perfettamente:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej

Eccezionale! Stesso comando con una data leggibile dall'uomogit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets,

16

Nel mio caso, durante l'utilizzo dell'opzione --date, il mio processo git si è bloccato. Forse ho fatto qualcosa di terribile. E di conseguenza è apparso un file index.lock. Così ho cancellato manualmente i file .lock dalla cartella .git ed eseguito, per tutti i file modificati da salvare nelle date passate e questa volta ha funzionato. Grazie per tutte le risposte qui.

git commit --date="`date --date='2 day ago'`" -am "update"

5
Vedi il commento sopragit commit --date . Inoltre, il tuo messaggio di commit di esempio dovrebbe essere modificato per scoraggiare i poveri messaggi di una riga come questi @JstRoRR
Christopher,

4
Attenzione: git --datemodificherà solo $ GIT_AUTHOR_DATE ... quindi a seconda delle circostanze vedrai la data corrente allegata al commit ($ GIT_COMMITTER_DATE)
Guido U. Draheim

9

Per effettuare un commit che sembra sia stato fatto in passato, devi impostare entrambi GIT_AUTHOR_DATEe GIT_COMMITTER_DATE:

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

dove date -d'...'può essere data esatta come 2019-01-01 12:00:00o relativa 5 months ago 24 days ago.

Per vedere entrambe le date nel registro di git usare:

git log --pretty=fuller

Questo funziona anche per i merge commit:

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

2

Puoi sempre cambiare una data sul tuo computer, effettuare un commit, quindi cambiare la data indietro e premere.


1
Penso che non sia la pratica giusta, potrebbe creare alcuni problemi sconosciuti
Vino,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.