はじめに
ついこの間まで「Git 何それおいしいの?」という状態だったのですが、ChatGPTと、チャット文面やファイル添付やダウンロードリンクの形でソースコードをやり取りするのに限界を感じたため、Codex で、GitHub リポジトリへの PR の形で修正してもらって、手元で動作確認してマージして育てていく…という程度のことができるようになりたいと思い、今更ながら使い始めました。
自分用のメモとしてあげておきます。
あくまで自分用なので、読みやすさとかは気にしてません。なんなら寧ろ、なんだか妙にわかりにくかった、ということを伝えたい気分。
知りたいことの要点や必要な情報だけがスッキリとまとまった、チートシートやアンチョコ、虎の巻といったような、スカッと後味爽やかで、ゴクゴクと情報摂取できるようなものは到底書けないので、逆に、私が理解する途中に生じた雑音を多く含んだものになっています。
世の書籍・記事たちは、「Gitってこんなに簡単だよ!」とか言ってきますが(※もちろん混乱しやすいという評判も見かけますが。。)、「よくわからん!」という愚痴の羅列です。・・・感情に紐づいた記憶って、思い出しやすいっていうじゃないですか。
-
主に、init したり clone したりするような初期のやりくり(※)が終わった後の話だけです。
※ GitBash 上で
gitとだけ打つと、最初にstart a working area (see also: git help tutorial) clone Clone a repository into a new directory init Create an empty Git repository or reinitialize an existing oneって出るやつです。
今のところ、そうしょっちゅうする事でもなさそうだし、特にメモしておくこともなかったので。usage が結構充実している気がするので、こんなもん書いたり見返したりしてないで、その場で
-hをつけて手を動かすのもアリな気もする。ちなみに
-hじゃなくて--helpってやっちゃうと、Git for Windows には man が無くて、manpage がブラウザで開いてくるので、パッと usage を見たい場合は-h | lessとかするのが良さげだと思いました。
最初は、Git の運用を チャットで都度教えてもらいながら進めていたのですが、「そういう時にはこうしてください」と Git のコマンドリストを即座にバーッと出してきてくれます。チャットでやり取りできるという点においては、CUI のありがたさが身に沁みたのですが、何をやっているのかいまいちよくわからないという状況に陥りました。
掘り下げて質問すれば答えてくれるんだけども、チャットがしょっちゅう Git に関する質問まみれになるし、断片的な回答を聞いているだけではやはり体系的に理解ができず困ってしまいました。
世の中には、何年も前に投稿された Git に関する記事等がそれはもうたくさんあります。
最初はそれらを読んでいたのですが、途中からは、読んで疑問に思ったところを結局 ChatGPT に質問するばっかりになってしまったため、果たして本当に正しい知識が身についているのかどうか一抹の不安がぬぐえまないままになっています。 なので、間違っていてもご愛敬なのです。
(今思えば、作業を進めているチャットの中で質問していたのがよくなかったかと思います。作業チャットと質問チャットを分けたらよかったのかも。もしくは、Git勉強会専用のチャットを作って「SVN しか使ったことがない Git 初心者でも、Git のことが体系的に理解できるように大まかなカリキュラムを組んで教えてください。こちらからの質問には適宜答えてください。会話がカリキュラムから大幅に脱線したり、後で説明する予定の事柄だった場合はやんわりと本筋に戻るように促してください。」とか指示したら、いい感じに教えてもらえたのかも?・・・とか思ったり、思わなかったり。)
用語
2026年において未だに、SVN を亀のアイコンで親しまれているアプリから一般ユーザーレベルで使ったことしかない人間的には、コマンド以前にまず「分散型リポジトリ」という考え方と、Git の用語が腹落ちするまでがネックでした。
用語に関しては、同義の物事を指す言葉に対して統一感を求めてはいけない界隈だという心構えを持って、ただの文章として素直に読むことでストレス無く過ごすことができるようになりました。
正直言って、https://git-scm.com/docs/gitglossary/ja#_nameを見つけた時に「ああー、無駄なこと書いてたかも」って思いました。
なんで皆さま、いの一番にコレを紹介せずに、自分の言葉で記事を書くんでしょうか。昔はこんな便利なものはなかったのでしょうか?・・・それとも、もしかしたら参考文献とか言ってチョロっと書かれてたのかもしれませんが、そこまで見ないっス。自分はググってて見つけました。
…と言いながら、自分の書いたものはそのまま上げるんですが。
なぜなら、あそこに書かれている内容の視座が、自分の理解よりも遥かな高みにあってほとんど共感できない(頭から読んでいくと何が書いてあるのかサッパリ訳が分からない)ので、地ベタから見上げた今の景色で見えるものを書いた方が、自分の頭に定着させられる気がしたからです。(勘違いしているところがあるにしても)
分散型リポジトリ
集中型/分散型
- SVN は「集中型リポジトリ」といって、サーバ側に単一のリポジトリが存在していて、そこでバージョン管理を行います。クライアント(ローカル)には、ある時点のスナップショットのコピーしか存在しません。修正した内容をコミットすることで、サーバ側にあるリポジトリへと変更内容(ファイル)が送り込まれてリビジョンが刻まれることで、ファイルがバージョン管理されます。
- Git は「分散型リポジトリ」といって、各ローカルにリポジトリが存在して各々バージョン管理を行います。ネットワークの向こう側にあるのはサーバではなく、遠隔にあるだけで、手元にあるのと同じようなリポジトリというだけの感じになります。リポジトリ間ではコミットをやり取りします。
ローカルにSVNサーバを立てて、ローカルでSVNリポジトリを回すのとも違うのですね。
SVNのリポジトリと作業コピーは、例え同一端末内であってもパスとして完全な別物になりますが、Gitのリポジトリとワークツリーは不可分とまでは言いませんが、基本一緒くたな関係になります。(ワークツリーを持たないベアリポジトリとか、リポジトリ:ワークツリー=1:nの関係とか、なんならGIT_DIRを別の場所に置いたりもできるらしいのですが、普通にinitすると1:1というか、むしろ一塊になってる)
あとは、サービスが立ち上がっているわけではなくファイルベースのオブジェクトで管理されているとかの違いもあるのかも。
ローカルリポジトリ
基本はこれを管理していきます。
- ローカルで完結してバージョン管理を行うことができます。
- リモートリポジトリを指定して、コミットを送り込んだり、取り込んだりすることができます。
学習の初期にネットで記事を漁っていた頃、
- Git は「ワークツリー・ステージ・リポジトリ」の3つの領域の状態を操作する。
というような説明に出くわすことがありました。
ローカルリポジトリの概念がよくわからないところへ、後述しますが一つ目の「ワークツリー」という用語のブレブレっぷりは言うに及ばす、二つ目は「ステージ」と「インデックス」でマチマチ。三つ目は「HEAD」だったり「コミット」だったり「コミット履歴」だとか「コミットツリー」だとか「コミットオブジェクト」だとか「Gitディレクトリ」だとかいうバリエーションも…あったような、なかったような。
リポジトリ = ワークツリー + ステージ + リポジトリ? 自己参照してる!?…とか混乱してました。
広義のリポジトリ(Gitリポジトリ、ローカルリポジトリ、ワーキングディレクトリ+.gitディレクトリ)と、狭義のリポジトリ(リポジトリデータベース、.gitディレクトリ、コミット履歴+α)が文脈によって使い分けられていたはずの説明を、よくわからないままにゴッチャになって読んでいたためと思われます。(今もよくわかってませんが)
あとは、コミットって名詞なの?動詞なの?…とか。リポジトリに登録するコマンドの動詞的な意味の「コミット」と、比較したり共有したりする「コミット(コミットオブジェクト?)」という名詞的な意味が、何の説明もなく文脈上で普通に使い分けられているような気がします。
あちこちの記事をつまみ食いのように読むと、それぞれ単一の記事に閉じて読んでいる限りは用語の解釈にブレがないので、読んでいる時にはなんだか分かったような気になれるのですが、ちょっと後になると、「つまりこの用語の指し示す概念の範囲ってどこなのよ?」って感じになることが多々ありました。
解決策は、そんな些細なことは、いちいち気にしないことでした。
ワークツリー
作業場です。
- 普通のファイルシステム上のファイルと同じように直接ファイルを作成したり変更したり削除したりすることができます。
- 「ワークツリー」、「作業ツリー」、「作業ディレクトリ」、「ワーキングツリー」、「ワーキングディレクトリ」、「作業ディレクトリ」、「worktree」、「Working tree」、「Working directory」・・・etc.、などと呼ばれます。 ほぼ同義で、普段は表記の揺らぎのようなものとして気にせず読むようにしています。
- 実際には、「チェックアウトされた管理対象のファイルだけを指す用語(Working tree系)」と、「indexやHEADのようなメタデータまで込みで指す用語(worktree系)」とで使い分けがあるみたいなのですが、気にして読み分けたりはする必要がないというか、仮に誤用されていたとしても言わんとするところが分かった気になれる程度には注意が向かなくなってきます。(ただ表記揺れに対する感度が下がっただけかもしれない)。
- 管理対象から省きたいファイルなどを指定できます。(
.gitignoreでググる宜し) - 類似の用語っぽい物に「作業コピー」がありますが、それだとSVNリポジトリからチェックアウトしてきた物みたいに思えてしまうので、Git の文脈では使わないようにしています。
- 「ワークツリー」は、ステージしてコミットすることでバージョン管理するべきファイルそのものの置き場なので、そもそも「コピー」という概念に当たらない気がします。
ステージ
コミット時に、コミット履歴に追加する変更内容のセットを取りまとめるための場所です。
- 「ステージ」、「ステージングエリア」、「インデックス」、「キャッシュ」、「staging area」、「index」などなど、 様々な呼び方をされますが、こちらもただの表記揺れという認識です。 厳密にはどうか知りませんが。
- インデックスに追加する。ステージングする。ステージにあげる。ステージに追加する。addする。…も同じような意味で認識しています。実際にはどうだか(以下略
個人的な所感としては「コミットという弾丸を発射する前に装填する場所」というイメージだったのですが、ローカルに刻んだだけのコミットは、まだ弾痕というほど強固なものではないという認識を得るにつけて、「そうでもないな」という認識に変わりました。(だから何だという話ですが。。)
コミット
コミット時点でインデックスに登録されていたファイル一式のスナップショットです。
SVN 的なリビジョンに相当します。SVN だとコミットって動詞的なイメージを持っていたのですが、Git では名詞的な用語(でもある)という気がします。
コミット毎に、親コミットへの参照を持っていて、その履歴を辿ることで、どういう変更を、いつ、誰が、どんなファイルに対して行ったのか…みたいな事を、コミット間を比べて調べることができます。
ちなみに、Git にはちゃんと revision という用語も出てきます。
fatal: ambiguous argument 'xxx': unknown revision or path not in the working tree.
「致命的エラー:曖昧な引数'xxx':不明なリビジョン、または、ワークツリーに存在しないパス。」みたいな感じで。
どうにも「リビジョン」っていうと、SVNでインクリメントされていく通番的なイメージで、Gitのコミットってのはハッシュで一意に識別される独立したブツというイメージなのですが、https://git-scm.com/docs/gitglossary/ja#def_revisionを読むに、リビジョンは、commit(名詞)と同義だそうです。
後述するブランチ名などのラベルを介してではなく、コミットそのものを指す用語としては、コミットID、コミットハッシュ、コミットSHA、SHA、などと呼称されることもあります。
ブランチ
コミットの履歴が積み重なったもの、の先端のコミット、に付けられる呼び名。
ブランチ名は、そのブランチの先端のコミットを指す可動ラベルとして機能します。(プログラマ的にはポインタって言った方が分かりやすいのでしょうか。リファレンスでもエイリアスでも、何とでも好きに呼んで良いんですよね・・・ね?)
リポジトリ作成時の初期ブランチとして main という名前が付けられていることが多いです。(設定による。昔は master なことが多かったらしい。記事の作成時期などによりけり)
ブランチを育てる。ブランチを伸ばす。コミットを積む。コミットを進める。コミットを生やす。…たぶんみんな同じことを言っています。
別の名前のブランチを作成することで、競合する資材に対する異なる変更内容を、それぞれのブランチのコミットに積み上げることができます。
後述する pull や push といった操作は、現在のブランチを対象に行われます。
(※fetch は リモート名を指定しなかった場合、カレントブランチの upstream に対応するリモートを対象にします。upstream先が登録されていなければ、origin が対象になります。対象になったリモートに対応する複数のリモート追跡ブランチがまとめて fetch されます。詳細は後述。)
リモートリポジトリ
ローカルとは別の場所にコミットの履歴を保管するところです。
ローカルからここにコミットを送り付けたり、ここからローカルにコミットを取り込んだりすることができます。
ホスティングサービスだったり、SSHで繋いだ先だったり?
リモートブランチ
リモートリポジトリに存在するブランチです。
ローカルにあるブランチと同様、コミットに付けられた可動ラベルです。
リモート追跡ブランチ
リモートリポジトリの中のブランチの状態を、ローカル側に記録しておくための入れ物です。
ローカルブランチとリモートブランチは、このリモート追跡ブランチを介して、両者の最終コミット同士の前後関係や差分を確認します。
リモート追跡ブランチは通常、<リモート名>/<ブランチ名> という名前になります。例えば、リモート origin の main ブランチに対応するリモート追跡ブランチは origin/main といった具合です。
一方で、ローカルブランチ名は、追跡先のリモートブランチ(upstream)と同じ名前になるとは限りません。(後述するswitch --track -c ~の説明を参照)
- 参考までに、ローカルブランチに対する追跡先を記憶させる場合は、以下のようにするようです。
- ローカルブランチをリモートに送りつつ対応付けする、
git push -u - リモートにあるものをローカルに取り込みつつ対応付けする、
switch --track
- ローカルブランチをリモートに送りつつ対応付けする、
HEAD
現在の場所を指し示すラベルです。
HEADがブランチを指している場合は、そこが「現在のブランチ」ということになります。
ローカルのブランチしか指せません。HEADがリモート追跡ブランチを指すことはないそうです。
「現在のブランチ」のことを、カレントブランチと呼んだりします。
- アクティブなブランチとも呼んだりします。
- アクティブブランチとも呼ぶかもしれません。
- 作業中のブランチと呼んだりもします。
- ワーキングブランチは別の意味の用語?というよりも文脈にもよりそうで微妙。よくわかりません。…「カレントワーキングブランチ」とまで言えば「現在のブランチ」のことを指すんでしょうけども。
※英語の原文でも、current / active / working と文脈に応じて使われているようです。たぶんですが、状態としてのCurrent、操作対象としてのActive、変更を行っている場所としてのWorking。…みたいな?
ブランチ名はそのブランチの先端のコミット、ブランチの成長点、次世代コミットの親、…を指すラベルで、要するに現在のブランチの最新のコミットのことになります。禅問答チックですね。
例えば、HEADがmainブランチを指している場合は、main がカレントブランチになり、mainというブランチ名は、そのブランチで最後に行われたコミットのことを参照します。
コミットすると、HEADの指していたブランチ名がそのブランチの最新コミットを指す状態に置き換わると同時に、そのブランチを参照しているHEADの指すポイントも自動的に最新コミットという事になります。(さっきまでのHEADは、HEAD^になります)
スイッチすると、HEADは切り替え先ブランチの最新コミットを指す状態になります。
detached HEAD と言って、ブランチではなく特定のコミットを指すこともあります。どのブランチにも乗っていないという状態で、その状態でコミットすると、HEADが新しいコミットを指します。どのブランチも伸びません。
detached HEAD
HEADがブランチを参照していない状態です。
思いつく用途としては、どのブランチの先端でもない、過去の「ある時点」のコミットを対象に、その時点のファイル一式の状態をワークツリーに展開して、中身を見てみたり、動かしてみたり、過去に他のブランチの時系列上で行ったのとは違う修正方法を試してみたり…といったことをするためにあるんじゃないかな?と思いました。よくわかりませんし、今のところ自分で試してみたいと思いませんが。
※実際には、どれかのブランチの先端に相当するコミットだろうが、最新コミットだろうが、とにかく直接コミットを参照している・ブランチ名を経由してない、という状態です。
「ブランチからデタッチされているHEAD」という意味であって、「コミットにデタッチしているHEAD」という意味ではありません。
デタッチは、取り外されてるとか分離しているという意味なので。逆説的に、HEADはブランチにアタッチ(?)されているのが通常の状態ということですな?
detached HEAD 状態で行った修正を、新しいブランチとしてコミットすることもできるとかどうとか。現時点のどこかのブランチの先端からの枝分かれじゃなくても、別ブランチをニョキっと生やせるということでしょうか。
detached HEAD 状態のことを、「コミットをチェックアウトしている」とも言うらしいです。要するに普段は「ブランチをチェックアウトしている」のですな
- 通常の
HEAD- ブランチをチェックアウトしている
-
HEADがブランチ名という参照を経由してコミットを間接参照している
-
detached HEAD状態- コミットをチェックアウトしている
-
HEADがコミットを直接参照している
HEAD の短縮形@と、その仲間(?)たち
@をHEADの短縮形として使うことができます。
@{-1}のようにして、checkout / switch で移動したブランチやコミットの履歴を辿って参照できます。
- コミットしてHEADの指すコミットポイントが変わった場合はこの移動の履歴に積まれません。
似て非なるものに、<ref>@{...}というsuffix記法があります。
-
<branch>@{upstream}で、そのブランチに設定されたアップストリーム先を参照します。<branch>@{u}と省略可能。- 現在のブランチのアップストリーム先を確認するには、
git rev-parse --abbrev-ref @{u}とします。
- 現在のブランチのアップストリーム先を確認するには、
-
<ref>@{1}で、そのリファレンスの reflog のひとつ前を表します。 -
<branch>や<ref>を略すと、「現在のブランチ」を後置修飾したことになります。-
HEADがブランチを指している場合-
@{u}で カレントブランチのアップストリーム先を参照します。-
これだけ見ると、
HEADの短縮形の@に{u}という書き方のように見えて、ブランチのアップストリームは<ブランチ名>{u}なのかな? …とか思っちゃうかもしれませんが 違います。上に書いた通り、
<ブランチ名>@{u}と書く必要があり、この場合の@はHEADの短縮系ではなく、後置修飾子なのです。
-
-
@{1}で、カレントブランチの reflog のひとつ前を参照します。- 「
HEAD」 の reflog ではなく、「HEADが指しているブランチ」の reflog なので注意。 -
HEAD自身の reflog を辿りたい場合は、HEAD@{1}になります。
- 「
-
-
HEADがコミットを指している場合(detached HEAD 状態)-
HEADがブランチを指していない=現在のブランチがない、ということで@{u}や@{1}は使えないらしいです。 - ただし、detached HEAD 状態でも、
HEAD@{1}は可能です。HEAD自身はリファレンスで、直前に指していた場所を辿れるためです。この場合も、@がHEADの短縮形ではなく、refの後置修飾子だからです。
-
-
直前のswitch前に戻りたい、というときにgit switch @{-1}としますが、これにはさらに省略形があって、git switch -になります。便利だと喜べば良いのか、ややこしがれば良いのか、不思議な気分です。
リファレンスって?
ラベルとか、可動ラベルとか、ポインタとか、ポイント先とか、エイリアスとか、リファレンスとか、refとか、参照とか、呼び名とか、・・・この文章内でも 我ながらメチャクチャに書いてますが、 ChatGPTと話してると本当になんでもアリです。こっちが使うからチャッピーも使う。どんどん用語がブレる。怖い。
細かいことは、.git/refsの中とかを読みましょう。ということになりますでしょうか。(説明放棄)
-
HEADは現在チェックアウトしているものを指すリファレンスです。- 普通はどれかのブランチ名へのリファレンスになっています。カレントブランチです。
- たまにコミットそのものへのリファレンスにもなる。detached HEAD です。
-
<ブランチ名>はコミット成長点(次世代コミットの親になるコミット)へのリファレンス。 - コミットはリファレンスではない。
-
<コミットID>@{u}はあり得ない。コミットは upstream を持たないため。 -
<コミットID>@{1}はあり得ない。コミットはリファレンスではなく付け変わることが無いため。- コミットに付ける札がリファレンス。その札がどのコミットに順に張り付けられていたのかの履歴が reflog。
-
さらに「札」とか言う表現を使ってしまいました。どんどん表記揺れが気にならなくなってくる不思議。
ブランチ名やHEADのリファレンスは、他のリファレンスを間接参照するか、コミットを直接参照するかして、最終的には何らかのコミットを参照します。(refというもの全般に話を拡大するとコミットだけを指すというわけではないらしいのですが、少なくともここに書いてある程度の内容においては=コミットというテイで書きます)
コミット自身はリファレンスではありません。コミットやリファレンス、その他諸々コミットっぽいものをまとめて、「コミットの性質をもつもの」=「commit-ish」と呼ぶようです。「コミッティッシュ」と発声するのでしょうか。(https://git-scm.com/docs/gitglossary/jaを参照あれ)
これでさらに commit-ish という表現の幅が増えました。やったね。
・・・というか、このへんで、これはもはや用語の不統一などではなく、同じ物でも見方・表現の仕方が違うだけで、例えるなら正面顔と横顔の違いみたいなものだと、ありのままを受け入れることにしました。 キュビズム的な?
過去のある時点のコミットを指すものとしては、タグといったものもありますが割愛。
用語の説明はこのくらいにしておきます。
基本的なコマンド
まずは何はともあれ、リポジトリへGO
cd <リポジトリのパス>
- これは Git のコマンドでも何でもないけど、これをしなきゃ始まらないやつ。
- GitBash 上で、
.gitディレクトリが存在するパスに移動すると、パスの後ろに(ブランチ名)が出るようになるので、リポジトリに入ったことがわかりやすいです。
リモートリポジトリの確認
そう頻繁に確認するものではないと思いますが。
# 基本形
git remote
- 普通は
originって出るやつ。-
origin以外にupstreamとかも出るかもしれない。 -
mainとかmasterとかはブランチ名なので、出ません。
-
- ローカルでバージョン管理するためだけに
git initして使ってる場合は、不要。- リモートと対応付いてない、ただのローカルリポジトリの場合は、何にも出ない。
-
git remote add origin git@github.com:user/repo.gitとかすると意味が出てくる。 -
git clone https://github.com/user/repo.gitみたいにしてた場合も意味がある。
# もうちょっと詳しく確認したい場合
git remote -v
- fetch 先と push 先の URL がそれぞれ出る。
- 具体的にどっから fetch されるんだっけ?みたいな時に。
- SSH だっけ、HTTPS だっけ?みたいな時にも?
ブランチの確認
今コミットすると、どこのブランチが伸びるのか(カレントブランチ)を確認します。
- コミット前や、スイッチ前などには、念のため確認するのがいいのかも。
- ステージング前とか、ワークツリーに変更を行う前にも確認しておいたほうがいいのかも。
- シェルプロンプトにカッコ書きでカレントブランチ名が出ているので、似たようなブランチ名があった場合に取り違えてないか等、あくまで念のため程度に。
# 基本形
git branch
- ローカルブランチだけ表示。カレントブランチの行頭に
*が付く。 - ローカルブランチが山ほどあったりしなければ、これでいい気がする。
- 適宜整理していかないと、そのうち山ほどのブランチが存在してしまう状態に陥るんですが。
# カレントブランチだけ表示したい
git branch --show-current
- 1行だけ表示。
*とかも付かない。 - ほかのコマンドと組み合わせる時とかに便利。
# 全部のブランチを出したい
git branch -a
- リモート追跡ブランチも全部出る。
- もちろんローカルブランチも出る。
- カレントブランチの先頭には
*が付く。
# リモート追跡ブランチだけ出したい
git branch -r
- リモート追跡ブランチのみ表示される。(リモートブランチではない)
-
-aオプションの時にはremotes/origin/~みたいに出てたのが、origin/~ってなるけど同じもの。
# 詳細情報も出したい
git branch -vv
- ブランチごとのコミットID(ハッシュ)、リモート追跡ブランチ、コミットコメントが出る。
-
-vva(-vv -aと同義) や-vvrみたいな組み合わせも可。 -
-vもある。-vは、ローカルブランチに対するリモート追跡ブランチが表示されず、コミットIDとコミットコメントだけ出る。- なので、
-vrと-vvrは同じ内容が出る。(リモート追跡ブランチは-vでも-vvでも変わらない) - リモートと対応づいてなければ、
-vと-vvでも同じ内容が出る。 - 「どのリモートとか関係ないんじゃい、ブランチごとのコミットだけ確認したいんじゃい。」というときに
-vを使うと思われ。
- なので、
- 要するに、普段は
-vvでおk。 ※もしローカルで使ってるだけなら-vでおk。
ワークツリー・インデックスの状態確認
カレントブランチにおける、ワークツリー・インデックスの状態を確認します。
- 「とりあえず、今のワークツリーとステージは、どんなんだったっけ?」って時に実行。
- VSCodeの「ソース管理」ビューに視線を走らせるくらいの気安さでバシバシと叩くべし。
# 基本形
git status
-
割とダラッとした文章で出るので、後述の
-sbを付けた方がすっきりと見やすいかも。 - 対応付けたリモート追跡ブランチとの乖離状況(何コミット分か程度)も出ます。
- 本当のリモートブランチではなく、リモート追跡ブランチとブランチとの乖離なので、fetch してからじゃないと正しい乖離状態はわからないので注意。
-
indexとHEAD(の指すコミット) に差がなく、working treeとindexにも差がなく、さらに、無視されていない未追跡ファイルも存在しないことを、クリーンな状態というらしいです。(nothing to commit, working tree cleanって出る。)- ちなみに、コミットされていない変更があるときは、ダーティと呼ぶらしいです。なんだかひどい言われようだと思ったのですが、実際問題クリーンな状態を前提に考えないと、色々なコマンドを実行するのが怖い気がしてしまうビビりの初心者です。
-
indexとHEADとworking treeの三つまとめて「ワークツリーがクリーン」ということで。 - 横文字で「worktreeがクリーン」とか「working tree がクリーン」っていうとなんか違うような気がしてしまう不思議。・・・表記揺れの過剰摂取により表記揺れ過敏症を発症しているかもしれません。
# 簡潔・端的に表示
git status -sb
-
--short --branchの略 -
-sと-bを同時に指定することでブランチの情報も省略形で出してる。 -
-sは変更ファイルがあれば表示。変更ファイルがないと何も出ない。<xy> <path>という形で表示される。-
1文字目の
<x>は、ステージの状態(index vs HEAD) -
2文字目の
<y>は、ワークツリーの状態(working tree vs index) -
' ':未修正 'M':変更 'D':削除 'A':追加 'R':改名 …等がある。
例:
M A.txt ← A.txt は、ステージ済みの変更(未コミット) M B.txt ← B.txt は、未ステージの変更 D C.txt ← C.txt は、ステージ済みの削除(未コミット) D D.txt ← D.txt は、未ステージの削除 ?? E.txt ← E.txt は、未追跡の新規ファイル(.gitignoreで除外もされていないもの)※
https://git-scm.com/docs/git-status#_short_formatを見るのが確実。 -
-
普段はこっちを見ればいい気がする。
ファイル内容差分の確認
git diffは使い方がいろいろと複雑な奴です。
なにせ、比較元・先の指定と、比較対象の指定が必要なので。
比較対象は単純にファイルパスをワイルドカードっぽく指定するだけですが、比較元・先の組み合わせは、ワークツリー、ステージ、特定のコミット・HEAD・ローカルブランチ・リモート追跡ブランチ等のリファレンス(要するに commit-ish)、なんでもござれです。
- 比較元・先を指定しない
- 「ワークツリーとステージ」の比較。(ステージ前の確認に)
-
--stagedや--cachedを指定すると、「ステージとHEAD」の比較。(コミット前の確認に)
- 比較先(commit-ish)だけ指定する
- 「ワークツリーと指定したコミット」の比較。
-
--stagedや--cachedを指定すると、「ステージと指定したコミット」の比較。
- 比較元・先を両方指定する
- 指定した二つのコミット2点間の差分(2点の間に含まれる範囲の差分ではなく、ピンポイントに2点の差分)
-
git diff A..Bとgit diff A Bは同義。- 二つ並べても、二つを
..で繋いでも、aとbの間の範囲ではなくて、aとbの2つだけが対象。 - 後述する
git logでは、git log HEAD^..HEADとgit log HEAD^ HEADでは意味が違うので要注意。
- 二つ並べても、二つを
-
--の後ろにファイルパスを指定して、比較対象を指定できる。- diff 直後の引数が commit-ish として有効ではない場合、
--の後ろに指定されたファイルパスとして解釈を試みる。 -
.はカレントディレクトリ以下
- diff 直後の引数が commit-ish として有効ではない場合、
ワークツリーの差分の確認
# 基本形(現在のリポジトリのワークツリーとindexの差分がすべて出る。)
git diff
# カレントディレクトリ以下の未ステージ差分
git diff .
# 特定ファイルだけ見たい場合
git diff <ファイル名>
# または
git diff -- <ファイル名>
# 特定ディレクトリ以下のみを見たい場合
git diff <ディレクトリ名>
# または
git diff -- <ディレクトリ名>
# 差異ファイル名だけを出したい場合
git diff --name-only
# 改行コードと空白の違いを無視
git diff -w
# 空白行の有無を無視
git diff --ignore-blank-lines
# 差異の前後表示行数を変更(いわゆるdiffコンテキスト)
git diff -U<前後行数>
- ワークツリーと、index の差分です。
- ワークツリーと、HEAD の差分ではないので注意。
-
--付けると、その後ろはファイルパスとして解釈されます。(HEAD だろうがブランチ名やコミットハッシュっぽかろうが)-
--をつけていなくて、コミットを指すラベル(HEADやコミットハッシュやブランチ名やら)に解釈されると、ワークツリーとの比較対象になってしまうので注意?
-
- addしてない(ステージングされていない)既存ファイルの変更点が出ます。
- ただし、新規追加ファイルは未追跡状態なので add されてなくても差分として出ないので注意。
ステージの差分の確認
git diff --cached
# または
git diff --staged
#ファイル名を指定
git diff --staged <ファイル名>
# または
git diff --staged -- <ファイル名>
- index と HEAD の差分を確認します。
- 要するに、今コミット対象になっている内容です。
- その他のオプションは↑に書いたものが同じように使えます。
コミットの差分の確認
# 基本形
git diff <コミットA> <コミットB>
# または
git diff <コミットA>..<コミットB>
# 一世代前と比較したい場合
git diff <コミット>^ <コミット>
# 特定のファイルを見たい場合は以下のように
git diff <コミットA> <コミットB> -- <ファイル名>
# または
git diff <コミット>^ <コミット> -- <ファイル名>
-
<commit>^という書き方で、親を指定できます。-
^は^1の省略形。「1番目の親」の意味。^^で複数世代遡ることができます。 -
<commit>^2^とすると、「2番目の親」の「1番目の親」(父方の母、みたいな) - 通常の2者マージの場合、「1番目の親(
^1)」はマージ先(マージ時の現在のブランチ)で、「2番目の親(^2)」はマージ元(マージしてきた側)
-
-
<commit>~という書き方もある。こちらは「何番目」の親か、ではなく「何世代前」の親かを指定できる。-
<commit>~3^2のように書けば、「3世代前の親」の「2番目の親」になる。
-
指定したコミットの差分だけ確認
git show <コミット>
# 特定のファイルを見たい場合
git show <コミット> -- <ファイル名>
- その単一コミットで発生した差分だけが見える。
- ただしマージコミットは show で見るとワケワカランので、
git show -m <merge-commit>とする(らしい)。 - それぞれの親に対して個別に diff する(
git diff <merge-commit>^1...<merge-commit>とgit diff <merge-commit>^2...<merge-commit>する)のもアリ。
ブランチ間の差分の確認
# 基本形
git diff <ブランチA> <ブランチB>
# 特定のファイルを見たい場合は以下のように
git diff main origin/main -- <ファイル名>
- ブランチ名も commit-ish なので、要するにコミット間の比較と同じ文法ですね。
- <ブランチ名>というラベルは「そのブランチの最新のコミット」と同義なので
main^という指定もアリ。
diffまとめ
git diff だけでやたらとバリエーションがあるので、簡略化したまとめをば。
# worktree vs index
# 未ステージの差分
git diff
# index vs HEAD
# ステージ済みの差分
git diff --cached
# または
git diff --staged
# worktree vs HEAD
# ステージ済み + 未ステージの差分
git diff HEAD
# worktree vs HEAD^
# 1つ前のコミットから見た、現在の作業ツリー全体の差分
git diff HEAD^
ステージ~コミット
ステージング
#カレントディレクトリ以下の、新規・変更・削除を反映(※過去のバージョンでは削除は対象外だったこともある)
git add .
#カレントディレクトリ以下の、新規・変更を反映(削除を除く場合)
git add --no-all .
# ファイルを指定
git add <ファイル名>
# 全ディレクトリの、変更・削除を反映(新規は対象外)
git add -u
# 全ディレクトリの、新規・変更・削除を反映
git add -A
- ワークツリーの内容を index に反映します。
- add したくない変更済みのファイルがある場合は、ファイル名まで指定するように。
- 常にaddしたくない
.gitignoreに書くような奴じゃなくて、今はこいつをコミットはしたくないなーってやつね。 - ワークツリー上では一気に変更したけど、関連する変更の単位ごとにファイルのグループを分けてコミットを刻みたい時とか。
- 常にaddしたくない
-
ワークツリーから消しちゃったファイルは←昔の話。.では反映されないので、狙い撃ちしたい場合は、-u <ファイル名>。- ワークツリーから削除したファイルも
.で反映されます。削除だけ反映したくない場合は、--no-allを指定します。
- ワークツリーから削除したファイルも
ワークツリーを index の状態に戻す
# カレントディレクトリ以下の変更を全部戻す
git restore .
# ファイル決め打ちで戻す
git restore <ファイル名>
- ワークツリーを index の状態に戻します。
- 直近の変更を捨てて、最後に add したときの状態に戻したい場合。
-
git restoreだけだと、ちゃんとパスを指定しろ!って怒られます。(.も大概に怖い気がしますが)
index を HEAD の状態に戻す
# 全部戻す
git restore --staged .
# または
git reset
# ファイル決め打ちで戻す
git restore --staged <ファイル名>
- ステージの取り消しです。
-
git diffと違って、git restore --cachedは無効なので、注意。 - ワークツリーは変わらず残っているので、再度 add すれば元通り。(addしたときからさらにワークツリー上で変わっていたら元通りというわけではない?)
-
git resetは、git reset --mixed HEADの省略形です。後述する「指定したコミットの状態に戻す」も参照。
コミット
git commit -m <メッセージ>
- コミット後は、
git status -sbして、ステージ済みの差分がちゃんとなくなっていることを確認しておきましょう。 - リモート追跡ブランチと紐づいているローカルブランチにコミットした場合、リモートと差異(ahead)がある状態になります。
指定したコミットの状態に戻す
#HEADの指すブランチを一世代前のコミットに戻す
git reset @^
# 厳密には
git reset --mixed HEAD^
-
この例では、ステージと、HEADが指すブランチの先端が、一世代前のコミットに移動します。ワークツリーは変更されません。
- さっきまでHEADが指していたコミット自体は、即座に消えるわけではありません。が、次にコミットしたときには一歩下がった今のコミットが親になります。
-
古いコミットを指定して、
git reset <コミット>とすれば、HEADが指していたブランチの先端がそのコミットになります。(ブランチ先端から辿れないコミットにも付け替え可能)-
git switch --detach <commit>とすれば、HEADの指すコミットだけが変わり、どのブランチの先端も動かない代わりに、detached HEAD 状態に遷移します。つまり、resetは、ブランチからHEADをデタッチ(分離)させずに、そのブランチが指し示すコミットを付け替えるコマンドなのですな?
-
-
リモートブランチに push したコミットがある状態で、 reset してローカルブランチ先端の梯子を外すようなことをすると共同作業的に変なことになるので、打ち消しコミット(
revert)というものを使うらしいです。 -
オプションで、ワークツリー、ステージ、HEADのどこまで戻すのかが変わります。
-
--softを指定すると、HEADのみが対象になる。ワークツリーとステージは変更されない。- ステージから再度コミットし直すことが可能。
- 仮コミットだったので、やっぱやーめたってしたい場合とか?
-
git stashのpushする代わりに、カレントブランチにコミットを仮で積んでおいて、別件作業が終わった後にpopするような感覚で、ブランチを戻してからreset --softというのもアリな気がしなくもない。
-
- コミットメッセージ直したいとか?(直前コミットを修正する場合は
git commit --amendというコマンドもある。割愛。)
-
--mixed(未指定の場合のデフォルトと同じ)を指定すると、HEADとステージを対象にします。ワークツリーは変更されません。- ワークツリーを別途変更してなければ、再度addしてcommitすれば、同じ内容でコミットし直すことになる。(ハッシュは別になりますが)
- addしすぎちゃってた、addしたりなかった、みたいな時に?
- ステージからめちゃ直したい時は
--mixedで、ステージをちょっとだけ直したい場合は--softを使うんでしょうか?
- ステージからめちゃ直したい時は
-
--hardを指定すると、ワークツリー・ステージ・HEADすべてが対象になる。
-
「ブランチの先端」、「ブランチの先端」、と繰り返し書いていると、ブランチってのは先端そのもののことなんだよ!・・・と言いたくなる気持ちが分かってきました。
コミット情報の確認
コミットを確認します。
# 基本形。カレントブランチのログが出る。
git log
#あるコミットの持っている履歴が全部出る。
git log <commit-ish>
#コミットAとコミットBの間の履歴を出す。(コミットBから到達可能で、Aから到達可能なものは省く)
git log <A>..<B>
# コミットが大量にあって見づらい場合
git log --oneline --graph <A>..<B>
-
--onelineを指定すると、1コミット1行でスリムに表示されます。- 短縮コミットハッシュと、コミットメッセージの1行目だけが表示されます。
- 普段はコミット毎に、Author と Date とコミットメッセージがでる。
-
git log --pretty=format:"%h %s"と指定しているのと同じ。-
%hは、短縮コミットハッシュ。リポジトリ内で一意に識別できる任意の桁数 -
%sは、コミットメッセージのサマリ。1行目。
-
-
-
--graphは、コミットの親子関係や分岐・マージをASCIIで描画してくれるオプション(一直線だとありがたみがない)。 -
git diffのところで書いたけど、こちらは二つだけが対象ではなく、その間の範囲が対象になります。- 二つ以上の commit-ish を指定することもできるが、それは、複数のコミットの持つ履歴が全部出る。
-
^<commit-ish>と指定すると、そのコミットポイントから過去方向に辿れるログを除外する意味になる。- 指定した複数の commit-ish へ到達可能なコミットを全て足し合わせたもの(和集合)を出します。
-
^A Bは、Aに至るログを除外し、それ以外のBに至るログを表示します。A..BはA~Bの間なので、同じ意味になる。Aまでのログ、Aよりも前を除外してるんですな。 - どっちかというと、
git log A..Bという指定のほうが、git log ^A Bという指定の糖衣構文だそうです。- さらに
A...Bみたいな書き方もあるらしい。AまたはBから辿れるけど、AとBの両方から辿れるコミットはを除く・・・?使い道が想像できない。。よくわからんので割愛。
- さらに
共同作業
- VSCode のソース管理で「変更の同期」ボタンとかを押すのに該当するコマンド。
- ・・・共同といっても、一人で GitHub のプライベートリポジトリとローカルリポジトリをやり取りするのも共同なので。一人でも大丈夫。
-
commitは共同作業(collaborate)のカテゴリではない。※svn上がりの人間が混乱するところである。
fetch
# 基本形
git fetch
# または
git fetch <リモート名>
- リモートの最新情報を取ってきて、手元のリモート追跡ブランチを更新する。
- ローカルのワークツリーやステージには何の影響も及ぼさないので、迷わず打つべし。
- 一足飛びにブランチ統合までしようとする
pullとは違う。
- 一足飛びにブランチ統合までしようとする
- <リモート名>を指定しなかった場合は以下のようになります。
- カレントブランチにアップストリーム先が設定されている場合、アップストリーム先が属するリモートを対象に fetch します。
- カレントブランチにアップストリーム先が設定されていない場合は、通常は
originを対象に fetch します。 - 対象となるブランチの範囲は、リモートごとのfetch設定によりけり。普通にcloneされている場合は(その後設定変更もしてなければ)、そのリモート上の全ブランチに対応するリモート追跡ブランチがまとめて更新されます。
「リモート」「ローカル」「ブランチ」「リモートブランチ」「ローカルブランチ」「アップストリーム」「リモート追跡ブランチ」・・・ややこしいですね。
# 通常は、こちらで良い気がする。
git fetch --prune
- リモート追跡ブランチの更新のついでに、不要になった(削除済みのリモートブランチに対応する)リモート追跡ブランチを削除する。
- こっちを使っておいたほうが気持ちいいかも。
pull
# 基本形
git pull
- fetch して("fetch した"に非ず。pull することで内部的に fetch される)、その結果を現在のローカルブランチに取り込む。
- 取り込み方は設定によって
mergeになったりrebaseになったりする。 - 状況や設定によってはマージコミットができたりするので、迷わず打つのはお勧めしない。
- 具体的には、リモートが進んでいるだけの場合(fast-forward可能な時)は大丈夫。
-
git pull --ff-onlyってやれば、自動でマージコミットが作られたりしない(fast-forwardできなければエラーになるはず)ので、こっちのが安全かも。
# 早送り可能な場合だけ実施
git pull --ff-only
- 早送り可能な場合だけ pull する。早送り不可な(ローカルとリモートでそれぞれ枝が伸びてる)場合は pull しない。
- ただし fetch はされた状態になる。(リモート追跡ブランチが更新されるだけなので、気軽に実行してOK)
# 統合方法に merge を指定
git pull --no-rebase
- 早送り可能なら fast-forward で pullする。(リモート側で増えている分のコミットを取り込むだけ)
- ローカルとリモートの両方に新しいコミットがある場合、マージコミットが作成されます。
-
どこでブランチが分岐して合流したかの記録が残ります。
-
元のコミットがそのまま利用されます。(新しいコミットは追加されるマージコミットだけ)
-
履歴が複雑になりやすい
-
共有ブランチでも比較的安全に使いやすい
A---B---C origin/main \ D---E main↓pull
A---B-------C origin/main \ \ D---E---M main↓push
A---B-------C \ \ D---E---M main,origin/main
-
# 統合方法に rebase を指定
git pull --rebase
- 早送り可能なら fast-forward で pullする。(リモート側で増えている分のコミットを取り込むだけ)
- ローカルとリモートの両方に新しいコミットがある場合、リモートの変更の後ろに、ローカルのコミットを「つけ直し(rebase)」ます。
-
履歴が一直線に付け替えられて、ローカルコミットのコミットIDが付け変わります。(リモートのコミットはそのまま利用されます)
-
自分専用の作業ブランチの履歴をきれいに保ちたい場合など。
A---B---C origin/main \ D---E main↓pull
A---B---C origin/main \ \---D'---E' main↓push
A---B---C \ \---D'---E' main,origin/main
-
# pull時の統合方法を確認(リポジトリ単位)
git config --show-origin --get pull.rebase
- 全体の設定としてrebaseするかどうか。
- true なら rebase、false なら merge。
# pull時の統合方法を確認(ブランチ単位)
git config --show-origin --get branch.$(git branch --show-current).rebase
- カレントブランチの設定として rebase するかどうか。
- 返却無しなら未設定(
pull.rebaseに従う)。 - 返却ありの場合は、
pull.rebaseより優先される。 - true なら rebase、false なら merge。
# 早送りの扱い
git config --show-origin --get pull.ff
- merge方式で統合する時の fast-forward(早送り)の扱いがどう設定されているかを確認する。
- true なら、可能な時は fast-forward し、分岐していれば merge する。
- false なら、fast-forward 可能な場合でも、merge commit を作成する。
- only なら、早送り可能な場合だけ fast-forward する。早送りできない場合は失敗して止まる。
- 返却無し、かつ、分岐時の統合方法が指定されていない場合は、only と同じ(fast-forward できる時だけする。)
push
# 初めて push するとき
git push -u <リモート名> <ブランチ名>
# 2回目以降 push するとき
git push
-
<リモート名>は、git remoteしたときに出るやつのどれか。originとか。- 一度付けると、
ブランチ名に対するアップストリーム先として登録されるので、次からは付けなくてもOK。
- 一度付けると、
-
<ブランチ名>も一発目は付けておく。(まだアップストリーム先がない状態なはずなので)- 一度でも
push -u <何某>したとかで、カレントブランチにアップストリーム先が登録されていれば、省略可能です。
- 一度でも
作業フローの例
- 上記までのコマンドを押さえつつ、+α
- きれいにこんなケース分岐しないこともあると思いますが、あくまで参考に。
まずは fetch
cd <リポジトリのパス>
# リモート追跡ブランチ最新化
git fetch --prune
- オンラインだったら、何はともあれコレ。
- ワークツリーにも、ステージにも、HEADにも何の影響もありません。
- 各ブランチの追跡状況(ahead/behind)が変わります。
現在のブランチを確認
# ブランチ確認
git branch -vv
- 今どのブランチ向けの作業をしてるのか、各ブランチのコミットID、どのリモートに紐づいているか、リモートとのズレ(ahead/behind)の有無などを確認します。
- もしカレントブランチが想定と違っていた場合は、
git switch <ブランチ名>する必要がありますが、スイッチする場合でも、この次のgit status -sbで未追跡・未ステージ・未コミットの変更がない(ワークツリーがクリーンである)ことを確認しておいたほうが安パイかも。- もしHEADが想定違うブランチだった時に、ワークツリーがダーティなまま「あっブランチ違ってた」という感じで気軽に switch すると、ワークツリー・インデックスの変更ファイルと、switch先ブランチで変更が競合していた場合にエラーになったりするかもしれない。(よくわかってません)
以下、現在のブランチはあっていたという想定で、フローを続けます。(違っていた場合は switchする。割愛。)
リモートと差異がある場合
ローカルのほうが進んでいる(ahead)のか、リモートのほうが進んでいる(behind)のかで、やることが違います。
git branch -vvでどっちが先んじているのかわかるのですが、具体的にどういう変更(コミット)があるのか確認します。
git log <ブランチ名>..origin/<ブランチ名>
# 他のブランチと、そのブランチの追跡先を比較したい場合
git log <ブランチ名>..<ブランチ名>@{u}
リモートのほうが進んでいる場合(behind)
- 自分の運用だと、Codex からの PR を GitHub の main ブランチに取り込んだ後の状態とか。
- メインマシンのリポジトリから GitHub 側に push した後、サブマシンで続きをやろうとしたときとか?
※リモートの GitHub リポジトリよりも、メインマシン側のローカルリポジトリがさらに進んでいないとも限らないので、一人でやってる場合でも、無駄な競合を避けたいならその辺はある程度は注意する必要があるのではないかと思うのです。(数日おきとか、下手したら数か月おき程度にしか触らないような緩いスパンで作業してる場合はなおさら)
まずは何がコミットされてるのか確認。
# リモート側に増えているコミットを確認。
git log --oneline @..@{u}
# コミットログ全文も見たい場合。
#(これもいいけど、MarkDownがコンソールにドバっと出ても見づらいので、↑推奨?)
git log @..@{u}
取り込んでもいいコミットと判断できたら、pull。
git pull --ff-only
- 初心者なのでなので、手堅く fast forward を明示しようかと思います。
- 一人では、あんまり rebase/merge が必要な状態にならないと思うのです。
- 自宅のデスクトップPCのローカルリポジトリと、持ち運び用ノートPCのローカルリポジトリの両方で同じブランチを別々に伸ばしたりするようなことをすると、一人でも rebase/merge する羽目になるのかも?
- それぞれのPCのワークツリーやステージの中途半端な状態を気軽に同じブランチにコミットしまくったりすると、競合が発生するかも。
- 別々のことをやりたいなら、別ブランチにするのがいいと思う。
- ブランチの目的に沿ってコミットされてたら、別PCにもとりあえず取り込んでおいたほうがいい気がする。一人なら誰とも相談する必要ないので、自分で把握すべし。
- pullした後は、
git branch -vvとかして、リモートとローカルのブランチの間に差分がなくなっていることを確認しておきましょう。
pullした後は、後述する「未コミットの変更ファイルがなく、リモートブランチとの差異もない場合」のフローになります。
ローカルのほうが進んでいる場合(ahead)
ローカルのほうが進んでいたとしても、無条件にリモートに push していいわけではないので、変更点を確認して、ちゃんとテストも済ました内容である、という確証があるものだけ、push するようにします。
# ローカルにあるコミットを確認。
git log @{u}..@
git push
リモートとの差異が無い場合
ローカルリポジトリ内で、ワークツリーやINDEXに変更が加えられていないか確認して、変更が無ければ新しい修正のスタート地点に立っているということですね。
変更があればその修正を続けて行けば良いかと。
ローカルの状態を確認
# ワークツリー、ステージの状態を確認
git status -sb
- 変更の有無を確認します。
未コミットの変更ファイル等がある場合
-
差分を確認し、ワークツリーの資材を十分にテストをしてから、問題なければステージ&コミットします。
- 用途別のブランチだったらある程度気軽に履歴を刻んでコミットしても構わないかもです。
- リモートを経由して複数環境にバラまきたい場合とか?
- mainブランチだったら注意したほうがいいかも。
# ステージ git add . # コミット git commit -m "<コミットメッセージ>" - 用途別のブランチだったらある程度気軽に履歴を刻んでコミットしても構わないかもです。
未コミットの変更ファイルがない場合。
凪の状態です。pullした後とか?
※pushした後だと、未ステージだったワークツリーとかが残ってる可能性もあるので、このフローに来るとは限らない。
ここから何か新しい修正を始めたい場合には、変更を実施するためのブランチを作成します。
- どうせ自分一人しか触ってないリポジトリだし、ちょれードキュメント修正だけだから、main にいきなりコミットするぜ!というような場合は不要?
- プログラムソースに手を入れるような場合は、一人でやるにしても、ブランチを分けたほうが良いような気がします。
- ブランチ内なら何度コミットしてもブランチ単位で変更目的を保てますし、そのことで main にマージされたときのマージコミットの目的も明確になります。(基本的にスカッシュマージしかするつもりがないので。。)
- コミットなんてpushするまではいくらでもコネコネできるから、ローカルのmainに直接コミットするぜ、という流派も?ない?
新しいブランチを作成
# 現在のコミットを起点に新しいブランチを作って、切り替える。
git switch -c <作業ブランチ名>
# 念のため、切り替わってることを確認
git branch --show-current
ちなみにswitch -cっていうのは
# 現在のコミットを起点にブランチを作る。
git branch <作業ブランチ名>
# 作ったブランチに切り替える
git switch <作業ブランチ名>
ってやるのと同じことです。switch -cを使っておいた方が切り替え忘れもないので安心かも。
参考までに、昔の書き方だとこういうのもあるらしい。
git checkout -b <作業ブランチ名>
ワークツリーの修正、ステージ、コミット
# テスト完了後、変更したファイルを確認
git status -sb
# 修正内容を確認
git diff .
# または、ファイル一個ずつの差分を見たい場合
git diff <pathspec>
# 問題なかったら、ステージ
git add .
# または、ステージ対象を絞りたい場合
git add <pathspec>
# ステージ済みのコミット対象を確認
git diff --staged
#コミット
git commit -m "<コミットメッセージ>"
GitHubへpush
作業ブランチを、リモートに共有します。
ローカルで main に直接マージしてしまってもいいのですが、せっかくリモートと繋がってるので。
(main に直マージしてから origin/main に push する程度の変更なら、最初から main にコミットして push するのも一考)
git push -u origin HEAD
- 今いる場所で行っている変更なので、わざわざ現在のブランチ名を書いたりせず
HEADでOK。 - GitHubへログインすると、画面に、
<ブランチ名> had recent pushes 2 minutes agoとか目立つように出ているので、そこのCompare & pull requestボタンをクリック。 - PRタイトルと本文を適当に書いて、マージ。何マージにするかはお好みで。
fetch と switch
リモート側で main にマージしたので、まずは fetch。
git fetch
- これで、
origin/mainが PR を取り込んだ状態に進みます。
mainブランチに戻りましょう。
git switch main
# または(直前までmainに居た場合)
git switch -
確認(慎重を期す場合)
リモート追跡ブランチが進んでいるのを確認。
git branch -vv
-
behind 1とか出るはず。 - ここまで毎度確認する必要はありませんが、初めのうちは順を追って確認してもいいかも。
- そもそもわかりきってるなら、fetch もスッ飛ばして いきなり pull というのもアリ。
内容の確認
# 差分の内容を確認したいならこう
git diff @ @{u}
# コミットのサマリを表示
git log --oneline @..@{u}
- PR をマージしたときのコミットが出てくるのを確認。
pull
git pull --ff-only
- 以上。
-
git branch -vvとすれば、behind がなくなってるのを確認できます。
リモートに、新しいブランチが存在していないか確認
git fetch --prune
git branch -r
存在していた場合は、ローカルリポジトリに、ローカルブランチとして取り込んで中身を見てみましょう。
- Codexに作ってもらった機能修正ブランチとか。
- 他の端末でブランチ切って機能追加対応して、ある程度の動作確認までは終わったけど、mainに戻すのはまだ怖いなーって、GitHubにpushした場合とか?
新しいブランチの変更点を確認
#コミットログを確認
git log --oneline --graph @..origin/<リモートブランチ名>
HEADと、ターゲットのリモート追跡ブランチを比較して、今のHEADを親に持つブランチなのか、そうじゃないのか確認してみたり。
-
新しいブランチが、今のHEADの子の場合。
A --- B --- C (main) HEAD \ \--- D (new branch) -
新しいブランチが、今のHEADの子じゃない場合。
A --- B --- C (main) HEAD \ \--- D (new branch)
どっちにしろ、スイッチ先のコミットの中身をワークツリーに展開されます。
ワークツリーがダーティで、未コミットの変更が失われる可能性がある場合switchされません。
ワークツリーとステージの確認
#変更点を確認
git status -sb
- ブランチを切り替える前に、今のワークツリーとステージに変更がないかも確認しておくのが吉。
- ファイルが被ってて、かつ、残しておく必要のある変更だったら…コミットしておく?(後でマージするハメになるのか?)
- そもそもなんで競合するような変更をしてるんだっけ?という、回し方の話にもっていった方がいいような。
リモートブランチに対応付けたローカルブランチを作って切り替える
# リモートと同じブランチ名で、ローカルブランチを作成する。
git switch --track origin/<リモートブランチ名>
-
「リモートブランチ」を追跡する「リモート追跡ブランチ」は fetch で既に取れきているので、これで作られるのは「リモート追跡ブランチ」および「リモートブランチ」と同じ名前をした「ローカルブランチ」です。
概念として、
- リモートに存在する、ブランチ(いわゆる「リモートブランチ」。fetch/pull/pushでしか手が届かない場所)
- ローカルに存在する、リモート追跡ブランチ(いわゆる「リモート追跡ブランチ」。
git branch -rで出るやつ。うっかり「リモートブランチ」と呼んでしまったりするが別物。※fetchするまでは特に) - ローカルに存在する、リモート追跡ブランチと対応付けされたブランチ(これは「ローカルブランチ」)
- ローカルに存在する、リモート追跡ブランチと対応付けされていないブランチ(これも「ローカルブランチ」)
の違いに注意。
# 短縮名でローカルブランチを作成
git switch --track -c <短縮名> origin/<リモートブランチ名>
-
-cはcreateのc。指定した名前でローカルブランチを作る。 -
-cでリモートに存在しない名前のローカルブランチを作りつつ、--trackでリモートブランチを追跡するように指示しています。 - 上述の通り、
-cをつけずに、--trackだけでgit switch --track origin/<リモートブランチ名>とすると、リモートと同じ名前のローカルブランチが create される。 - 単純に
--trackする場合よりもホンのちょっぴりタイプ量が増えるが、この後に短縮名を使用する回数のほうが多いと思うので、こちらがお勧め。Codexさん命名のブランチ名は長かったりするので。
CodexのPRを取り込むまでの流れ
当初の目的に沿った用途として、「CodexにPRを作成してもらって、GitHubにブランチが増えてる状態」になっているところからの流れになります。
Codexにタスクを投入して、タスクが完了すると「PRを作成する」というボタンが表示されるようになります。
「PTを作成する」ボタンをクリックすると「PRを表示する」というボタン名に変わります。
「PRを表示する」をクリックすると、GitHub 上で「Pull request」が追加されているところが見えるページが表示されます。
※ここまで行くと、↑↑の「リモートに新しいブランチが存在している」というケースに到達するので、↑の通りにローカルでgit switch --track ...することで、動作確認できるようになります。
「Pull request」の「Conversation」に概略を書いてくれているので、ファイル内容の差分と合わせて確認しましょう。英語で書かれるかもしれませんが、その場合はAGENT.mdで日本語で書くように指示すれば日本語で書いてくれるようになります。むしろ、Pull request にはどういうことを書くべきかも指示しておいた方がいいかも。修正の目的、修正の概要や、検証したこと、検証できなかったこと、等。
今の自分のリポジトリには、検証できなかったことを書くように指示してあるので、例えばこの実行環境に pwsh / powershell が無いため PowerShell パーサーによる静的構文チェックと、WinForms GUI 上での手動操作(〇〇を△△すると■■されること。・・・。)は実行できていませんのような感じに申告してきてくれます。手元でちゃんと実行するようにしています。(もしかして手元にCodexアプリを入れて、ローカルの実行環境を使わせてしまうようなことも最近はできるんでしょうか?…全くそこまで追い付けてませんが。追々と。。)
・・・で、自動Codexレビューを有効にしている場合、最初の書き込みに対してeyesのリアクションが付いているときは、Codexがレビューしている状態ですので、しばらく待ちましょう。(fetch して switch して、軽く動作確認してみるくらいの時間で、レビューが終わる感じがします)
レビューが終われば、eyesのリアクションがなくなって、コメントがあれば追加されています。
※今のところレビューコメントはガッツリ英語で書かれているので、そのうちAGENT.mdを修正しようかと思っています。。
レビューコメントで提案されている修正を対処してほしい場合は、そのレビューコメントの Reply に@codex address that feedbackと書き込みます。
@codex address that feedback
手元でも指摘通りの誤動作を確認しました。
以下の注意点を踏まえて修正対応を行ってください。
- ・・・
- ・・・
というような感じにコメントすると、Codexタスクが自動的に動き出して、タスクが完了した後Codex側に「ブランチを更新する」ボタンが出ます。
ブランチを更新した後GitHubを見に行けば、Pull request のコミットが増えて、Conversation にもさらにコメントが書き足されているはずです。
ソースの修正内容を確認するなり、手元にも新しいコミットをpullしてきて動作確認してみて問題がなければ、「Resolve conversation」ボタンを押してあげましょう。
レビューコメントが複数個出ていて、相互に関連しそうな場合は、Codex Reviewの下にぶら下がっている各コメントのReplyに@codex address that feedbackと書くのではなく、Add a commentのところに、@codex address all review feedbackみたいに書くことで、まとめて対処してくれます。
@codex address all review feedback
以下n件のレビュー指摘を、相互に矛盾しない形でまとめて対応してください。
- xxxx
- xxxx
- :
特にn件目とm件目は、処理が関連しているため、個別の継ぎ足し対応ではなく、全体として整合するように修正してください。
・・・みたいな。
変更内容の確認・実機上での動作確認が取れたら、各コメントの「Resolve conversation」ボタンを押してあげて、main にマージする。
・・・というような流れで作業を行っています。
なぜか、@codex address all review feedbackと書いても、コード修正ではなく、もう一度コードレビューが実行されることがありました。(しかも、同じ問題をもう一度報告してくるだけ)
その場合は、@codex address all review feedbackの文末に、コミットしてください。などと書くと、修正してブランチ更新の準備までしてくれることがあります。ここで言っているコミットというのは、Codex側に存在しているリポジトリにコミットしてもらって、GitHubへpushできる直前まで作業をしてください。というお願いになります。(pushは、Codex CloudのUIから「ブランチを更新する」を押さないと実行されないようです)
GitHubのリポジトリに勝手にコミットされるわけでもありませんし、ましてや、自分の手元のPCに存在しているリポジトリにコミットされたりすることはないので混乱しないように。自分から見たローカルリポジトリと、Codexが見ているローカルリポジトリは別物です。その二つの中継点としてGitHubのリモートリポジトリが使われている、という感じになります。
ちなみにレビューのトリガーを「PRのオープン時」にしていると、ブランチ更新時にはレビューしてくれないのですが、@codex reviewとコメントすればレビューしてくれます。
複数コメントまとめて一気に対処してもらった場合などは、再レビューもしてもらった方がいいかも。
「全てのプッシュ時」にしておけば、自動でレビューしてくれるのかもしれませんが、週間利用上限残高と相談ですかねー。
唐突にあとがき
区切りもへったくれもなく、以上です。
AIに質問してはワケワカラン!と根掘り葉掘りしていたことを書き出すだけで、こんな分量になってしまいました。
だって、理解が形になる前に怒涛の勢いで新概念がブチ込まれてくるんですもの。何とか聞き取れたと思った部分や、自分が必要だと思った分だけでも、こうして書き留めておかないと、出てくるワードが、ガチの用語なのか、慣習的な名前なのか、説明文の中の日常語なのか、gitの内部構造のことを言っているのか、曖昧にしておいて問題ないものなのか、後になって引っかかるものなのか、・・・わからないんです。
そもそもgit mergeについてすら取り上げていないことにお気づきでしょうか。
rebaseとかcherry-pickとかrevertとかについて一切触れていないとかいうレベルではないというやつです。←全く理解していません。今のところ必要としてないので。。
Codex からの PR を取り込むこと以外には、そんなに使いこなす気もないので、こうなりました。(むしろそのためだけに、これだけ調べる羽目になるとは思ってませんでした。)
Gitについて分かったのかどうかと自分で振り返ってみると、あんまり分かったような気がしません。実際に競合が起こった場合や、マージについてのところもアヤフヤなままです。何一つ理解も実践もしていませんし。そもそも「?」と書いた個所は、憶測で書いています。なんなら「?」が付いていなくても憶測交じりです。
AI聞きかじりで Git を使う危うさのようなものを、我が事ながらヒシヒシと感じますね。
もしこれからちゃんと自分で使い続ける事があれば、なんという恥ずかしい記事を上げてしまったんだろう、と悶える時が来るのでしょうか。楽しみです。