1. ホーム
  2. git

[解決済み] 過去のコミットを簡単に修正するには?

2022-05-13 18:01:43

質問

私は今 git の過去のコミットで一つのファイルを修正する を読みましたが、残念ながらその解決策はコミットを「並べ替える」もので、私が望んでいるものではありません。そこで質問です。

時々、私は (無関係な) 機能に取り組んでいるときに、私のコードのバグに気づきます。迅速な 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

しかし、このようなことが頻繁に起こるので、上記のようなシーケンスが煩わしくなってきました。特に'interactive rebase'は退屈です。上記の手順で、過去の任意のコミットをステージングされた変更で修正できるようなショートカットはないでしょうか?これが履歴を変えることは完全に承知していますが、私はあまりにも頻繁に間違いを犯すので、次のようなものが本当に欲しいのです。

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.

plumbing toolsなどを使ってコミットを書き換えるようなスマートなスクリプトとか?

どのように解決するのですか?

解答を更新しました。

少し前に、新しい --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.exe を探し、それを GIT_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)