2020年、BLM運動に伴い、Git界隈でもデフォルトブランチをmaster
からmain
に変更しようという動きが起こりました。Gitのデフォルトブランチ名変更は、リモートリポジトリがなければ非常に簡単です。リモートリポジトリと連携している場合も、自分が主導権を握っている(リモートリポジトリに書き込める)場合は比較的単純で、ドキュメントも多数見つかります。しかし、書き込めず、基本的にread-onlyで使っているリモートリポジトリの場合、少し面倒ですので、こちらにまとめておきます。
「read-onlyなのだから、単にcloneしなおせばよい」という意見もあると思います。しかし、巨大リポジトリの場合、cloneしなおすにはそれなりの資源の浪費を伴います。また、問題が起きたときこそ学びの機会なのに、cloneしなおしただけでは何の学びも得られません。折角なので、問題解決しつつ、その過程でGitの内部構造について少しでも多くの方々に理解を深めていただけることを目指します。
結論
最終的に一番シンプルと考えた手順は以下のとおりです。「ここだけ知りたい!」というかたはこちらをどうぞ。
# Step 1: ローカルブランチ名を変更する
git branch -m master main
# Step 2: リモートリポジトリのmainブランチを認識させる
git remote set-branches origin main
# Step 3: リモートリポジトリのmainを取得し、ローカルブランチmainの上流ブランチに設定する
git fetch --set-upstream origin main
# Step 4: リモートリポジトリのデフォルトブランチをmainに変更する
## リモートリポジトリの情報をもとに自動的に設定する方法(ネットワークアクセス必要)
git remote set-head -a origin
## 明示する方法(ネットワークアクセス不要)
git remote set-head origin main
git pullできない!
git pull
していたリモートリポジトリでデフォルトブランチ名を変更してしまうと、git pull
が失敗します。私の場合、GitHubのmicrosoft/vscode-docsというリポジトリをpullしようとしたら、エラーになりました。
% git pull
fatal: couldn't find remote ref refs/heads/master
git pull
の中で行われているgit fetch
で失敗します。なお、git pull
は、git fetch
した上でgit merge FETCH_HEAD
するのと等価です。
% git fetch
fatal: couldn't find remote ref refs/heads/master
GitHub上のWeb UIでリポジトリを眺めてみれば、一目瞭然。master
からmain
への名前の変更が原因です。単なるブランチ名前の変更なので、cloneしなおさずに対応していきます。
なお、git remote update
でもできそうですが、これもエラーになります。
% git remote update
Fetching origin
fatal: couldn't find remote ref refs/heads/master
error: Could not fetch origin
何が起きているのか?
内部構造を確認しながら対応していきましょう。
まず、エラーが起きたときの状態を確認します。
% git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/master
ローカルブランチはmaster
のみで、リモートリポジトリは1つだけ(表示されていませんが、GitHub上のmicrosoft/vscode-docsリポジトリ)がorigin
という名前で登録されていることがわかります。リモート追跡ブランチ (remote-tracking branch) として、origin
の参照するリポジトリのmaster
を追跡するorigin/master
ブランチのみがあります。また、origin/HEAD
がorigin/master
を指しているので、origin
のデフォルトブランチはmaster
である、とこのリポジトリは認識しています。origin/HEAD
については、後で解説します。
実際には、origin
側にはすでにmaster
はなく、main
をデフォルトブランチとしており、それに合わせてローカルでもmain
を使いたいので、これらのmaster
はすべてmain
に変更されなくてはなりません。
リポジトリ内部を見ていきます。まずはconfigです。
% cat .git/config
[core]
repositoryformatversion = 0
(snip)
[remote "origin"]
url = https://github.com/microsoft/vscode-docs.git
fetch = +refs/heads/master:refs/remotes/origin/master
[branch "master"]
remote = origin
merge = refs/heads/master
まず、master
のremote
の設定がorigin
になっているので、ローカルのmaster
ブランチのリモートとしてoriginを参照しています。つまり、master
で何も引数をつけずにgit fetch
を実行したときにはorigin
をチェックしに行きます。
その際に、そのorigin
のどこのブランチを見に行くかというと、origin
のfetch
の設定にあるとおり、master
ブランチのみを見に行きます(すべてのブランチを見に行くような挙動にも、この設定で変えられます)。すでにmaster
はありませんので、これはエラーになりますね。
最後に、master
のremote
がorigin
、merge
がrefs/heads/master
になっているので、master
で何も引数をつけずにgit merge
を実行するとorigin/master
をマージします。要は、master
はorigin/master
を上流ブランチ (upstream branch) としています
これらのmaster
はすべてmain
に変える必要があります。
最後に、上のgit branch -a
の結果やconfigに登場した各種ブランチやHEADの実体です。
% ls .git/refs/heads .git/refs/remotes/origin
.git/refs/heads:
master
.git/refs/remotes/origin:
HEAD master
% cat .git/refs/heads/master
b301ccfa39274d648a654fe69f32b47e6d0577c4
% cat .git/refs/remotes/origin/master
b301ccfa39274d648a654fe69f32b47e6d0577c4
% cat .git/refs/remotes/origin/HEAD
ref: refs/remotes/origin/master
% cat .git/HEAD
ref: refs/heads/master
% cat .git/FETCH_HEAD
b301ccfa39274d648a654fe69f32b47e6d0577c4 branch 'master' of https://github.com/microsoft/vscode-docs
最後の.git/FETCH_HEAD
はgit fetch
の操作で取得したブランチの情報を一時的に(git pull
でマージするために)記録するもので、git fetch
の結果次第では空にもなります。そんなに重要ではないのですが、一応載せています。
実体がわかっているので、すべて手で書き換えたりファイル名を変更したりしても対応できます。とはいえ、それは推奨される方法ではないので、ここではgit
コマンドを使いながら対応します。
Step 1: ローカルブランチ名を変更する
まず最初にすべきことは、ローカルのmaster
ブランチをmain
にすることです。ローカルブランチ名の変更はたまに発生することで、通常運用の範囲内なので簡単ですね。
% git branch -m master main
これにより、ローカルのmaster
がmain
に変わりました。
% git branch -a
* main
remotes/origin/HEAD -> origin/master
remotes/origin/master
configもそこだけ変わっており、他はまったく何も変わっていません。リモートリポジトリの処理に関しては何の問題解決にもなっていないとも言えます。
% cat .git/config
[core]
repositoryformatversion = 0
(snip)
[remote "origin"]
url = https://github.com/microsoft/vscode-docs.git
fetch = +refs/heads/master:refs/remotes/origin/master
[branch "main"]
remote = origin
merge = refs/heads/master
実体です。変化がある部分のみ記載します。特に説明は不要でしょう。
% ls .git/refs/heads .git/refs/remotes/origin
.git/refs/heads:
main
.git/refs/remotes/origin:
HEAD master
% cat .git/refs/heads/main
b301ccfa39274d648a654fe69f32b47e6d0577c4
% cat .git/HEAD
ref: refs/heads/main
Step 2: リモートリポジトリのmainブランチを認識させる
git pull
やgit fetch
を実行したときにエラーになる直接の原因は、リモートリポジトリのmaster
を見に行っているのにそのmaster
がないという点にありました。そこで、まずはmain
を見に行くようにする必要があります。現在、ローカルではリモートのmain
ブランチの存在を一切認識していないので、認識させます。
% git remote set-branches origin main
これをしておかないと、たとえgit fetch origin main
とmain
を明示して取りに行っても、origin/main
のデータの取得には成功するものの、origin/main
がリモート追跡ブランチとして登録されません。
このgit remote set-branches
はconfigをいじるだけなので、ネットワークアクセスがない状態で実行できます。また、ブランチのリストにも影響を与えません。
% git branch -a
* main
remotes/origin/HEAD -> origin/master
remotes/origin/master
configは、origin
のfetch
のみが変わっており、取得元も取得先もmain
になっていることがわかります。
% cat .git/config
[core]
repositoryformatversion = 0
(snip)
[remote "origin"]
url = https://github.com/microsoft/vscode-docs.git
fetch = +refs/heads/main:refs/remotes/origin/main
[branch "main"]
remote = origin
merge = refs/heads/master
なお、git remote set-branches --add origin main
としてやると、こんな感じでmaster
ブランチをfetch
する設定を残せます。今回は不要なので置き換えています。
[remote "origin"]
url = https://github.com/microsoft/vscode-docs.git
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/main:refs/remotes/origin/main
実体です。一切変化はありません。まず、git branch -a
の結果からわかるとおり、ブランチには特に変化はありません。また、実際にリモートリポジトリに最新データを取りに行ったわけでもないので、.git/FETCH_HEAD
にも変化はありません。
% ls .git/refs/heads .git/refs/remotes/origin
.git/refs/heads:
main
.git/refs/remotes/origin:
HEAD master
% cat .git/FETCH_HEAD
b301ccfa39274d648a654fe69f32b47e6d0577c4 branch 'master' of https://github.com/microsoft/vscode-docs
Step 3: リモートリポジトリのmainを取得し、ローカルブランチmainの上流ブランチに設定する
Step 2で準備したので、実際にリモートリポジトリのmain
ブランチをリモート追跡ブランチorigin/main
としてローカルに取得します。その上で、それをmain
の上流ブランチに設定します。
% git fetch --set-upstream origin main
From https://github.com/microsoft/vscode-docs
* branch main -> FETCH_HEAD
* [new branch] main -> origin/main
この操作で行われる3つの処理
ここでやっていることは3点あります。
- リモートリポジトリの
main
を構成するデータ(コミットやblobなどのオブジェクト)をネットワーク経由で取得 - リモート追跡ブランチ
origin/main
を登録 - ローカルブランチ
main
の上流ブランチをリモート追跡ブランチorigin/main
に変更
順に見ていきましょう。
1は、特に説明は不要でしょう。コミットやblobなどのオブジェクトがなければそもそもリモートリポジトリのmain
ブランチをローカルで表現できないので必須です。わからなければPro Gitの『10.2 Gitの内側 - Gitオブジェクト』を読むとイメージが掴めるはずです。
この1だけであれば、実は最初の段階からgit fetch origin main
で可能でした。しかしそれだと2以降ができないので、Step 2を実施しました。
2では、origin/main
をリモート追跡ブランチとして登録します。何度も書いているように、Step 2でorigin/main
を認識させたことで、この追跡が可能になりました。
2までであれば、実は、git fetch
というシンプルなコマンドで可能でした。Step 2で、main
でgit fetch
するとorigin/main
を更新しに行くように設定したためです。しかし、これだけだと、リモート追跡ブランチorigin/main
はローカルブランチmain
の上流ブランチになっていません。なので、少し中途半端な状態です。
3は、現在のmain
のマージ対象がorigin/master
になっているのを、origin/main
に変えてやる作業です。2でorigin/main
を更新しても、現在のmain
のマージ元がorigin/master
になっている以上、3をやらないと、origin/main
の更新内容がmain
に入ってこないのです。
3は、--set-upstream
オプションによって実現されています。このオプションをつけないと2止まりです。そしてこのオプションをつけるときはブランチの指定が必要なので、git fetch --set-upstream
ではダメで、git fetch --set-upstream origin main
とする必要があります。
操作の結果
説明が長くなりました。先程の操作の結果を載せます。
まず、ブランチについては、リモート追跡ブランチorigin/main
が加わっています。これは上の説明の2にあたります。
% git branch -a
* main
remotes/origin/HEAD -> origin/master
remotes/origin/main
remotes/origin/master
configではmain
のmerge
がrefs/heads/main
に書き換わっています。これは上の説明の3にあたります。
% cat .git/config
[core]
repositoryformatversion = 0
(snip)
[remote "origin"]
url = https://github.com/microsoft/vscode-docs.git
fetch = +refs/heads/main:refs/remotes/origin/main
[branch "main"]
remote = origin
merge = refs/heads/main
実体です。
% ls .git/refs/heads .git/refs/remotes/origin
.git/refs/heads:
main
.git/refs/remotes/origin:
HEAD main master
% cat .git/refs/heads/main
ed61526ebb39ec00809fce8a75fa4189d0d51ad6
% cat .git/refs/remotes/origin/main
ed61526ebb39ec00809fce8a75fa4189d0d51ad6
% cat .git/refs/remotes/origin/master
b301ccfa39274d648a654fe69f32b47e6d0577c4
% cat .git/refs/remotes/origin/HEAD
ref: refs/remotes/origin/master
% cat .git/FETCH_HEAD
ed61526ebb39ec00809fce8a75fa4189d0d51ad6 branch 'main' of https://github.com/microsoft/vscode-docs
origin/main
用の参照ができています。main
の参照の内容も、origin/main
の参照と同じSHA1ハッシュ値に変わっています。一方で、origin/master
の参照はfetch
の指定から外れたので更新されていません。
git fetch
に伴って.git/FETCH_HEAD
が更新されています。
git branch -a
と.git/refs/remotes/origin/HEAD
の内容から、origin/HEAD
はorigin/master
を指したままだとわかります。
Step 4: リモートリポジトリのデフォルトブランチをmainに変更する
概ね問題なくなりました。ただ、デフォルトブランチを表すorigin/HEAD
が引き続きorigin/master
を指してしまっているので、最後にこれを直します。以下のコマンドのどちらかになります。
リモートリポジトリの情報をもとに自動的に設定する方法(ネットワークアクセス必要):
% git remote set-head -a origin
origin/HEAD set to main
明示する方法(ネットワークアクセス不要):
git remote set-head origin main
前者は、実際にリモートリポジトリにアクセスして、どのブランチがデフォルトブランチになっているのかの情報を取得します。そのため、(リモートリポジトリがネットワークの向こうにある場合は)ネットワークアクセスが必要ですし、ローカルアクセスのみの後者に比べると遅いです(といっても、通常のインターネット回線なら、かかっても1秒程度かと思いますが)。
origin/HEADの役割
origin/HEAD
の役割は、「ブランチ名を指定するところでorigin
と指定したときに、それをorigin
のデフォルトブランチ(たとえばorigin/main
)のことだと解釈できるようにする」というものです。たとえば、git log origin
と実行したときにgit log origin/main
のことだと解釈してくれるのは、このorigin/HEAD
が正しく設定されているからです。
したがって、上のコマンドでorigin/HEAD
の向く先をorigin/master
からorigin/main
に直さないと、origin
と指定したときにorigin/master
のほうを見に行ってしまい、混乱のもとです。
操作の結果
origin/HEAD
が正しく設定されました。
% git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
remotes/origin/master
もちろん、実体も適切です。
% cat .git/refs/remotes/origin/HEAD
ref: refs/remotes/origin/main
完成形
これで、リモートリポジトリのデフォルトブランチ名変更への追従は終わりました。あとは今までどおりgit pull
などで使えます。
% git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
remotes/origin/master
% cat .git/config
[core]
repositoryformatversion = 0
(snip)
[remote "origin"]
url = https://github.com/microsoft/vscode-docs.git
fetch = +refs/heads/main:refs/remotes/origin/main
[branch "main"]
remote = origin
merge = refs/heads/main
origin/master
が残っているのが少し気になりますが、害はありませんし消そうと思えば消せるので、ひとまずこれで完成とします(git fetch --prune
やgit remote prune origin
やgit remote update --prune
で消えるかと思ったのですが、消えませんでした)。
最後に
「git pull
できない」という問題をきっかけに、リモートリポジトリのデフォルトブランチ名変更(master → main)に追従する方法を扱いながら、Gitの内部構造の解説を少ししました。少し冗長だったかもしれません。
Gitのいいところは、そのパフォーマンスとは裏腹に、シンプルなディレクトリやテキストファイルでデータが表現されているので、いざとなれば自分でも簡単なUnixコマンドなどである程度調査や問題解決ができるところです。ぜひ触って親しんでいただければと思います。
用語・バージョン
用語:
-
デフォルトブランチ:
master
やmain
のような、すべての履歴の中心となるブランチ。何もブランチを指定しない場合に参照されるブランチ。 - デフォルトブランチ名のデフォルト:様々なリポジトリの「デフォルトブランチ」の名前に使う文字列のデフォルト。バラバラだと操作しにくいのでふつうは組織などで統一する。
- ローカルブランチ:ローカルリポジトリで作成したブランチ。リポジトリ作成時に自動的に作られるもの以外は、基本的に自分で作る。
-
リモート追跡ブランチ:ローカルリポジトリのブランチのうち、リモートリポジトリのブランチがそのまま反映される(リモートリポジトリのブランチを追跡する)もの。
origin/main
など。 - 上流ブランチ:あるローカルブランチがあるリモート追跡ブランチの上に作られている場合、そのリモート追跡ブランチはそのローカルブランチの上流ブランチであると言う。
確認に用いたバージョン:
% git --version
git version 2.28.0
参考資料:デフォルトブランチ関連の動き
-
2020-06-23: Gitのデフォルトブランチ名の変更に関する声明
- Software Freedom ConservancyとGitプロジェクトが、Gitのデフォルトブランチ名を攻撃的(offensive)だと感じる人がいるので変更に向けて動く旨を、声明として発表しました。
- 参考:Regarding Git and Branch Naming (Software Freedom Conservancy, June 23, 2020)
-
2020-07-27: Git 2.28での
init.defaultBranch
の導入-
init.defaultBranch
という設定で、git init
による新規リポジトリ作成時のデフォルトブランチを設定可能になりました。それまでもリポジトリ作成後の変更(運用)は自由でしたが、作成時はmasterにハードコードされていました。 - 参考:Highlights from Git 2.28 (The GitHub Blog, July 27, 2020)
-
-
2020-08-26: GitHubで新規リポジトリのデフォルトブランチ名のデフォルトを設定可能に
- デフォルトブランチ名のデフォルトをアカウントごとに個人設定で設定可能になりました。
- 参考:Set the default branch for newly-created repositories (The GitHub Blog, August 26, 2020)
-
2020-10-01: GitHubで新規リポジトリのデフォルトブランチ名のデフォルトがmainに
- デフォルトブランチ名のデフォルトを設定で変更していない場合、masterでなくmainが使われるようになりました。8月26日の変更をさらに一歩推し進めたものです。
- 参考:The default branch for newly-created repositories is now main (The GitHub Blog, October 1, 2020)