過去のコミットを簡単に修正するにはどうすればよいですか?


115

はgitの過去のコミットで1つのファイルの修正を読んだだけですが、残念ながら、受け入れられたソリューションはコミットを「並べ替え」ます。これは私が望んでいることではありません。だから私の質問です:

(無関係な)機能に取り組んでいるときに、コードにバグがあることに時々気づきます。すぐにgit blame、バグが数コミット前に導入されたことを明らかにします(私はかなりたくさんコミットしているので、通常、バグを導入したのは最新のコミットではありません)。この時点で、私は通常これを行います:

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

ただし、これは頻繁に発生するため、上記のシーケンスは煩わしくなります。特に「インタラクティブなリベース」は退屈です。上記のシーケンスへのショートカットはありますか?これにより、過去の任意のコミットを段階的な変更で修正できますか?これが歴史を変えることを私は完全に認識していますが、私は間違いをしていることが多いので、次のようなものが本当に好きです

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.

配管ツールなどを使用してコミットを書き換えることができるスマートスクリプトでしょうか?


コミットを「並べ替える」とはどういう意味ですか?履歴を変更する場合、変更されたコミット以降のすべてのコミットは異なる必要がありますが、リンクされた質問に対する受け入れられた回答は、意味のある意味でコミットを並べ替えません。
CBベイリー

1
@Charles:次のように並べ替えることを意味します:HEAD〜5が壊れたコミットであることに気付いた場合、リンクされた質問で受け入れられた回答に従うと、HEAD(ブランチの先端)が修正されたコミットになります。ただし、HEAD〜5を修正済みのコミットにしたい-これは、インタラクティブなリベースを使用し、修正のために1つのコミットを編集するときに得られるものです。
Frerich Raabe

はい。ただし、rebaseコマンドはマスターを再チェックアウトし、その後のすべてのコミットを修正されたコミットにリベースします。これはあなたが運転している方法ではないrebase -iですか?
CBベイリー

実際、その答えには潜在的な問題があると思いますrebase --onto tmp bad-commit master。書かれているように、それは不良コミットを固定コミット状態に適用しようとします。
CBベイリー

ここでは、フィックスアップ/リベースのプロセスを自動化するための別のツールです:stackoverflow.com/a/24656286/1058622
ミカEloranta

回答:


164

更新された回答

しばらく前に、に適したログメッセージでコミットを構築するために使用できる新しい--fixup引数が追加されgit commitましたgit 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

元の回答

これは、先ほど書いた小さなPythonスクリプトgit fixupで、元の質問で期待していたこのロジックを実装しています。スクリプトは、いくつかの変更をステージングしたと想定し、それらの変更を特定のコミットに適用します。

:このスクリプトはWindows固有です。を使用しgit.exeGIT_EDITOR環境変数を探して設定しますset。他のオペレーティングシステムの必要に応じてこれを調整します。

このスクリプトを使用して、「壊れたソースの修正、ステージの修正、git fixupの実行」ワークフローを正確に実装できます。

#!/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
リベースを使用git stashgit stash popて、クリーンな作業ディレクトリが不要になる
Tobias Kienzler

@TobiasKienzler:使用についてgit stashgit stash pop、あなたしている権利を、残念ながら:git stashあるくらい、それはLinuxやOS / Xであるよりも、Windows上で遅くなります。私の作業ディレクトリは通常クリーンであるため、コマンドを遅くしないようにこの手順を省略しました。
Frerich Raabe

特にネットワーク共有で作業している場合は確認できます:-/
Tobias Kienzler '08

1
いいね。私は誤っgit rebase -i --fixupてを実行し、修正されたコミットから開始点としてリベースしたため、私の場合、sha引数は必要ありませんでした。
fwielstra 2012

1
しばしば--autosquashを使用している人々のために、デフォルトの動作するように設定することが有用であり得る: git config --global rebase.autosquash true
taktak004

31

私がしていることは:

git add ...#修正を追加します。
git commit#コミットされましたが、間違った場所にあります。
git rebase -i HEAD〜5#リベースのために最後の5つのコミットを調べます。

エディターが開き、直近の5つのコミットのリストが表示されます。変化する:

08e833cを選びます。良い変化1。
ピック9134ac9良い変化2。
5adda55を選択してください。
pick 400bce4良い変化3。
pick 2bc82n1悪い変更の修正。

...に:

08e833cを選びます。良い変化1。
ピック9134ac9良い変化2。
5adda55を選択してください。
f 2bc82n1悪い変更の修正。#上に移動して、「pick」を「fixup」の「f」に変更します。
pick 400bce4良い変化3。

エディターを保存して終了すると、修正はそれが属するコミットに戻されます。

あなたがそれを数回行った後、あなたはあなたの睡眠中に数秒でそれをします。インタラクティブなリベースは、gitで本当に私を売り込んだ機能です。これ以上にとても便利です...


9
明らかに、HEAD〜5をHEAD〜nに変更してさらに戻ることができます。アップストリームにプッシュした履歴をいじりたくないので、通常は「git rebase -i origin / master」と入力して、プッシュされていない履歴のみを変更するようにします。
クリスジェンキンス

4
これは私がいつもやっていたこととよく似ています。FWIW、あなたはの--autosquashスイッチに興味があるかもしれませgit rebaseん。これは、エディタのステップを自動的に並べ替えます。これを利用してgit fixupコマンドを実装するスクリプトについては、私の応答を参照してください。
Frerich Raabe

コミットハッシュを並べ替えることができるとは知りませんでした。
アーロンフランケ2018年

すばらしい!すべてのリベース作業が行われたことを確認するためだけに、別の機能ブランチがあります。そしてマスターのような共通のブランチを台無しにしないでください。
ジェイ・モディ

22

パーティーには少し遅れますが、著者が想像したとおりに機能する解決策があります。

これを.gitconfigに追加してください:

[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' -"

使用例:

git add -p
git fixup HEAD~5

ただし、ステージングされていない変更がある場合は、リベースの前にそれらを隠しておく必要があります。

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

警告を出す代わりに、エイリアスを変更して自動的に隠しておくこともできます。ただし、フィックスアップが正しく適用されない場合は、競合を修正した後、スタッシュを手動でポップする必要があります。保存とポップの両方を手動で行うと、一貫性が増し、混乱が少なくなります。


これはかなり役に立ちます。私にとって最も一般的な使用例は、前のコミットへの変更を修正することです。そのためgit fixup HEAD、エイリアスを作成しました。私はまた、私が思うに修正を使用することができました。
バッタ

ありがとうございました!私はまた、最後のコミットでそれを最も頻繁に使用しますが、迅速な修正のために別のエイリアスがあります。amend = commit --amend --reuse-message=HEAD次に、単に入力するgit amendgit amend -a、コミットメッセージのエディターをスキップします。
dschlyter 16

3
amendの問題は、綴り方が思い出せないことです。私はいつも考えなければなりません、それは修正または修正であり、それは良くありません。
バッタ

11

1つのコミットを修正するには:

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

ここで、a0b1c2d3は修正が必要なコミットで、2は変更したい+1が貼り付けられたコミットの数です。

注:-iなしでgit rebase --autosquashは機能しませんが、-iを使用すると機能します。これは奇妙です。


2016年--autosquash-iまだ機能しません。
ジョナサンクロス

1
マンページが言うように:このオプションは、--interactiveオプションが使用されている場合にのみ有効です。しかし、エディタをスキップする簡単な方法があります:EDITOR=true git rebase --autosquash -i
joeytwiddle

次のように言って、2番目のステップは機能しません。リベースするブランチを指定してください。
djangonaut

git rebase --autosquash -i HEAD〜2(2は、変更する+1貼り付けられたコミットの数です。
SérgioAug

6

更新:スクリプトのよりクリーンなバージョンは、https//github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixupにあります

私は似たようなものを探していました。ただし、このPythonスクリプトは複雑すぎるようです。そのため、私は自分の解決策をまとめました。

まず、私のgitエイリアスは次のようになります(ここから借用):

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

これで、bash関数は非常に単純になります。

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
}

このコードは最初に現在のすべての変更をステージングします(自分でファイルをステージングする場合は、この部分を削除できます)。次に、フィックスアップ(必要に応じてスカッシュを使用することもできます)コミットを作成します。その後--autosquash、引数として指定したコミットの親のフラグを使用してインタラクティブなリベースを開始します。これにより、構成されたテキストエディターが開きます。そのため、すべてが期待どおりであることを確認でき、エディターを閉じるだけでプロセスが終了します。

if [[ "$1" == HEAD* ]](から借り一部ここで、その後HEADがフィックスアップ後に変位され、あなたの(あなたが現在の変更を修正したいコミット)を基準に、あなたが使用している場合ので、たとえば、使用されている)、HEAD〜2が作成されているコミット同じコミットを参照するには、HEAD〜3を使用する必要があります。


興味深い代替案。+1
VonC

4

「null」エディターを使用することで、インタラクティブなステージを回避できます。

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

これはの/bin/true代わりにエディターとして使用され/usr/bin/vimます。プロンプトなしで、gitの提案を常に受け​​入れます。


実際、これは2010年9月30日の「元の回答」のPythonスクリプトの回答で私がしたこととまったく同じです(スクリプトの下部にある方法に注意してくださいcall(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i" ...)。
Frerich Raabe

4

修正ワークフローについて本当に気になったのは、毎回変更を押しつぶしたいコミットを自分で見つけなければならないことでした。これを支援する「git fixup」コマンドを作成しました。

このコマンドはフィックスアップコミットを作成し、関連するコミットをgit-depsを使用して自動的に見つけるという魔法が追加されているため、ワークフローは次のようになります。

# 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

これは、ステージングされた変更が作業ツリー(マスターとHEADの間)の特定のコミットに明確に起因する場合にのみ機能します。たとえば、コメントのタイプミスや新しく導入された(または名前が変更された)メソッドの名前など、これを使用する小さな変更の種類がこれに該当することがよくあります。そうでない場合は、少なくとも候補コミットのリストが表示されます。

私はこれを毎日のワークフローで頻繁に使用して以前に変更された行への小さな変更を作業中のブランチのコミットにすばやく統合します。スクリプトは見た目ほど美しくなく、zshで書かれていますが、書き直す必要性を感じたことがないので、十分にうまく機能してきました。

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


2

このエイリアスを使用して、特定のファイルのフィックスアップを作成できます。

[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' -"

でいくつかの変更を加えたmyfile.txtが、それらを新しいコミットに入れたくない場合は、最後に変更されたコミットのgit fixup-file myfile.txtを作成し、それからを行います。fixup!myfile.txtrebase --autosquash


非常に賢く、git rebase自動的に呼び出されない方がいいと思います。
hurikhan77

2

commit --fixupそしてrebase --autosquash素晴らしいですが、彼らは十分に行いません。一連のコミットがA-B-Cあり、1つ以上の既存のコミットに属する変更を作業ツリーにさらに書き込んだ場合、手動で履歴を確認し、どのコミットに属する変更を決定し、ステージングして、fixup!コミット。しかし、gitはすでにすべてを私に代わって実行するのに十分な情報にアクセスできるので、私はそれだけを行うPerlスクリプトを作成しました。

git diffスクリプト内の各ハンクについてgit blame、関連する行に最後に触れたコミットを見つけるために使用しgit commit --fixup、適切なfixup!コミットを書き込むための呼び出しを行います。基本的に、以前手動で行っていたのと同じことを行います。

もしそれが便利だと思ったら、遠慮なくそれを改善し、繰り返してgitください。多分いつか私たちはそのような機能を適切に手に入れるでしょう。インタラクティブなリベースによって導入されたマージの競合を解決する方法を理解できるツールが欲しいです。


また、自動化についても夢を見ていました。Gitは、パッチを壊すことなく、可能な限り過去の履歴に戻そうとします。しかし、あなたの方法はおそらくもっと正気です。あなたがそれを試みたのを見るのは素晴らしいことです。やってみよう!(もちろん、フィックスアップパッチがファイルの他の場所にあり、開発者だけがそれがどのコミットに属しているかを知っている場合があります。あるいは、テストスイートの新しいテストは、マシンが修正を行うべき場所を見つけるのに役立ちます。)
joeytwiddle 2016年

1

gcfフィックスアップコミットとリベースを自動的に実行するために呼び出される小さなシェル関数を作成しました。

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

たとえば、次のコマンドを使用して、最新の前の2番目のコミットにパッチを適用できます。 gcf HEAD~~

こちらが機能です。あなたはあなたにそれを貼り付けることができます~/.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'

--autostash必要に応じて、コミットされていない変更を隠してポップするために使用します。

--autosquash--interactiveリベースが必要ですが、ダミーを使用して相互作用を回避しEDITORます。

--no-fork-pointまれな状況(新しいブランチから分岐していて、誰かがすでに過去のコミットをリベースしている場合)で、コミットが黙ってドロップされるのを防ぎます。


0

私は自動化された方法を認識していませんが、これは人間によるボット化を容易にする可能性のあるソリューションです:

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

これを利用してgit fixupコマンドを実装するスクリプトについては、私の応答を参照してください。
Frerich Raabe

@Frerich Raabe:いいですね、私は知りません--autosquash
Tobias Kienzler

0

私はhttps://github.com/tummychow/git-absorbをお勧めします

エレベーターピッチ

いくつかのコミットがある機能ブランチがあります。チームメイトがブランチをレビューし、いくつかのバグを指摘しました。バグの修正はありますが、アトミックコミットを信じているため、修正を示す不透明なコミットにそれらすべてを押し込みたくありません。のコミットSHAを手動で見つけるgit commit --fixupか、手動のインタラクティブリベースを実行する代わりに、次のようにします。

  • git add $FILES_YOU_FIXED

  • git absorb --and-rebase

  • または: git rebase -i --autosquash master

git absorb変更しても安全なコミットと、それらの各コミットに属するインデックス付きの変更が自動的に識別されます。その後、フィックスアップを作成します!それらの変更のそれぞれに対してコミットします。信頼できない場合は、その出力を手動で確認し、修正プログラムをgitの組み込みの自動スカッシュ機能を使用して機能ブランチに折りたたむことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.