|
[[328447]] Gitの核となる価値の一つは、コミット履歴を編集できることです。履歴を神聖な記録のように扱うバージョン管理システムとは異なり、Gitではコミット履歴をニーズに合わせて変更できます。これにより、優れたソフトウェア設計プラクティスを維持するためにリファクタリングを使用するのと同じように、構造化されたコミット履歴を構築するための強力なツールが豊富に提供されます。これらのツールは、初心者だけでなく経験豊富なGitユーザーにとっても扱いにくい場合がありますが、このガイドは強力なgit-rebaseの謎を解き明かすのに役立ちます。 一般的に、公開ブランチ、共有ブランチ、または安定ブランチの履歴は変更しないことが推奨されています。フィーチャーブランチと個人用ブランチの履歴の編集は許可されており、まだプッシュされていないコミットの編集も可能です。コミットを編集した後は、 git push -fを使用することで、変更内容を個人用ブランチまたはフィーチャーブランチに強制的にプッシュできます。
これらの警告にもかかわらず、このガイドで説明されているすべての操作は非破壊的であることに留意してください。実際、Gitでデータを永久に失うことは非常に困難です。ガイドの最後には、間違いを修正する方法も記載されています。 サンドボックスを設定する実際のリポジトリを壊したくないので、このガイドではサンドボックス化されたリポジトリを使用します。開始するには、以下のコマンドを実行してください。1 -
git init / tmp / rebase - sandbox -
cd / tmp / rebase - sandbox -
git commit -- allow - empty - m "Initial commit"
問題が発生した場合は、 rm -rf /tmp/rebase-sandboxを実行し、これらの手順を再度実行してやり直してください。このガイドの各ステップは新しいサンドボックスで実行できるため、すべてのタスクをやり直す必要はありません。 最新のコミットを修正するまずは簡単なことから始めましょう。最新のコミットを修正しましょう。サンドボックスにファイルを追加して、ミスをしてみましょう。 -
echo "Hello wrold!" > greeting . txt -
git add greeting . txt -
git commit - m "Add greeting.txt"
このエラーの修正は非常に簡単です。ファイルを編集し、 --amendを付けてコミットするだけです。例: -
echo "Hello world!" > greeting . txt -
git commit - a -- amend
-aを指定すると、Git が既に認識しているファイル(Git によって追加されたファイルなど)がすべて自動的にステージングされます。一方、 --amend指定すると、変更内容が最新のコミットに統合されます。保存してエディタを終了します(必要に応じてコミットメッセージを変更できます)。`git git showを実行すると、修正されたコミットを確認できます。 -
commit f5f19fbf6d35b2db37dcac3a55289ff9602e4d00 ( HEAD -> master ) -
Author : Drew DeVault Date : Sun Apr 28 11 : 09 : 47 2019 - 0400 Add greeting . txt-
diff -- git a / greeting . txt b / greeting . txt -
new file mode 100644 -
index 0000000. . cd08755 -
--- / dev / null -
+++ b / greeting . txt -
@@ - 0 , 0 + 1 @@ -
+ Hello world !
古いコミットを修正する--amend最新のコミットにのみ適用されます。古いコミットを修正する必要がある場合はどうすればよいでしょうか?まずはサンドボックスを適切に設定してみましょう。
-
echo "Hello!" > greeting . txt -
git add greeting . txt -
git commit - m "Add greeting.txt" -
echo "Goodbye world!" > farewell . txt -
git add farewell . txt -
git commit - m "Add farewell.txt"
greeting.txtに"world"が抜けているようです。これを修正するために、通常のコミットを書いてみましょう。 -
echo "Hello world!" > greeting . txt -
git commit - a - m "fixup greeting.txt"
ファイルは正しく表示されているように見えますが、履歴をもう少し改善できます。新しいコミットを使って、最後のコミットを「 fixup 」しましょう。そのためには、新しいツールであるインタラクティブ・リベースを導入する必要があります。この方法で最後の3つのコミットを編集するため、 git rebase -i HEAD~3を実行します( -iインタラクティブの略です)。これにより、以下に示すようにテキストエディタが開きます。 -
pick 8d3fc77 Add greeting . txt -
pick 2a73a77 Add farewell . txt -
pick 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はこれらのコミットをすべて履歴から削除し、1行ずつ実行します。デフォルトでは、各コミットをpick 、ヒープから呼び出してブランチに追加します。このファイルに全く編集を加えていない場合は、最初に戻って各コミットをそのまま選択します。ここで、私のお気に入りの機能の一つであるfixup使ってみましょう。3行目を編集し、操作をpickからfixupに変更し、「修正」したいコミットの直後に移動します。 -
pick 8d3fc77 Add greeting . txt -
fixup 0b9d0bb fixup greeting . txt -
pick 2a73a77 Add farewell . txt
ヒント: 次回はスピードを上げるために、 fだけを使って省略することもできます。
保存してエディターを終了すると、Gitが以下のコマンドを実行します。ログを確認して結果を確認できます。 -
$ git log - 2 -- oneline -
fcff6ae ( HEAD -> master ) Add farewell . txt -
a479e94 Add greeting . txt
複数の提出物を 1 つにまとめます。作業中に、小さなマイルストーンに到達したり、以前のコミットのバグを修正したりする際に、多くのコミットを書くと便利だと感じるかもしれません。しかし、作業をmasterブランチにマージする前に、これらのコミットをまとめて「 squash 」することで、履歴を整理するのに役立ちます。そのためには、「 squash 」操作を使用します。まずは、いくつかのコミットを書きましょう。作業をスピードアップするために、以下のコミットをコピー&ペーストするだけです。 -
git checkout - b squash -
for c in H ello , ' ' w orld ; do echo "$c" >> squash . txt git add squash . txt git commit - m "Add '$c' to squash.txt"-
done
「Hello, world」というファイルを作るのには、かなりの作業が必要です! もう一度インタラクティブなリベースを実行して、コミットをまとめてフラット化してみましょう。この作業を試すために、まずブランチをチェックアウトしたことに注意してください。`git git rebase -i masterで作成したブランチを使用しているため、すべてのコミットを素早くリベースできます。結果: -
pick 1e85199 Add 'H' to squash . txt -
pick fff6631 Add 'e' to squash . txt -
pick b354c74 Add 'l' to squash . txt -
pick 04aaf74 Add 'l' to squash . txt -
pick 9b0f720 Add 'o' to squash . txt -
pick 66b114d Add ',' to squash . txt -
pick dc158cd Add ' ' to squash . txt -
pick dfcf9d6 Add 'w' to squash . txt -
pick 7a85f34 Add 'o' to squash . txt -
pick c275c27 Add 'r' to squash . txt -
pick a513fd1 Add 'l' to squash . txt -
pick 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として保存します。これをローカルの master ブランチと組み合わせて、 git rebase -i origin/master上流ブランチにまだマージされていないすべてのコミットをリベースするのに非常に便利です。
これらの変更をすべて最初のコミットにまとめます。これを行うには、最初の行を除くすべての「 pick 」操作を「 squash 」に変更します。 -
pick 1e85199 Add 'H' to squash . txt -
squash fff6631 Add 'e' to squash . txt -
squash b354c74 Add 'l' to squash . txt -
squash 04aaf74 Add 'l' to squash . txt -
squash 9b0f720 Add 'o' to squash . txt -
squash 66b114d Add ',' to squash . txt -
squash dc158cd Add ' ' to squash . txt -
squash dfcf9d6 Add 'w' to squash . txt -
squash 7a85f34 Add 'o' to squash . txt -
squash c275c27 Add 'r' to squash . txt -
squash a513fd1 Add 'l' to squash . txt -
squash 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 28 14 : 21 : 56 2019 - 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 -
#
デフォルトでは、すべてのコミットのメッセージが統合されてフラット化されますが、このままの状態を維持するのは望ましくありません。ただし、古いコミットメッセージは新しいコミットメッセージを書く際に役立つため、参考としてここに含めています。 ヒント: 前のセクションで説明した「 fixup 」コマンドもこの目的に使用できますが、フラット化されたコミット メッセージは破棄されます。
すべてのコンテンツを削除し、次のようなより良いコミット メッセージに置き換えてみましょう。 -
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 28 14 : 21 : 56 2019 - 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 c785f476c7dff76f21ce2cad7c51cf2af00a44b6 ( HEAD -> squash ) -
Author : Drew DeVault -
Date : Sun Apr 28 14 : 21 : 56 2019 - 0400 Add squash . txt with contents "Hello, world"
先に進む前に、変更をmasterブランチにプルし、このドラフトを削除しましょう。`git git mergeと同じようにgit rebase使うこともできますが、マージコミットを作成しなくて済みます。 -
git checkout master -
git rebase squash -
git branch - D squash
実際に無関係な履歴をマージする場合を除き、通常はgit merge使用は避けるべきです。2つの異なるブランチがある場合、` git mergeそれらのマージ時期を追跡するのに非常に便利です。通常の操作では、リベースの方が適切です。 コミットを複数に分割する時には逆の問題、つまりコミットが大きすぎるという問題が発生することもあります。そこで、コミットを分割する方法を見てみましょう。今回は実際にコードを書いてみましょう。まずは簡単なCプログラム2から始めましょう(このコードスニペットをコピーしてシェルに貼り付ければ、簡単に実行できます)。 -
cat << EOF > main . c -
int main ( int argc , char * argv []) { return 0 ;-
} -
EOF
まずは送信してください: -
git add main . c -
git commit - m "Add C program skeleton"
次に、このプログラムを少し拡張します。 -
cat << EOF > main . c -
# include & ltstdio . h > -
const char * get_name () { static char buf [ 128 ]; scanf ( "%s" , buf ); return buf ;-
} -
int main ( int argc , char * argv []) { printf ( "What's your name? " ); const char * name = get_name (); printf ( "Hello, %s!\n" , name ); return 0 ;-
} -
EOF
提出後、それをどのように分解するかを学ぶ準備をすることができます。 -
git commit - a - m "Flesh out C program"
最初のステップは、対話型リベースを開始することです。`git git rebase -i HEAD~2を使用して、これら2つのコミットをリベースします。リベースプランは以下のとおりです。 -
pick 237b246 Add C program skeleton -
pick b3f188b Flesh out C program -
# Rebase c785f47 .. b3f188b onto c785f47 ( 2 commands ) -
# -
# Commands : -
# p , pick < commit > = use commit -
# e , edit < commit > = use commit , but stop for amending
2番目のコミットのコマンドをpickからeditに変更し、保存してエディタを閉じます。Git はこれを少し検討した後、次のような提案を表示します。 -
Stopped at b3f188b ... Flesh out C program -
You can amend the commit now , with git commit -- amend-
Once you are satisfied with your changes , run git rebase -- continue
下記のようにコミットに新しい変更を加えることもできますが、 git reset HEAD^ `を実行することで「ソフトリセット」を実行できます。その後git status実行すると、最新のコミットが取り消され、その変更が作業ツリーに追加されます。 -
Last commands done ( 2 commands done ): pick 237b246 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' . ( 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 . c-
no changes added to commit ( use "git add" and / or "git commit -a" )
これを詳しく説明するために、対話型コミットを実行します。これにより、作業ツリー内の特定の変更のみを選択的にコミットできます。このプロセスを開始するには、 git commit -pを実行すると、以下のプロンプトが表示されます。 -
diff -- git a / main . cb / main . c -
index b1d9c2c .. 3463610 100644 -
--- a / main . c -
+++ b / main . c -
@@ - 1 , 3 + 1 , 14 @@ -
+# include & ltstdio . h > -
+ -
+ const char * get_name () { -
+ static char buf [ 128 ]; -
+ scanf ( "%s" , buf ); -
+ return buf ; -
+} -
+ int main ( int argc , char * argv []) {-
+ printf ( "What's your name? " ); -
+ const char * name = get_name (); -
+ printf ( "Hello, %s!\n" , 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 [ 128 ]; -
+ 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 237b246 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にこのコミットの編集を終了し、次のリベースコマンドに進むことを伝えます。これで完了です! git logを実行して作業内容を確認してください。 -
$ git log - 3 -- oneline -
fe19cc3 ( HEAD -> master ) Prompt user for their name -
659a489 Add get_name function to C program -
237b246 Add C program skeleton
提出物の並べ替え簡単です。まずはサンドボックスの設定から始めましょう。 -
echo "Goodbye now!" > farewell . txt -
git add farewell . txt -
git commit - m "Add farewell.txt" -
echo "Hello there!" > greeting . txt -
git add greeting . txt -
git commit - m "Add greeting.txt" -
echo "How're you doing?" > inquiry . txt -
git add inquiry . txt -
git commit - m "Add inquiry.txt"
git log次のようになります。 -
f03baa5 ( HEAD -> master ) Add inquiry . txt -
a4cebf7 Add greeting . txt -
90bb015 Add farewell . txt
明らかに、これは順序が狂っています。最後の3つのコミットを対話的にリベースすることで、この問題を解決しましょう。`git git rebase -i HEAD~3を実行すると、リベースプランが表示されます。 -
pick 90bb015 Add farewell . txt -
pick a4cebf7 Add greeting . txt -
pick 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 . txt -
pick f03baa5 Add inquiry . txt -
pick 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 -
origin -
$ git config branch .< branch >. merge -
refs / heads /< branch >
もう一つのオプションがあります。こちらの方がより便利で、履歴もより明確になります。`git git pull --rebase 。マージとは異なり、これは基本的に以下のコマンドと同等です。 -
git fetch origin -
git rebase origin /< branch >
merge メソッドの方がシンプルで分かりやすいですが、 git rebase使い方を理解していれば、rebase メソッドでほぼ何でもできます。必要に応じて、以下のようにデフォルトの動作に設定することもできます。 -
git config -- global pull . rebase true
これを実行すると、技術的には次のセクションで説明したプロセスを適用することになります。したがって、これを意図的に実行するとはどういう意味なのかも説明しましょう。 git rebaseを使用してリベースする皮肉なことに、私が最も使わないGitのリベース機能は、まさに「ブランチのリベース」です。例えば、次のようなブランチがあるとします。 -
A -- B -- C -- D --> master \- - E -- F --> feature - 1 \- - G --> feature - 2
結果的に、 feature-2 feature-1の変更には依存せず、コミット E に依存していることがわかりました。そのため、 master外部ではコミット E をベースとして使用できます。したがって、回避策は以下のとおりです。 -
git rebase -- onto master feature - 1 feature - 2
非対話型リベースは、関連するすべてのコミットに対してデフォルトの操作( pick ) 5を実行します。つまり、 feature-1 feature-2 2のコミットをmasterにリロードするだけです。これで履歴は次のようになります。 -
A -- B -- C -- D --> master | \- - G --> feature - 2 \- - E -- F --> feature - 1
紛争の解決マージ競合の解決に関する詳細な情報は、このガイドの範囲外です。将来的には別のガイドを参照してください。一般的な競合解決方法に精通していることを前提として、このセクションはリベースに特化しています。 リベース操作中にマージ競合が発生することがあります。これらの競合は、他のマージ競合と同様に処理できます。Gitは影響を受けるファイルに競合フラグを設定し、 git status解決すべき問題を表示します。`git git addまたはgit rmを使用してファイルを解決済みとしてマークできます。ただし、 git rebaseのコンテキストでは、2つのオプションに注意する必要があります。 まず、競合の解決方法を説明します。`git git mergeによって発生した競合を解決する場合、 git commitよりも適切なリベースコマンドはgit rebase --continueです。ただし、 git rebase --skipという別のオプションもあります。これは作業中のコミットをスキップし、リベースに含めません。これは、非対話型リベースを実行するときに最もよく発生します。Git は、「他の」ブランチからプルしているコミットが、「私たち」ブランチのコミットと競合するコミットの更新バージョンであることを認識しません。 助けて!壊しちゃった!リベースは確かに難しい場合があります。ミスをして必要なコミットを失ってしまった場合は、 git reflogを使えば翌日の時間を節約できます。このコマンドを実行すると、参照(ブランチとタグ)を変更するために行われたすべてのアクションが表示されます。各行には古い参照が指しているものが表示され、不足していると思われるGitコミットに対してgit cherry-pick 、 git checkout 、 git showなどのアクションを実行できます。
リポジトリの最初のコミットをリベースするには特別なコマンド (つまり、 git rebase --root ) が必要なので、このチュートリアルの残りの部分を簡略化するために空の最初のコミットを追加しました。 このプログラムをコンパイルするには、 cc -o main main.cを実行し、次に./main main` を実行して結果を確認します。 これは実際には「ハイブリッドリセット」です。「ソフトリセット」( git reset --softを使用)は変更をステージングするため、 git addで再度追加する必要がなく、すべての変更を一度にコミットできます。これは私たちが望んでいることではありません。コミットを分割するために、一部の変更のみを選択的にステージングしたいのです。 実際には、これは上流ブランチ自体がリベースされているか、または一部のコミットが削除/フラット化されているかによって異なります。`git git pull --rebase git rebaseおよびgit merge-baseレプリカポイントこの状況から回復し、非ローカルコミットのリベースを回避するためのメカニズムが用意されています。 実際には、これはGitのバージョンに依存します。バージョン2.26.0までは、デフォルトの非対話型の動作は対話型の動作と若干異なっていましたが、一般的には大きな違いはありませんでした。 |