中心にある付加価値の一つは、履歴を編集できることです。履歴を神聖な記録として扱うバージョン管理システムとは異なり、Gitでは必要に応じて履歴を修正することが可能です。これは、リファクタリングが良いソフトウェア設計のプラクティスを維持するために使われるのと同じように、良いコミット履歴を編むための強力なツールの数々を提供します。これらのツールは、新しい Git ユーザーや経験豊富な Git ユーザーにとっても少々とっつきにくいものですが、このガイドでは強力な git-rebase の使い方をわかりやすく説明します。
注意すべき点: 一般に、公開ブランチや共有ブランチ、安定版ブランチの履歴は編集しないことを推奨します。機能ブランチや個人ブランチ、そしてまだプッシュされていないコミットの履歴を編集することは可能です。コミットを編集した後で git push -f を使用すると、個人ブランチやトピックブランチに強制的にプッシュすることができます。
この悲惨な警告にもかかわらず、このガイドで述べられていることはすべて非破壊的な操作であることを述べておく価値があります。実際、Gitでデータを永久に失うことは非常に困難です。このガイドの最後には、もし間違いを犯してしまった場合の修正方法が書かれています。
サンドボックスの設定
実際のリポジトリを壊したくないので、このガイドではサンドボックス化されたリポジトリを使用します。以下のコマンドを実行して開始してください。
git init /tmp/rebase-sandboxcd /tmp/rebase-sandboxgit commit --allow-empty -m"Initial commit"
問題が発生した場合は、 rm -rf /tmp/rebase-sandbox実行し、これらのステップを再実行してやり直すだけです。このガイドの各ステップは新しいサンドボックスで実行できるので、すべてのタスクをやり直す必要はありません。
最近のコミットの修正
まずは簡単なことから始めましょう。直近のコミットを修正するのです。サンドボックスにファイルを追加してミスを犯してみましょう。
echo "Hello wrold!" >greeting.txtgit add greeting.txtgit commit -m"Add greeting.txt"
このバグの修正はとても簡単です。次のようにファイルを編集して--amendをつけて投稿するだけです:
echo "Hello world!" >greeting.txtgit commit -a --amend
a を指定すると、Git がすでに知っているすべてのファイルを自動的にステージします。また、--amend を指定すると、変更を最新のコミットにまとめます。保存してエディタを終了します。修復されたコミットは、git show を実行すれば見ることができます。
commit f5f19fbf6d35b2db37dcac3a55289ff (HEAD -> master)Author: Drew DeVaultDate: Sun Apr : -0400Add greeting.txtdiff --git a/greeting.txt b/greeting.txtnew file modeindex ..cd78055--- /dev/null+++ b/greeting.txt@@ -0,0 +1 @@+Hello world!
古いコミットの修正
--amend は最近のコミットにのみ適用されます。古いコミットを修正する必要がある場合はどうなるでしょうか?まずはサンドボックスの設定から始めましょう:
echo "Hello!" >greeting.txtgit add greeting.txtgit commit -m"Add greeting.txt"echo "Goodbye world!" >farewell.txtgit add farewell.txtgit commit -m"Add farewell.txt"
greeting.txt "world "が抜けているようです。これを修正するために普通にコミットを書きましょう:
echo "Hello world!" >greeting.txtgit commit -a -m"fixup greeting.txt"
これでファイルは正しく見えますが、履歴はもう少しよくなります。最後のコミットを新しいコミットで「修正」することができます。これを行うには、対話的リベースという新しいツールを導入する必要があります。最後の 3 つのコミットをこの方法で編集するので、git rebase -i HEAD~3 を実行します:
pick 8d3fc77 Add greeting.txtpick 2a73a77 Add farewell.txtpick 0b9d0bb fixup greeting.txt# Rebase f5f19fb..0b9d0bb onto f5f19fb (3 commands)# Commands:# p, pick <commit> = use commit# f, fixup <commit> = like "squash", but discard this commit's log message
このファイルを編集することで、Git に履歴の編集方法を指示することができます。この要約は、基本変更計画のこの部分に関連する詳細な内容だけに削りましたが、テキストエディタで完全な要約を見ることができます。
保存してエディターを閉じると、Git はそれらのコミットをすべて履歴から削除し、一行ずつ実行します。デフォルトでは、それぞれのコミットをヒープから取り出してブランチに追加します。このファイルにまったく編集が加えられていない場合は、最初に戻ってそのまま各コミットを実行します。さて、いよいよ私のお気に入りの機能のひとつである repair を使ってみましょう。三行目を編集してアクションを pick から fixup に変更し、「修正」したいコミットの直後に移動します:
pick 8d3fc77 Add greeting.txtfixup 0b9d0bb fixup greeting.txtpick 2a73a77 Add farewell.txt
ヒント:次回のスピードを上げるために、fと省略することもできます。
保存してエディターを終了すると、Gitがこれらのコマンドを実行します。ログを見て結果を確認することができます:
$ git log -2 --onelinefcff6ae (HEAD -> master) Add farewell.txta479e94 Add greeting.txt
複数の投稿を1つにフラット化
小さなマイルストーンに取り組むときや前のコミットのバグを修正するときには、たくさんのコミットを書くと便利です。しかし、master ブランチに作業をマージする際には、これらのコミットを "スクシャッシュ" して履歴をわかりやすくしておくと便利です。これを行うには、"squash" 操作を使用します。まずはたくさんのコミットを書いてみましょう。スピードアップしたい場合は、それをコピーして貼り付けるだけです:
git checkout -b squashfor c in H e l l o , ' ' w o r l d; doecho "$c" >>squash.txtgit add squash.txtgit commit -m"Add '$c' to squash.txt"
"こんにちは、世界 "というファイルを作るには、いろいろなことが必要です。ベースの別のインタラクティブなバリエーションを開始し、それらを一緒につぶしてみましょう。これを試すために、最初にブランチがチェックアウトされたことに注意しましょう。git rebase -i master でブランチが作成されたので、すべてのコミットをすばやくリベースすることができます。その結果
pick 1e85199 Add 'H' to squash.txtpick fff6631 Add 'e' to squash.txtpick b354c74 Add 'l' to squash.txtpick 04aaf74 Add 'l' to squash.txtpick 9b0f720 Add 'o' to squash.txtpick d Add ',' to squash.txtpick dc158cd Add ' ' to squash.txtpick dfcf9d6 Add 'w' to squash.txtpick 7a85f34 Add 'o' to squash.txtpick c275c27 Add 'r' to squash.txtpick a513fd1 Add 'l' to squash.txtpick 6b608ae Add 'd' to squash.txt# Rebase 1af1b46..6b608ae onto 1af1b46 (12 commands)# Commands:# p, pick <commit> = use commit# s, squash <commit> = use commit, but meld into previous commit
ヒント: ローカルの master ブランチはリモートの master ブランチとは独立して進化し、Git はリモートブランチを origin/master として保存します。このテクニックと組み合わせることで、上流にマージされていないコミットをリベースするのにとても便利な方法と
git rebase -i origin/master!
を実行すると、これらの変更がすべて最初のコミットに取り込まれます。これを行うには、以下のように最初の行以外のすべての "pick" アクションを "squash" に変更します:
pick 1e85199 Add 'H' to squash.txtsquash fff6631 Add 'e' to squash.txtsquash b354c74 Add 'l' to squash.txtsquash 04aaf74 Add 'l' to squash.txtsquash 9b0f720 Add 'o' to squash.txtsquash d Add ',' to squash.txtsquash dc158cd Add ' ' to squash.txtsquash dfcf9d6 Add 'w' to squash.txtsquash 7a85f34 Add 'o' to squash.txtsquash c275c27 Add 'r' to squash.txtsquash a513fd1 Add 'l' to squash.txtsquash 6b608ae Add 'd' to squash.txt
保存してエディターを閉じると、Git はしばらく考えてから再びエディターを開き、最終的なコミットメッセージを修正します。次のように表示されます:
# This is a combination of 12 commits.# This is the 1st commit message:Add 'H' to squash.txt# This is the commit message #2:Add 'e' to squash.txt# This is the commit message #3:Add 'l' to squash.txt# This is the commit message #4:Add 'l' to squash.txt# This is the commit message #5:Add 'o' to squash.txt# This is the commit message #6:Add ',' to squash.txt# This is the commit message #7:Add ' ' to squash.txt# This is the commit message #8:Add 'w' to squash.txt# This is the commit message #9:Add 'o' to squash.txt# This is the commit message #10:Add 'r' to squash.txt# This is the commit message #11:Add 'l' to squash.txt# This is the commit message #12:Add 'd' to squash.txt# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# Date: Sun Apr : -0400# interactive rebase in progress; onto 1af1b46# Last commands done (12 commands done):# squash a513fd1 Add 'l' to squash.txt# squash 6b608ae Add 'd' to squash.txt# No commands remaining.# You are currently rebasing branch 'squash' on '1af1b46'.# Changes to be committed:# new file: squash.txt
ヒント:前のセクションで説明した "repair "コマンドもこの目的に使えますが、これはつぶれたコミットを破棄します。
すべてを削除して、以下のようなよりよいコミットメッセージに置き換えてみましょう:
Add squash.txt with contents "Hello, world"# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# Date: Sun Apr : -0400# interactive rebase in progress; onto 1af1b46# Last commands done (12 commands done):# squash a513fd1 Add 'l' to squash.txt# squash 6b608ae Add 'd' to squash.txt# No commands remaining.# You are currently rebasing branch 'squash' on '1af1b46'.# Changes to be committed:# new file: squash.txt
保存してエディターを終了し、Gitログを確認してください!
commit cc7dff76f21ce2cad7c51cf2af (HEAD -> squash)Author: Drew DeVaultDate: Sun Apr : -0400Add squash.txt with contents "Hello, world"
次に進む前に、行った変更を master ブランチに取り込んでドラフトを削除しましょう。git rebase は git merge と同じように使えますが、マージコミットを作成せずに済みます:
git checkout mastergit rebase squashgit branch -D squash
一般に、関係のない履歴を実際にマージするのでなければ git merge の使用は避けたいものです。また、2 つの異なるブランチがある場合は、マージするタイミングを管理するために git merge が非常に便利です。通常の作業では、ベースを変更するほうが適切でしょう。
コミットを複数の
コミットが大きすぎるという逆の問題が起こることもあります。コミットを分割してみましょう。今回は実際のコードを書いてみましょう。まずは簡単なC言語プログラムから始めましょう:
cat <<EOF >main.cint main(int argc, char *argv[]) {return 0;
まず提出してください:
git add main.cgit commit -m"Add C program skeleton"
そして、その手順を少し拡大します:
cat <<EOF >main.c#include <stdio.h>const char *get_name() {static char buf;scanf("%s", buf);return buf;int main(int argc, char *argv[]) {printf("What's your name? ");const char *name = get_name();printf("Hello, %s! ", name);return 0;
提出が済んだら、それをどのように分解するかを学びます:
git commit -a -m"Flesh out C program"
最初のステップは、対話的なベース変更を開始することです。以下の varbase プランをもとに、 git rebase -i HEAD~2 2 つのコミットを varbase してみましょう:
pick Add C program skeletonpick b3f188b Flesh out C program# Rebase c785f47..b3f188b onto c commands)# Commands:# p, pick <commit> = use commit# e, edit <commit> = use commit, but stop for amending
二番目のコミットのコマンドを pick から edit に変更し、保存してエディターを閉じます:
Stopped at b3f188b... Flesh out C programYou can amend the commit now, withgit commit --amendOnce you are satisfied with your changes, rungit rebase --continue
新しい変更をコミットする方法は後述しますが、git reset HEAD^ を実行することで「ソフトリセット」を行うことができます。そのあとで git status を実行すると、最新のコミットがキャンセルされてその変更が作業ツリーに追加されていることがわかります:
Last commands done (2 commands done):pick Add C program skeletonedit b3f188b Flesh out C programNo commands remaining.You are currently splitting a commit while rebasing branch 'master' on 'c785f47'.(Once your working directory is clean, run "git rebase --continue")Changes not staged for commit:(use "git add ..." to update what will be committed)(use "git checkout -- ..." to discard changes in working directory)modified: main.cno changes added to commit (use "git add" and/or "git commit -a")
分割されるようにするためです。これにより、作業ツリーの特定の変更だけを選択的にコミットできるようになります。git commit -p を実行してこの処理を開始すると、次のようなプロンプトが表示されます:
diff --git a/main.c b/main.cindex b1d9c2c..644--- a/main.c+++ b/main.c@@ -1,3 +1,14 @@+#include <stdio.h>+const char *get_name() {+ static char buf;+ scanf("%s", buf);+ return buf;int main(int argc, char *argv[]) {+ printf("What's your name? ");+ const char *name = get_name();+ printf("Hello, %s! ", name);return 0;Stage this hunk [y,n,q,a,d,s,e,?]?
Git は、コミットするための「チャンク」をひとつしか与えてくれません。しかし、それでは大きすぎます。そこで、s コマンドを使ってその「塊」を小さな部分に分割してみましょう。
Split into 2 hunks.@@ -1 +1,9 @@+#include <stdio.h>+const char *get_name() {+ static char buf;+ scanf("%s", buf);+ return buf;int main(int argc, char *argv[]) {Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?
ヒント:他のオプションが気になる場合は、? サマリー表示。
この塊の方が良さそうです。単一の独立した変更です。yを押して質問に答え、qを押して対話型セッションを終了し、コミットを続けましょう。適切なコミットメッセージを求めるエディターがポップアップします。
Add get_name function to C program# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# interactive rebase in progress; onto c785f47# Last commands done (2 commands done):# pick Add C program skeleton# edit b3f188b Flesh out C program# No commands remaining.# You are currently splitting a commit while rebasing branch 'master' on 'c785f47'.# Changes to be committed:# modified: main.c# Changes not staged for commit:# modified: main.c
保存してエディターを閉じ、2回目のコミットを実行します。もう一回対話的にコミットを実行することも可能ですが、このコミットでは残りの変更点のみをコミットしたいので、次のようにします:
git commit -a -m"Prompt user for their name"git rebase --continue
最後のコマンドは Git にこのコミットの編集が終わったことを伝え、次の varbase コマンドに移ります。これで終わりです!git log を実行すると、作業の成果を見ることができます:
$ git log -3 --onelinefe19cc3 (HEAD -> master) Prompt user for their nameAdd get_name function to C programAdd C program skeleton
提出書類の並び替え
とても簡単です。サンドボックスの設定から始めましょう:
echo "Goodbye now!" >farewell.txtgit add farewell.txtgit commit -m"Add farewell.txt"echo "Hello there!" >greeting.txtgit add greeting.txtgit commit -m"Add greeting.txt"echo "How're you doing?" >inquiry.txtgit add inquiry.txtgit commit -m"Add inquiry.txt"
これで git log 次のようになります:
f03baa5 (HEAD -> master) Add inquiry.txta4cebf7 Add greeting.txt90bb015 Add farewell.txt
明らかに、これはすべて順番が狂っています。最後の3つのコミットを対話的にベース変更することで、これを修正しましょう。 git rebase -i HEAD~3実行すると、この varbase プランが表示されます:
pick 90bb015 Add farewell.txtpick a4cebf7 Add greeting.txtpick f03baa5 Add inquiry.txt# Rebase fe19cc3..f03baa5 onto fe19cc3 (3 commands)# Commands:# p, pick <commit> = use commit# These lines can be re-ordered; they are executed from top to bottom.
解決方法は簡単です。コミットを表示したい順番に行を並べ替えるだけです。以下のようになります:
pick a4cebf7 Add greeting.txtpick f03baa5 Add inquiry.txtpick 90bb015 Add farewell.txt
保存してエディターを閉じれば、あとは Git がやってくれます。コンフリクトの解決については次のセクションを参照ください。
git pull -rebase
上流で更新されたブランチ <branch> に対してコミットを行った場合、通常 git pull はマージコミットを作成します。この点で、git pull のデフォルトの挙動は同等です:
git fetch origin <branch>git merge origin/<branch>
ローカル <branch> 元のリモートからのブランチを追跡 <branch> ように設定されているとします:
$ git config branch.<branch>.remote$ git config branch.<branch>.mergerefs/heads/<branch>
もう1つのオプションがあり、これは通常より便利で、履歴がより明確になりますgit pull --rebaseマージのアプローチとは異なり、これは本質的に以下のものと同じです:
git fetch origingit rebase origin/<branch>
merge メソッドのほうがシンプルでわかりやすいですが、git rebase の使い方を理解していれば variable-base メソッドでほとんど何でもできるようになります。お望みなら、以下のようにデフォルトの挙動として設定することもできます:
git config --global pull.rebase true
リベースには git rebase を使います。
皮肉なことに、私が一番使っていない Git の changebase 機能が、その名の由来となっている changebase branches です。たとえば次のようなブランチがあったとしましょう:
A--B--C--D--> master\--E--F--> feature-1\--G--> feature-2
その結果、feature-2はfeature-1の変更に依存せず、コミットEに依存していることがわかりました:
git rebase --onto master feature-1 feature-2
非対話的な varbase は、feature-1 にない feature-2 からのコミットを master にリダイレクトします。履歴はこのようになります:
A--B--C--D--> master| \--G--> feature-2\--E--F--> feature-1
コンフリクトの解決
マージの衝突の解決に関する詳細な情報は、このガイドの範囲を超えています。あなたがコンフリクトを解決する通常の方法に精通していると仮定して、ここでは特に変数ベースに適用されるセクションを示します。
git は影響を受けるファイルにコンフリクトフラグを設定し、git status は解決すべき内容を表示します。そして、git add や git rm を使ってファイルを解決済みとしてマークすることができます。しかし、git のリベースには注意すべきオプションがふたつあります。
まず第一に、コンフリクトの解決方法です。git commit のようなコマンドで git merge によるコンフリクトを解決するのではなく、より適切なリベースコマンドは git rebase --continue です。 しかし、もうひとつ別のオプションがあります。 git rebase --skip です。 これは作業中のコミットをスキップし、リベースには含めません。これは、非対話的なリベースを行う場合によくあることで、"other" ブランチから引っ張ってきたコミットが "" ブランチで競合しているコミットの新しいバージョンであることに Git が気づかないというものです。
助けて 壊しちゃった
ベースを変更するのが難しいことがあるのは間違いありません。ミスを犯して必要なコミットを失ってしまった場合は、git reflog を使用しましょう。このコマンドを実行すると、参照を変更するすべてのアクションが表示されます。各行には、古い参照が指す内容が表示され、git cherry-pick、git checkout、git show、あるいはその他のアクションを実行することができます。
このチュートリアルの残りの部分を簡単にするために、空の初期コミットを追加しました。
このプログラムをコンパイルするには、
\--G--> feature-2実行し、次に./mainを実行して結果を確認してください。これは実際には「ハイブリッドリセット」です。ソフトリセット」は変更を保持したままなので、git で再度追加する必要はなく、すべての変更を一度にコミットすることができます。これは、あなたが望んでいるものではありません。コミットを分割するために、一部の変更を選択的にステージしたいのです。
git pull --rebase は、git rebase や git merge-base の "" メカニズムを使用してローカルでないコミットのリベースを回避し、この状況から回復しようとします。
実際には、これはGitのバージョンに依存します。バージョン 2.26.0 までは、デフォルトの非対話的な振る舞いは対話的な振る舞いとは若干異なっていました。





