Geçmiş bir kaydetmeyi nasıl kolayca düzeltebilirim?


117

Git'te geçmiş bir işlemde tek bir dosyayı değiştirmeyi okudum ama ne yazık ki kabul edilen çözüm, taahhütleri 'yeniden sıralıyor', ki bu istediğim şey değil. İşte sorum şu:

Arada sırada, (ilgisiz) bir özellik üzerinde çalışırken kodumda bir hata fark ediyorum. Daha git blamesonra hızlı bir şekilde , hatanın birkaç işlemden önce tanıtıldığı ortaya çıkar (Oldukça fazla işlem yaparım, bu nedenle genellikle hatayı ortaya çıkaran en son işlem değildir). Bu noktada, genellikle şunu yapıyorum:

git stash                      # temporarily put my work aside
git rebase -i <bad_commit>~1   # rebase one step before the bad commit
                               # mark broken commit for editing
vim <affected_sources>         # fix the bug
git add <affected_sources>     # stage fixes
git commit -C <bad_commit>     # commit fixes using same log message as before
git rebase --continue          # base all later changes onto this

Ancak, bu o kadar sık ​​olur ki, yukarıdaki sıra sinir bozucu hale geliyor. Özellikle 'etkileşimli geri ödeme' sıkıcı. Yukarıdaki sekans için, aşamalı değişikliklerle geçmişte keyfi bir işlemi değiştirmeme izin veren herhangi bir kısayol var mı? Bunun tarihi değiştirdiğinin tamamen farkındayım, ancak o kadar sık ​​hata yapıyorum ki gerçekten böyle bir şey olmasını çok isterim

vim <affected_sources>             # fix bug
git add -p <affected_sources>      # Mark my 'fixup' hungs for staging
git fixup <bad_commit>             # amend the specified commit with staged changes,
                                   # rebase any successors of bad commit on rewritten 
                                   # commit.

Belki sıhhi tesisat araçlarını kullanarak taahhütleri yeniden yazabilen akıllı bir komut dosyası olabilir mi?


İşlemleri "yeniden sipariş etmek" ile ne demek istiyorsun? Değiştirdiğiniz kaydedilmesini beri tüm kaydedilmesini sonra geçmişini değiştiriyorsanız sahip farklı olmak, ancak bağlantılı soruya kabul cevabı yeniden sipariş vermez anlamlı bir anlamda kaydedilmesini.
CB Bailey

1
@Charles: Şu şekilde yeniden sıralamayı kastetmiştim: HEAD ~ 5'in bozuk kaydetme olduğunu fark edersem, bağlantılı soruda aşağıdaki kabul edilen yanıt HEAD'i (dalın ucu) sabit kaydetme yapacaktır. Ancak, HEAD ~ 5'in sabit taahhüt olmasını istiyorum - bu, etkileşimli bir yeniden temel kullanırken ve düzeltme için tek bir kaydetmeyi düzenlerken elde ettiğiniz şeydir.
Frerich Raabe

Evet, ancak daha sonra rebase komutu ana bilgisayarı yeniden ödeyecek ve sonraki tüm taahhütleri sabit taahhüdüne yeniden bağlayacaktır. Bu şekilde sürmüyor musun rebase -i?
CB Bailey

Aslında, bu yanıtla ilgili olası bir sorun var, bence öyle olmalı rebase --onto tmp bad-commit master. Yazıldığı gibi, kötü taahhüdü sabit taahhüt durumuna uygulamaya çalışacaktır.
CB Bailey

Düzeltme / yeniden
temelleme

Yanıtlar:


166

GÜNCEL CEVAP

Bir süre önce, uygun bir günlük mesajı ile bir commit oluşturmak için kullanılabilecek yeni bir --fixupargüman eklendi . Dolayısıyla, geçmiş bir işlemi düzeltmenin en basit yolu artık:git commitgit rebase --interactive --autosquash

$ git add ...                           # Stage a fix
$ git commit --fixup=a0b1c2d3           # Perform the commit to fix broken a0b1c2d3
$ git rebase -i --autosquash a0b1c2d3~1 # Now merge fixup commit into broken commit

ORİJİNAL CEVAP

İşte bir süre önce yazdığım ve git fixuporijinal sorumda umduğum bu mantığı uygulayan küçük bir Python betiği . Komut dosyası, bazı değişiklikleri hazırladığınızı varsayar ve ardından bu değişiklikleri verilen kaydetmeye uygular.

NOT : Bu komut dosyası Windows'a özeldir; kullanarak ortam değişkenini arar git.exeve ayarlar . Bunu diğer işletim sistemleri için gerektiği gibi ayarlayın.GIT_EDITORset

Bu komut dosyasını kullanarak, istediğim 'bozuk kaynakları düzelt, aşama düzeltmeleri, git düzeltmeyi çalıştır' iş akışını tam olarak uygulayabilirim:

#!/usr/bin/env python
from subprocess import call
import sys

# Taken from http://stackoverflow.com/questions/377017/test-if-executable-exists-in python
def which(program):
    import os
    def is_exe(fpath):
        return os.path.exists(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None

if len(sys.argv) != 2:
    print "Usage: git fixup <commit>"
    sys.exit(1)

git = which("git.exe")
if not git:
    print "git-fixup: failed to locate git executable"
    sys.exit(2)

broken_commit = sys.argv[1]
if call([git, "rev-parse", "--verify", "--quiet", broken_commit]) != 0:
    print "git-fixup: %s is not a valid commit" % broken_commit
    sys.exit(3)

if call([git, "diff", "--staged", "--quiet"]) == 0:
    print "git-fixup: cannot fixup past commit; no fix staged."
    sys.exit(4)

if call([git, "diff", "--quiet"]) != 0:
    print "git-fixup: cannot fixup past commit; working directory must be clean."
    sys.exit(5)

call([git, "commit", "--fixup=" + broken_commit])
call(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i", "--autosquash", broken_commit + "~1"], shell=True)

2
Artık temiz bir çalışma dizini gerektirmemek için yeniden git stashgit stash pop
tabanınızı

@TobiasKienzler: kullanma hakkında git stashve git stash pop: maalesef haklısın, ama git stasholan çok o Linux veya OS / X üzerinde daha yavaş Windows üzerinde. Çalışma dizinim genellikle temiz olduğundan, komutu yavaşlatmamak için bu adımı atladım.
Frerich Raabe

Bunu, özellikle bir ağ paylaşımı üzerinde çalışırken onaylayabilirim: - /
Tobias Kienzler

1
Güzel. Yanlışlıkla yaptım git rebase -i --fixupve sabitlenmiş taahhütten başlangıç ​​noktası olarak yeniden desteklendi, bu yüzden benim durumumda sha argümanına gerek yoktu.
fwielstra

1
--Autosquash'i sıklıkla kullanan kişiler için, bunu varsayılan davranış olarak ayarlamak faydalı olabilir: git config --global rebase.autosquash true
taktak004

31

Yaptığım şey:

git add ... # Düzeltmeyi ekleyin.
git commit # Kaydedildi, ancak yanlış yerde.
git rebase -i HEAD ~ 5 # Yeniden satış için son 5 kaydı inceleyin.

Editörünüz, karışmaya hazır son 5 işlemin bir listesiyle açılacaktır. Değişiklik:

08e833c'yi seçin İyi değişiklik 1.
9134ac9'u seçin İyi değişiklik 2.
5adda55'i seç Kötü değişiklik!
400bce4'ü seç 3.
2bc82n1'i seçin Kötü değişikliğin düzeltilmesi.

... için:

08e833c'yi seçin İyi değişiklik 1.
9134ac9'u seçin İyi değişiklik 2.
5adda55'i seç Kötü değişiklik!
f 2bc82n1 Kötü değişiklik düzeltildi. # Yukarı hareket ettirin ve 'düzeltme' için 'seç'i' f 'olarak değiştirin.
400bce4'ü seç 3.

Düzenleyicinizi kaydedin ve çıkın; düzeltme, ait olduğu kaydetmeye geri sıkıştırılacaktır.

Bunu birkaç kez yaptıktan sonra, uykunuzda saniyeler içinde yapacaksınız. Etkileşimli yeniden satış, beni gerçekten satan özelliktir. Bunun için inanılmaz derecede yararlı ve daha fazlası ...


9
Açıktır ki, daha da geriye gitmek için HEAD ~ 5'i HEAD ~ n olarak değiştirebilirsiniz. Yukarı akıma ittiğiniz herhangi bir geçmişe karışmak istemezsiniz, bu yüzden yalnızca çalıştırılmamış geçmişi değiştirdiğimden emin olmak için genellikle 'git rebase -i origin / master' yazıyorum.
Kris Jenkins

4
Bu her zaman yaptığım şeye çok benziyor; FWIW, şunun için --autosquashgeçiş ilginizi çekebilir :git rebase düzenleyicideki adımları sizin için otomatik olarak yeniden sıralayan . Bir git fixupkomutu uygulamak için bundan yararlanan bir komut dosyası için yanıtıma bakın .
Frerich Raabe

Taahhüt karmalarını yeniden sipariş edebileceğinizi bilmiyordum, güzel!
Aaron Franke

Bu harika! Sadece tüm geri ödeme işinin yapıldığından emin olmak için ayrı bir özellik dalı vardır. Ve usta gibi ortak dalla uğraşmamak.
Jay Modi

22

Partiye biraz geç ama işte yazarın hayal ettiği gibi çalışan bir çözüm.

Bunu .gitconfig dosyanıza ekleyin:

[alias]
    fixup = "!sh -c '(git diff-files --quiet || (echo Unstaged changes, please commit or stash with --keep-index; exit 1)) && COMMIT=$(git rev-parse $1) && git commit --fixup=$COMMIT && git rebase -i --autosquash $COMMIT~1' -"

Örnek kullanım:

git add -p
git fixup HEAD~5

Ancak, aşamalı olmayan değişiklikleriniz varsa, bunları yeniden ödemeden önce saklamalısınız.

git add -p
git stash --keep-index
git fixup HEAD~5
git stash pop

Takma adı, bir uyarı vermek yerine otomatik olarak saklanacak şekilde değiştirebilirsiniz. Ancak, düzeltme düzgün bir şekilde uygulanmazsa, çakışmaları düzelttikten sonra zulayı manuel olarak açmanız gerekir. Hem kaydetmeyi hem de patlatmayı manuel olarak yapmak daha tutarlı ve daha az kafa karıştırıcı görünüyor.


Bu oldukça faydalıdır. Benim için en yaygın kullanım durumu, değişiklikleri önceki commit üzerinde düzeltmektir, git fixup HEADben de bunun için bir takma ad oluşturdum. Sanırım bunun için düzeltmeyi de kullanabilirim.
çekirge

Teşekkür ederim! Ayrıca bunu en sık son işlemede kullanıyorum, ancak hızlı bir düzeltme için başka bir takma adım var. amend = commit --amend --reuse-message=HEADArdından , commit mesajı için düzenleyiciyi yazabilir git amendveya git amend -aatlayabilirsiniz.
dschlyter

3
Amend ile ilgili sorun, onu nasıl heceleyeceğimi hatırlamamam. Her zaman düşünmek zorundayım, düzeltmek mi, düzeltmek mi ve bu iyi değil.
çekirge

12

Bir kaydı düzeltmek için:

git commit --fixup a0b1c2d3 .
git rebase --autosquash -i HEAD~2

burada a0b1c2d3, düzeltme istediğiniz kesinleştirilir ve burada 2, değiştirmek istediğiniz +1 yapıştırılan işlemlerin sayısıdır.

Not: git rebase --autosquash, -i olmadan çalışmadı ama -i ile çalıştı, bu garip.


2016 ve --autosquasholmadan -ihala çalışmıyor.
Jonathan Çapraz

1
Man sayfasının dediği gibi: Bu seçenek yalnızca --interactive seçeneği kullanıldığında geçerlidir. Ancak editörü atlamanın kolay bir yolu var:EDITOR=true git rebase --autosquash -i
joeytwiddle

2. adım benim için çalışmıyor: Lütfen hangi şubeye karşı yeniden ödeme yapmak istediğinizi belirtin.
djangonaut

git rebase --autosquash -i HEAD ~ 2 (burada 2, değiştirmek istediğiniz +1 yapıştırılan kaydetme sayısıdır.
Sérgio

6

GÜNCELLEME: Komut dosyasının daha temiz bir sürümü artık burada bulunabilir: https://github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup .

Ben de benzer bir şey arıyordum. Bu Python betiği çok karmaşık görünüyor, bu yüzden kendi çözümümü bir araya getirdim:

İlk olarak, git takma adlarım şöyle görünür ( buradan ödünç alınmıştır ):

[alias]
  fixup = !sh -c 'git commit --fixup=$1' -
  squash = !sh -c 'git commit --squash=$1' -
  ri = rebase --interactive --autosquash

Şimdi bash işlevi oldukça basit hale geliyor:

function gf {
  if [ $# -eq 1 ]
  then
    if [[ "$1" == HEAD* ]]
    then
      git add -A; git fixup $1; git ri $1~2
    else
      git add -A; git fixup $1; git ri $1~1
    fi
  else
    echo "Usage: gf <commit-ref> "
  fi
}

Bu kod ilk olarak tüm mevcut değişiklikleri aşamalandırır (dosyaları kendiniz hazırlamak isterseniz bu bölümü kaldırabilirsiniz). Ardından düzeltme işlemini oluşturur (ihtiyacınız olan şey buysa squash da kullanılabilir) commit'i oluşturur. Bundan sonra --autosquash, argüman olarak verdiğiniz commit'in üstündeki bayrakla etkileşimli bir yeniden temel başlatır . Bu, yapılandırılmış metin düzenleyicinizi açacaktır, böylece her şeyin beklediğiniz gibi olduğunu doğrulayabilirsiniz ve düzenleyiciyi kapatmanız işlemi bitirecektir.

if [[ "$1" == HEAD* ]](Ödünç kısmı burada kullanırsanız, örneğin, BAŞ ~ 2 senin kadar taahhüt çünkü) düzeltme oluşturuldu kesinleştirme sonra referans ardından BAŞ yerinden edilecektir (mevcut değişiklikleri düzeltmek istiyorum işlemek), kullanılır ve aynı commit'e başvurmak için HEAD ~ 3 kullanmanız gerekir.


İlginç bir alternatif. +1
VonC

4

Bir "boş" düzenleyici kullanarak etkileşimli aşamadan kaçınabilirsiniz:

$ EDITOR=true git rebase --autosquash -i ...

Bu /bin/true, yerine düzenleyici olarak kullanılacaktır /usr/bin/vim. Git'in önerdiği her şeyi sormadan her zaman kabul eder.


Aslında, 30 Eylül 2010 tarihli Python betiği cevabımdaki 'orijinal cevabımda' yaptığım tam olarak buydu (betiğin altında nasıl yazıyor olduğuna dikkat edin call(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i" ...).
Frerich Raabe

4

Düzeltme iş akışıyla ilgili beni gerçekten rahatsız eden şey, her seferinde değişikliği sıkıştırmak istediğim taahhüdü kendim bulmam gerektiğiydi. Buna yardımcı olan bir "git fixup" komutu oluşturdum.

Bu komut, kullandığı ek büyü ile düzeltme işlemlerini oluşturur , ilgili commit'i otomatik olarak bulmak için git-deps commit'leri oluşturur, bu nedenle iş akışı genellikle:

# discover and fix typo in a previously committed change
git add -p # stage only typo fix
git fixup

# at some later point squash all the fixup commits that came up
git rebase --autosquash master

Bu yalnızca, aşamalı değişiklikler açık bir şekilde çalışma ağacındaki (ana ve HEAD arasında) belirli bir işleme atfedilebiliyorsa işe yarar. Bunun için kullandığım küçük değişiklik türleri için, örneğin yeni tanıtılan (veya yeniden adlandırılmış) yöntemlerin yorumlarında veya adlarında yazım hataları gibi çok sık durumun böyle olduğunu görüyorum. Aksi takdirde, en azından aday taahhütlerin bir listesini gösterecektir.

Bunu daha önce değiştirilmiş satırlardaki küçük değişiklikleri hızlı bir şekilde çalışma dalımdaki taahhütlere entegre etmek için günlük iş akışımda çok kullanıyorum . Senaryo olabildiğince güzel değil ve zsh ile yazılmış, ancak işi benim için yeterince iyi yapıyor ve şimdi onu yeniden yazma gereğini hiç hissetmedim:

https://github.com/Valodim/git-fixup


2

Bu diğer adı kullanarak belirli bir dosya için düzeltme oluşturabilirsiniz .

[alias]
...
# fixup for a file, using the commit where it was last modified
fixup-file = "!sh -c '\
        [ $(git diff          --numstat $1 | wc -l) -eq 1 ] && git add $1 && \
        [ $(git diff --cached --numstat $1 | wc -l) -eq 1 ] || (echo No changes staged. ; exit 1) && \
        COMMIT=$(git log -n 1 --pretty=format:"%H" $1) && \
            git commit --fixup=$COMMIT && \
            git rebase -i --autosquash $COMMIT~1' -"

İçinde bazı değişiklikler yaptıysanız, myfile.txtancak bunları yeni bir işleme koymak istemiyorsanız, kayıt için en son değiştirildiği yerde git fixup-file myfile.txtbir oluşturacak ve sonra yapacaktır .fixup!myfile.txtrebase --autosquash


Çok zekice, git rebasebunun otomatik olarak çağrılmamasını tercih ederim .
hurikhan77

2

commit --fixupve rebase --autosquashharika, ama yeterince yapmıyorlar. Bir dizi taahhüdüm olduğunda A-B-Cve çalışma ağacıma bu mevcut işlemlerden birine veya daha fazlasına ait bazı değişiklikler yazdığımda, geçmişe manuel olarak bakmam, hangi değişikliklerin hangi taahhütlere ait olduğuna karar vermem, onları aşamalandırmam ve fixup!taahhüt. Ancak git'in benim için tüm bunları yapabilecek kadar bilgiye zaten erişimi var, bu yüzden bir Perl betiği yazdım .

git diffKomut dosyasındaki her bir hunk git blameiçin, ilgili satırlara en son dokunan commit'i bulmak için kullanır ve esasen daha önce manuel olarak yaptığım şeyi yaparak git commit --fixupuygun fixup!commit'leri yazmaya çağırır .

Faydalı bulursanız, lütfen onu geliştirmekten ve yinelemekten çekinmeyin ve belki bir gün böyle bir özelliği gituygun şekilde elde ederiz . Etkileşimli bir yeniden ödeme ile sunulduğunda bir birleştirme çatışmasının nasıl çözülmesi gerektiğini anlayabilen bir araç görmeyi çok isterim.


Ayrıca otomasyonla ilgili hayallerim de vardı: git, yama kırılmadan onu olabildiğince geriye götürmeli. Ama yönteminiz muhtemelen daha mantıklı. Bunu denediğini görmek harika. Ben deneyeceğim! (Elbette, düzeltme yamasının dosyanın başka bir yerinde göründüğü ve yalnızca geliştiricinin hangi işleme ait olduğunu bildiği zamanlar vardır. Veya test paketindeki yeni bir test, makinenin düzeltmenin nereye gitmesi gerektiğini belirlemesine yardımcı olabilir.)
joeytwiddle

1

gcfDüzeltme işlemini ve yeniden temellemeyi otomatik olarak gerçekleştirmek için küçük bir kabuk işlevi yazdım :

$ git add -p

  ... select hunks for the patch with y/n ...

$ gcf <earlier_commit_id>

  That commits the fixup and does the rebase.  Done!  You can get back to coding.

Örneğin, en sondan önceki ikinci işlemeye şu şekilde yama uygulayabilirsiniz: gcf HEAD~~

İşte fonksiyon . Yapıştırabilirsiniz.~/.bashrc

git_commit_immediate_fixup() {
  local commit_to_amend="$1"
  if [ -z "$commit_to_amend" ]; then
    echo "You must provide a commit to fixup!"; return 1
  fi

  # Get a static commit ref in case the commit is something relative like HEAD~
  commit_to_amend="$(git rev-parse "${commit_to_amend}")" || return 2

  #echo ">> Committing"
  git commit --no-verify --fixup "${commit_to_amend}" || return 3

  #echo ">> Performing rebase"
  EDITOR=true git rebase --interactive --autosquash --autostash \
                --rebase-merges --no-fork-point "${commit_to_amend}~"
}

alias gcf='git_commit_immediate_fixup'

Kullanır --autostashGerektiğinde taahhüt edilmeyen değişiklikleri saklamak ve açmak için .

--autosquashyeniden --interactiveödeme gerektirir , ancak bir kukla kullanarak etkileşimden kaçınırız EDITOR.

--no-fork-pointnadir durumlarda (yeni bir şubeden ayrıldığınızda ve birisi geçmiş taahhütleri yeniden başlattığında) taahhütleri sessizce bırakılmaya karşı korur .


0

Otomatikleştirilmiş bir yolun farkında değilim, ancak işte botlaştırmayı daha kolay hale getirebilecek bir çözüm:

git stash
# write the patch
git add -p <file>
git commit -m"whatever"   # message doesn't matter, will be replaced via 'fixup'
git rebase -i <bad-commit-id>~1
# now cut&paste the "whatever" line from the bottom to the second line
# (i.e. below <bad-commit>) and change its 'pick' into 'fixup'
# -> the fix commit will be merged into the <bad-commit> without changing the
# commit message
git stash pop

Bir git fixupkomutu uygulamak için bundan yararlanan bir komut dosyası için yanıtıma bakın .
Frerich Raabe

@Frerich Raabe: Kulağa hoş geliyor, bilmiyorum--autosquash
Tobias Kienzler

0

Https://github.com/tummychow/git-absorb'u tavsiye ederim :

Asansör Konuşması

Birkaç kaydetme içeren bir özellik şubeniz var. Takım arkadaşınız şubeyi inceledi ve birkaç hataya işaret etti. Böcekler için düzeltmeleriniz var, ancak hepsini, düzeltmeler yazan opak bir işleme sokmak istemezsiniz, çünkü atomik işlemlere inanıyorsunuz. Manuel olarak commit SHA'ları bulmak git commit --fixupveya manuel etkileşimli bir yeniden taban çalıştırmak yerine şunu yapın :

  • git add $FILES_YOU_FIXED

  • git absorb --and-rebase

  • veya: git rebase -i --autosquash master

git absorbhangi kayıtların değiştirilmesinin güvenli olduğunu ve hangi indekslenmiş değişikliklerin bu kayıtların her birine ait olduğunu otomatik olarak belirleyecektir. Daha sonra düzeltme yazacak! bu değişikliklerin her biri için taahhüt eder. Güvenmiyorsanız, çıktısını manuel olarak kontrol edebilir ve ardından düzeltmeleri git'in yerleşik otomatik sıkıştırma işlevselliği ile özellik şubenize katlayabilirsiniz.

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.