🐲

【第3回】Gitについて調べた

2024/01/10に公開

最初に

社内発信するネタ作りで、演習本を通じてGitを少し触ったのでいくつかの記事に分けてメモ書き程度にまとめる。Gitの理解はterraformやhelmなどの学習を進める上で第一歩になるのかなというところで、Gitの理解の過程として学習した。

実務レベルでわかる/使いこなせるようになるGit入門コマンドライン演習80

今回のテーマはGitコマンド。Gitを触っていていくつか機能の内容やコマンド名レベルで似通ったコマンドがあることが分かった。今回は完全主観で気になった3組のコマンドをピックアップし、違いについてを調べた。使い分けなどの判断ができるように整理できればと思う。

各章はWeb調査をもとに要約、整理した内容を記載している。参考にした記事は参考情報としてリンクも載せるので興味ある方はご参照ください。

似たコマンド

checkout vs. switch

概要

checkoutコマンドとswitchコマンドは機能的に似ているというよりは、もともと複数の用途向けにcheckoutコマンドが実装されていたが、ブランチ操作向けのgit switchコマンドとファイル操作向けのgit restoreコマンドに切り出された経緯が分かった。そのため、基本的にブランチ操作に関して同じことが実現可能。この経緯からgit switchコマンドだけでなく、git checkoutコマンドはgit restoreコマンドとも機能的に似ているといえることが分かった。

使用時の判断基準

Gitのバージョンが2.23のときにswitchコマンドとrestoreコマンドは用意された。そのため、利用しているGitのバージョンが2.23以降かどうかが1つの判断基準になる。
基本的には誤動作防止の観点からブランチ操作を行う場合は機能が限定的なgit switchコマンドの利用が推奨されている。
参考情報:Highlights from Git 2.23

fetch vs. pull

概要

fetchコマンドとpullコマンドはリモートリポジトリから情報を取得するという観点では似た機能だが、pullコマンドはfetchコマンドの処理を実行した上でmergeコマンドまたはrebaseコマンドの処理を実行する必要があり、その必要性に違いがある。

使用時の判断基準

今回はリスク低減の観点から判断基準を整理した。fetchコマンドはそのあとに別のコマンドの実行が必要なため、わざわざ使うことは非効率で利用するならpullコマンド一択な気もしたが、pullコマンドにはpullコマンドのリスクがあることが分かった。
Gitではリモートリポジトリの指定に通常エイリアスが使われる。pullコマンドのときもエイリアスを使ってリモートリポジトリを指定する。そのため、複数のリモートリポジトリを使う環境であったり、エイリアスの向け先を変更する操作が多い場合は予期せぬリポジトリやブランチとのマージなどを実行してしまうリスクが考えられる。
そのため、トラッキングしているリモートリポジトリやブランチを変える操作が多い場合などには、安全のためfetchコマンドとmergeコマンドなどをそれぞれ実行することが良いと考える。

restore vs. reset

概要

restoreコマンドとresetコマンドはどちらも作業ディレクトリなどの状態を変更するためのコマンドで、restoreコマンドは指定しソースからコンテンツを復元するコマンドになる。復元するためにはソースとパスを指定する必要がある。resetコマンドはステージングやコミットしたことを取り消すコマンドになる。取り消すためにはソースとリセットモードを指定する必要がある。

git restoreコマンドの試し打ち

git restoreコマンドの動作のイメージが難しかったので試し打ちしてみた。一例に、HEADをソースとしたファイルパスを指定して復元する場合の動作を確認する。ソース指定は省略可能で、省略された場合はHEADをソースとする。ここではHEADを指してることを前提に進める。

Tips:
余談。HEADはカレントブランチを指すポインタのこと。例外的にカレントブランチ以外を指すこともあり、その状態はdetached HEADと呼ばれる。detached HEADの状態は非推奨のため、ここではHEADはカレントブランチを指している前提で説明を進める。.git/HEADから指示しているブランチが確認できる。

Tips:
余談。カレントブランチは当該ブランチの最新のコミットを指すポインタのこと。例外的に最新のコミット以外を指すことも可能だが通常は実施しないため、ここではカレントブランチを指している前提で説明を進める。.git/refs/heads/配下のブランチ名のファイルのデータから指し示すコミットが確認できる。

git restoreコマンドではソースとパスのほかにオプションを指定することにより復元先を制御できる。例えば、--worktreeを指定したときや省略したときは作業ディレクトリ上のファイルがHEADのソースをもとにして復元されるが、--stagedを指定するとインデックス上のファイルがHEADのソースをもとにして復元される。どちらのオプションも指定するとそれぞれの領域でファイルが復元される。

git restore <ファイルパス> または git restore --worktree <ファイルパス>
git restore --staged <ファイルパス>
git restore --worktree --staged <ファイルパス>

git-restore last updated in 2.43.0

Tips:
余談。Gitの各領域について。大きくはリモートとローカルの領域がある。ローカルには更に3つの領域がある。
・作業ディレクトリ
ファイルの新規作成したときに配置される先の作業ディレクトリ。ワークツリーとも呼ばれ、開発作業はこの領域で行われる。
・ステージング領域
ステージング領域。git addコマンドを使って作業ディレクトリからステージング領域にアップロードすることができる。ステージング領域はインデックス領域とも呼ばれる。
・Gitリポジトリ
Gitリポジトリ。git commitコマンドを使ってステージング領域からアップロードすることができる。ステージング領域を経由せずに作業ディレクトリからGitリポジトリに直接アップロードすることもできる。

Tips:
余談。Gitリポジトリやインデックスにあるファイルの内容を確認する方法は順にgit showコマンドやgit diff --cachedコマンドなどから確認することができる。最新のコミットIDを確認したい場合はgit log -1から確認することができる。-1の数を変更すれば出力させたい任意の数のログを出力できる。

git show <コミットID>
git diff --cached
git log --oneline -1

次に、git restoreコマンドでソースを指定する方法についても確認する。ソースとしてコミットIDやブランチ名、タグ名、ツリーオブジェクトのハッシュが利用できる。

Tips:
余談。ツリーオブジェクトはコミットするときに作成されるオブジェクトの一つ。Gitではステージング領域にアップロードするときとコミットするときにファイルやコミット情報などをオブジェクトとして保存する。コミットはツリーオブジェクトを跨いでコミットオブジェクトと紐づけられる。そのため、ツリーオブジェクトのハッシュを指定することでソースを特定することができる。

git restore --source=<コミットID> <ファイルパス>
git restore --source=<ブランチ名> <ファイルパス>
git restore --source=<タグ名> <ファイルパス>
git restore --source=<ツリーオブジェクトのハッシュ> <ファイルパス>

git resetコマンドの試し打ち

git resetコマンドも例に漏れず動作のイメージが難しかったので試し打ちしてみた。一例に、ファイルを新規作成してコミット(後述のコミットCに対応する。)した後にHEAD^をソースとする取り消しを行った場合の動作を確認する。

Tips:
余談。HEAD^はHEADの一つ前のコミットを指す。仮にA、B、Cの順にコミットして最新のコミットがコミットCの場合、HEADはコミットCを指し示し、HEAD^はコミットBを指し示している。

コミットA -> コミットB -> コミットC

git resetコマンドではソース(HEAD^やコミットIDなど)と合わせてリセットモードを指定することにより取り消す範囲を制御できる。例えば、--softを指定したときは最後のコミット(コミットC)をなかったことにしてステージングエリア上の変更内容は残した状態にできる。--mixedを指定したときやリセットモードの指定を省略したときは最後のコミット(コミットC)と最後のステージングをなかったことにして作業ディレクトリ上の変更内容だけは残した状態にできる。--hardを指定したときは最後のコミットとステージングと作業ディレクトリのすべての変更内容をなかったことにできる。

git reset --soft HEAD^

上のコマンドを実行した後にgit statusコマンドを実行するとChanges to be committedとなる。これは最後のコミットが取り消され、ステージング領域には残っているためコミットできるものがあることを示している。また、新規作成されたファイルとファイルの内容が残っていることも確認できる。

git reset --mixed HEAD^

上のコマンドを実行した後にgit statusコマンドを実行するとUntracked filesとなる。これは最後のコミットに加えてステージングも取り消され、ステージングから取り消されたことに合わせてインデックスがなくなるため、新規作成したファイルとファイルの内容が追跡されていないファイルとして残っていることを示している。(ファイルの内容も残っている。)

git reset --hard HEAD^

最後に、上のコマンドを実行した後にgit statusコマンドを実行するとnothing to commit, working tree cleanとなる。これは最後のコミットとステージングは当然取り消され、新規作成されたファイルも取り消され、文字通りに作業ディレクトリがクリーンな状態にまで取り消されていることを示している。

使用時の判断基準

基本的には変更したい対象がファイル単位かコミット単位かで判断することができる。git restoreコマンドはファイル単位で復元したいときに使うことができ、git resetコマンドはコミット単位で作業ディレクトリやステージング領域の変更を取り消したいときに有効的に使える。ただし、git restoreコマンドがコミット履歴を変更することはないが、resetコマンドはコミット履歴を変更する可能性があるため、git resetコマンドを使う場合は特に注意が必要。例えば、--hardを指定してgit resetコマンドを実行すると、指定したコミット以降の変更が完全に削除され元に戻すことができなくなる。また、消した後にリモートにプッシュするとリモートと他開発者のローカルの状態に差分が出て開発作業に支障が出る可能性がある。そのため、git resetコマンドの実行と実行した後にプッシュする操作には特に注意が必要となる。

本の紹介

Gitはこちらの書籍を使いながら学習を進めました。まだ全体の7割くらいのところまでを進めた状況ですが、ユースケースごとに演習課題が設定されているためgitコマンドを試しながら進めやすい本だと思いました。サンプルがGithubにアップロードされているため困ったときに確認もしやすく、その点もポイント高いと思います。
実務レベルでわかる/使いこなせるようになるGit入門コマンドライン演習80
参考情報:実務レベルでわかる/使いこなせるようになるGit入門コマンドライン演習80

まとめ

Gitを触っていると名前が似てるコマンドもあれば機能が似ているコマンドもあって使い分けが難しいなと思うことも多かったですが、実際に使ってみたり経緯を調べることで自分なりにユースケースをイメージしながら整理できて良かったです。ただ、似ているコマンドランキング第一位はmergeコマンドとrebaseコマンドですが、今回は体力が尽きてそれは書けなかったので別の機会にチャレンジできればと思います。ちなみに、今回紹介したコマンド以外として他にもcherry-pickとrebase、resetとrevertも似てるなと思いました。

次回はもともと別のテーマを考えていましたが、最近GitHubの認定プログラムが一般公開されたことを知ったので気分転換に秒殺で読める記事にまとめつつ紹介できればと思います。

GitHub Certifications are generally available

免責事項

内容の正確性、安全性等を保証するものでなく、何らの責任を負うものではありません。本記事内容のご利用により、万一、ご利用者様に何らかの不都合や損害が発生したとしても、何らの責任を負うものではありません。

Discussion