🗃️

git cloneを早くしたいんだよなぁ

2023/10/19に公開

モチベーション

デプロイメントパイプラインの中で、結構大きめのMonorepoをcloneしている。それが遅いのでどうにかしたい。

どうやら対象のリポジトリは33Gくらいあるっぽい。(通常のclone後)

環境

$ git -v
git version 2.42.0
$ scalar version
git version 2.42.0

対応

gitにはいろいろなcloneの仕方があり、軽くすることができる。詳細は参考に載せているリンクだったり、helpを見て欲しい。

$ git clone -h
usage: git clone [<options>] [--] <repo> [<dir>]

    -v, --verbose         be more verbose
    -q, --quiet           be more quiet
    --progress            force progress reporting
    --reject-shallow      don't clone shallow repository
    -n, --no-checkout     don't create a checkout
    --bare                create a bare repository
    --mirror              create a mirror repository (implies bare)
    -l, --local           to clone from a local repository
    --no-hardlinks        don't use local hardlinks, always copy
    -s, --shared          setup as shared repository
    --recurse-submodules[=<pathspec>]
                          initialize submodules in the clone
    --recursive[=<pathspec>]
                          alias of --recurse-submodules
    -j, --jobs <n>        number of submodules cloned in parallel
    --template <template-directory>
                          directory from which templates will be used
    --reference <repo>    reference repository
    --reference-if-able <repo>
                          reference repository
    --dissociate          use --reference only while cloning
    -o, --origin <name>   use <name> instead of 'origin' to track upstream
    -b, --branch <branch> checkout <branch> instead of the remote's HEAD
    -u, --upload-pack <path>
                          path to git-upload-pack on the remote
    --depth <depth>       create a shallow clone of that depth
    --shallow-since <time>
                          create a shallow clone since a specific time
    --shallow-exclude <revision>
                          deepen history of shallow clone, excluding rev
    --single-branch       clone only one branch, HEAD or --branch
    --no-tags             don't clone any tags, and make later fetches not to follow them
    --shallow-submodules  any cloned submodules will be shallow
    --separate-git-dir <gitdir>
                          separate git dir from working tree
    -c, --config <key=value>
                          set config inside the new repository
    --server-option <server-specific>
                          option to transmit
    -4, --ipv4            use IPv4 addresses only
    -6, --ipv6            use IPv6 addresses only
    --filter <args>       object filtering
    --also-filter-submodules
                          apply partial clone filters to submodules
    --remote-submodules   any cloned submodules will use their remote-tracking branch
    --sparse              initialize sparse-checkout file to include only files at root
    --bundle-uri <uri>    a URI for downloading bundles before fetching from origin remote

で、使うのが --filter=blob:none--no-checkout オプション。
scalarという大規模リポジトリ向けのコマンドでcloneするときと同じclone方式になる。

実践

IntelliJだったらリポジトリでかいだろとういことで試していく。

$ time git clone git@github.com:JetBrains/intellij-community.git

Cloning into 'intellij-community'...
remote: Enumerating objects: 8641478, done.
remote: Counting objects: 100% (23050/23050), done.
remote: Compressing objects: 100% (8602/8602), done.
remote: Total 8641478 (delta 10917), reused 21922 (delta 9869), pack-reused 8618428
Receiving objects: 100% (8641478/8641478), 4.36 GiB | 8.64 MiB/s, done.
Resolving deltas: 100% (5147491/5147491), done.
Updating files: 100% (212804/212804), done.
git clone git@github.com:JetBrains/intellij-community.git  334.45s user 143.63s system 74% cpu 10:45.88 total

$ du -h -d 0 intellij-community/
6.0G	intellij-community/

我が家のネットワークの問題もあると思うが、10分以上かかっている…

$ time git clone --filter=blob:none --no-checkout git@github.com:JetBrains/intellij-community.git

Cloning into 'intellij-community'...
remote: Enumerating objects: 6863068, done.
remote: Counting objects: 100% (18691/18691), done.
remote: Compressing objects: 100% (5920/5920), done.
remote: Total 6863068 (delta 9107), reused 17717 (delta 8203), pack-reused 6844377
Receiving objects: 100% (6863068/6863068), 733.09 MiB | 9.50 MiB/s, done.
Resolving deltas: 100% (3693568/3693568), done.
git clone --filter=blob:none --no-checkout   87.21s user 76.39s system 128% cpu 2:07.74 total

--filter=blob:none --no-checkout だと2分ちょい。

du -h -d 0 intellij-community/
954M	intellij-community/

そりゃ軽いよねー。

ls -la intellij-community/
total 0
drwxr-xr-x@  3 tenten0213  staff   96 10 19 19:19 .
drwxr-xr-x@  3 tenten0213  staff   96 10 19 19:19 ..
drwxr-xr-x@ 11 tenten0213  staff  352 10 19 19:21 .git

--filter=blob:none はHEADより過去のBlobを取得しないし、 --no-checkout でHEADのBlobも取得しない。

じゃあどうやってデプロイメントパイプラインを流していくのかというと、必要となるディレクトリだけチェックアウトしていく。

本来はこんな感じでいっぱいディレクトリがあるわけなんだけど、絞ってjavaディレクトリだけチェックアウトしてみる。

$ ls intellij-community/
CODE_OF_CONDUCT.md				getPlugins.sh					notebooks
CONTRIBUTING.md					idea						platform
Dockerfile					images						plugins
LICENSE.txt					installers.cmd					python
NOTICE.txt					intellij.idea.community.main.android.iml	qodana.yaml
README.md					intellij.idea.community.main.iml		resources
RegExpSupport					intellij.yaml					resources-en
aether-dependency-resolver			java						spellchecker
bin						jps						test-log.properties
build						json						tests.cmd
build.txt					jupyter						tools
build.xml					jvm						uast
community-resources				lib						updater
docs						license						xml
getPlugins.bat					native

一部のディレクトリだけチェックアウトするには git sparse-checkout コマンドを使う。

https://git-scm.com/docs/git-sparse-checkout

こんな感じで初期化&欲しいディレクトリを追加してー

$ cd intellij-community
$ git sparse-checkout init --cone
$ git sparse-checkout add java
$ git sparse-checkout list
java

部分的なチェックアウトになるので、サイズも小さいし、21秒でチェックアウトできた。

$ time git checkout master
remote: Enumerating objects: 46218, done.
remote: Counting objects: 100% (19640/19640), done.
remote: Compressing objects: 100% (13979/13979), done.
remote: Total 46218 (delta 7823), reused 5873 (delta 5658), pack-reused 26578
Receiving objects: 100% (46218/46218), 29.14 MiB | 6.88 MiB/s, done.
Resolving deltas: 100% (14452/14452), done.
Updating files: 100% (49454/49454), done.
Already on 'master'
Your branch is up to date with 'origin/master'.
git checkout master  5.14s user 7.63s system 59% cpu 21.399 total
$ ls java
compiler		java-analysis-api	java-frontback-tests	java-psi-api		jsp-base-openapi	mockJDK-1.8		structuralsearch-java
debugger		java-analysis-impl	java-impl		java-psi-impl		jsp-openapi		mockJDK-1.9		testFramework
execution		java-features-trainer	java-impl-inspections	java-runtime		jsp-spi			openapi			typeMigration
ide-customization	java-frontback-impl	java-impl-refactorings	java-structure-view	manifest		performancePlugin
ide-resources		java-frontback-psi-api	java-indexing-api	java-tests		mockJDK-1.4		plugin
idea-ui			java-frontback-psi-impl	java-indexing-impl	jdkAnnotations		mockJDK-1.7		remote-servers

こんな感じで、欲しいディレクトリだけチェックアウトしていけば巨大なmonorepo全体をチェックアウトするより早くすむ、はず。

参考

Discussion