git clone解説

以下は社内向け勉強会のLT枠で話した内容をベースにして編集増補したものである。増補しただけでなくそもそも1回分を記事にしたものではなかったりもするので、この内容を5分で話したわけではない。

## git cloneコマンドの概要

git cloneはリポジトリを複製するためのコマンドである。

通常、単純にオプションのない`git clone <複製元リポジトリ>`で実行されることが多い。これによって行われる処理は以下の通りである。

* 複製元リポジトリのすべてのブランチ（およびその中にあるすべての[オブジェクト](https://qiita.com/yunano/items/49cee64b98e242458a15#git-pushコマンドの概要)）をリモート追跡ブランチとして取得する
* 複製元リポジトリのHEADが指していたブランチ、つまり複製元で現在作業中だったブランチへ切り替える
* 切り替え後のブランチの指すコミットのファイルをインデックスおよびワークツリーにコピーする

その一方、git cloneのオプションにはクローンに掛かる時間を短縮するためのオプションが数多く用意されている。

## git cloneコマンドの基本の形

git cloneコマンドは`git clone [オプション] <複製元リポジトリ> [<複製先ディレクトリ>]`という形を取る。

複製先ディレクトリには存在しないディレクトリか空のディレクトリを指定する。指定したディレクトリはなければ作成されてから複製が行われる。

複製先ディレクトリが省略された場合は、リポジトリの"humanish"が指定されたものと扱われる。"humanish"は複製元リポジトリが例えば<https://github.com/your_account/repo1.git>ならば"repo1"である。詳しくは次の手順で決定されている。

* 複製元リポジトリの値から最初の"://"までを取り除く
* 上記から最後の"\@"までを取り除く
* 上記から末尾の一連のスペースと"/"を取り除く
* 上記から末尾の"/.git"を取り除き、さらに末尾の一連の"/"を取り除く
* 上記が"/"を含まず、":"を含み、":"の後がすべて数字の場合、":"以降を取り除く（これは":"以降がポート番号と見なされるからである）
* 上記から最後の"/"か":"かのどちらか後に出てきた方までを取り除く
* 上記から末尾の".git"を取り除く。あるいは指定した複製元リポジトリがgit bundleによって作成されたバンドルファイルである場合、代わりに末尾の".bundle"を取り除く

複製元リポジトリはローカルのパス、SCPのパス指定形式（<ユーザー名>\@<ホスト>:<パス>）、URLのいずれかを指定する。URLのスキーム、つまり"://"以前の部分には通常http, https, ssh, git, fileが使用できるが、他のスキームもリモートヘルパー（例：[git-remote-dropbox](https://github.com/anishathalye/git-remote-dropbox)）と呼ばれる拡張がインストールされていれば使用できる。

### 標準的なgit cloneで行われる処理

標準的な、つまりオプションのない`git clone <複製元リポジトリ>`で行われる処理は以下の通りである。

* 複製元リポジトリのすべてのブランチ（およびその中にあるすべての[オブジェクト](https://qiita.com/yunano/items/49cee64b98e242458a15#git-pushコマンドの概要)）をリモート追跡ブランチとして取得する
* 複製元リポジトリのHEADが指していたブランチ、つまり複製元で現在作業中だったブランチへ切り替える
* 切り替え後のブランチの指すコミットのファイルをインデックスおよびワークツリーにコピーする

## クローン実行時間の短縮

しかしこの標準的なgit cloneを実行するとリポジトリ内のオブジェクトがすべて取得されるため、複製元リポジトリが非常に大きければ非常に時間が掛かることになる。

そのため、git cloneにはいくつかの手段でクローンに掛かる時間を短縮するためのオプションが存在する。オプションには大きく分けて「ダウンロード量を減らす」ものと「ファイルコピー量を減らす」ものとがある。

### ダウンロード量を減らすオプション

#### 単一のブランチだけを取得

[オプションのないgit cloneで行われる処理](#git-cloneコマンドの概要)のうち最初の処理である「複製元リポジトリのすべてのブランチをリモート追跡ブランチとして取得」について、--single-branchオプションを付けた場合代わりに1つのブランチだけを取得することができる。このブランチは複製元リポジトリのHEADが指していたブランチである。但し、「-b (ブランチ名)」オプションを付けてgit cloneを行っていた場合は-bオプションの後に指定したブランチとなる。

つまり通常のクローンでは実行後に「すべてのリモート追跡ブランチと1つのローカルブランチ」となるところを、--single-branchオプションを付けて実行すると「1つのリモート追跡ブランチと1つのローカルブランチ」となる。

注意事項としては--single-branchオプションを使ってgit cloneを行った場合、`git fetch`を実行した時に他のブランチを取得できる設定にはならない。つまり、`git config remote.origin.fetch`を実行すると以下のような結果になる。但し、「-o (名前)」オプションを付けてgit cloneを行っていた場合はコマンド及び結果の"origin"の部分が-oオプションの後に指定した名前となる。

```text
+refs/heads/main:refs/remotes/origin/main
```

これは`git fetch`を実行した時にmainブランチだけを取得できる、という設定である。この状態から、もし`git fetch`を実行した時にすべてのブランチを取得できる設定にしたければ`git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`を実行すれば良い。また、その後に`git fetch`すれば--single-branchオプションなしでクローンしたのと同じ状態となる。

#### シャロークローン

ブランチのコミット履歴のうち、最新のいくつかのコミットだけを取得するクローンの仕方をシャロークローンと呼ぶ。

シャロークローンを行うオプションは3つある。

* 「--depth (数字)」を指定すると最新の指定した数のコミットを取得する
* 「--shallow-since (日時)」を指定すると[CommitDate](https://qiita.com/yunano/items/86e9657ca5cbfdbbd7ed#コミット情報)が指定した日時より新しいコミットを取得する
* 「--shallow-exclude (タグかブランチ)」で指定したタグかブランチが指すコミット（複製元リポジトリのHEADが指していたブランチの履歴に含まれない場合は最新の共通祖先）より新しいコミットを取得する

また、シャロークローン時に--no-single-branchオプションが指定されていなければ、前項の--single-branchオプションが暗黙的に指定されたものと見なされ、単一のブランチだけが取得される。

シャロークローンを行った場合、切り捨てられたコミットを使う作業は全くできないため、コミットを行う作業用リポジトリにするのにはあまり適さない。一方、直近分の閲覧用途や、CI/CD等で最新分をすぐに使い捨てる用途には適している。この場合は「--depth 1」を指定することになるだろう。

なお、シャロークローン後にgit fetchやgit pullを行うとほとんどのコミット履歴を取得してしまうことがあるようだ。また、git fetchやgit pullには--unshallowオプションがあり、明示的にシャロークローン状態を解除して完全なクローンにすることが可能である。これにより、一旦シャロークローンで最新の閲覧をできるようにしてから後でゆっくりとすべてを取得する、ということができる。ただし、単一のブランチだけ取得されている場合に--unshallowによって単一のブランチ以外も取得するようになるわけではない。

#### パーシャルクローン

オブジェクトを必要になる時までダウンロードしないクローンの仕方をパーシャルクローンと呼ぶ。

パーシャルクローンを行うには--filterオプションを使用する。例えば「--filter blob:none」を指定してクローンすると必要になるまで[ブロブ](https://qiita.com/yunano/items/49cee64b98e242458a15#git-pushコマンドの概要)がダウンロードされないし、「--filter blob:limit=1m」を指定すると1MiB以上のブロブは必要となるまでダウンロードされない。

このようにブロブのみをフィルターした場合、当然取得していないブロブを参照するような作業、例えば`git diff`等に掛かる時間は増えるトレードオフはあるが、作業用リポジトリとするのにも問題はない。

#### ローカルへのクローン

複製元リポジトリにローカルのパスを指定した場合、通常のネットワーク経由のダウンロードではなく単純なコピーが行われ、さらにオブジェクト置き場である.git/objects内のファイルは可能ならば複製元リポジトリのファイルのハードリンクとしてクローン時間やスペースを節約しようとする。

ローカルのパスでなくfileスキームのURLにしたり、--no-localオプションを指定したりした場合は通常通りのローカルでないクローンとなる。また、ネットワークを介した転送でなくコピーで良いが.git/objects内のファイルをハードリンクではなく普通のファイルにしたい場合は--no-hardlinksオプションを指定することによってもできる。例えばバックアップ目的でクローンしている場合等ハードリンクではなくコピーにしたい場合に有用である。

なお、シャロークローンやパーシャルクローンができるのはローカルでないクローンの場合のみである。--single-branchオプションはローカルへのクローンでも有効だが、オブジェクトのハードリンクあるいはコピーは切り替わるブランチに所属するもの以外も行われる。

### ファイルコピー量を減らすオプション

このファイルコピーは[オプションのないgit cloneで行われる処理](#git-cloneコマンドの概要)のうち、最後の「切り替え後のブランチの指すコミットのファイルをインデックスおよびワークツリーにコピーする」部分のことであり、チェックアウトと呼ぶ。

ファイルコピーと言っても圧縮されてれば展開する時間も必要になるので、ファイルコピー量が減ればその時間も減らすことができる。

また、これらのオプションはパーシャルクローンとの相性が良い。パーシャルクローンは必要時までダウンロードが行われないからである。

#### スパースチェックアウト

一部のディレクトリのファイルだけをチェックアウトすることをスパースチェックアウトと呼ぶ。

git cloneに--sparseオプションを指定した場合、ルートディレクトリのファイルだけチェックアウトされる。

その後に別のディレクトリのファイルをチェックアウトするなら、`git sparse-checkout add [<ディレクトリ>...]`あるいは`git sparse-checkout set [<ディレクトリ>...]`を実行するとサブディレクトリまで再帰的にチェックアウトされる。addとsetの違いはaddは追加でチェックアウトされるが、setはこれまでチェックアウトされたルートディレクトリのファイル以外は消え、指定されたディレクトリがチェックアウトされることである。

また、`git sparse-checkout disable`でスパースチェックアウトを終了し、すべてのファイルをチェックアウトすることができる。

スパースチェックアウトは一部のディレクトリでのみ作業を行う場合に有用である。

#### チェックアウトを行わない

-nあるいは--no-checkoutオプションを指定した場合、すべてのファイルのチェックアウトを行わない。

スパースチェックアウトではチェックアウトされていないファイルはまだチェックアウトされていない状態だが（そのため、`git status`で見ると変更なしになっている）、-nオプションではファイルがチェックアウトされずにチェックアウトが完了した状態となる（そのため、`git status`で見ると各ファイルがインデックスおよびワークツリーから削除された状態になっている）。

なお、チェックアウトせずにクローンした後に`git checkout HEAD .`あるいは`git restore -s HEAD -SW .`を実行すれば-nなしでクローンしたのと同じ状態となる。

-nオプションは、Gitにスパースチェックアウト機能が追加されたv2.25時点ではスパースチェックアウトを始める時に使用していたが、現在（v2.27以降）はその用途では使用できない。ブランチの最新コミットが不要な時（すぐに新規のブランチをブランチの最新コミット以外から作る時等）少しでもクローンの完了を早くしたい、という場合等に有用である。

### 別のリポジトリのオブジェクトを参照する

同じ複製元からの2回目以降のクローンを行う場合等、ローカルマシン上に同じオブジェクトを持つ別のリポジトリがすでにある場合、オブジェクトは別のリポジトリを参照するようにしてクローン時にダウンロード量もファイルコピー量も減らすことができる。

そのようにするにはgit cloneに「--reference <別リポジトリのローカルパス>」オプションあるいは「--reference-if-able <別リポジトリのローカルパス>」オプションを指定する。--referenceの場合は別リポジトリがなければエラーとなり、--reference-if-ableの場合は別リポジトリがなければ警告され参照を使わないクローンになる。参照元に存在しないオブジェクトは複製元から取得される。

クローンが終わった後もずっと参照し続けるので、クローン後の操作で参照元にオブジェクトがなくなれば、あるいは参照元リポジトリ自体がなくなればオブジェクトを参照することができなくなりエラーとなる。オブジェクトを削除するgit gcは他のコマンドでも内部的にトリガーされる。従って参照元では、ブランチの削除等でどのブランチの履歴からも外れたコミットを発生させる等、オブジェクトが不要になるようなあらゆる行為は行ってはならない。

なお、追加でクローン時に--dissociateオプションも指定した場合、参照を行わず代わりにクローン時に参照元として指定したリポジトリからオブジェクトをファイルコピーするだけなので、その後に参照元が削除されても安全である。

## その他の便利なオプション

git cloneにはクローン時間を短縮するもの以外にもいくつかのオプションがある。良く使われるのを見掛けるものを紹介する。

* --bare

クローンした時に複製先のリポジトリをベアリポジトリにする。ベアリポジトリとは、サーバとしてだけ振る舞い、その上では作業を行わないことが想定されているリポジトリである。リポジトリがプッシュ先やフェッチ（プル）元になるには、設定でわざわざ許可しない限りはベアリポジトリである必要がある。

ベアリポジトリでないリポジトリが.git以下に各種のリポジトリ情報ファイルが置かれているのに対し、ベアリポジトリは.gitではなくリポジトリのルートに直接リポジトリ情報ファイルが置かれる。そのためベアリポジトリはワークツリーを持たない。ワークツリーを持たないとチェックアウトもできないため、暗黙的に-nオプションも付いている扱いとなる。

* --mirror

--bareオプションと同じくベアリポジトリになるが、それに加えて今後も`git remote update`で複製元と同期、つまり各ローカルブランチが指すコミットを再度複製元と同じにすることができる。

* --recurse-submodules, --recursive

git submoduleによって登録されていた複製元の外部リポジトリも一緒にクローンする。これらのオプションを付けずにクローンした後、`git submodule update --init --recursive`を実行するのと同じである。

* -b <ブランチ名>, --branch <ブランチ名>

ブランチへの切り替えの際に、このオプションで指定したブランチへ切り替える。何も指定しなかった場合は複製元リポジトリのHEADが指していたブランチとなる。

* -o <リポジトリ名>, --origin <リポジトリ名>

リモートのリポジトリ名を指定したものにする。何も指定しなかった場合は"origin"となる。

* -c <キー>=<値>, --config <キー>=<値>

複製先リポジトリに指定したキーと値を持つ設定を加える。つまり`git config <キー> <値>`を実行したのと同じになる。このオプションは複数回指定できる。

これで追加した設定はクローン自体にも影響するがremote.<リポジトリ名>.mirrorやremote.<リポジトリ名>.tagOptといった一部のオプションはクローン後に有効になる。これらはそれぞれ--mirrorオプションや--no-tagsオプション（すべてのタグをクローンしない）でクローン時に有効にできる。