まえがき
git に関する知見をいくつかの視点から紹介します。ひとつでも新しい発見があれば幸いです。
リモート追跡ブランチの残骸を削除する
remote branch を削除しようと思うと
git push origin --delete feat1
とすれば可能だ。これで remote branch も remote-tracking branch も削除される。
ところで remote の feat1 を他人によってすでに削除されている場合、ローカルに残っている remote-tracking branch は消せない。以下のようなエラーになる。
$ 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(新鮮じゃない、腐りかけている)していることが判る。
$ 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 は以下の二通りの方法で削除することができる。
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 すると以下の提案を受けます
$ 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 が無い状態です。以下のコマンドで状態を確認することができると思います。
git branch -vv
git status -sb
upstream を unset しする場合は以下。
$ 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 する場合は以下。
$ 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 にできる。
$ git co HEAD~
そうすると以下のような提案を受けることになるが、はじめて detached になると焦る。
git switch -c <new-branch-name>
git switch -
そもそも detached じゃない場合は、HEAD は branch を参照している。以下のような状態。
$ cat .git/HEAD
ref: refs/heads/feat2
ところが、detached になっているのは以下のような状態。ブランチを参照するのではなく、直接コミットハッシュを指している。
$ 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 される。
$ 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
または、以下のように、今いるブランチの情報を確認できる。
$ git status -sb
## feat2...origin/feat2
逆に remote の情報を見ることで、どのブランチが追跡されているか確認することができる。以下では、feat2 と main が tracked になっているのが見える。
$ 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 しました。
$ 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)
他ユーザーからの見え方
$ 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 はどちらからも同じように見えます。
$ 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 すれば済みます。
$ 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 がどうなっているのか見ておきたいです。
$ 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
と同じです。それはそうですよね。
$ cat .git/refs/heads/pr_5
ad50b826f784c6fc973650d62600b5815f81ab34
別のアプローチとして(ほぼ同じだが)、以下でもよい。
git switch -c new_br
git pull origin pull/5/head
ときどき記事で以下のような方法を見る。(僕はこの方法は使わない)
$ git fetch origin pull/5/head:refs/remotes/pr/pr_5
これは、fetch 先が refs/remotes/pr/pr_5
となっている。こうすると refs/head
に入ってこないので、git branch
しても pr_5 というブランチは出てこない。
上のようにやった場合は、つづけて以下のようにするらしい。
$ 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
$ 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
で調べるとよくわかります。
$ git rev-parse --short @
1ce9f4b
$ git rev-parse --short @^2~2
35eee15