Git'i geçmişte nasıl işler hale getirebilirim?


222

Kendi kişisel kullanımım için her şeyi Git'e dönüştürüyorum ve zaten depoda bulunan bir dosyanın bazı eski sürümlerini buldum. Dosyanın doğru bir geçmişine sahip olmak için dosyanın "değiştirilme tarihi" ne göre geçmişe doğru sırada nasıl kaydederim?

Bana böyle bir şeyin işe yarayacağı söylendi:

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

Kısa ve basit cevaplar: stackoverflow.com/a/34639957/2708266
Yash

33
Bunun cevabını arayan insanların sadece GitHub "katkı çizgisini" bu şekilde sürekli tutmak isteyip istemediklerini merak ediyorum
ZitRo

1
@ZitRo evet. Ve git commit --date="xxx day ago" -m "yyy"eğer merak ediyorsa, sadece bu amaç için ayar yeterlidir.
Vlas Sokolov

alexpeattie.com/blog/working-with-dates-in-git : nazik bir açıklama arıyorsanız
thepurpleowl

Yanıtlar:


198

Size verilen tavsiyeler kusurludur. Koşulsuz olarak GIT_AUTHOR_DATE değerini bir olarak ayarlamak --env-filterher taahhüt tarihini yeniden yazar. Ayrıca, git taahhüdü içeride kullanmak olağandışı olurdu --index-filter.

Burada birden çok bağımsız sorunla uğraşıyorsunuz.

"Şimdi" Dışındaki Tarihleri ​​Belirtme

Her bir taahhüdün iki tarihi vardır: yazar tarihi ve geçiş tarihi. Yeni bir tamamlama yazan herhangi bir komut için GIT_AUTHOR_DATE ve GIT_COMMITTER_DATE ortam değişkenleri aracılığıyla değerler sağlayarak her birini geçersiz kılabilirsiniz. Git-commit (1) veya aşağısında “Tarih Biçimleri” konusuna bakın:

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

Normal kullanım sırasında yeni bir komut yazan tek komut git command'dur . Ayrıca --date, yazar tarihini doğrudan belirlemenizi sağlayan bir seçenek de vardır . Öngörülen kullanımınız git filter-branch --env-filteryukarıda belirtilen ortam değişkenlerini de içerir (bunlar seçeneğin adlandırıldığı “env” nin bir parçasıdır; git-filter-branşında (1) “Seçenekler” ve temel “sıhhi tesisat” komutuna git-commit bakın ağaç (1) .

Dosyayı Tek Bir Ref Geçmişine Ekleme

Deponuz çok basitse (yani yalnızca tek bir dalınız, etiketiniz yoksa), işi yapmak için git rebase'i kullanabilirsiniz .

Aşağıdaki komutlarda, “A” yerine komutun nesne adını (SHA-1 karması) kullanın. Git commit komutunu çalıştırdığınızda “tarih geçersiz kılma” yöntemlerinden birini kullanmayı unutmayın .

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

A'yı yeni dosyayı içerecek şekilde güncellemek istiyorsanız (eklendiği yerde yeni bir taahhüt oluşturmak yerine) git commit --amendyerine kullanın git commit. Sonuç şöyle görünecektir:

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

Yukarıdakiler, yeni taahhüdünüzün ebeveyni olması gereken taahhüdü adlandırabildiğiniz sürece çalışır. Yeni dosyanızın yeni bir root komutu aracılığıyla eklenmesini istiyorsanız (ebeveyn yok), o zaman biraz farklı bir şeye ihtiyacınız vardır:

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 --orphannispeten yeni (Git 1.7.2), ancak Git'in eski sürümlerinde çalışan aynı şeyi yapmanın başka yolları da var .

Çok Referanslı Geçmişe Dosya Ekleme

Deponuz daha karmaşıksa (yani birden fazla ref (dallar, etiketler vb.) Varsa), muhtemelen git filter-branch kullanmanız gerekecektir . Git filtre-dalını kullanmadan önce , tüm deponuzun bir yedek kopyasını almalısınız. Tüm çalışma ağacınızın (.git dizini dahil) basit bir katran arşivi yeterlidir. git filter-branch yedekleme referansları yapar, ancak .gitdizininizi silerek ve yedeklemenizden geri yükleyerek doğru olmayan bir filtreden kurtulmak genellikle daha kolaydır .

Not: Aşağıdaki örnekler git update-index --addyerine alt düzey komutunu kullanır git add. Git add'i kullanabilirsiniz , ancak önce dosyayı harici bir konumdan beklenen yola kopyalamanız gerekir ( --index-filterkomutunu boş olan geçici bir GIT_WORK_TREE içinde çalıştırır).

Yeni dosyanızın varolan her işleme eklenmesini istiyorsanız, bunu yapabilirsiniz:

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

Mevcut taahhütlerin tarihlerini değiştirmek için gerçekten bir neden görmüyorum --env-filter 'GIT_AUTHOR_DATE=…'. Eğer kullansaydınız, şartlı hale getirmelisiniz ki her taahhüt için tarihi yeniden yazsın.

Yeni dosyanızın yalnızca varolan bir işlemden sonra (“A”) yalnızca taahhütlerde görünmesini istiyorsanız, bunu yapabilirsiniz:

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

Dosyanın geçmişinizin ortasına eklenecek yeni bir komutla eklenmesini istiyorsanız, git filter-branch'ı kullanmadan önce yeni bir komut oluşturmanız ve git filter- branch'a eklemeniz --parent-filtergerekir :

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

Ayrıca dosyanın ilk olarak yeni bir root komutuna eklenmesini de ayarlayabilirsiniz: git rebase bölümünden “yetim” yöntemiyle yeni root komutunuzu oluşturun (yakalayın new_commit), koşulsuz kullanın --index-filterve --parent-filterbenzerlerini yapın "sed -e \"s/^$/-p $new_commit/\"".


Kullanım örneğinin ilk örneğinde, "Çok Yönlü Geçmişe Dosya Ekleme": --index-filterTarafından döndürülen taahhütlere başvurmanın bir yolu var git rev-listmı? Şu anda bir rev-list alt kümesine uygulanan dizin filtresi görüyorum. Herhangi bir içgörü için teşekkür ederiz.
Hedgehog

@Hedgehog: Tüm "multi-ref" örnekleri -- --all, herhangi bir ref'den ulaşılabilen tüm taahhütleri işlemek için kullanır . Son örnek, yalnızca belirli taahhütlerin nasıl değiştirileceğini gösterir (GIT_COMMIT'i istediğiniz gibi test edin). Yalnızca belirli bir taahhüt listesini değiştirmek için filtrelemeden önce listeyi kaydedebilir (örn. git rev-list … >/tmp/commits_to_rewrite), Ardından filtrenin içindeki üyeliği test edebilirsiniz (örn. if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index …). Tam olarak neyi başarmak istiyorsun? Yorumlarda açıklanamayacak kadar fazla ise yeni bir soru başlatmak isteyebilirsiniz.
Chris Johnsen

Benim durumumda, kaynak kontrolü altına almak istediğim küçük bir projem var. (Bunu yapmamıştım, çünkü bu solo bir proje ve hangi sistemi kullanacağım hakkında karar vermedim). "Kaynak denetimim", yalnızca tüm proje ağacımı yeni bir dizine çoğaltma ve önceki sürüm dizinini yeniden adlandırma meselesiydi. Git'te yeniyim (daha önce SCCS, RCS ve CVS'ye benzeyen çirkin tescilli bir RCS türevi kullandıktan sonra), bu yüzden benimle konuşmaktan endişe etmeyin. Cevabın "Tek Ref Geçmişine Dosya Ekleme" bölümünün bir varyasyonunu içerdiği izlenimini edindim. Doğru?
Steve

1
@Steve: “Tek ref” senaryosu, geçmişiniz tek bir geliştirme çizgisinden olsaydı geçerlidir (yani, tüm anlık görüntülerin tek bir doğrusal dalın ardışık noktaları olarak görülmesi mantıklı olacaktır). Geçmişinizde birden fazla şubeniz varsa (veya varsa) "çoklu başvuru" senaryosu geçerlidir. Geçmişiniz karmaşık değilse (farklı istemciler için "çatallı" sürümler, "geliştirme", vb. Sırasında yeni işler tekrarlanırken "hata düzeltme" dalını korudu), muhtemelen bir "tek ref" durumuna bakıyorsunuzdur. Ancak , durumunuz sorununkinden farklı gibi görünüyor ...
Chris Johnsen

1
@Steve: Bir dizi geçmiş dizin anlık görüntünüz olduğundan ve zaten bir Git deponuz olmadığından, muhtemelen import-directories.perlGit'inizden contrib/(veya import-tars.perl, veya import-zips.py…) yeni bir Git deposu oluşturmak için kullanabilirsiniz. "Eski" zaman damgaları). Cevabımdaki rebase/ filter-branchteknikleri yalnızca mevcut bir havuzun geçmişinin “dışında bırakılmış” bir dosya eklemek istiyorsanız gereklidir.
Chris Johnsen

119

İşlemi her zamanki gibi oluşturabilirsiniz, ancak işlem yaptığınızda ortam değişkenlerini GIT_AUTHOR_DATEve GIT_COMMITTER_DATEuygun tarihlere ayarlayın .

Tabii ki, bu taahhüt şubenizin ucunda olacaktır (yani mevcut HEAD taahhüdünün önünde). Eğer depoda geri itmek istiyorsanız, biraz süslü olmalısınız. Diyelim ki bu geçmişe sahipsin:

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

Ve yeni taahhüdünüzün ("X" olarak işaretlenmiş) ikinci görünmesini istiyorsunuz :

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

En kolay yol, ilk taahhütten ayrılmak, yeni taahhüdünüzü eklemek, daha sonra diğer tüm taahhütleri yenisinin üstüne yeniden oluşturmak olacaktır. Şöyle ki:

$ 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
ben her zaman bu iyi cevabı bulmak, ama sonra tarih biçimini bulmak için koşturmak zorunda, bu yüzden burada bir dahaki sefere 'Cum Tem 26 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
Daha fazlası var, örneğin oldukça hatırlatıcı 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian

3
Bu arada, '-0400' kısmı saat dilimi dengesini belirler. Düzgün seçtiğinizden emin olun ... çünkü değilse, zaman buna göre değişecektir. Örneğin, Chicago'da yaşayan kendimde, '-0600' (Kuzey Amerika CST) seçmeliydim. Kodları burada bulabilirsiniz: timeanddate.com/time/zones
evaldeslacasa

2
tarihin tekrarlanması gerektiğinden, başka bir değişken oluşturmayı daha kolay buldum: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

Bu sorunun oldukça eski olduğunu biliyorum, ama aslında benim için işe yaradı:

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

16
Bu mükemmel çalıştı. --dateİnsan tarafından okunabilir göreceli tarih biçimini de destekleyen bir sürprizim var .
ZitRo

@ZitRo neden 10 gün değil?
Alex78191

10
Uyarı: git --dateSadece $ GIT_AUTHOR_DATE değiştirecektir ... bu yüzden koşullara bağlı olarak size taahhüt ($ GIT_COMMITTER_DATE) bağlı geçerli tarih görürsünüz
Guido U. Draheim

3
@ GuidoU.Draheim ne dediğini üzerinde etiketleme. Bir taahhüdün tüm ayrıntılarını kullanarak kontrol edebilirsiniz git show <commit-hash> --format=fuller. Orada, AuthorDatebelirttiğiniz tarih, ancak CommitDatetaahhüdün gerçek tarihi olduğunu göreceksiniz .
d4nyll

Öyleyse, Komite'yi değiştirmenin veya tamamen geçmiş tarihli bir taahhüt yapmanın bir yolu yok mu? @ d4nyll
Rahul

35

Zamanla Benim durumumda ben sürümleri bir demet kurtarmıştı Dosyam onların modifikasyon tarihleri kullanarak GIT'de myFile tarihini koymak istedim dosyam vb myfile_bak, myfile_old, myfile_2010, yedekleme / olarak. Bu yüzden, en eskisini dosyam olarak yeniden adlandırın git add myfile, sonra git commit --date=(modification date from ls -l) myfile, en eski olanı dosyam olarak yeniden adlandırın, başka bir git --date ile tamamlayın, tekrarlayın ...

Bunu biraz otomatikleştirmek için, dosyanın değiştirme zamanını almak üzere shell-foo kullanabilirsiniz. İle başladım ls -lve cutstat (1) daha direkt

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


1
Güzel. Git günlerimden önce dosyaları değiştirdiğim zamanı kullanmak istediğim dosyaları kesinlikle aldım . Ancak, bu yalnızca taahhüt tarihini belirlemez (yazarın tarihini değil mi?)
Xeoncross

Git-scm.com/docs/git-commit adresinden: --date" İşlemde kullanılan yazar tarihini geçersiz kıl." git logTarihi, YazarTarihi gibi görünüyor git log --pretty=fuller, hem YazarTarihi hem de Kararlılık Derecesini gösteriyor.
skierpage

16
git commitSeçenek --dateyalnızca değiştirecektir GIT_AUTHOR_DATEdeğil GIT_COMMITTER_DATE. As Pro Git Kitabı açıklıyor: "committer son uygulayan kişinin işinin ise yazar, aslen eser yazdı kişidir." Tarihler bağlamında GIT_AUTHOR_DATEdosyanın değiştirildiği GIT_COMMITTER_DATEtarih, işlendiği tarih. Burada, varsayılan olarak, git logyazar tarihlerini "Tarih" olarak görüntülediğini , ancak bir --sinceseçenek verildiğinde filtreleme için taahhüt tarihlerini kullandığını belirtmek önemlidir .
Christopher

OS X 10.11.1'de stat için hiçbir -c seçeneği yoktur
thinsoldier

stat -c %yMacOS (ve diğer BSD varyantları) için eşdeğeridir stat -f %m.
victor

21

Aşağıda, I üzerinde değişiklikler işlemek için kullandığınız fooiçin N=1geçmişte günlerde:

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

Eğer bir hatta eski tarihe işlemek istiyorsanız, sadece değiştirmek, 3 gün geri demek dateargüman: date -v-3d.

Örneğin, dün bir şey yapmayı unuttuğunuzda bu gerçekten yararlıdır.

GÜNCELLEME : --dategibi --date "3 days ago"veya çift ifadeleri de kabul eder --date "yesterday". Böylece bir satır komutuna indirgeyebiliriz:

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

6
Uyarı: git --dateSadece $ GIT_AUTHOR_DATE değiştirecektir ... bu yüzden koşullara bağlı olarak size taahhüt ($ GIT_COMMITTER_DATE) bağlı geçerli tarih görürsünüz
Guido U. Draheim

birlikte 2 yaklaşım karıştırma neredeyse mükemmel uyum sağlar:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej

Müthiş! İnsan tarafından okunabilir bir tarihle aynı komutgit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets

16

Benim durumumda, --date seçeneğini kullanırken git işlemim çöktü. Belki korkunç bir şey yaptım. Ve sonuç olarak bazı index.lock dosyası ortaya çıktı. Bu yüzden .lock dosyalarını .git klasöründen el ile sildim ve yürüttüm, değiştirilen tüm dosyaların geçerlilik tarihlerinde işlenmesi ve bu sefer işe yaraması. Buradaki tüm cevaplar için teşekkürler.

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

5
Yukarıdaki açıklamayagit commit --date bakın . Ayrıca, örnek taahhüt mesajınız, bu gibi zayıf tek satırlı mesajları engellemek için değiştirilmelidir @JstRoRR
Christopher

4
Uyarı: git --dateSadece $ GIT_AUTHOR_DATE değiştirecektir ... bu yüzden koşullara bağlı olarak size taahhüt ($ GIT_COMMITTER_DATE) bağlı geçerli tarih görürsünüz
Guido U. Draheim

9

Geçmişte yapılanlar gibi bir hem ayarlamak zorunda görünüyor işlemek yapmak için GIT_AUTHOR_DATEve GIT_COMMITTER_DATE:

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

nerede date -d'...'tam gibi tarih olabilir 2019-01-01 12:00:00ya da benzeri göreceli 5 months ago 24 days ago.

Git günlüğünde her iki tarihi görmek için:

git log --pretty=fuller

Bu, birleştirme taahhütleri için de geçerlidir:

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

2

Bilgisayarınızdaki bir tarihi istediğiniz zaman değiştirebilir, bir taahhütte bulunabilir, ardından tarihi geri değiştirebilir ve itebilirsiniz.


1
Bence bu doğru uygulama değil, bazı bilinmeyen sorunlar yaratabilir
Vino

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.