2018年10月14日日曜日

2: Gitのチェックアウト動作

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

理に適っているかどうか、意図が何かは別にして、Gitのチェックアウトはこのように動作します。

話題


About: Git

この記事の目次


開始コンテキスト


  • 読者は、Gitの全体像の知識を持っている。
  • 読者は、Gitの基本的操作(ステージングする、コミットする、チェックアウトする、等)の知識を持っている。

ターゲットコンテキスト



  • 読者は、Gitのチェックアウトがどのように動作するかを理解する。

オリエンテーション


Hypothesizer 7
まず、思い出そう、チェックアウトには2種類あることを。'コミットチェックアウト' と 'ファイル群チェックアウト'があり、それらは、コンセプト上、大きく異なっている。

レポジトリには、どの時点においても(コミットがまだ全然ない時を除き)、単一のカレントコミット(それが実は'HEAD'である)があり、コミットチェックアウトはどれも、カレントコミットが指定されたコミットになるようにセットする(ブランチとはコミットへのポインターであることに注意しよう)。カレントコミットは、その時点において私たちが作業する対象のコミットである。

コミットチェックアウト操作は、基本的には、ワーキングツリーもセットアップし、新たなカレントコミットに登録されているファイル群をワーキングツリーが保持する状態にする(それは自然なことだ、なぜなら、新たなカレントコミットに対して作業し始める際、私たちは普通、まず、新たなカレントコミットの真正のファイル群を手元に置きたいと思うだろう?)。...そう、基本的には、としか言えない。それはもっと複雑であり、それがここでの主題だ。

他方で、ファイル群チェックアウトは、指定されたコミット(カレントコミットでも別のコミットでもよい)に登録されたファイルのコンテンツを使用して、カレントコミットのワーキングツリーにファイルを作成またはワーキングツリーのファイルを置換する(カレントコミットは同じコミットであり続ける)。

ただし、注意すべき重要なことが1点あり、本体で議論する。


本体


1: 理解を超えたコミットチェックアウトの動作


Hypothesizer 7
'コミットチェックアウト'が基本的に何かは'オリエンテーション' が明確にしたはずだが、その動作は理解を超えている(その指針が理解不能(少なくとも私には)で、その合理性が疑わしい(少なくとも私には)という意味で)。しかし、あるがままに動作をリストすることはできる。

注意として、コミットチェックアウトがカレントコミットをあるコミットから別のコミットへと変更する時、前者および後者のコミットを、それぞれ、'前カレントコミット'、'新カレントコミット'と、私は呼ぶ。

第一に、前カレントコミットがクリーンな時(全ての変更がコミット済みという意味)は、特に言及することはない。

第二に、前カレントコミットにコミットされていない変更(ステージングされてなくてもされていても)があった時は、コミットチェックアウトは、複雑な動作をする。

実のところ、コミットチェックアウトは、ステージングエリアおよびワーキングツリーを、新カレントコミットに登録されたファイル群で満たそうと常にするわけではなく、場合により、前カレントコミットに対して行われていたコミットされていない変更を、新カレントコミットへとキャリーオーバーしようとする。

正直に言って、後者の試みの合理性が私にはよく分からない。そうした変更は前カレントコミットのためのものであったと考えるのが自然(そうした変更が新カレントコミットのためなのなら、一体、なぜ、私は、それらの変更を前カレントコミットにしようとするのであろうか?)であり(少なくとも私には)、私は、前カレントコミットのためのそれらの変更を新カレントコミットへキャリーオーバーしてほしくない。ほぼ確実に、それらの変更を前カレントコミットにコミットするのを私がただ忘れただけであって、忘れている旨をGitが知らせてくれればありがたい。

加えて、Gitがいつ後者を試みようとするかの基準が私には理解できない。

実は、Gitのコミットチェックアウト動作を理解するために私が行なったテストが以下に挙げるものだ。以下の記述を説明すると、例えば、テスト、'A-1'では、前カレントコミットには当該ファイルがコミットされておらず('none'は、そこにはファイルがないことを意味する)、当該ファイルは'ccc'という内容でステージングされており、当該ファイルは'ccc'という内容でワーキングツリーに存在する。新カレントコミットには当該ファイルがコミットされていない(新カレントコミットは、チェックアウト前にはレポジトリ内にのみ存在するので、その時点でのそのファイルに対するステージング状態やワーキングツリーに状態などは存在しない)。チェックアウトの結果メッセージは "A ccc.txt"で、チェックアウト後には、新カレントコミットには当該ファイルは'ccc'という内容でステージングされ、当該ファイルは'ccc'という内容でワーキングツリーに存在する。'メッセージ'の'none'は、全然何のメッセージもないことを意味しておらず、特に言及するべきメッセージがないという意味である。"A . . ."というメッセージは、追加されたファイルとしての変更が新カレントコミットへキャリーオーバーされたことを意味する。"M . . ."というメッセージは、変更されたファイルとしての変更が新カレントコミットへキャリーオーバーされたことを意味する。"D . . ."というメッセージは、削除されたファイルとしての変更が新カレントコミットへキャリーオーバーされたことを意味する。エラーは、Gitは、新カレントコミットとして登録された状態を復元しようと試みたが、その復元をすると、前カレントコミットへ行なわれた変更が失われてしまうことを発見したことを意味する。

# 追加された1ファイルに対するテスト群 開始

テスト番号: A-1
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> ccc
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: A ccc.txt
ステージングされた: ccc
ワーキング: ccc

テスト番号: A-2
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> cccc
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: A ccc.txt
ステージングされた: ccc
ワーキング: cccc

テスト番号: A-3
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> none
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: none
ステージングされた: ccc
ワーキング: none
※変更が新カレントコミットへキャリーオーバーされたのに、メッセージがない。

テスト番号: A-4
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> ccc
新カレントコミット: コミットされた -> ccc
--> チェックアウト -->
メッセージ: none
ステージングされた: ccc
ワーキング: ccc

テスト番号: A-5
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> cccc
新カレントコミット: コミットされた -> ccc
--> チェックアウト -->
メッセージ: M ccc.txt
ステージングされた: ccc
ワーキング: cccc

テスト番号: A-6
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> none
新カレントコミット: コミットされた -> ccc
--> チェックアウト -->
メッセージ: D ccc.txt
ステージングされた: ccc
ワーキング: none

テスト番号: A-7
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> ccc
新カレントコミット: コミットされた -> cccc
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: A-8
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> cccc
新カレントコミット: コミットされた -> cccc
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: A-9
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> none
新カレントコミット: コミットされた -> cccc
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: A-10
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> ccc
新カレントコミット: コミットされた -> ccccc
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: A-11
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> cccc
新カレントコミット: コミットされた -> ccccc
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: A-12
前カレントコミット: コミットされた -> none, ステージングされた -> ccc, ワーキング -> none
新カレントコミット: コミットされた -> ccccc
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

# 追加された1ファイルに対するテスト群 終了

# 変更された1ファイルに対するテスト群 開始

テスト番号: M-1
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ : error: Your local changes to the following files would be overwritten by checkout:
ステージングされた : n/a
ワーキング : n/a

テスト番号: M-2
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ : error: Your local changes to the following files would be overwritten by checkout:
ステージングされた : n/a
ワーキング : n/a

テスト番号: M-3
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaaa
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-4
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaa
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-5
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> none
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ : error: Your local changes to the following files would be overwritten by checkout:
ステージングされた : n/a
ワーキング : n/a

テスト番号: M-6
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: M aaa.txt
ステージングされた: aaa
ワーキング: aaaa

テスト番号: M-7
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: M aaa.txt
ステージングされた: aaaa
ワーキング: aaaa

テスト番号: M-8
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaaa
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: M aaa.txt
ステージングされた: aaaa
ワーキング: aaaaa

テスト番号: M-9
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaa
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: M aaa.txt
ステージングされた: aaaa
ワーキング: aaa

テスト番号: M-10
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> none
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: D aaa.txt
ステージングされた: aaaa
ワーキング: none
※ステージングされた変更が新カレントコミットへキャリーオーバーされたのに、メッセージが、ワーキングツリーでの削除についてのものである。

テスト番号: M-11
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-12
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: none
ステージングされた: aaaa
ワーキング: aaaa
※前カレントコミットへのステージングが通知無く失われた。

テスト番号: M-13
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaaa
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: M aaa.txt
ステージングされた: aaaa
ワーキング: aaaaa

テスト番号: M-14
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaa
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: M aaa.txt
ステージングされた: aaaa
ワーキング: aaa

テスト番号: M-15
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> none
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: D aaa.txt
ステージングされた: aaaa
ワーキング: none

テスト番号: M-16
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-17
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-18
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaaaa
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-19
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> aaa
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: M-20
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaaa, ワーキング -> none
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

# 変更された1ファイルに対するテスト群 終了

# 削除された1ファイルに対するテスト群 開始

テスト番号: R-1
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> none
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: none
ステージングされた: none
ワーキング: none

テスト番号: R-2
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaa
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: error: The following untracked working tree files would be removed by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-3
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> none
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: none
ステージングされた: none
ワーキング: none
※前カレントコミットへのステージングが通知無く失われた。

テスト番号: R-4
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaaa
新カレントコミット: コミットされた -> none
--> チェックアウト -->
メッセージ: error: The following untracked working tree files would be removed by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-5
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> none
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: D aaa.txt
ステージングされた: aaa
ワーキング: none

テスト番号: R-6
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaa
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: D aaa.txt
ステージングされた: none
ワーキング: aaa

テスト番号: R-7
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> none
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: D aaa.txt
ステージングされた: none
ワーキング: none

テスト番号: R-8
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaa
--> チェックアウト -->
メッセージ: D aaa.txt
ステージングされた: none
ワーキング: aaaa

テスト番号: R-9
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> none
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: none
ステージングされた: aaaa
ワーキング: aaaa

テスト番号: R-10
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaa
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-11
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> none
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-12
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-13
前カレントコミット: コミットされた -> aaa, ステージングされた -> aaa, ワーキング -> none
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: none
ステージングされた: aaaaa
ワーキング: aaaaa

テスト番号: R-14
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaa
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-15
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> none
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

テスト番号: R-16
前カレントコミット: コミットされた -> aaa, ステージングされた -> none, ワーキング -> aaaa
新カレントコミット: コミットされた -> aaaaa
--> チェックアウト -->
メッセージ: error: Your local changes to the following files would be overwritten by checkout:
ステージングされた: n/a
ワーキング: n/a

# 削除された1ファイルに対するテスト群 終了

ふーむ...、一部に妙な結果があると言わせてもらおう。

第一に、Gitは、ある時には、変更をキャリーオーバーしようとし、またある時には、新カレントコミットに登録された状態を単に復元しようとするが、それはどういう基準に基づいているのだろうか。

まあ、仮説として、新カレントコミット内のファイルコンテンツが前カレントコミットレポジトリ内のファイルコンテンツに一致する('none'は'none'に一致するとして)時のみ、Gitは変更をキャリーオーバーしようとすると考えてみたが、結果の一部が仮説に反する('A-5'、'A-6'、'M-13'、'M-14'、'M-15')。別の仮説として、新カレントコミット内のファイルコンテンツが前カレントコミットレポジトリ内のファイルコンテンツまたは前カレントコミットステージングエリア内のファイルコンテンツに一致する('none'は'none'に一致するとして)時のみ、Gitは変更をキャリーオーバーしようとすると考えてみたが、結果の一部が仮説に反する('R2'、'R4')。...単に、削除には別の基準があるということだろうか?...ふーむ、どちらにしろ、基準が何であれ、その動作は私には意味をなさない。

それに、一部のメッセージやメッセージがないことが私には不合理に思える。実際、'A-3'はなぜ、ステージングされた変更がキャリーオーバーされた旨のメッセージを出さないのか?ユーザがその事実に気付かなければ、ファイルは新カレントコミットへ意図に反してコミットされてしまいかねない...。また、'M-12'と'R-3'では、前カレントコミットへのステージングが、何の通知もなくただ消滅してしまった(確かに、コンテンツ自体は、新カレントコミットのステージングエリアに残っているが、それが前カレントコミットへステージングされたという事実は、ただ消滅してしまった)。また、'M-10'では、"D"メッセージは混乱を招く。なぜなら、削除ではなく、変更が新カレントコミットにステージングされているのだから(新カレントコミットへの次のコミットは、ファイルを削除するのではなく、変更するだろう)。

正直なところ、Gitがなぜそのように複雑な動作をしなければならないのか、私にはよく分からない。前カレントコミットにコミットされていない変更があるときは、単にワーニングを与えてチェックアウトをブロックすればよい('-f'フラグが指定されていなければ)ように、私は思う。

常に'-f'フラグを使えばよいと?...あの、ワーニングは必要なんです。'-f' フラグを使うとそれが与えられない。


2: 注意すべきファイル群チェックアウト動作


Hypothesizer 7
ファイル群チェックアウトは、単にファイル群をワーキングツリーに置くのではなく、それらのファイル群を自動的にステージングする。

ふーむ、これも、私が好まない動作だ。...ほとんど常に私は、ファイルを、必ずコミットするという確信を持ってチェックアウトしない。私は、まずファイルを検査し(典型的には、プロジェクトを再ビルドし、テストを若干行なって)、その後に、それをコミットすると決定する。ファイルが自動的にステージングされるのは困ったことであり、そうしたファイルを意図せずコミットしてしまわないように、気を付けなければならない。

'show'を使うべきだと?表示しただけではテストができません。表示された結果をファイルにリダイレクトすればよいと?...まあ、そうしなければならないのであればそうしますが、それは特に望ましくはありません(リダイレクトのファイルパスを指定しなければならないし、複数ファイルを一度にチェックアウトすることもできない)...


3: 結びとその先


Hypothesizer 7
これで、Gitのチェックアウトがどのように動作するかを私は理解したよう(その指針は理解していないが)だ。

簡潔に言うと、コミットチェックアウトにおいては、Gitは、場合によって、親切に(本当に?)、前カレントコミットのためのコミットされていない変更を新カレントコミットへキャリーオーバーし、ファイル群チェックアウトにおいては、Gitは、親切に(本当に?)、ユーザがファイル群を検査するのを待たずにファイル群をステージングする。

その動作を私が調査したのは、それが、ファイル変更日時を格納・復元するという私の計画に影響する(有害に)からだ。例えば、ワーキングツリー内の変更が前カレントコミットから新カレントコミットへとキャリーオーバーされるから、新カレントコミットに登録されているファイル変更日時を単純に復元するという訳にはいかない。

ふーむ、正直なところ、Gitを学べば学ぶほど、Gitが嫌いになってくる。...誓って、Gitに高い期待を抱いたからこそ私はGitを学び始めたのだが、Gitの考え方は私にはよく合わない...


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>