Bagaimana saya membuat komitmen Git di masa lalu?

222

Saya mengonversikan semuanya menjadi Git untuk penggunaan pribadi saya dan saya menemukan beberapa versi lama dari file yang sudah ada di repositori. Bagaimana cara saya mengkomitnya ke histori dalam urutan yang benar sesuai dengan "tanggal modifikasi" file sehingga saya memiliki riwayat file yang akurat?

Saya diberitahu bahwa ini akan berhasil:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  
tidak diketahui
sumber
Jawaban singkat dan sederhana: stackoverflow.com/a/34639957/2708266
Yash
33
Saya ingin tahu apakah orang yang mencari jawaban untuk ini hanya ingin menjaga "kontribusi
sto
1
@ ZenRo ya. Dan pengaturan git commit --date="xxx day ago" -m "yyy"cukup untuk tujuan itu jika ada yang bertanya-tanya.
Vlas Sokolov
alexpeattie.com/blog/working-with-dates-in-git : jika mencari penjelasan yang lembut
thepurpleowl

Jawaban:

198

Nasihat yang Anda berikan cacat. Menyetel GIT_AUTHOR_DATE tanpa syarat dalam suatu --env-filterakan menulis ulang tanggal setiap komit. Juga, tidak biasa menggunakan komit git di dalam --index-filter.

Anda berurusan dengan banyak masalah independen di sini.

Menentukan Tanggal Selain "sekarang"

Setiap komit memiliki dua tanggal: tanggal penulis dan tanggal committer. Anda dapat mengesampingkan masing-masing dengan memberikan nilai melalui variabel lingkungan GIT_AUTHOR_DATE dan GIT_COMMITTER_DATE untuk perintah apa pun yang menulis komit baru. Lihat “Format Tanggal” di git-commit (1) atau di bawah ini:

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

Satu-satunya perintah yang menulis komit baru selama penggunaan normal adalah git commit . Ini juga memiliki --dateopsi yang memungkinkan Anda menentukan secara langsung tanggal penulis. Penggunaan yang Anda antisipasi mencakup git filter-branch --env-filterjuga menggunakan variabel lingkungan yang disebutkan di atas (ini adalah bagian dari "env" setelah opsi tersebut dinamai; lihat "Opsi" di cabang-filter-git (1) dan perintah "plumbing" yang mendasari perintah git-commit -tree (1) .

Memasukkan File ke dalam Riwayat ref Tunggal

Jika repositori Anda sangat sederhana (yaitu Anda hanya memiliki satu cabang, tanpa tag), maka Anda mungkin dapat menggunakan git rebase untuk melakukan pekerjaannya.

Dalam perintah berikut, gunakan nama objek (SHA-1 hash) dari komit bukan "A". Jangan lupa untuk menggunakan salah satu metode "date override" saat Anda menjalankan 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

Jika Anda ingin memperbarui A untuk memasukkan file baru (alih-alih membuat komit baru di mana ia ditambahkan), maka gunakan git commit --amendalih-alih git commit. Hasilnya akan terlihat seperti ini:

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

Cara di atas berfungsi selama Anda dapat menyebutkan komit yang seharusnya menjadi induk dari komit baru Anda. Jika Anda benar-benar ingin file baru Anda ditambahkan melalui komit root baru (tidak ada orang tua), maka Anda perlu sesuatu yang sedikit berbeda:

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 --orphanrelatif baru (Git 1.7.2), tetapi ada cara lain untuk melakukan hal yang sama yang bekerja pada Git versi lama.

Memasukkan File ke dalam Riwayat Multi- ref

Jika repositori Anda lebih kompleks (mis. Ia memiliki lebih dari satu ref (cabang, tag, dll.)), Maka Anda mungkin perlu menggunakan git filter-branch . Sebelum menggunakan cabang-git filter , Anda harus membuat salinan cadangan seluruh repositori Anda. Arsip tar sederhana dari seluruh pohon kerja Anda (termasuk direktori .git) sudah cukup. git filter-branch memang membuat referensi cadangan, tetapi seringkali lebih mudah untuk memulihkan dari penyaringan yang tidak tepat dengan hanya menghapus .gitdirektori Anda dan mengembalikannya dari cadangan Anda.

Catatan: Contoh-contoh di bawah ini menggunakan perintah tingkat bawah git update-index --addsebagai ganti git add. Anda dapat menggunakan git add , tetapi pertama-tama Anda harus menyalin file dari beberapa lokasi eksternal ke jalur yang diharapkan ( --index-filtermenjalankan perintahnya dalam GIT_WORK_TREE sementara yang kosong).

Jika Anda ingin file baru Anda ditambahkan ke setiap komit yang ada, maka Anda dapat melakukan ini:

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

Saya tidak benar-benar melihat alasan untuk mengubah tanggal dari komitmen yang ada --env-filter 'GIT_AUTHOR_DATE=…'. Jika Anda menggunakannya, Anda harus membuatnya bersyarat sehingga akan menulis ulang tanggal untuk setiap komit.

Jika Anda ingin file baru Anda hanya muncul di komit setelah beberapa komit yang ada ("A"), maka Anda dapat melakukan ini:

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

Jika Anda ingin file ditambahkan melalui komit baru yang akan dimasukkan ke tengah-tengah riwayat Anda, maka Anda perlu membuat komit baru sebelum menggunakan cabang-git filter dan menambahkan --parent-filterke cabang-git filter :

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

Anda juga dapat mengatur agar file ditambahkan pertama kali dalam komit root baru: buat komit root baru Anda melalui metode "yatim" dari bagian git rebase (tangkap dalam new_commit), gunakan tanpa syarat --index-filter, dan --parent-filtersejenisnya "sed -e \"s/^$/-p $new_commit/\"".

Chris Johnsen
sumber
Dalam contoh pertama kasus penggunaan, "Memasukkan File ke Riwayat Multi-ref": Apakah ada cara untuk --index-filtermenerapkan komit yang dikembalikan oleh git rev-list? Saat ini saya melihat filter indeks diterapkan pada sub-set daftar-rev. Hargai wawasan apa pun.
Hedgehog
@Hedgehog: Semua contoh "multi-ref" digunakan -- --alluntuk memproses semua komit yang dapat dijangkau dari referensi apa pun. Contoh terakhir menunjukkan bagaimana mengubah hanya komit tertentu (cukup uji GIT_COMMIT untuk apa pun yang Anda suka). Untuk hanya mengubah daftar komitmen tertentu, Anda bisa menyimpan daftar sebelum memfilter (mis. git rev-list … >/tmp/commits_to_rewrite), Lalu menguji keanggotaan di dalam filter (mis if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index ….). Apa, tepatnya, yang ingin Anda capai? Anda mungkin ingin memulai pertanyaan baru jika terlalu banyak untuk dijelaskan dalam komentar.
Chris Johnsen
Dalam kasus saya, saya memiliki proyek kecil yang saya maksudkan untuk ditempatkan di bawah kendali sumber. (Saya belum melakukannya, karena ini proyek solo dan saya belum memutuskan sistem mana yang akan digunakan). "Kontrol sumber" saya hanyalah masalah menduplikasi seluruh pohon proyek saya ke direktori baru, dan mengganti nama direktori versi sebelumnya. Saya baru mengenal Git (setelah sebelumnya menggunakan SCCS, RCS, dan turunan RCS yang jelek dan menyerupai CVS), jadi jangan khawatir tentang berbicara dengan saya. Saya mendapat kesan bahwa jawabannya melibatkan variasi bagian "Memasukkan File Ke dalam Sejarah Ref Tunggal". Benar?
Steve
1
@Steve: Skenario "single-ref" berlaku jika riwayat Anda berasal dari satu garis pengembangan (yaitu, akan masuk akal jika semua foto dipandang sebagai titik-titik berurutan dari satu cabang linear tunggal). Skenario “multi-ref” berlaku jika Anda memiliki (atau pernah) beberapa cabang dalam sejarah Anda. Kecuali jika riwayat Anda rumit (versi "bercabang" untuk klien yang berbeda, mempertahankan cabang "perbaikan bug" sementara pekerjaan baru terjadi kembali dalam "pengembangan", dll.), Maka Anda mungkin melihat situasi "satu-ref". Namun , sepertinya situasimu berbeda dari pertanyaan ...
Chris Johnsen
1
@Steve: Karena Anda memiliki serangkaian snapshot direktori historis — dan Anda belum memiliki repositori Git — maka Anda mungkin bisa menggunakan import-directories.perldari Git's contrib/(atau import-tars.perl, atau import-zips.py...) untuk membuat repositori Git baru dari snapshot Anda (bahkan dengan Cap waktu "lama"). The rebase/ filter-branchteknik dalam jawaban saya hanya diperlukan jika Anda ingin memasukkan file yang “ditinggalkan” sejarah repositori yang sudah ada.
Chris Johnsen
119

Anda bisa membuat komit seperti biasa, tetapi ketika Anda komit, atur variabel lingkungan GIT_AUTHOR_DATEdan GIT_COMMITTER_DATEke datetimes yang sesuai.

Tentu saja, ini akan membuat komit di ujung cabang Anda (yaitu, di depan komit HEAD saat ini). Jika Anda ingin mendorongnya lebih jauh dalam repo, Anda harus mendapatkan sedikit kemewahan. Katakanlah Anda memiliki riwayat ini:

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

Dan Anda ingin komit baru Anda (ditandai sebagai "X") muncul kedua :

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

Cara termudah adalah dengan bercabang dari komit pertama, tambahkan komit baru Anda, lalu rebase semua komit lainnya di atas komit baru. Seperti itu:

$ 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
sumber
25
saya selalu menemukan jawaban yang baik ini, tetapi kemudian harus bergegas untuk menemukan format tanggal, jadi ini dia untuk waktu berikutnya 'Jumat 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
Ada lagi, misalnya yang agak mnemonik 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian
3
By the way, bagian '-0400' menetapkan zona waktu offset. Berhati-hatilah memilihnya dengan benar ... karena jika tidak, waktu Anda akan berubah berdasarkan itu. Sebagai contoh dalam kasus saya sendiri, yang tinggal di Chicago, saya harus memilih '-0600' (CST Amerika Utara). Anda dapat menemukan kode di sini: timeanddate.com/time/zones
evaldeslacasa
2
karena tanggal perlu diulang, saya merasa lebih mudah untuk membuat variabel lain: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

Saya tahu pertanyaan ini sudah cukup lama, tetapi itulah yang sebenarnya berhasil bagi saya:

git commit --date="10 day ago" -m "Your commit message" 
Hyder B.
sumber
16
Ini bekerja dengan sempurna. Saya terkejut bahwa itu --datejuga mendukung format tanggal relatif yang dapat dibaca manusia.
ZitRo
@ ZenRo mengapa tidak 10 hari?
Alex78191
10
Peringatan: git --datehanya akan mengubah $ GIT_AUTHOR_DATE ... jadi tergantung pada keadaan Anda akan melihat tanggal saat ini terlampir pada komit ($ GIT_COMMITTER_DATE)
Guido U. Draheim
3
Menandai apa yang dikatakan @ GuidoU.Draheim. Anda dapat memeriksa detail lengkap komit menggunakan git show <commit-hash> --format=fuller. Di sana, Anda akan melihat AuthorDatetanggal yang Anda tentukan, tetapi CommitDatetanggal yang sebenarnya dari komit.
d4nyll
Jadi, tidak ada cara untuk mengubah CommitDate atau membuat komit yang sudah ketinggalan zaman? @ d4nyll
Rahul
35

Dalam kasus saya dari waktu ke waktu saya telah menyimpan banyak versi myfile seperti myfile_bak, myfile_old, myfile_2010, backup / myfile dll. Saya ingin memasukkan sejarah myfile di git menggunakan tanggal modifikasi mereka. Jadi, ganti nama tertua ke myfile,, git add myfilelalu git commit --date=(modification date from ls -l) myfile, ganti nama tertua berikutnya ke myfile, git lain komit dengan --tanggal, ulangi ...

Untuk mengotomatiskan hal ini, Anda dapat menggunakan shell-foo untuk mendapatkan waktu modifikasi file. Saya mulai dengan ls -ldan cut, tetapi stat (1) lebih langsung

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

skierpage
sumber
1
Bagus. Saya pasti punya file dari sebelum hari git yang ingin saya gunakan untuk waktu modifikasi file. Namun, bukankah ini hanya menetapkan tanggal komit (bukan tanggal penulis?)
Xeoncross
Dari git-scm.com/docs/git-commit: --date"Ganti tanggal penulis yang digunakan dalam komit." git logTanggal tampaknya adalah AuthorDate, git log --pretty=fullermenunjukkan AuthorDate dan CommitDate.
skierpage
16
The git commitpilihan --datehanya akan memodifikasi GIT_AUTHOR_DATE, tidak GIT_COMMITTER_DATE. Seperti yang dijelaskan oleh Buku Pro Git : "Penulis adalah orang yang awalnya menulis karya, sedangkan komuter adalah orang yang terakhir mengaplikasikan karya." Dalam konteks tanggal, GIT_AUTHOR_DATEadalah tanggal file diubah, sedangkan GIT_COMMITTER_DATEtanggal adalah komitmennya. Penting di sini untuk mencatat bahwa secara default, git logmenampilkan tanggal penulis sebagai "Tanggal" tetapi kemudian menggunakan tanggal komit untuk pemfilteran ketika diberi --sinceopsi.
Christopher
Tidak ada opsi -c untuk stat di OS X 10.11.1
thinsoldier
Setara dengan stat -c %ydi macOS (dan varian BSD lainnya) adalah stat -f %m.
pemenang
21

Berikut ini adalah apa yang saya gunakan untuk melakukan perubahan pada hari foo- N=1hari di masa lalu:

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

Jika Anda ingin berkomitmen pada tanggal yang lebih lama, katakan 3 hari yang lalu, ubah saja dateargumen:date -v-3d .

Itu sangat berguna ketika Anda lupa untuk melakukan sesuatu kemarin, misalnya.

UPDATE : --datejuga menerima ekspresi suka --date "3 days ago"atau bahkan --date "yesterday". Jadi kita bisa menguranginya menjadi satu perintah baris:

git add foo ; git commit --date "yesterday" -m "Update"
Vilson Vieira
sumber
6
Peringatan: git --datehanya akan mengubah $ GIT_AUTHOR_DATE ... jadi tergantung pada keadaan Anda akan melihat tanggal saat ini terlampir pada komit ($ GIT_COMMITTER_DATE)
Guido U. Draheim
menggabungkan 2 pendekatan bersama membuat hampir sempurna:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej
Luar biasa! Perintah yang sama dengan tanggal yang dapat dibaca manusiagit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets
16

Dalam kasus saya, saat menggunakan opsi --date, proses git saya macet. Mungkin saya melakukan sesuatu yang mengerikan. Dan sebagai hasilnya beberapa file index.lock muncul. Jadi saya secara manual menghapus file .lock dari folder .git dan dieksekusi, untuk semua file yang dimodifikasi untuk dikomit dalam melewati tanggal dan itu bekerja saat ini. Terima kasih atas semua jawaban di sini.

git commit --date="`date --date='2 day ago'`" -am "update"
JstRoRR
sumber
5
Lihat komentar di atas padagit commit --date . Juga, pesan komit contoh Anda harus diubah untuk mencegah pesan satu baris yang buruk seperti @JstRoRR ini
Christopher
4
Peringatan: git --datehanya akan mengubah $ GIT_AUTHOR_DATE ... jadi tergantung pada keadaan Anda akan melihat tanggal saat ini terlampir pada komit ($ GIT_COMMITTER_DATE)
Guido U. Draheim
9

Untuk membuat komit yang sepertinya dilakukan di masa lalu, Anda harus menetapkan keduanya GIT_AUTHOR_DATEdan GIT_COMMITTER_DATE:

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

di mana date -d'...'bisa tanggal yang tepat suka 2019-01-01 12:00:00atau suka relatif5 months ago 24 days ago .

Untuk melihat kedua tanggal dalam log git gunakan:

git log --pretty=fuller

Ini juga berfungsi untuk gabungan komitmen:

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

Anda selalu dapat mengubah tanggal di komputer Anda, membuat komit, lalu mengubah tanggal kembali dan mendorong.

Nik
sumber
1
Saya pikir itu bukan praktik yang tepat, mungkin membuat beberapa masalah yang tidak diketahui
Vino