Gitなにもわからない

## はじめに

Gitがわからない。
なぜわからないのか？

仮説：動作が見えないからではないか
↓
解決案：.git/内を参照すると、ファイルの変更として見ることができるので、どういう動きをしているのかを確認することで仕組みを理解できないか


### やってみてわかったことまとめ
- branchというのは、言葉から想像していた線形のものではなく、特定のcommitを指す点だった。
- ファイルをステージングした時点で.git/object/内にバックアップが取られていた。(なのでこまめにaddするとリポジトリの容量が増えてしまう。)
- commitは差分ではなくスナップショットと言われている意味がわかった
- 至るところで現れるハッシュの使い道がわかった。
- stashはcommitだった
- `commit -ammed`は上書きじゃなかった
- resetは言葉から想像するいわゆるリセットではなく特定コミットへの切り替えがメインで、インデックスとワーキングツリーをリセットするのはおまけ
- なぜ空のディレクトリを記録できないのかわかった(ディレクトリはgitオブジェクトではなく、indexでのファイルのパスでしかないため)

など

### この記事で扱う範囲
GitHub前提で書いています。
とりあえず使えるようになるのが目的なので、`git gc`の動作については扱いません。
pullはfetchとmerge、checkoutはswitchとrestoreを使うので扱いません。

## 1. .git/とは

GitHubなどのリモートリポジトリを`git clone`してきたり、ローカルで`git init`でローカルリポジトリを作成すると現れます。

### 1-1. .git/の中身

全体像を示すのでまだ説明していない言葉も出てきますが、後から登場します。

- 一部のファイルはバイナリファイルなので、エディタでそのまま開いても中身が見られません
- リポジトリの状態によって、ファイルやディレクトリはあったりなかったりします

#### ファイル構成

```:directory
 .git/
  ├ hooks/　
  │  ├ applypatch-msg.sample  
  │  ├ commit-msg.sample
  │  ├ fsmonitor-watchman.sample
  │  ├ post-update.sample
  │  ├ pre-applypatch.sample
  │  ├ pre-commit.sample
  │  ├ pre-merge-commit.sample
  │  ├ prepare-commit-msg.sample
  │  ├ pre-push.sample
  │  ├ pre-rebase.sample
  │  ├ pre-receive.sample
  │  ├ push-to-checkout.sample
  │  └ update.sample
  ├ info/
  │  ├ exclude　
  │  └ attributes  
  ├ logs/
  │　├ refs/
  │　│　├ heads/
  │　│　│  └ ブランチ名
  │　│　├ remotes/
  │　│　│  └ リモート名/
  │　│　│     └ HEAD
  │　│　├ stash
  │　│　└ tags/
  │　│     └ タグ名
  │　└ HEAD
  ├  lost-found/
  │   ├ commit/
  │   └ other/
  ├ objects/
  │  ├ info/
  │  │  ├ commit-graph
  │  │  └ packs
  │  └ pack/ 
  │     ├ pack-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.idx
  │     └ pack-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.pack
  ├ refs/
  │  ├ heads/
  │  │  └ ブランチ名
  │  ├ remotes/
  │  │  └ リモート名/
  │  │      └ HEAD
  │  ├ tags/
  │  └ stash
  ├ AUTO_MERGE
  ├ CHERRY_PICK_HEAD
  ├ COMMIT_EDITMSG
  ├ config
  ├ description
  ├ FETCH_HEAD
  ├ HEAD
  ├ index
  ├ MERGE_HEAD
  ├ MERGE_MODE
  ├ MERGE_MSG
  ├ ORIG_HEAD
  ├ packed-refs
  └ REBASE_HEAD
```  


##### .git/hooks/
hooksのサンプルファイルが13個格納されていますが、この記事には関係ないので触れません。
hooksのサンプルファイルは、末尾の.sampleを外せばフックスクリプトとして動作します。

##### .git/info/exclude
自分専用の.gitignoreのようなものです。
.gitignore と同様にGit管理から除外されます。
他のリポジトリ(=リモートや他人のローカル)には影響しません。

```.git/info/exclude
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
``` 


##### .git/info/attributes


##### .git/logs/refs/heads/ブランチ名
commitの履歴が下に追加されていきます。

```.git/logs/refs/heads/ブランチ名
0000000000000000000000000000000000000000 commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	branch: Created from HEAD
``` 




##### .git/logs/refs/remotes/リモート名/HEAD
```.git/logs/refs/remotes/リモート名/HEAD
ref: refs/remotes/origin/ブランチ名
```

##### .git/logs/refs/stash

##### .git/logs/refs/tags/タグ名


##### .git/logs/HEAD
ローカルでのHEADの移動履歴が追記されていきます。
```.git/logs/HEAD
commitのハッシュ1(40桁) commitのハッシュ2(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	clone: from github.com:ユーザー名/リポジトリ名.git
``` 

##### .git/lost-found/commit/
##### .git/lost-found/other/
##### .git/objects/info/commit-graph


##### .git/objects/info/packs
```.git/objects/info/packs
.git/objects/pack/pack-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.packへの参照
```


##### .git/objects/
2文字のディレクトリの中にファイル名が38桁のファイルが入ります。
2+38の40文字がgitオブジェクトのハッシュになります。

##### .git/objects/pack/
パックしたファイルが格納されます。(idxとpack)
git gcするたびにファイル名は変わります。


##### .git/refs/heads/ブランチ名
ローカルにある各ブランチが指すcommitへの参照です。
```.git/refs/heads/ブランチ名
ブランチのHEADが指すコミットのハッシュ(40桁)
```

##### .git/refs/remotes/リモート名/HEAD
リモートブランチのHEADのブランチへの参照です。
```.git/refs/remotes/リモート名/HEAD
ref: refs/remotes/origin/ブランチ名
```


##### .git/refs/remotes/リモート名/ブランチ名
最後に取得された時点のリモートリポジトリのブランチのcommitの情報が入っています。
```.git/refs/remotes/リモート名/ブランチ名
リモートブランチのHEADが指すcommitのハッシュ(40桁)
```



##### .git/refs/tags/タグ名
軽量タグ、アノテーションタグを使用するとタグ名のファイルが作成されます。


|tagの種類|参照|
|---|---|
|軽量タグ|commitオブジェクト|
|アノテーションタグ|tagオブジェクト|

```.git/refs/tags/タグ名
tagオブジェクトのハッシュ
```

##### .git/refs/stash
```.git/refs/stash
最後に行ったstashのcommitのハッシュ
```


##### .git/AUTO_MERGE
```.git/AUTO_MERGE
コンフリクトしている状態のファイルを含むcommitのハッシュ
```

##### .git/CHERRY_PICK_HEAD
チェリーピックでブランチに統合されたcommitを記録しています。
```.git/CHERRY_PICK_HEAD
cherry-pickしているcommitのハッシュ
```


##### .git/COMMIT_EDITMSG
```.git/COMMIT_EDITMSG
最後に実行したcommitのコミットメッセージ
```

##### .git/config
上流ブランチの設定はここに記載されます。
```.git/config
[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	symlinks = false
	ignorecase = true
[remote "リモート名"]
	url = git@github.com:ユーザー名/リモートリポジトリ名.git
	fetch = +refs/heads/*:refs/remotes/リモート名/*
[branch "ローカルブランチ名"]
	remote = リモート名
    merge = refs/heads/上流ブランチ名
```


##### .git/description
```.git/description
 //翻訳：無名のリポジトリ。このファイル'description'を編集してリポジトリに名前をつけてください。
 Unnamed repository; edit this file 'description' to name the repository.
```

###### 用途
- gitweb
- リポジトリ名を動的に読み取るカスタムフックの開発用



##### .git/FETCH_HEAD
最後にgit clone、git fetchなどをしたときのリモートリポジトリの状態が記録されています。
- git cloneやgit fetch(ブランチ指定なし)をした場合は複数が記録されている
- git fetch リモート名 ブランチ名などで1つのブランチだけfetchした場合は1つだけ記録されている

```console:terminal
そのブランチが指すコミットのハッシュ(40桁) (not-for-merge)	branch 'ブランチ名' of github.com:ユーザー名/リモートリポジトリ名
``` 
「not-for-merge」はマージする対象でないと判定されたブランチに表示されます。





##### .git/HEAD
現在のブランチが記録されています。
refは通常は、ブランチを指します。
HEADが直接コミットを指す場合、「detached HEAD」(ブランチから切り離されたHEAD)と呼ばれます。
.git/HEADで示しているブランチのHEADのハッシュは.git/refs/heads/ブランチ名で確認できます。

```.git/HEAD
ref: refs/heads/ブランチ名
```


##### .git/index
バイナリファイルなのでそのままエディタで開いても中身は見られません。
追跡中のファイルが記録されています。

##### .git/MERGE_HEAD
`git merge`を実行するときにブランチにマージするコミットを記録しています。
```.git/MERGE_HEAD
マージされるブランチ名のHEADが指すcommitのハッシュ
```

##### .git/MERGE_MODE
マージが進行中であることを知らせるためだけに作成されます。
```.git/MERGE_MODE
(空)
```




##### .git/MERGE_MSG
```.git/MERGE_MSG　
コンフリクト解消後のgit commitのデフォルトのメッセージ
```




##### .git/ORIG_HEAD
HEADを移動するコマンドを実行したときに、コマンド実行時のHEADだったコミットのハッシュが記録されています。
```.git/ORIG_HEAD
commitのハッシュ(40桁)
```




##### .git/FETCH_HEAD
```.git/FETCH_HEAD
 commitのハッシュ(40桁)		branch 'ブランチ名' of github.com:ユーザー名/リポジトリ名
```

##### .git/packed-refs

git gcを実行すると、不要なコミットなどが削除され、refがpacked-refs という一つのファイルにまとめられます。(git gcは明示的に叩かなくても、内部で適宜実行されます。)



##### .git/REBASE_HEAD
rebase実行前のブランチのHEADのcommitを記録します。
```REBASE_HEAD
commitのハッシュ(40桁)
```





### 1-2. gitオブジェクト

#### Gitオブジェクト(4種類)
- commit
- blob
- tree 
- tag

![1-1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/a14b00cd-2912-6bf7-873b-1f3148c6c80d.png)
![1-1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/2bf59b09-2473-394e-9fc9-19f14f421ab3.png)
![1-1-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/2849eefa-1afc-aade-8362-9f44b86fb47e.png)




##### gitオブジェクトのタイプ(ハッシュから)
```console:terminal
$ git cat-file -t gitオブジェクトのハッシュ
```


##### gitオブジェクトの確認
ワーキングツリーのファイルパスからは確認できません。
(同じファイルパスのものがたくさんblobオブジェクトとして保存されるので、ファイルパスからは一意に特定できない)

```console:terminal
//gitオブジェクトのハッシュから確認
$ git cat-file -p gitオブジェクトのハッシュ
 
//ブランチ名から確認(ブランチ名の場合はcommitオブジェクト)
$ git cat-file -p ブランチ名
```


##### commitオブジェクトをgit cat-fileすると確認できるもの
- トップレベルのtreeオブジェクトへの参照
- コミットしたユーザーの情報
- タイムスタンプ
- コミットメッセージ
- 親コミットへの参照(1つ前のコミット) 
  
|親コミットの数|例|
|---|---|
|0|initial commit|
|1|通常のコミット|
|2|merge commit、stash|
|3以上|octopusマージ|


##### blobオブジェクトをgit cat-fileすると確認できるもの
- blobオブジェクトの中身
※ファイル名やパスはblobオブジェクトではなくtreeオブジェクトに記録されているので、確認できません。



##### treeオブジェクトをgit cat-fileすると確認できるもの
そのtreeオブジェクトが参照している各Gitオブジェクトの
- タイプ(blob or tree)
- ハッシュ
- ファイル名(ディレクトリ名)

|数字|種類|
|---|---|
|100644|blob(通常のファイル)|
|120000|シンボリックリンク|
|040000|tree(ディレクトリ)|
|100755|実行可能ファイル|




##### tagオブジェクトをgit cat-fileすると確認できるもの

|tagの種類|参照など|
|---|---|
|軽量タグ|commitオブジェクト|
|アノテーションタグ|tagオブジェクトへの参照、メッセージ|


```console:terminal
//現在のHEADが指すcommitオブジェクトに軽量タグをつける
$ git tag タグ名

//commitを指定して軽量タグをつける
$ git tag タグ名 コミットのハッシュ

//現在のHEADが指すcommitオブジェクトにアノテーションタグをつける
$ git tag -a タグ名 -m "メッセージ"

//commitを指定してアノテーションタグをつける
$ git tag -a タグ名 -m "メッセージ" commitのハッシュ
``` 



##### タグが追加されているのを確認
```console::terminal
$ git log
```

##### .git/refs/tags/タグ名が作成される
```.git/refs/tags/タグ名
 参照するcommitオブジェクトのハッシュ(軽量タグの場合)
 参照するtagオブジェクトのハッシュ(アノテーションタグの場合)
```

##### アノテーションタグ(tagオブジェクト)の確認
```console::terminal
$ git cat-file -p ハッシュ

object 参照するcommitオブジェクトのハッシュ(40桁)
type commit
tag アノテーションタグ名
tagger ユーザー名 <メールアドレス> UNIX時間 +0900

メッセージ
```
 

##### その他
```console::terminal
//タグの確認(ローカル)
$ git tag	

//タグの確認(リモート)
$ git ls-remote --tags

//タグの中身の確認
$ git show タグ名	
　
//特定のタグを削除(ローカル)
$ git tag -d タグ名

//特定のタグを削除(リモート)
$ git push リモート名 --delete タグ名

//タグをリモートリポジトリにプッシュ(すべてのタグ)
$ git push --tag
 
//タグをリモートリポジトリにプッシュ(特定のタグ)
$ git push リモート名 タグ名
```
































### 1-3. ref(リファレンス)


#### branchとは
commitにつけるポインタです。commitが進むと、自動的に移動します。ブランチ名でcommitを指定できます。


#### branchの確認

##### Git操作
```console:terminal
//branchの確認(ローカル)
$ git branch	

//branchの確認(ローカルとリモート追跡)
$ git branch -a
$ git branch --all

//branchの確認(リモート追跡)
$ git branch -r
$ git branch --remotes
``` 




#### 上流ブランチの設定

##### Git操作 
```console:terminal
//branchの確認(ローカルと上流の対応)
$ git branch -vv

//上流ブランチの設定(ローカルブランチとリモートブランチの紐付け)
$ git branch -u リモート名/リモート追跡ブランチ名 ローカルブランチ名	

//上流ブランチの設定(ローカルブランチとリモートブランチの紐付け)の解除
$ git branch --unset-upstream ローカルブランチ名
```


##### .git内で起こること
設定すると下記が追記され、削除すると消えます。
```.git/config
[branch "ブランチ名"]
    remote = リモート名
    merge = refs/heads/上流ブランチ名
```



#### リモート追跡ブランチの削除

##### Git操作
```console:terminal
//特定のリモート追跡ブランチの削除
$ git branch -d -r リモート名/リモート追跡ブランチ名	
　
//リモート追跡ブランチの削除(リモートで削除されたもの)
$ git fetch --prune
$ git fetch -p
$ git remote prune リモート名
```


##### .git内で起こること

.git/refs/remotes/リモート名/ブランチ名が削除されます。


#### ブランチの作成


![2-1-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/99b68f42-2bf9-aa4a-1194-c88b44dbf9fa.png)






##### Git操作

```console:terminal
//現在のcommitからbranchを作成
$ git branch 作成するブランチ名

//特定のcommitからbranchを作成
$ git branch 作成するブランチ名 commitのハッシュ

//リモートブランチからローカルブランチの作成
$ git branch 作成するブランチ名 リモートブランチ名
``` 


##### .git内で起こること

- .git/refs/heads/ブランチ名が作成される

```.git/refs/heads/ブランチ名
branchが指すcommitオブジェクトのハッシュ
```

- .git/logs/refs/heads/ブランチ名が作成される
ログが下に追記されていきます。
```.git/logs/refs/heads/ブランチ名
親commitオブジェクトのハッシュ commitオブジェクトのハッシュ ユーザー名 <メールアドレス> Linux時間 +0900	branch: Created from 作成元のブランチ名
```
最初の行の「親commitオブジェクトのハッシュ」は「0000000000000000000000000000000000000000」になります。
最後の行のcommitオブジェクトのハッシュは、.git/refs/heads/ブランチ名に記載されている値と一致します。


#### .git/refs/heads/ブランチ名に記載されるものがcommitオブジェクトであることを確認する
.git/refs/heads/ブランチ名のファイルに書き込まれているハッシュを`git cat-file`します。
```console:terminal
$ git cat-file -p .git/refs/heads/ブランチ名のハッシュ
 
tree treeオブジェクトのハッシュ
parent 親commitオブジェクトのハッシュ
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900

 
$ git cat-file -t commitオブジェクトのハッシュ
 
commit
```
commit オブジェクトと確認できました。




#### branchの切り替え



![2-1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/79af718a-02a5-5b25-3bd8-79f811015ee3.png)



##### Git操作
ワーキングツリー上での変更後や、ステージング後にcommitせずにブランチ移動したら？
変更がぶつかる場合はブランチ移動できない(作業を失わないようにgitが止めてくれる)
変更がぶつからない場合(編集箇所がコンフリクトしない場合や、新規ファイルなど)はもったまま移動する

```console:terminal
//branchを指定して切り替え
$ git switch ブランチ名
 
//branchを作成して切り替え
$ git switch -c ブランチ名
 
//指定したコミットからbranchを作成して切り替え
$ git switch -d ブランチ名 branchを作成したいcommitのハッシュ	
```


##### .git内で起こること

- .git/HEADが更新される
```.git/HEAD
ref: refs/heads/切り替え後のブランチ名
```
- indexが切り替え後のbranchの追跡済みファイルの情報に切り替わる
- FETCH_HEADはタイムスタンプが更新される
- .git/logs/HEADの下にログが追記される





#### detached HEAD
branchを介さず、直接commitオブジェクトを参照している場合はdetached HEAD(ブランチがない)という状態になります。

用途
- 過去のcommitの状態の再現
- 実験的に変更を加えてみる


![2-1-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/69c3f460-7a0b-0193-e651-77bc0a22eccd.png)



##### detached HEAD状態で特定のcommitに移動する
```console:terminal
//ブランチ名を介さずコミットを直接指定して切り替える
$ git switch -d コミットのハッシュ
$ git switch --detach コミットのハッシュ
```
detached HEADの状態でもcommitはできるが、他のbranchに切り替えると参照できなくなります。
(commitのハッシュを記録しておけば戻ることは可能です。)



##### detached HEADであることの確認
```console:terminal
//確認
$ git status
 
HEAD detached at xxxxxx

  
//確認
$ git branch
  
* (HEAD detached at xxxxxxx)
```

.git/HEADでも確認可能
```.git/HEAD
branchのパスではなく、直接commitのハッシュが記載されている
```

##### detached HEAD状態から抜け出す
```terminal
//試したものを破棄したい場合は他の既存のブランチに切り替える
$ git switch ブランチ名

//試したものを使いたい場合は、新規にbranchを作る
$ git branch ブランチ名
```




#### ブランチ名変更
##### Git操作
```console:terminal
//ブランチ名の変更(変更後の名前と同名ブランチがあると失敗)
$ git branch -m 変更前のブランチ名 変更後のブランチ名	
 
//現在のブランチ名を変更する(強制上書き)
$ git branch -M 変更後のブランチ名	
```

##### .git内で起こること

- .git/refs/heads/旧ブランチ名→新ブランチ名
- .git/logs/refs/heads/旧ブランチ名→新ブランチ名
- 現在のブランチの名前を変更すると.git/HEADも書き換わる


#### ブランチ削除

##### Git操作

```console:terminal
//branchを削除(他のbranchにmerge済のbranchのみ)
$ git branch -d 削除したいブランチ名

//branchを削除(他のbranchにmergeしていないbranchも削除可能)
$ git branch -D 削除したいブランチ名 
```

##### .git内で起こること

- .git/configが変更される
- リモートにpush済のbranchを削除すると、下記が削除される
```.git/congig
[branch "削除したブランチ名"]
    remote = origin
	merge = refs/heads/削除した上流ブランチ名
```
- .git/refs/heads/ブランチ名　が削除される
- .git/logs/refs/heads/ブランチ名　が削除される




#### リモートのbranchの削除

```console:terminal
//リモートブランチの削除
$ git push -dリモート名 ブランチ名
```




#### 削除したローカルブランチを復活させる

##### Git操作
どのコミットを復活させたいのか確認のためHEADの履歴を表示をします。
```console:terminal
$ git reflog
```

実際に復活させてみます。
```console:terminal
$ git branch 復活後のブランチ名 HEAD@{n}
```

##### .git内で起こること

- .git/refs/heads/復活後のブランチ名 が作成される
```.git/refs/heads/復活後のブランチ名
branchが指すcommitオブジェクトのハッシュ
```

- .git/logs/refs/heads/復活後のブランチ名 が作成される
```.git/logs/refs/heads/
0000000000000000000000000000000000000000 commitオブジェクトのハッシュ ユーザー名 <メールアドレス> Linux時間 +0900
branch: Created from HEAD@{n}
```












#### commitせずにbranchを切り替えした場合(stashを使わない場合)
ワーキングツリーのファイルやステージングされたファイルがcommitオブジェクトに紐づけされていないので、切り替え後のbranchと混じってしまいます。(もしくは衝突する場合は切り替え自体できません。)

```console:terminal
//ステージングがぶつかる場合
error: Your local changes to the following files would be overwritten by checkout:
Please commit your changes or stash them before you switch branches.

//ワーキングツリーがぶつかる場合(untracked fileがぶつかる)
error: The following untracked working tree files would be overwritten by checkout:
Please move or remove them before you switch branches.
```

#### commitせずにbranchを切り替えした場合(stashを使う場合)
stashしてからブランチを切り替えしてgit stash popすると、auto-mergeされます。
コンフリクトしなければ、その時点ではステージングはもともと存在した方の状態のままです。
コンフリクトしたらboth addedになります。
その時点でステージングを確認すると、同じファイルが2つになります。(ハッシュは違います。)
コンフリクトを解消する場合に片方のみを採用すると、採用した方のハッシュのまま残ります。


#### stash
見た目上、まだローカルコミットしていない内容(ステージングしているかどうかは関係なし)を一時的に避難します。(実際にはcommitされます。)
stashの対象はGit管理下のファイルです。(untracked fileは退避されません。)



![2-1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/6d664e5e-71ef-2182-8189-1fbc4a37060b.png)

##### ユースケース
- いますぐ別のbranchで作業したいことができたけど現在の作業内容をcommitしたくないとき
- 作業ブランチを間違えたときに今までの作業（commitしていないものに限る）を正しいbranchに適用したいとき

##### Git操作

```console:terminal
//変更差分を退避させる(untracked fileは退避されない)
$ git stash
 
//変更差分を退避させる(名前付き、untracked fileは退避されない)
//git stash listで退避のリストを見るときに便利
$ git stash save スタッシュ名

  
//変更差分を退避させる(untracked fileも変更も退避される)
$ git stash -u
 
//変更を退避させる(ステージングしたファイルは除外)
$ git stash -k	
$ git stash --keep-index
``` 



##### .git内で起こること

stashした時点で
- ORIG_HEADはかわらない
- HEADはかわらない(ハッシュは更新されないのでブランチ自体には影響を与えない)
- ワーキングツリーとステージングはHEADと同じ状態になる
- reflogは増える
- 「.git/refs/stash」と「.git/logs/refs/stash」が作成(すでにある場合は変更)される
- 「.git/refs/stash」は最新のstashのcommitのハッシュ
- 「.git/logs/refs/stash」は現在stashのcommitすべてのハッシュ
- stashはコミット(stashするものも、しないものも含むコミット)　親2つ　元々のコミットと、編集したファイル自体はblobオブジェクトとして.git/objects以下に保存されている
- .git/refs/stashが作成される
```.git/refs/stash
参照するcommitオブジェクトのハッシュ(最新のstash)
```

###### .git/refs/stashに書かれているハッシュが何か確認する
```console:terminal
$ git cat-file -p .git/refs/stashで参照されているハッシュ(40桁)

tree treeオブジェクトのハッシュ(40桁)
parent 親commitオブジェクト1のハッシュ(40桁)
parent 親commitオブジェクト2のハッシュ(40桁)
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900

WIP on ブランチ名: ハッシュ7桁 コミットメッセージ
```
最新commitと、もう一つのcommitの2つを親に持つcommitオブジェクトだとわかる

###### もう一つの親を確認
```console:terminal
$ git cat-file -p commitオブジェクト1のハッシュ(40桁 or 7桁) 

tree treeオブジェクトのハッシュ(40桁)
parent 親commitオブジェクト1のハッシュ(40桁)
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900
 
index on ブランチ名: ハッシュ7桁 コミットメッセージ
```

このcommitオブジェクトも最新commitオブジェクトを親に持つことがわかる


|commit|含む内容|
|---|---|
|親が1つのcommit|ステージング時点のファイルを含む|
|親が2つのcommit|ステージング時点のファイル、、ワーキングツリーの変更両方を含む|




###### stashが複数ある場合
- .git/refs/stashには最新のstashのハッシュ値しか保存されない
- stashの一覧は.git/logs/refs/stashに保存されている
- .git/logs/refs/stashにすべてのstashの情報が書き足されていく
```.git/logs/refs/stash
0000000000000000000000000000000000000000 commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	WIP on ブランチ名: stashした時のHEADのcommitオブジェクトのハッシュ(7桁)  stashした時のHEADのcommitのコミットメッセージ
commitのハッシュ(40桁) commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	WIP on ブランチ名: stashした時のHEADのcommitオブジェクトのハッシュ(7桁)  stashした時のHEADのcommitのコミットメッセージ
``` 







```:ORIG_HEAD
commitのハッシュ(40桁)
```

```console:terminal
//reflogの最新に履歴が追加される
$ git reflog 

xxxxxxx (HEAD -> ブランチ名) HEAD@{n}: reset: moving to HEAD
```






#### 退避したものを確認
```console:terminal
//避難した作業の一覧を表示
$ git stash list 
 
//git stash listとgit diffの合わせ技
$ git stash list -p
 
//stash履歴詳細
$ git stash show

//N番目にスタッシュしたファイルの一覧を表示
$ git stash show stash@{n}
　
//N番目にスタッシュしたファイルの変更差分を表示
$ git stash show -p stash@{n}
``` 



|stash|消す|消さない|
|---|---|---|
|復元する|pop|apply|
|復元しない|drop|	-


#### 退避したものを復元

stashしていたファイルの復元します。
- ステージングもワーキングツリーも戻る
- 別ブランチで復元するとマージされるが、コンフリクトが起こる場合もある

```console:terminal
//最新の作業を復元
$ git stash apply 
$ git stash apply --index
 
//特定の作業を復元する
$ git stash apply スタッシュ名 
$ git stash apply stash@{n} 
 
//最新からn番目の作業を戻す
$ git stash apply n
 
//退避した中の最新の作業を復元する(戻した内容は退避リストから削除)
$ git stash pop
```  




#### 退避したものを削除
```console:terminal
//避難した中の最新の作業を削除する
$ git stash drop 
 
//特定の避難した作業を削除
$ git stash drop スタッシュ名 
$ git stash drop stash@{n}
 
//最新から2番目のものを削除する
$ git stash drop 2

//避難した作業すべてを削除
$ git stash clear 
```

##### .git内で起こること

- 最新のstashを削除すると、.git/refs/stashのハッシュが、その次に新しいものに書き換わる
- .git/logs/refs/stashから、削除したstashが消える
- stashをすべて消すと.git/refs/stashと.git/logs/refs/stashが削除される
- 削除したstashのcommitオブジェクトはそのまま残る





#### branchを切る前にファイルを編集してしまった場合の対応
git stashで変更を退避、ブランチを作成して切り替えて、git stash popで退避させておいた変更を復旧します。

```console:terminal
//前回のコミット以降の操作を退避
$ git stash

//ブランチを作成して切り替え
$ git switch -c 新規作成するブランチ名

//退避した操作を再反映する
$ git stash pop
``` 
## 2. Git操作と.git/内の変化

![2-0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/d1c1e629-0564-904b-58cf-52216c09ffe6.png)


|名前|特徴|対象|
|---|---|---|
|ワーキングツリー|実際にファイルの変更作業をする場所|.gitの外の実ファイル(.gitignoreなどの設定ファイルは除く)|
|ステージングエリア|.gitが追跡中のファイルが登録される場所(ファイルは最後にステージングされた時点の状態)|.git/index(addされたファイルはblobファイルとして.git/objects/内)|
|ローカルリポジトリ|変更の履歴を記録している場所|.git/内|



|用語|Gitコマンド|意味|内部的な動作|
|---|---|---|---|
|ステージング|git add|ワーキングツリーでのファイルの変更情報をステージングエリアに送ること|blobオブジェクトの生成、.git/indexの更新など|
|コミット|git commit|ステージングエリアに集められたファイルの情報をローカルリポジトリに記録すること|commitオブジェクト、treeオブジェクトの生成、HEADの移動など|


### 2-1. ステージング

#### ファイルの作成
##### Git操作
```console:terminal
//ワーキングツリーにファイルを作成
$ touch test.txt
```

##### .git内で起こること
この時点では.git/内になにも動きなし


#### ファイルの編集
##### Git操作
この時点では.git/内になにも動きなし



#### ステージング
##### Git操作
ステージングする前に現在インデックスされているファイルを確認します。
```console:terminal
//現在インデックスされているファイル
$ git ls-files --stage

//.git/indexが持っている情報全部を表示
$ git ls-files --stage --debug	
```

先程作成したtext.txtをステージングします。
```console:terminal
$ git add test.txt
```




##### .git内で起こること

blobオブジェクトのファイルが作成されます。
(バイナリファイルなのでエディタでそのまま開くと文字化けしています。)
```.git/objects/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xK��OR0`
```

cat-fileコマンドで中身を確認します。

```console:terminal
 $ git cat-file -p ハッシュ
 
 (ファイルの中身)
 ※ファイルの中身が空だと何も表示されない
```

indexファイルの中身が更新されます。
```console:terminal
$ git ls-files --stage
```


#### その他のステージング
```console:teminal
//部分ごとにステージング
$ git add -p ファイル名

//変更があったすべてのワーキングツリーの変更をステージングに加える
$ git add .	

//変更があった追跡中のファイルのワーキングツリーの変更をステージングに加える
$ git add -u	
```





#### インデックスされているファイルの確認
```console:terminal
$ git ls-files --stage

//.git/indexが持っている情報全部を表示
$ git ls-files --stage --debug
```




#### ファイルのステージングを取り消す
ワーキングツリーには影響しません。

##### Git操作
```console:terminal
//add前に戻す
$ git restore --staged ファイル名
$ git restore -S ファイル名
```

##### .git内で起こること

- .git/indexが更新される


#### 追跡を外す
ワーキングツリーのファイルはそのままです。

##### Git操作
```console:terminal
//インデックスから外す
$ git rm --cached ファイル名

//コミットしてローカルリポジトリから削除
$ git commit -m "コミットメッセージ"
```

##### .git内で起こること
- .git/indexが更新される


```console:terminal
 $ git ls-files --stage
 $ git ls-files -s
 
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名　
```










### 2-2. commit

#### commit



![2-2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/54ed12c1-ac5b-2ed4-de69-fec5a5e8360e.png)



##### Git操作

```console:terminal
$ git commit -m "コミットメッセージ"
```

##### .git内で起こること

- commitオブジェクトが作成される
- treeオブジェクトが作成される
- ブランチ名の指すcommitが変わるので.git/refs/heads/ブランチ名のハッシュが更新される
- .git/logs/HEADにログが追加される
- .git/COMMIT_EDITMSGに先程のコミット時のコミットメッセージが書き込まれる(最後のもののみ)
```.git/COMMIT_EDITMSG
コミットメッセージ
```
- branchが新しく作成されたcommitオブジェクトを参照するようになる
```.git/refs/heads/ブランチ名
新しく作成されたcommitオブジェクトのハッシュ
```
- .git/logs/HEADに履歴が追記される
```.git/logs/HEAD
　commitのハッシュ(40桁) commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit (initial): コミットメッセージ

```

- .git/logs/refs/heads/ブランチ名に履歴が追記される
```.git/logs/refs/heads/ブランチ名
commitのハッシュ(40桁) commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit (initial): コミットメッセージ
```


##### この時点でのgit logを確認する
```console:terminal
$ git log
commit ハッシュ(40桁) (HEAD -> ブランチ名)
Author: ユーザー名 <メールアドレス>
Date:   曜日 月 日 時:分:秒 年 +0900

    コミットメッセージ
```

##### この時点でのステージングを確認する
```console:terminal
$ git ls-files --stage
100644 blobオブジェクトのハッシュ(40桁) 0       test.txt
```


##### オブジェクトのタイプ確認
```console:terminal
$ git cat-file -t

blobオブジェクトのハッシュ(40桁)
```
tree、blobともに、別のcommitでも、変更が無いファイルについては、同じハッシュ値なのでオブジェクトのファイルは増えません。逆にいえば、変更をステージングするとどんどん増えていきます。


- .git/refs/heads/ブランチ名に格納されるハッシュが新しいcommitオブジェクトのものに書き換わる








#### commitの上書き
内部的には上書きではなく、同じ親を持つ別のcommitの新規作成です。



![2-2-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/4ae33c0d-5625-cdd7-fda3-861fd24e57d6.png)





##### Git操作

```console:terminal
//前のcommitに追加したい変更をステージングする
$ git add ファイル名

//commitの上書き(コミットメッセージ指定なし)
$ git commit --amend
 
//commitの上書き(コミットメッセージ指定あり)
$ git commit --amend -m "コミットメッセージ"
``` 

コミットメッセージを指定していない場合は、COMMIT_EDITMSGが開くので編集して保存して閉じます。

#### commit履歴の確認
```console:terminal
$ git log
```

commitのハッシュが変わっているので、別のcommitオブジェクトが作成されたことがわかります。
上書き前のcommitも残っているのでハッシュから確認できます。





### 2-3. commitの指定方法



#### ブランチ名で指定する
そのブランチが指しているcommitを指定できる

#### commitのハッシュで指定する


|桁数|説明|
|---|---|
|7桁|commitオブジェクトのハッシュの最初の7桁で指定する|
|40桁|commitオブジェクトのハッシュの最初の40桁で指定する|





#### HEADからの相対関係で指定する
親が複数ある場合は最初の親が選択されます。


|どのcommitを指すか|~|^|~n|~|^|~n|
|---|---|---|---|---|---|---|
|現在のHEADが指すcommit|HEAD|HEAD|HEAD|@|@|@|
|現在のHEADから1つ前のcommit|HEAD~|HEAD^|HEAD~1(1は省略可能)|@~|@^|@~1(1は省略可能)|
|現在のHEADから2つ前のcommit|HEAD~~|HEAD^^|HEAD~2|@~~|@^^|@~2|
|現在のHEADから3つ前のcommit|HEAD~~~|HEAD^^^|HEAD~3|@~~~|@^^|@~3|


![2-3-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/8be0cda2-3ee3-9755-e094-968c0255734a.png)



#### 複数の親がある場合に、親を選ぶ
|どのcommitを指すか|^n|
|---|---|
|現在のHEADの2番目の親|HEAD^2|
|現在のHEADの3番目の親|HEAD^3|

![2-3-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/1635f041-a46a-82ad-5c75-13ffb1f0d642.png)




```console:terminal
//親が3つになる例
//untrackedファイルも含めたgit stash
$ git stash -u
$ git stash -a

//親が3つ以上になる例
//octopusマージ 現在のブランチに2つ以上のブランチをマージ
$ git merge --strategy=octopus ブランチ名1 ブランチ名2 ブランチ名3 ...
```


#### シンボリック参照での指定


|commitの指定|記載場所|
|---|---|
|ORIG_HEAD|.git/ORIG_HEADに記録されているcommit|
|FETCH_HEAD|.git/FETCH_HEADに記録されているcommit|
|CHERRY_PICK_HEAD|.git/CHERRY_PICK_HEADに記録されているcommit|
|REBASE_HEAD|.git/REBASE_HEADに記録されているcommit|
|MERGE_HEAD|.git/MERGE_HEADに記録されているcommit|







### 2-4. リモートリポジトリ

複数人での開発を行うために使う、ネット上のリポジトリです。他の人の成果を取り込んだり、自分の成果を共有したりしながら開発を進めることができます。

#### branchの種類

|ブランチの種類|説明|
|---|---|
|上流ブランチ|ローカルリポジトリにある、リモートリポジトリのブランチを追跡するbranch<br>git fetchやgit pullが成功すると更新される<br>実体は.git/refs/remotes/|
|リモート追跡ブランチ|branchの指定なしで git mergeしたときにマージ対象になるbranch|


![2-1-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/09c87271-0ffc-9130-fd12-9669ce75424d.png)



#### リモートリポジトリの登録、削除
ローカルリポジトリに、リモートリポジトリの登録、削除を行います。

##### Git操作

```console:terminal
//追加前の確認
//リモート名とリポジトリの一覧を表示
$ git remote -v 	

//リモートリポジトリをリモート名をつけて登録
$ git remote add リモート名 リモートリポジトリのurl

//追加後の確認
$ git remote -v 	
 
//リモートの登録を削除
$ git remote rm リモート名
 
//削除後の確認
$ git remote -v
```

##### .git内で起こること

`git remote add`でリモートを追加すると下記が追記され、git remote rmで削除すると削除されます。
```:.git/config
[remote "リモート名"]
	url = git@github.com:ユーザー名/リポジトリ名.git
	fetch = +refs/heads/*:refs/remotes/リモート名/*
```


#### その他
```console:terminal
//リモート名の変更
$ git remote rename 変更前のリモート名 変更後のリモート名	

//リモートの詳細を確認
$ git remote show リモート名
``` 


#### commitをリモートリポジトリに送る
##### Git操作

```console:terminal
//現在のbranchの変更をリモートブランチに反映させる(リモートと紐づけできている場合は「リモート名 ブランチ名」省略可能)
$ git push  リモート名 ブランチ名

//現在のbranchの変更をリモートブランチに反映させる(リモート追跡ブランチとの紐付けもする)
$ git push -u リモート名 ブランチ名
$ git push --set-upstream リモート名 ブランチ名

//現在のbranchの変更をリモートブランチに反映させる(強制的)
$ git push --force-with-lease --force-if-include	
```


##### .git内で起こること
- -uオプションを付けてローカルで作成したbranchをpushした場合
```.git/config
[branch "ブランチ名"]
	remote = リモート名
	merge = refs/heads/上流ブランチ名
```

- .git/FETCH_HEADでpushされたbranchが一番上に表示される(ラグあり)
```.git/FETCH_HEAD
コミットのハッシュ(40桁)		branch 'ブランチ名' of github.com:ユーザー名/リモートリポジトリ名
```
- .git/logs/refs/remotes/origin/ブランチ名が、そのブランチの初めてのpushの場合に作成される(更新の場合は下に追記されていく)
```.git/logs/refs/remotes/origin/ブランチ名
0000000000000000000000000000000000000000 コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	update by push

```

.git/refs/remotes/origin/ブランチ名がそのbranchの初めてのpushの場合に作成される(更新のpushの場合はハッシュの更新)
```.git/refs/remotes/origin/ブランチ名
新しいコミットのハッシュ
```


##### その他
```console:terminal
//リモートブランチの削除
$ git push --delete リモート名 リモートのブランチ名
$ git push -d リモート名 リモートのブランチ名
```



#### リモートリポジトリの最新情報をローカルリポジトリ(リモート追跡ブランチ)に持ってくる


リモート追跡ブランチの場所は.git/refs/remotes/origin/ブランチ名です。

##### Git操作
```console:terminal
//リモート追跡ブランチも含めてローカルにあるブランチ表示
//(リモート追跡ブランチは前回fetch時の状態なのでリアルタイムに更新されていない)
$ git branch -a

リモートブランチはremotes/origin/ブランチ名の形で表示される

//リモートリポジトリのすべてのブランチの更新履歴をリモート追跡ブランチに取り込む
//(リモートで削除されているブランチがあっても、ローカルにあるリモート追跡ブランチは残ったまま)
$ git fetch

//リモートブランチの更新情報をリモート追跡ブランチに取り込む
$ git fetch リモート名 リモートブランチ名

//リモートで削除されたブランチのリモート追跡ブランチを削除
$ git fetch --prune
$ git fetch -p
```

##### .git内で起こること
- git fetchを実行すると.git/FETCH_HEADの内容に、取得したリモートリポジトリの状態が反映される
- git fetch --pruneを実行すると、リモートで削除されていたブランチに対応する下記ファイルが削除される
「.git/refs/remotes/ブランチ名」
「.git/logs/refs/remotes/origin/ブランチ名」


#### fetchしてとってきたブランチに移動する
```console:terminal
//リモート名不要で、ブランチ名のみ
$ git switch ブランチ名
$ git switch -c ブランチ名
```

- .git/refs/heads/ブランチ名が作成される
```.git/refs/heads/ブランチ名
ブランチのHEADが指しているコミットのハッシュ 
```

- .git/logs/refs/heads/ブランチ名が更新作成される(無い場合は作成される)
```.git/logs/refs/heads/ブランチ名
0000000000000000000000000000000000000000 コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	clone: from github.com:ユーザー名/リポジトリ名.git
コミットのハッシュ(40桁) コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit: コミットメッセージ
コミットのハッシュ(40桁) コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit: コミットメッセージ
```


##### Git操作
ブランチの上流ブランチを確認します。
```console:terminal
$ git branch -vv
```

##### .git内で起こること
参照するだけなので何も起きません。


#### リモートブランチの変更を取り込む
リモート追跡ブランチが上流ブランチとして登録されている場合、引数なしのmergeで変更を取り込めます。
```console:terminal
$ git merge

```

　















### 2-5. 比較





#### コミットログの確認

現在のcommitオブジェクトから親(parent)を辿ることによって到達可能なコミットを表示します。
HEADから順に表示されます。
(HEADを過去のcommitに移動している場合、それより新しいcommitは表示されません)

```console:terminal
//現在のブランチのコミットログを出力
$ git log

//全ブランチのコミットログを出力
$ git log --all

//最後のN個のコミットのコミットログを出力
$ git log -n

//特定のファイルのコミットログを出力
$ git log --follow ファイル名
``` 

##### git logの表示オプション
```console:terminal
//コミット番号のハッシュ値を省略表記(7桁)で出力
$ git log --abbrev-commit

//グラフで表示
$ git log --graph

//コミットログ(簡易版)を出力(コミット履歴を1行1履歴)
$ git log --oneline
```

##### 情報の追加表示
```console:terminal
//ファイルの修正内容を追加表示
$ git log -p
 
//変更されたファイルのパスと差分量を追加表示
$ git log --stat
 
//ファイル毎の削除、追加行数を追加表示
$ git log --numstat
 
//変更したファイルを追加表示
$ git log --name-status
```  





##### 絞り込み
```console:terminal
//commitを日付で絞り込み(以降)
$ git log --after="YYYY/MM/DD"	
$ git log --since="YYYY/MM/DD"

//commitを日付で絞り込み(以前)
$ git log --before="YYYY/MM/DD"
$ git log --until="YYYY/MM/DD"

//commitを特定のファイルのみで絞り込み
$ git log -- パス/ファイル名		

//commitをコミッターで絞り込み
$ git log --author="ユーザー名"	

//commitをコミットメッセージに含まれる文字列で絞り込み
$ git log --grep='コミットメッセージに含まれる文字"	

//commitをマージコミットのみに絞り込み
$ git log --merges

//commitをマージコミット以外に絞り込み
$ git log --no-merges	

//複数の親がある場合最初の親コミットのみを追う
$ git log --first-parent	

//コミット1の子孫、かつコミット2の祖先のみに絞り込み
$ git log --ancestry-path コミット1...コミット2
```


#### ファイルの状態の比較
##### ワーキングツリーとindexの差分
```console:terminal
//ワーキングツリーとindexの差分
$ git diff 
 
//ワーキングツリーとindexの差分(特定のファイルについて)
$ git diff ファイル名
```


##### indexとcommitの差分
```console:terminal
//indexとHEADの比較 (git diff --cached HEADと同義)
$ git diff --cached
 
//上記と同じ(git diff --staged HEADと同義)
$ git diff --staged
 
//indexとHEADの比較(特定のファイルについて)
$ git diff --cached ファイル名
$ git diff --staged ファイル名

//indexと特定のコミットの差分
$ git diff commitのハッシュ --staged	

//indexと特定のコミットの差分(特定のファイルについて)
$ git diff commitのハッシュ --staged ファイル名	
```

##### commitとcommitの差分
```console:terminal
//「最新のコミット」と「最新のコミットのひとつ前」との差分
$ git diff HEAD~

//「最新のコミット」と「最新のコミットのひとつ前」との差分(特定のファイルについて)
$ git diff HEAD~ ファイル名
 
//現在のHEADと特定のcommitの差分
$ git diff 比較するcommitのハッシュ
$ git diff 比較するブランチ名

//現在のHEADと特定のcommitの差分(特定のファイルについて)
$ git diff 比較するcommitのハッシュ ファイル名 
$ git diff 比較するブランチ ファイル名  
 
//特定の2つのcommitの差分
$ git diff commitのハッシュ1  commitのハッシュ2
$ git diff commitのハッシュ1..commitのハッシュ2
$ git diff ブランチ名1 ブランチ名2
$ git diff リモート追跡ブランチ名 ローカルブランチ名

//特定の2つのcommitの差分(特定のファイルについて)
$ git diff commit1のハッシュ commit2のハッシュ ファイル名
$ git diff ブランチ名1 ブランチ名2 ファイル名
$ git diff リモート追跡ブランチ名 ローカルブランチ名 ファイル名
```

##### ワーキングツリーとcommitの差分
```console:terminal
//ワーキングツリーと特定のコミットの差分
$ git diff commitのハッシュ

//ワーキングツリーと特定のコミットの差分(特定のファイルについて)
$ git diff commitのハッシュ ファイル名
```

##### diffの表示オプション
```console:terminal
//ファイル名のみ表示
$ git diff --name-only	
```

##### diffの絞り込みオプション
```console:terminal
//変更した差分だけに絞り込み
$ git diff --diff-filter=M

//削除した差分だけに絞り込み
$ git diff --diff-filter=D	

//リネームした差分だけに絞り込み
$ git diff --diff-filter=R	
```


#### コミットの中身を確認する
```console:terminal
//特定のコミットの中身を確認
$ git show コミットのハッシュ

//HEADのコミットの中身を確認(HEADは省略可能)
$ git show HEAD

//コミットの中身を確認(ファイルを指定)
$ git show コミットのハッシュ:ファイル名
```







#### ローカルリポジトリの状態を確認する

```console:terminal　
//staged/modified/untracked の一覧、差分情報などのステータスを表示
$ git status

//簡易形式で表示
$ git status -s
$ git status --short
```
　




|ファイルのステータス||
|---|---|
|staged|ステージングされているファイル(indexとHEADに差分がある)|
|modified|変更されているファイル(ワーキングツリーとindexに差分がある)|
|untracked|Gitで追跡されていない、gitignoreによって無視されないファイル|




#### 特定のファイルの行の変更履歴の確認]

```console:terminal
//行の変更履歴(ファイル内で誰が何をいつ変更したか)
$ git blame ファイル名	

行の変更履歴(ファイル内で誰が何をいつ変更したか) 省略形
$ git blame -s ファイル名

//ファイルの指定した行の変更履歴を出力する
$ git blame -L 行番号1,行番号2,... ファイル名	
```


#### 各ブランチのcommitを表示

```console:terminal
//各ブランチのcommitを表示
$ git show-branch

//リモート追跡ブランチも表示
$ git show-branch --all

//もっと多く表示
$ git show-branch --more=n
```

#### 2つのコミットの分岐元のコミットの確認
```console:terminal
//2つのコミットが分岐したcommitのハッシュの確認(コミットのハッシュで指定)
$ git merge-base commit1のハッシュ commit2のハッシュ	

//2つのコミットが分岐したcommitのハッシュの確認(ブランチ名で指定)
$ git merge-base ブランチ1　ブランチ2
``` 






### 2-6.ファイル操作


#### ファイルのリネーム
追跡済みのファイルのリネームが対象です。
追跡されていないファイルにはgit mvは使用できません。

##### Git操作
ステージングする前に現在インデックスされているファイルを確認しておきます。
```console:terminal
//リネーム前に現在インデックスされているファイルの確認
$ git ls-files --stage

//ファイル名の変更をステージングする
$ git mv 現在のファイル名 変更後のファイル名

//現在インデックスされているファイルの確認
$ git ls-files --stage
```
ステージングでのファイル名が変わっていることがわかりますが、ハッシュは変わっていません。
blobオブジェクトは、ファイル名の情報を保持していなく、インデックスがファイルの構造と名前を保持しています。

 

##### .git内で起こること
- blobオブジェクトは追加なし
- .git/indexのファイル名が更新される


#### ファイルの移動

```console:terminal
//移動前にインデックスされているファイルの確認
$ git ls-files --stage

//gitコマンドでのファイルの移動(1回でステージングされる)
$ git mv 現在のパス/ファイル名 移動後のパス/ファイル名

//gitコマンドを使わないファイルの移動(ステージングまで2手かかる)
$ mv  現在のパス/ファイル名 移動後のパス/ファイル名
$ git add 移動後のパス/ファイル名

//現在インデックスされているファイルの確認
$ git ls-files --stage
```
 



##### .git内で起こること
- blobオブジェクトは追加なし
- .git/indexのパス/ファイル名が更新されます。 (git mvでも、mv+git addでもblobオブジェクトのハッシュは変わりません。)
(mv+addでもrenameになるのでcommitすると履歴が追えています。)





#### ファイルの削除

Git管理下にないファイルをワーキングツリーから手動削除なら、最初からなかったのと同じ扱いです。
Git管理下にあるファイルをgit rmコマンドで消すと、インデックスからも消えるので「削除」をaddする必要はありません。
Git管理下にあるファイルを(git rmではなく)rmで削除する場合は、削除済みのファイルをaddする必要があります。
```console:terminal
//gitコマンドでのファイル削除(追跡済みファイルのみ有効)(1回でステージングされる)
$ git rm ファイル名

//gitコマンドを使わないファイル削除(追跡済みファイルの場合は別途addが必要)(ステージングまで2手かかる)
$ rm ファイル名
$ git add ファイル名

//gitコマンドでのファイル削除(ファイル削除をステージングする)(ワーキングツリーのファイルは残る) 　
$ git rm --cached ファイル名
``` 
 
 
 
 

#### 追跡されていないファイルの削除
```console:terminal
//Gitで追跡されていないファイルすべてを確認
$git clean -n	

//Gitで追跡されていないファイルすべてを削除
$ git clean	
 
//Gitで追跡されていないファイルすべてを削除(強制)
$ git clean -f	

//Gitで追跡されていない特定のファイルを削除(強制)
$ git clean -f ファイル名

//Gitで追跡されていないファイルを対話モードで削除
$ git clean -i	
```




#### .git内で起こること
- blobオブジェクトは消えない
- .git/indexから削除したファイルが消える 

ステージングしたファイルをワーキングツリーで削除しても、addせずにコミットするとインデックスでは存在するのでコミットに含まれます。



#### ファイルの削除(git追跡されていないもののみ)
```console:terminal
//Gitで追跡されていないファイルすべてを確認
$ git clean -n

//Gitで追跡されていないすべてのファイルを削除
$ git clean -f

//Gitで追跡されていない特定のファイルを削除する
$ git clean -f ファイル名
```






#### ディレクトリ名のリネーム


git ls-filesコマンド
staging-area（ステージングエリア）でインデックスされているファイルを確認したい場合に利用するコマンド
-s(--stage)オプションでリポジトリ格納されたblobのhash値とファイル名の一覧を表示

```console:terminal
//リネーム前に現在インデックスされているファイルの確認
$ git ls-files --stage

//ディレクトリ名の変更をステージングする
$ git mv 現在のディレクトリ名 変更後のディレクトリ名

//現在インデックスされているファイルの確認
$ git ls-files --stage
```

ステージングでのファイル名が変わっていることがわかりますが、ハッシュは変わっていません。
blobオブジェクトは、ファイル名の情報を保持しておらず、インデックスがファイルの構造と名前を保持しています。
 


##### .git内で起こること
.git/indexで変更したディレクトリ名/ファイル名が変更されています。


#### ディレクトリの移動

```console:terminal
//リネーム前に現在インデックスされているファイルの確認
$ git ls-files --stage

//移動
$ git mv 現在のパス/ディレクトリ名 変更後のパス/ディレクトリ名

//現在インデックスされているファイルの確認
$ git ls-files --stage
``` 
ステージングでのファイル名が変わっていることがわかるが、ハッシュは変わっていない
blobオブジェクトは、ファイル名の情報を保持していない
インデックスがファイルの構造と名前を保持している


##### .git内で起こること
.git/indexで変更したディレクトリ名/ファイル名が変更されています。


#### ディレクトリの削除(中身ごと)
ディレクトリも(ディレクトリはtreeオブジェクトという点は異なる)

```console:terminal
//ワーキングツリーとステージングの両方を削除
$ git rm -r パス/ディレクトリ名

//ステージングのみ削除(ワーキングツリーのファイルは残る) 
$ git rm -r --cached パス/ディレクトリ名
```


#### 追跡されていないディレクトリの削除

```console:terminal
//Gitで追跡されていないディレクトリとファイルすべてを確認
$ git clean -dn
 
//Gitで追跡されていないディレクトリとファイルすべてを削除(強制)
$ git clean -df

//Gitで追跡されていない特定のディレクトリを削除
$ git clean -f ディレクトリ名	
```









### 2-7. ファイルの復元


![2-7-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/62f9b4fd-8279-5b86-95e8-8b642f87c9df.png)






#### ファイルがインデックスに登録されたときの状態をワーキングツリーに復元
コミットを指定しない場合は、インデックスからの復元になります。


##### Git操作
```console:terminal
//ステージングした時点の状態に復元　コミット識別子の引数が無い場合は、復元元は「インデックス」が対象
$ git restore ファイル名

//ステージングした時点の状態に、部分ごとに区切って復元箇所を指定
$ git restore -p ファイル名
```

##### .git内で起こること
- .gitでは何も起きない




#### 特定のコミットのファイルを復元
ファイルがcommitされた時の状態をワーキングツリーに復元します。
削除したファイルを復活させるのにも使えます。


##### Git操作
```console:terminal
//HEADから復元
$ git restore -s HEAD ファイル名

//HEADからいくつ前のコミットか指定して復元
$ git restore -s @~n ファイル名
$ git restore -s HEAD~n ファイル名
　
//commitのハッシュを指定して復元
$ git restore -s コミットのハッシュ ファイル名

//commitのハッシュを指定して部分ごとに区切って復元箇所を指定
$ git restore -p -s commitのハッシュ ファイル名

//コミット時点の状態をステージングに反映
$ git restore --staged ファイル名	
   
//コミット時点の状態をワーキングツリーに反映
$ git restore --source=HEAD ファイル名
 
//コミット時点の状態をステージングとワーキングツリー両方に反映
$ git restore --source=HEAD --staged --worktree ファイル名
```  
restoreの-s/--sourceオプションdでは、戻したいコミットを指定できます。

##### .git内で起こること
- .gitでは何も起きない











### 2-8. HEADの移動


`git reset`で任意のcommitに移動することができます。
リセットといっても、何かをある時点に戻すのがメインではなく、commitを参照するHEADを動かしているだけです。オプションで、ステージングとワーキングツリーにどの時点の状態を反映させるか決められます


![2-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/6bea765b-7573-916e-5431-7570d1ad2d5a.png)




#### 現在のHEADの確認
##### Git操作
```console:terminal
$ git log -1
```

##### .git内で起こること
- .gitでは何も起きない

##### .git内のファイルからHEADのcommitのハッシュを確認する
現在のブランチ
```.git/HEAD
ref: refs/heads/ブランチ名
```
↓
そのブランチ名のファイルからハッシュを確認します。
```.git/refs/heads/ブランチ名
現在のbranchが指すcommitのハッシュ(40桁)
````









#### HEADの移動
##### Git操作


```console:terminal
//ステージングをHEADのコミット状態に戻す。ワーキングツリーとHEADはそのまま(add取り消し)
//git reset --mixed HEADと同じ
$ git reset	

//特定のファイルをステージングからワーキングツリーに戻す(commit、indexを元に戻す)
//git reset --mixed HEAD ファイル名と同じ
$ git reset ファイル名

//コミットを指定してファイルを復元する(commit、indexをもとに戻す) 
$ git reset commitのハッシュ ファイル名	
```


|resetのオプション|ワーキングツリー|インデックス|コミット|
|---|---|---|---|
|--sof|-|-|指定のcommitに移動|
|--mixed|オプション省略時|-|戻したcommitの状態を反映|指定のコミットに移動|
|--hard|戻したコミットの状態を反映|戻したcommitの状態を反映|指定のcommitに移動|



どのHEADに移動するのか特定するためにHEADの動きのログを表示
```console:terminal
//移動したいHEADを調べる
//HEADの移動履歴を出力
$ git reflog

//操作履歴数を指定して出力
$ git reflog -n	

//実際にHEADを移動する
$ git reset --hard HEAD@{n}
```






##### .git内で起こること
- indexは戻したコミットまでの時点に戻っている
- commit、ステージ、ワーキングツリーに戻した先のHEADの状態が反映される



```.git/refs/heads/ブランチ名
新しくHEADが指すcommitブランチのハッシュに書き換わる
```

commitオブジェクトは親commitの参照しか持っていないので、git logでは子commitが表示できません。
git reflogでは過去のHEADの移動の履歴が見られるのでHEADよりも先のcommitへも移動できます。
reflogは、refの動きの履歴なのでブランチ横断での履歴になっています。
別ブランチのcommit履歴に戻すこともできます。
ワーキングツリーとステージングにしかないファイルはなくなります。(完全にcommit時点の状態になるため。)





### 2-9. commit履歴の改変

commit履歴の改変では、同じ部分を変更していた場合コンフリクト(衝突)することがあります。
コンフリクトが起こった場合は、どの変更を残すかの編集が必要になります。


#### マージの状況を確認する
```console:terminal
//現在のブランチにマージされているブランチを出力する
$ git branch --merged	

//現在のブランチにマージされていないブランチを出力する
$ git branch --no-merged	
```




#### branchの統合
branchの統合のことをbranchのマージと呼びます。

##### Git操作
```console:terminal
//現在のbranchに特定のブランチをマージする
$ git merge 特定のブランチ名

//現在のbranchに特定のbranchをマージする(コミットメッセージも指定)
$ git merge -m "コミットメッセージ" ブランチ名

//現在のbranchに特定のbranchをマージする(ファストフォワードマージでもマージコミットを作成)
$ git merge 特定のブランチ名 --no-ff	

//特定のリモート追跡ブランチを特定のローカルブランチにマージする
$ git merge オリジン名/ブランチ名 ブランチ名	
```


##### コンフリクトしたら
```console:terminal

//継続(競合を修正した後addとcommitを自分で行う)
$ git add 
$ git commit

//継続(競合を修正した後mergeの続きを行う)
$ git merge --continue


//中止(コンフリクトの編集をしていないとき)
$ git merge --abort

//中止(コンフリクトの編集をしているとき)
$ git reset --hard HEAD
```








##### .git内で起こること

コンフリクトを解消して、完全に片方だけ残す場合は、どちらかのblobオブジェクトが残ります。どちらとも完全に同じでなければ新しいファイルになるのでハッシュは変わります。
- コミットが作られて1つ進む
- コミットメッセージは自動で入る
```console:terminal
$ git log

Merge branch '取り込まれるブランチ名' into 取り込むブランチ名
```
- COMMIT_EDITMSGはそのまま
```COMMIT_EDITMSG
(merge元のbranchの最後のコミットメッセージのまま変化なし)
```
- commitがつくられて、HEADは1つすすむ
```.git/HEAD
コミットのハッシュ(40桁)
```


```console:terminal
//コミットログ確認
$ git log

1.マージしてできたcommit
2.取り込まれたブランチのHEADだったcommit
3.取り込むブランチのHEADだったcommit
```

ORIG_HEADは直前までHEADだった「取り込むbranchのHEADだったcommit」です。
```.git/ORIG_HEAD
取り込むブランチのHEADだったcommit
```

```.git/INDEDX
マージされた時点の状態
```


#### マージの種類
マージにはファストフォワードとノンファストフォワードがあります。

##### merge(fast-forward)
分岐してから片方は進んでいないので、ファストフォワードだとコンフリクトしません。
実質統合はされていないので、親は1つだけです。

commitが進むだけ(マージコミットはつくられない＝「ブランチをマージした」という履歴が残りません。)
結果的には元のブランチに直接コミットしたのと変わりません。(後から取り消しにくくなります。)


![2-11-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/7936616d-dd83-e20c-5ebc-c5a16df0116f.png)





##### merge(non-fast-forward)
マージしたことをコミット情報として持ちます。
分岐してから両方進んでいるので、コンフリクトする可能性があります。


![2-11-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/1db488ed-db15-9e2b-0aeb-7188b682b7d5.png)







#### マージの取り消し
```console:terminal
//マージが完了した後から取り消す
$ git reset --hard ORIG_HEAD
```





#### コミット履歴の付け替え
分岐したbranchを、もう片方にくっつけます。(commitログではrebase前の時刻だが、関係なく後につきます。)
くっつけたcommitは、前の影響を受けるので前とハッシュが変わります。
現在のブランチ名(付け替えられるブランチ名)がキープされます。


![2-11-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/476d1dbe-2bad-ecf3-9cd9-84b53cfa2f85.png)



```console:terminal
//2つのbranchが分岐したcommitのハッシュの確認
$ git merge-base ブランチ名1 ブランチ名2	
```

```console:terminal
//リベースを利用して現在のbranchを特定のbranchに付け替える
$ git rebase 付け替え先のブランチ名	

//リベースを利用して現在のブランチを特定のbranchに付け替える(インタラクティブモード)
$ git rebase --interactive ブランチ名
$ git rebase i ブランチ名
```

　



#### コミット履歴を改変する



![2-11-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/0fdca9a9-4243-4156-6f8b-5d863ffb7e5d.png)


```console:terminal
//コミット履歴を改変する
$ git rebase -i 改変したい一番古いcommitより1つ古いcommitのハッシュ
```
各コミットに対する操作を行います。

|選択肢|rebaseの各コミットへの操作|
|---|---|
|pick|そのまま|
|reword|コミットメッセージの編集|
|edit|コミット内容の編集|
|squash|直前のコミットとまとめる(コミットメッセージ編集)|
|fixup|直前のコミットとまとめる(コミットメッセージ破棄)|
|drop|コミットの削除|



##### リベースでコンフリクトが起きた時
```console:terminal
//リベース時のコンフリクト解消を中止
$ git rebase --abort
 
//リベース時のコンフリクト解消終了時に使用する
$ git rebase --continue
```


##### rebaseの取り消し
```console:terminal
//戻りたい時点を確認する 
$ git reflog

//戻す
$ git reset --hard HEAD@{n}
```



#### cherry-pick
特定のcommitを取り込みます。
差分ではなく、スナップショットなのでそのコミットで行った変更ではなく、そのcommit時点の状態を取り込みます。
指定したcommitは、cherry-pick時点のHEADの後に追加され、HEADは進みます。
追加されたcommitのハッシュは変わります。


![2-11-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/9a64d4a2-ba25-b858-9fa4-da457273e665.png)





```console:terminal
//特定のcommit時点の状態を取り込む
$ git cherry-pick 取り込むcommitのハッシュ
$ git cherry-pick ブランチ名

//複数のcommitをまとめてcherry-pickする(連続している場合)
$ git cherry-pick 取り込むcommitのハッシュ..取り込むcommitのハッシュ

//複数のcommitをまとめてcherry-pickする(連続していない場合)
$ git cherry-pick 取り込むcommitのハッシュ1 取り込むcommitのハッシュ2 取り込むcommitのハッシュ3 
``` 
 
 



##### コンフリクトしたら
```console:terminal
//チェリーピック時のコンフリクト解消作業を終了する(次にすすむ)
$ git cherry-pick --continue

//チェリーピック時のコンフリクト解消作業を中止する
$ git cherry-pick --abort
```

##### cherry-pickの取り消し
```console:terminal
//cherry-pickの取り消し
$ git reset --hard HEAD~
``` 






#### commitを打ち消す
特定のcommitを打ち消すための新しいcommitを作って、commitを打ち消します。


![2-11-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451263/721b8f83-f934-4e2f-d3e7-1cf7495f9e86.png)




##### 親が1つのcommitの場合
```console:terminal
//HEADのcommitを打ち消す(コミットメッセージを編集する)
$ git revert HEAD

//特定のcommitを打ち消す(コミットメッセージを編集する)
$ git revert 打ち消したいcommitのハッシュ 

//HEADのcommitを打ち消す(コミットメッセージを編集しない)
$ git revert HEAD --no-edit

//特定のcommitを打ち消す(コミットメッセージを編集しない)
$ git revert 打ち消したいcommitのハッシュ --no-edit
```
revertを実行すると、コミットメッセージを編集する画面になるので入力します。


##### 親が複数のcommitの場合
親が複数あるcommitオブジェクトの例：マージコミット
```console:terminal
 //親が複数ある場合は、親を指定するための-mオプションが必要
 error: commit 打ち消そうとしたcommitオジェクトのハッシュ is a merge but no -m option was given.
```

対象のマージコミットを確認して、最初に書かれる方を1、2番目を2とします。
```console:terminal
$ git revert -m 1 打ち消したいcommitのハッシュ
```

##### 親の確認方法

git showでの確認の場合

```console:terminal
$ git show

commit commitオブジェクトのハッシュ
Merge: 親1のcommitのハッシュ(7桁)  親2のcommitのハッシュ(7桁)
Author: ユーザー名 <メールアドレス>
Date:   曜日 月 日 時刻 年 +0900
```

git cat-fileでの確認の場合
```console:terminal
$ git cat-file -p commitオブジェクトのハッシュ40桁

tree trreeオブジェクトのハッシュ
parent 親1のcommitのハッシュ
parent 親2のcommitのハッシュ
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900

コミットメッセージ
```

git logでの確認の場合
```console:terminal
commit commitオブジェクトのハッシュ40桁
Merge: 親1のcommitのハッシュ(7桁)  親2のcommitのハッシュ(7桁)
Author: ユーザー名 <メールアドレス>
Date:   曜日 月 日 時刻 西暦 +0900

コミットメッセージ
```

##### commitしないで打ち消し生成までで止める
複数のcommitをrevertするときに、commitを一回にまとめるときなどに使います。
```console:terminal
$ git revert 打ち消したいコミットのハッシュ --no-commit
$ git revert 打ち消したいコミットのハッシュ -n
```

#### 複数のcommitを打ち消す
打ち消すcommitの数だけ新しくcommitが作られます。(新しい方から打ち消していきます。)
```console:terminal
//複数のcommitをrevertする(コミットメッセージを編集する) HEADでの指定
$ git revert HEAD(打ち消す中で最新のコミット)...HEAD~n(打ち消したい一番古いコミットより一つ古い)

//複数のcommitをrevertする(コミットメッセージを編集する) commitのハッシュでの指定
$ git revert 打ち消す中で最新のコミットのハッシュ...打ち消す最後のコミットより一つ古いコミットのハッシュ


//複数のcommitをrevertする(コミットメッセージを編集しない) HEADでの指定
$ git revert HEAD(打ち消す中で最新のコミット)...HEAD~n(打ち消したい一番古いコミットより一つ古い) --no-edit

//複数のcommitをrevertする(コミットメッセージを編集しない) commitのハッシュでの指定
$ git revert 打ち消す中で最初のコミットのハッシュ...打ち消す最後のコミットより一つ古いコミットのハッシュ --no-edit
``` 


#### 打ち消しを打ち消す
```console:terminal
//revertで作成したcommitをrevertする
$ git revert 特定のコミットのハッシュ

//revertで作成したcommitを削除する
$ git reset --hard HEAD~n
```