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.
配管ツールなどを使用してコミットを書き換えることができるスマートなスクリプトでしょうか?
しばらく前に、--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
を探し、set
を使用してGIT_EDITOR
環境変数を設定します。他のオペレーティングシステムの必要に応じて、これを調整します。
このスクリプトを使用して、「壊れたソースの修正、ステージの修正、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)
私がやることは:
git add ...#修正を追加します。 git commit#コミットされましたが、間違った場所にあります。 git rebase -i HEAD〜5#最後の5つのコミットを調べますリベース。
エディターが開き、直近の5つのコミットのリストが表示されます。変化する:
pick 08e833c適切な変更1。 pick 9134ac9適切な変更2。 pick 5adda55悪い変更! pick 400bce4適切な変更3。 pick 2bc82n1悪い変更の修正。
...に:
ピック08e833c良い変更1。 ピック9134ac9良い変更2。 ピック5adda55悪い変更! f 2bc82n1悪い変更の修正。 #上に移動し、「fixup」の「pick」を「f」に変更します。 pick 400bce4 Good change 3。
エディターを保存して終了すると、修正は所属するコミットに戻されます。
それを数回行った後、あなたはあなたの睡眠中に数秒でそれをします。インタラクティブなリベースは、gitで私を本当に売り込んだ機能です。これなどに非常に便利です...
パーティーに少し遅れましたが、著者が想像したとおりに機能するソリューションを以下に示します。
これを.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
警告を出す代わりに、エイリアスを自動的にstashに変更できます。ただし、修正がきれいに適用されない場合は、競合を修正した後に手動でスタッシュをポップする必要があります。保存とポップの両方を手動で行うと、一貫性が増し、混乱が少なくなります。
1つのコミットを修正するには:
git commit --fixup a0b1c2d3 .
git rebase --autosquash -i
ここで、a0b1c2d3は修正するコミットです。
注:git rebase --autosquashは-iなしでは機能しませんが、-iを使用すると機能しますが、これは奇妙です。
UPDATE:スクリプトのよりクリーンなバージョンがここにあります: https://github.com/deiwin/git-dotfiles/ blob/docs/bin/git-fixup 。
似たようなものを探していました。このPythonスクリプトはあまりにも複雑に思えますが、そのため私は自分の解決策をまとめました:
まず、私のgitエイリアスはそのように見えます( here から借用):
[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* ]]
部分( here から借用)が使用されます。たとえば、コミット(現在の変更を修正するコミット)参照としてHEAD〜2を使用すると、 HEADは修正コミットが作成された後に置き換えられます。同じコミットを参照するにはHEAD〜3を使用する必要があります。
「null」エディターを使用して、インタラクティブなステージを回避できます。
$ EDITOR=true git rebase --autosquash -i ...
これは、/bin/true
の代わりに/usr/bin/vim
をエディターとして使用します。プロンプトが表示されることなく、常にgitが提案するものはすべて受け入れます。
修正ワークフローについて本当に気になったのは、変更を毎回押しつぶしたいコミットを自分で見つけなければならなかったことです。これに役立つ「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の間)の特定のコミットに明確に起因している場合にのみ機能します。私がこれを使用するタイプの小さな変更の場合、それは非常に頻繁に当てはまることがわかります。コメントのタイプミスまたは新しく導入された(または名前が変更された)メソッドの名前。そうでない場合は、少なくともコミット候補のリストが表示されます。
毎日のワークフローでこれをalot使用して、以前に変更した行に対する小さな変更を作業ブランチのコミットにすばやく統合します。スクリプトは可能な限り美しくなく、zshで書かれていますが、それを書き換える必要性を感じなかったので、しばらくの間十分に仕事をしてきました。
修正コミットとリベースを自動的に実行するために、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.
たとえば、gcf HEAD~~
を使用して、最新のコミットの前に2番目のコミットにパッチを適用できます。
関数 です。 ~/.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 \
--preserve-merges "${commit_to_amend}~"
}
alias gcf='git_commit_immediate_fixup'
--autostash
を使用して、必要に応じてコミットされていない変更を隠してポップします。
--autosquash
には--interactive
リベースが必要ですが、ダミーのEDITOR
を使用して相互作用を回避しています。
commit --fixup
とrebase --autosquash
は優れていますが、十分ではありません。一連のコミットA-B-C
があり、1つ以上の既存のコミットに属する変更を作業ツリーにさらに書き込むと、履歴を手動で確認し、どの変更がどのコミットに属するかを判断する必要があります。それらをステージングし、fixup!
コミットを作成します。しかし、gitはすでに十分な情報にアクセスできるので、すべてのことができます。そのために Perlスクリプト を作成しました。
git diff
の各ハンクに対して、スクリプトはgit blame
を使用して、関連する行に最後に触れたコミットを見つけ、git commit --fixup
を呼び出して適切なfixup!
コミットを書き込み、本質的に同じことを行います前に手動でやっていたこと。
便利だと思ったら、お気軽に改善して反復してください。いつか、適切なgit
でそのような機能を利用できるようになるでしょう。インタラクティブなリベースによって導入されたマージの競合をどのように解決すべきかを理解できるツールが欲しいです。
私は自動化された方法を知っていませんが、人間がボット化する方が簡単かもしれないソリューションがあります:
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
このエイリアスを使用して、特定のファイルに対してfixupを作成できます。
[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.txt
を作成します変更すると、rebase --autosquash
になります。