git 中級者になりたいメモ

# まえがき

git に関する知見をいくつかの視点から紹介します。ひとつでも新しい発見があれば幸いです。

# リモート追跡ブランチの残骸を削除する

remote branch を削除しようと思うと

```bash
git push origin --delete feat1
```

とすれば可能だ。これで remote branch も remote-tracking branch も削除される。

ところで remote の feat1 を他人によってすでに削除されている場合、ローカルに残っている remote-tracking branch は消せない。以下のようなエラーになる。

```bash
$ git br -a
* feat1
  main
  remotes/origin/HEAD -> origin/main
  remotes/origin/feat1
  remotes/origin/main

$ git push origin --delete feat1
error: unable to delete 'feat1': remote ref does not exist
error: failed to push some refs to 'github.com:mochizukikotaro/git-study.git'
```

これは `remote show` することで、stale（新鮮じゃない、腐りかけている）していることが判る。

```bash
$ git remote show origin
* remote origin
  Fetch URL: git@github.com:mochizukikotaro/git-study.git
  Push  URL: git@github.com:mochizukikotaro/git-study.git
  HEAD branch: main
  Remote branches:
    main                      tracked
    refs/remotes/origin/feat1 stale (use 'git remote prune' to remove)
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (local out of date)
```

こういった branch は以下の二通りの方法で削除することができる。

```bash
git remote prune orign

# または

git fetch origin --prune
```

# push の -u オプションとは何か？

リポジトリを新たに作るとき、以下を打つようにと言われる。


>git remote add origin git@github.com:mochizukikotaro/git-study.git
git branch -M main
git push -u origin main


ここで push の -u オプションはなんだろう？

これは --set-upstream-to の省略系です。もしここで -u をつけなかった場合は git pull すると以下の提案を受けます

```bash
$ git pull
略
See git-pull(1) for details.

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> feat2
```

これは、作業ブランチに upstream が無い状態です。以下のコマンドで状態を確認することができると思います。

```bash
git branch -vv
git status -sb 
```

upstream を unset しする場合は以下。

```bash
$ git branch --unset-upstream
$ git status -sb
## feat2
$ git branch -vv
  branch fea2ffa insert hey feat2
* feat2  fea2ffa insert hey feat2
  main   67360e7 [origin/main: behind 4] add piyo.txt file
```

upstream を set する場合は以下。

```bash
$ git branch --set-upstream-to=origin/feat2 feat2
Branch 'feat2' set up to track remote branch 'feat2' from 'origin'.
~/D/s/git-study (feat2)
$ git status -sb
## feat2...origin/feat2
```

# detached HEAD を理解しておく

公式のドキュメントを読めば書いてある。
https://git-scm.com/docs/git-checkout#_detached_head

以下をうつと、detached HEAD にできる。

```bash
$ git co HEAD~
```

そうすると以下のような提案を受けることになるが、はじめて detached になると焦る。

```
  git switch -c <new-branch-name>
  git switch -
```

そもそも detached じゃない場合は、HEAD は branch を参照している。以下のような状態。

```bash
$ cat .git/HEAD
ref: refs/heads/feat2
```

ところが、detached になっているのは以下のような状態。ブランチを参照するのではなく、直接コミットハッシュを指している。

```bash
$ cat .git/HEAD
6b751be4d4829ef98b4dfb9613b41423bd9cdcfc
```

原理をわかっていれば、慌てなくて済む。detached になってもおちついて、switch -c や switch - しておけば良い。

ちなみに HEAD^ に戻しながら、new branch を作る（detached にならずに）方法は、`git switch -c new_branch HEAD^` でよい。

# upstream branch を確認する

以下のように branch の -vv オプションで、upstream branch を確認できる。ローカルの feat2 は origin/feat2 が upstream に設定されている。なので feat2 で `git pull` をすれば、そこから自動で pull される。

```bash
$ git branch -vv
  feat2 fea2ffa [origin/feat2] insert hey feat2
  feat3 6b751be insert mu name to hoge.txt
* feat4 6b751be insert mu name to hoge.txt
  main  67360e7 [origin/main: behind 4] add piyo.txt file
```

または、以下のように、今いるブランチの情報を確認できる。

```bash
$ git status -sb
## feat2...origin/feat2
```

逆に remote の情報を見ることで、どのブランチが追跡されているか確認することができる。以下では、feat2 と main が tracked になっているのが見える。

```bash
$ git remote show origin
* remote origin
  Fetch URL: git@github.com:mochizukikotaro/git-study.git
  Push  URL: git@github.com:mochizukikotaro/git-study.git
  HEAD branch: main
  Remote branches:
    feat2 tracked
    main  tracked
  Local branches configured for 'git pull':
    feat2 merges with remote feat2
    main  merges with remote main
  Local refs configured for 'git push':
    feat2 pushes to feat2 (up to date)
    main  pushes to main  (local out of date)
```

# プルリクを出したときの remote の見え方

まずは、プルリクを出したユーザーから見たとき。feat5 ブランチから push しました。

```diff
$ git push
$ git remote show origin
* remote origin
  Fetch URL: git@github.com:mochizukikotaro/git-study.git
  Push  URL: git@github.com:mochizukikotaro/git-study.git
  HEAD branch: main
  Remote branches:
    feat2 tracked
+   feat5 tracked
    main  tracked
  Local branch configured for 'git pull':
    main merges with remote main
  Local refs configured for 'git push':
    feat2 pushes to feat2 (up to date)
+   feat5 pushes to feat5 (up to date)
    main  pushes to main  (up to date)
```

他ユーザーからの見え方

```diff
$ git remote show origin
* remote origin
  Fetch URL: git@github.com:mochizukikotaro/git-study.git
  Push  URL: git@github.com:mochizukikotaro/git-study.git
  HEAD branch: main
  Remote branches:
    feat2 tracked
+   feat5 new (next fetch will store in remotes/origin)
    main  tracked
  Local branches configured for 'git pull':
    feat2 merges with remote feat2
    main  merges with remote main
  Local refs configured for 'git push':
    feat2 pushes to feat2 (up to date)
    main  pushes to main  (local out of date)
```

ls-remote はどちらからも同じように見えます。

```diff
$ git ls-remote origin
375efdafea49de18c971fa5a6e38c9626ef72f67	HEAD
fea2ffa5f90e4c756f6795bc0142d31ad02e992c	refs/heads/feat2
+ ad50b826f784c6fc973650d62600b5815f81ab34	refs/heads/feat5
375efdafea49de18c971fa5a6e38c9626ef72f67	refs/heads/main
75a92a643719a3949e6d2de91ef787b9a7365da3	refs/pull/2/head
6b751be4d4829ef98b4dfb9613b41423bd9cdcfc	refs/pull/3/head
cc0937c22bd7ea3e3bb4bcc88443fdcb2b7fa0c9	refs/pull/4/head
+ ad50b826f784c6fc973650d62600b5815f81ab34	refs/pull/5/head
+ 967839a6c917a33715046be70feae9c8268121b1	refs/pull/5/merge
```

# 他人のプルリクを持ってくる

hub コマンド使えばいいのですが。使わずにやろうとすると、ここを読めばいけます。
https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/checking-out-pull-requests-locally

たとえば、以下のように5番目のプルリクが出ているとします。良い感じに fetch すれば済みます。

```bash
$ git ls-remote origin | grep /5
ad50b826f784c6fc973650d62600b5815f81ab34	refs/pull/5/head
967839a6c917a33715046be70feae9c8268121b1	refs/pull/5/merge

$ git fetch origin pull/5/head:pr_5
$ git switch pr_5
```

このときに、refs がどうなっているのか見ておきたいです。

```diff
 $ la .git/refs/remotes/origin/
 total 16
 drwxr-xr-x  4 mochizuki  staff   128B 12 30 19:14 ./
 drwxr-xr-x  3 mochizuki  staff    96B 12 30 17:08 ../
 -rw-r--r--  1 mochizuki  staff    41B 12 30 19:14 feat2
 -rw-r--r--  1 mochizuki  staff    41B 12 30 19:09 main

 $ la .git/refs/heads/
 total 40
 drwxr-xr-x  7 mochizuki  staff   224B 12 31 15:16 ./
 drwxr-xr-x  5 mochizuki  staff   160B 12 30 17:08 ../
 -rw-r--r--  1 mochizuki  staff    41B 12 30 19:14 feat2
 -rw-r--r--  1 mochizuki  staff    41B 12 31 12:47 feat3
 -rw-r--r--  1 mochizuki  staff    41B 12 31 13:00 feat4
 -rw-r--r--  1 mochizuki  staff    41B 12 30 17:08 main
+ -rw-r--r--  1 mochizuki  staff    41B 12 31 15:16 pr_5
```

上のように、 `fetch origin pull/5/head:pr_5` のようにシンプルに `:pr_5` を指定しましたが、 `.git/refs/heads/` に入ってきました。`git branch` ですでに `pr_5` が存在しているので、そうですよね。

ハッシュも上でみた `refs/pull/5/head` と同じです。それはそうですよね。

```bash
$ cat .git/refs/heads/pr_5
ad50b826f784c6fc973650d62600b5815f81ab34
```

別のアプローチとして（ほぼ同じだが）、以下でもよい。

```
git switch -c new_br
git pull origin pull/5/head
```

ときどき記事で以下のような方法を見る。（僕はこの方法は使わない）

```bash
$ git fetch origin pull/5/head:refs/remotes/pr/pr_5
```

これは、fetch 先が `refs/remotes/pr/pr_5` となっている。こうすると `refs/head` に入ってこないので、`git branch` しても pr_5 というブランチは出てこない。

上のようにやった場合は、つづけて以下のようにするらしい。

```bash
$ git switch -c new_br pr/pr_5
あるいは↓など
$ git switch -c new_br refs/remotes/pr/pr_5
```

こうすることで、手元にもってこれるが。これをした場合、`git branch -D new_br` とブランチを削除しても `refs/remotes/pr` 配下ファイルは残ってします。これを上手に消す方法は見つけていない。僕としては、あまり大量にローカルに持ってきたくはないので、最初にしめした `git fetch origin pull/5/head:pr_5` というやり方が良いように思う。

この辺の仕組みは Git Objects や Git References のドキュメントを読むことでより理解がすすみました。
https://git-scm.com/book/en/v2

# tilde ~ と caret ^ の違い

以下を読めば理解できる。

https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git
https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection

```bash
$ git log --oneline --graph
*   1ce9f4b (HEAD -> a) Merge branch 'b' into a
|\
| * 0470c60 (b) b2
| * c8189fa b1
* | 27de526 a3
|/
* 35eee15 a2
* e63c007 a1
```

上のようなコミットログを自分で作ってみて、あとは　`rev-parse` で調べるとよくわかります。

```bash
$ git rev-parse --short @
1ce9f4b

$ git rev-parse --short @^2~2
35eee15
```



# 参考

ドキュメント 
https://git-scm.com/book/en/v2