- 例えばリモートとローカルのブランチに差が出た時に、リモートの変更差分をローカルブランチに反映する手順はいくつかあるかと思います。
- 2つのケースで変更差分を反映する方法を備忘として記載します。
- ベストプラクティスではないかもしれないことに留意ください。
知れること:リモートとローカルブランチに差が出た時にできること(rebaseとstash)
知れないこと:rebaseとstash以外の方法
目次
- ユースケース
- リモートの親ブランチが更新されたので、ローカルでもその変更を反映させたい:git rebase
- ローカルで変更した差分を一旦待避して、他の人の変更差分を取り入れて自分の変更も反映させたい:git stash
- 結局、rebaseとstashの違いってなに?
- おわりに
1. ユースケース
この記事では、rebaseとstashについて説明することになるので、ユースケースを最初に共有します。
私のrebaseのユースケース
やべ、ローカルで開発してるうちに、チームメンバーからリモートの親ブランチに変更がマージされて、このままローカルの開発ブランチをリモートにpushして、親ブランチにマージしようとすると、ローカルの親ブランチは変更差分が反映されていないtreeから伸びているから想定しない結果になっちまう!とりあ、ローカルの親ブランチにリモートの変更差分反映させちゃおう!って時に私は使ってます。
私のstashのユースケース
やべ、別の場所で同じ開発ブランチに変更差分発生したから、自分の開発してる変更差分は一旦待避させて、別の場所の変更差分を取り込んで、再度自分の変更差分を反映させよ!って時に私は使ってます。
2. リモートの親ブランチが更新されたので、ローカルでもその変更を反映させたい:git rebase
そんなときは、git rebaseを使いましょう。
特定のブランチのコミットを別のブランチの上に「再配置」することができます。
ユースケースを具体的にあげると、フィーチャーブランチ(開発ブランチ)がマスターブランチ(開発ブランチを切った親ブランチ)から派生している間にマスターブランチに新しいコミットが追加された場合に、フィーチャーブランチを最新のマスターブランチに合わせるために使用されます。これにより、フィーチャーブランチがマージされる際のコンフリクトを減らすことができます。
具体的な手順は以下です。
やること:リリースブランチに刺したいとき、まずリモートブランチの変更差分をローカルに取り込んで、ローカルで変更を加えたブランチの変更差分の根元に、リモートブランチの変更を反映させる。
git checkout 根本となるブランチ
git pull
git checkout 開発ブランチ
git rebase 根本となるブランチ (根本を現在の変更を反映したブランチに変更(rebase)する)
コンフリクト解消対応をエディタで実施
git add .
git rebase --continue
git push origin HEAD -f
一応、これでリモートの親ブランチの変更差分をローカルのtreeに反映できました。
注意点は、以下2つ。
1: git rebase --continue
git rebase --continueは、Gitのrebase操作中に発生したコンフリクト(2つの異なるコミットが同じ部分のコードを変更し、その結果がどうあるべきかGitが自動的に判断できない場合)を手動で解決した後に使用するコマンドです。
git rebaseを実行すると、選択したブランチの各コミットが対象ブランチの先頭に順番に適用されます。その際にコンフリクトが発生した場合、Gitはその時点でリベース操作を停止し、ユーザにコンフリクトの解決を依頼します。
コンフリクトが発生した場合、ユーザはエディタを使用してコンフリクトを解決します。コンフリクトが発生した部分は、通常、<<<<<<<、=======、>>>>>>>でマークされていて、これらの間にはそれぞれのコミットによる変更が表示されます。ユーザはこれらの変更のうちどちらかを選択するか、必要に応じて新たな変更を行い、その結果を保存します。
その後、git addコマンドを使用して変更をステージに上げ、最後にgit rebase --continueを実行します。このコマンドを実行すると、Gitは次のコミットの適用を試みます。これにより、再度コンフリクトが発生するか、あるいは全てのコミットが無事に適用されてrebaseが完了するか、どちらかの状態になります。
したがって、git rebase --continueはrebase操作の一部として存在し、手動で解決したコンフリクトを反映してrebase操作を続けるために必要なコマンドです。
2: git push origin HEAD -f
最後にpushをするときには、-fをつけてフォースpush(強制的にpush)する必要があります。
というのも、リベース操作は、特定ブランチのコミットを他のブランチに移動しますが、その過程でコミットの歴史が改変されます。
つまり、リベースによる新しいコミットは元のものと異なるハッシュ値を持ち、Gitはこれを新規のコミットと見なします。
リベース後の通常のpushでは、ローカルの新しいコミットとリモートの既存のコミットが一致しないため、pushが拒否されます。
以下のようなエラーがでます。
エラー
Updates were rejected because the tip of your current branch is behind its remote counterpart
この問題を解決するためには、git push -f または git push --forceを用いて、ローカルの状態を強制的にリモートリポジトリに反映します。これでリベースによる歴史の不一致が解消されます。
rebaseが何をしているものなのかは、baseをreすると覚えましょう。(rebase = 新しいベース[土台・基準レベル]を~に設定する)
3. ローカルで変更した差分を一旦待避して、他の人の変更差分を取り入れて自分の変更も反映させたい:git stash
git stashは作業中の変更を一時的に保管するためのコマンドです。現在の作業ディレクトリの状態(変更したファイル、ステージした変更、新たに追加したファイルなど)をスタックのような構造に保存します。これにより、作業中のブランチを切り替えたり、新たな作業を始めたりすることが可能になります。
また、これを使うことで、自分が作成した変更を一時的に待避させてから他の人の変更を取り込み、その後で自分の変更を再適用することができます。
具体的な手順は以下です。
やること:自分の変更をstashに保存し、リモートから最新の変更を取り込む。スタッシュに保存した自分の変更を再適用します。
git stash save "My changes"
git pull origin 変更差分を取り込みたいブランチ
git stash pop
これによって、自分の変更した差分に影響を与えることなく、他の人の変更を自分のブランチに取り込むことができます。
ただし、他の人の変更と自分の変更が同じファイルの同じ部分を変更している場合、git stash popを実行したときにコンフリクト(競合)が発生します。そのような場合は、コンフリクトを解消した後にコミットを作成する必要があるので注意が必要です。あとですね、stash popすると、一見成功じゃないような出力でてきますが、成功なので特にerrorやfaileのような文言なければ気にせずcommitできます。
覚え方はマジで特にこれといったものがないんですけど、英語の意味では、stash = 隠し場所 らしいです。
4.結局、rebaseとstashの違いってなに?
既にお分かりかと思いますが、一応2つの違いについて改めて説明します。
git rebase
主にコミットの歴史を改変するために使います。一般的には、ブランチの変更を他のブランチ(通常は親ブランチやbaseとなるブランチ)の最新の状態に合わせるために使用します。コンフリクトを解決するための操作が必要な場合があります。
イメージとしては、親ブランチの変更を反映します。
git stash
作業中の変更を一時的に保管することができます。これを使うと、作業中のブランチを一時的にクリーンな状態に戻し、別のブランチに移動したり新たな作業を始めたりすることができます。そして、後で元のブランチに戻って作業を再開するときに、保管していた変更を再適用することができます。
イメージとしては、自信の変更差分を保ちつつ、他の人の変更差分を開発ブランチに反映できます。(このユースケースでは。)
したがって、これらのコマンドは、ブランチやコミットの歴史に対する操作(rebase)と、作業中の変更に対する操作(stash)という、異なるユースケースで変更差分を反映させたいときに活用することができます。
おわりに
- treeの配置を直すためにrebaseしてたりすると、たまに頭おかしくなります。
- 記事を書く時は絵を多く使うように心がけていますが、本記事では文章が多くてすみません。
- でも、備忘だもん!