🌳🚀 CS Visualized: Useful Git Commands
Gitはとてもパワフルなツールですが、使ってみると...悪夢のように感じることもありますよね😐 僕はGitを使う時、頭の中で何が起こっているのかを視覚化するととても役立つと常々思っています。特定のコマンドを実行した時、ブランチどうしがどう相互作用していて、履歴にどんな影響が出るのか?なぜ同僚がmaster
にhard reset
をかけ、originにforce push
した後で.git
フォルダーをrimraf
で消すと泣いたのか?
そこで、最も一般的で役に立つコマンドのいくつかについて、視覚化した例を作成することにしました!🥳 ここで取り上げるコマンドは、様々なオプション引数を利用して動作を変更できますが、例では(多くの設定を追加することなく)コマンドのデフォルトの動作を中心に説明します!😄
マージ
複数のブランチを持っていると、新しい変更点をそれぞれ分離して、不承認だったり不具合のある変更をうっかり本番環境にプッシュすることなく管理できて便利です。変更が承認されたら、それを本番ブランチに組み込みたくなりますよね!
一つのブランチから別のブランチに変更を取り込む方法としてgit merge
があります。Gitが行うことのできるマージには二つのタイプがあります:ファストフォワードまたはノーファストフォワード🐢
この違いがすぐには意味が分からないかもしれませんが、ちょっと見てみましょう!
--ff
)
ファストフォワード (ファストフォワードマージは、現在のブランチがマージしようとしているブランチに比べて追加のコミットがない場合に発生します。Gitは…怠け者 で、まずは一番簡単な選択肢、つまりファストフォワードを試みます!このタイプのマージでは新しいコミットを作成せず、マージしようとしているブランチのコミットを現在のブランチにマージします🥳
素晴らしい!これで、dev
ブランチで行われた変更全てがmaster
ブランチでも利用可能になりました。それでは、ノーファストフォワードって一体何?
--no-ff
)
ノーファストフォワード (仮に現在のブランチに、マージしたいと思っているブランチにはない追加のコミットがないと良いのですが、残念ながらそれは稀です!私たちがコミットした変更がマージしようとしているブランチにはない場合、gitは_ノーファストフォワード_ マージを行います。
ノーファストフォワードマージでは、Gitはアクティブなブランチに新しい_マージコミット_ を作成します。このコミットの親コミットは、アクティブなブランチとマージしたいと思っているブランチの両方を指しています!
全く問題なし、完璧なマージです!🎉 master
ブランチには、dev
ブランチで行った変更が全て反映されました。
マージコンフリクト
Gitはブランチをどうマージし、ファイルにどんな変更を加えるかを上手く判断してくれますが、いつも自分自身でこの決定を下せるわけではありません🙂 これは、マージしようとしている二つのブランチが同じファイルの同じ行に変更を加えている場合や、一方のブランチが変更したファイルをもう一方のブランチが削除した場合などに発生します。
そのときは、Gitがどちらのオプションを維持したいかを決めてほしいと依頼してきます!例えば、両方のブランチでREADME.md
の最初の行を編集したとしましょう。
dev
ブランチをmaster
ブランチにマージしたい場合、マージコンフリクトが生じます:タイトルをHello!
にするかHey!
にするか、どちらにしますか?
ブランチをマージしようとすると、Gitはコンフリクトが発生している場所を表示してくれます。保存したくない変更を手動で取り除き、変更を保存し、再度変更されたファイルを追加し、変更をコミットすることができます🥳
やった!マージコンフリクトはよくイライラするものですが、これは全くもって理にかなっています:Gitはどの変更を維持したいかを単に_仮定_ すべきではありません。
リベース
今見たように、git merge
を実行することで、あるブランチから別のブランチに変更を適用することができます。もう一つの方法としてgit rebase
があります。
git rebase
は現在のブランチのコミットを_コピー_し、これらのコピーされたコミットを指定されたブランチの上に配置します。
完璧です、これでmaster
ブランチで行われた変更がdev
ブランチでも利用可能になりました!🎊
マージと比べて大きな違いは、Gitがどのファイルを保持するか、または保持しないかを調べようとしないことです。リベースするブランチが最新の変更を持っています!この方法ではマージコンフリクトに出くわすことはなく、きれいな線形のGit履歴を維持できます。
この例はmaster
ブランチでリベースを示しています。しかし、大きなプロジェクトでは通常、これを行いたくありません。git rebase
はプロジェクトの履歴を変更します。なぜなら、コピーされたコミット用の新しいハッシュが作成されるからです!
リベースは、特に特徴ブランチで作業していて、それからmasterブランチが更新された場合にすばらしいです。自分のブランチにすべてのアップデートを取り込むことができるため、将来のマージコンフリクトを防ぐことができます!😄
インタラクティブリベース
リベースする前に、コミットを修正することができます!😃 これを行う方法として、インタラクティブリベース があります。インタラクティブリベースは、現在作業中のブランチ上でいくつかのコミットを修正したいときにも有用です。
リベースしているコミットに対して実行できる6つのアクションがあります:
reword
: コミットメッセージを変更するedit
: このコミットを修正するsquash
: 前のコミットとこのコミットを結合するfixup
: 前のコミットとこのコミットを結合し、コミットのログメッセージは保持しないexec
: リベースしたい各コミットに対してコマンドを実行するdrop
: コミットを削除する
すばらしい!これにより、コミットに完全なコントロールを持つことができます。コミットを削除したい場合は、シンプルにdrop
できます。
あるいは、複数のコミットをひとつにまとめて履歴をクリーンにしたい場合も、問題ありません!
インタラクティブリベースは、リベースしようとしているコミット、活動中の現在のブランチ上で、高度なコントロールを提供します!
リセット
後で必要がない変更をコミットしてしまったこともあります。もしかしたらWIP
コミットか、バグを導入したコミットかもしれません🐛 その場合、git reset
を実行することができます。
git reset
は、ステージされたファイルを全て取り除き、HEAD
が指すべき場所をコントロールする機能です。
ソフトリセット
ソフトリセット は、HEAD
を指定されたコミットへ移動させます(あるいはHEAD
と比べたコミットのインデックスに)、ただし以降のコミットで導入された変更は取り除かれません!
例えば、style.css
ファイルを追加した9e78i
コミットと、index.js
ファイルを追加した035cc
コミットを保持したくないとします。しかし、新たに追加されたstyle.css
とindex.js
ファイルは保持したいのです!これにはソフトリセットがぴったりです。
git status
を入力すると、以前のコミットで行われた変更にまだアクセスできることがわかります。これは素晴らしいことです。なぜなら、これらのファイルの内容を修正して後で再コミットできるからです!
ハードリセット
場合によっては、特定のコミットによって導入された変更を保持したくないこともあるでしょう。ソフトリセットとは異なり、それらをこれ以上必要とせず、Gitは単純に作業ディレクトリおよびステージングされているファイルの変更を含め、指定されたコミット時点の状態にリセットすべきです!💣
Gitは9e78i
と035cc
で導入された変更を破棄し、ec5be
コミット時点の状態にリセットしました。
リバーティング
変更を元に戻す別の方法として、git revert
を実行することがあります。
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/lydiahallie/cs-visualized-useful-git-commands-37p1