LoginSignup
1
2

More than 3 years have passed since last update.

リモートGitリポジトリの特定リビジョンのファイル一覧を得る方法

Last updated at Posted at 2019-09-22

はじめに

これは「Git リポジトリのあるリビジョンのファイル全部を取得する方法があるんだから、ファイル一覧だけを効率よく取得する方法もどこかにあるのでは?」と思った人間が「結局通常のケースでは ssh して bare リポジトリを直接見るか、ローカルに shallow clone するのが最適解じゃねーか!」と気づくまでの記録です。

結論 (TL;DR)

  • ファイル一覧を得たいユーザーがそもそもリモートホストに ssh ログインできる場合
    • ssh <user>@<host> git --git-dir <repo> ls-tree -r <revision>
  • ファイル一覧を得たいユーザーがリモートホストに ssh ログインできない場合(リモートが GitHub の場合など)
    • git clone --bare --depth 1 --branch <revision> <remote-repo> && git --git-dir <repo> ls-tree -r <revision> (shallow clone)
    • もしくは git archive --remote <remote-repo> <revision> | tar -tf -
  • ファイル一覧を得たいユーザーは ssh ログインできないが、リモート側・クライアント側双方で Git v2.22 以上を使っていて、リモートリポジトリに対して git config する権限のあるアカウントがある場合
    • リモート側で git config uploadpack.allowfilter true && git config uploadpack.allowanysha1inwant true する
      • uploadpack.allowfilter: v2.22 より前のバージョンには --filter=sparse:path=<path> の脆弱性があるため注意
      • uploadpack.allowanysha1inwant: リモートリポジトリの任意の object をクライアント側から取得できるようにするつよいオプションなので扱いには注意
    • クライアント側で git clone --bare --depth 1 --branch <revision> --filter blob:none <remote-repo> && git --git-dir <repo> ls-tree -r <revision>

なお、「ssh ログインできる」とは「ssh 先のリモート bare リポジトリに対してある程度自由に操作を行える(シェルが nologin だったりミドルウェアによる制限がかかっていたりしない)」ことを指します。

下準備

今回はLAN内のリモートホスト example.localgit/git - GitHubgit clone --bare して置いてあります。

$ git clone --bare https://github.com/git/git  # (in git@example.local)

この bare リポジトリ全体のファイルサイズは 131 MB です。

$ du -sh git.git
131M    git.git

このリポジトリの v2.23.0 タグ時点でのファイル一覧を取得したい、という状況を考えます。

そもそもリモートホストに ssh ログインできる場合

bare リポジトリの特定リビジョンのファイル一覧を見るには git ls-tree を使います。-r は tree を再帰的に処理するオプションです。また、--git-dir オプション(または GIT_DIR 環境変数)を指定することで cd することなく操作対象の Git リポジトリを選択できます。

$ ssh <user>@<host> git --git-dir <repo> ls-tree -r <revision>

実行例は以下の通り。

$ time sh -c 'ssh git@example.local git --git-dir ./git.git ls-tree -r v2.23.0 | tail'
100644 blob a4285ac0eb2840fb3d128b81ab4e19ca3fa673ae    xdiff/xinclude.h
100644 blob 2809a28ca960147c285bc5a224ed377a0964663a    xdiff/xmacros.h
100644 blob 1659edb45393a6b57ca70654e67784e5d0025c65    xdiff/xmerge.c
100644 blob 3c5601b602a24cb6cd6e063466369b1a90a14f9f    xdiff/xpatience.c
100644 blob abeb8fb84e6d73086d612b831963a227e35743b8    xdiff/xprepare.c
100644 blob 947d9fc1bb8cf95719284de6563227485907988f    xdiff/xprepare.h
100644 blob 8442bd436efeab81afc25db9d89da082638fcca4    xdiff/xtypes.h
100644 blob cfa6e2220ffd0461ce3293911c86366f6ccb1b05    xdiff/xutils.c
100644 blob fba7bae03c7855ca90aff3f238321581a91a6676    xdiff/xutils.h
100644 blob d594cba3fc9d82d94b9277e886f2bee265e552f6    zlib.c

real    0m0.237s
user    0m0.024s
sys     0m0.003s

処理時間は 0.2 秒でした。流石にこれが一番高速ですね……。

リモートホストに ssh ログインできない場合

shallow clone する

自由に bare リポジトリを触る権限がない人は素直に --depth 1 を付けて shallow clone しましょう。--branch で対象リビジョンを指定することができます。

$ git clone --bare --depth 1 --branch <revision> <remote-repo> && git --git-dir <repo> ls-tree -r <revision>

実行例は以下の通り。

$ time sh -c 'git clone --bare --depth 1 --branch v2.23.0 git@example.local:git.git && git --git-dir git.git ls-tree -r v2.23.0 | tail'
Cloning into bare repository 'git.git'...
remote: Enumerating objects: 3719, done.
remote: Counting objects: 100% (3719/3719), done.
remote: Compressing objects: 100% (3330/3330), done.
remote: Total 3719 (delta 282), reused 1889 (delta 235)
Receiving objects: 100% (3719/3719), 8.54 MiB | 5.74 MiB/s, done.
Resolving deltas: 100% (282/282), done.
100644 blob a4285ac0eb2840fb3d128b81ab4e19ca3fa673ae    xdiff/xinclude.h
100644 blob 2809a28ca960147c285bc5a224ed377a0964663a    xdiff/xmacros.h
100644 blob 1659edb45393a6b57ca70654e67784e5d0025c65    xdiff/xmerge.c
100644 blob 3c5601b602a24cb6cd6e063466369b1a90a14f9f    xdiff/xpatience.c
100644 blob abeb8fb84e6d73086d612b831963a227e35743b8    xdiff/xprepare.c
100644 blob 947d9fc1bb8cf95719284de6563227485907988f    xdiff/xprepare.h
100644 blob 8442bd436efeab81afc25db9d89da082638fcca4    xdiff/xtypes.h
100644 blob cfa6e2220ffd0461ce3293911c86366f6ccb1b05    xdiff/xutils.c
100644 blob fba7bae03c7855ca90aff3f238321581a91a6676    xdiff/xutils.h
100644 blob d594cba3fc9d82d94b9277e886f2bee265e552f6    zlib.c

real    0m2.708s
user    0m0.593s
sys     0m0.161s

処理時間は 2.7 秒、展開後のリポジトリのサイズは 8.8 MB でした。

$ du -sh git.git
8.8M    git.git

ちなみに --bare でなく --no-checkout オプションを使ってもだいたい同じような速度になります。こちらでは bare でない普通のリポジトリとして clone されるので時と場合に応じて使い分けましょう。

git archive を使う

git archive--remote オプションと tar コマンドを組み合わせることでもファイル一覧を得られます。

$ time sh -c 'git archive --remote git@example.local:git.git v2.23.0 | tar -tf - | tail'
xdiff/xinclude.h
xdiff/xmacros.h
xdiff/xmerge.c
xdiff/xpatience.c
xdiff/xprepare.c
xdiff/xprepare.h
xdiff/xtypes.h
xdiff/xutils.c
xdiff/xutils.h
zlib.c

real    0m3.538s
user    0m0.307s
sys     0m0.279s

処理時間は 3.5 秒でした。今回は shallow clone した方が少し高速だったようです。

Git v2.22 以上を使っていてリモートリポジトリに対して git config する権限のあるアカウントがある場合

これは「自分はリモートリポジトリの git config 権限を持っている」「ssh 権限がない人でも高速にファイル一覧を取得できるようにしたい」というようなケースを想定しています。また、サーバー側に v2.22 以上の Git が入っている必要があります。実はもう少し古いバージョンでも可能なのですが脆弱性(後述)が存在するため注意が必要です。

あらかじめリモート側で以下の作業をしておきます。

$ cd git.git  # (in git@example.local)
$ git config uploadpack.allowfilter true
$ git config uploadpack.allowanysha1inwant true

git.git/config はこんな感じになります。

[core]
        repositoryformatversion = 0
        filemode = true
        bare = true
[remote "origin"]
        url = https://github.com/git/git
[uploadpack]
        allowfilter = true
        allowanysha1inwant = true

そして、クライアントで shallow clone する際に --filter=blob:none オプションを付けます。これにより blob (ファイルの中身)を除外して一覧のみをダウンロードすることができます。

$ git clone --bare --depth 1 --branch <revision> --filter blob:none <remote-repo> && git --git-dir <repo> ls-tree -r <revision>

実行例は以下の通り。

$ time sh -c 'git clone --bare --depth 1 --branch v2.23.0 --filter blob:none git@example.com:git.git && git --git-dir git.git ls-tree -r v2.23.0 | tail'
Cloning into bare repository 'git.git'...
remote: Enumerating objects: 194, done.
remote: Counting objects: 100% (194/194), done.
remote: Compressing objects: 100% (152/152), done.                                      
remote: Total 194 (delta 1), reused 132 (delta 1)
Receiving objects: 100% (194/194), 111.81 KiB | 11.18 MiB/s, done.
Resolving deltas: 100% (1/1), done.
100644 blob a4285ac0eb2840fb3d128b81ab4e19ca3fa673ae    xdiff/xinclude.h
100644 blob 2809a28ca960147c285bc5a224ed377a0964663a    xdiff/xmacros.h
100644 blob 1659edb45393a6b57ca70654e67784e5d0025c65    xdiff/xmerge.c
100644 blob 3c5601b602a24cb6cd6e063466369b1a90a14f9f    xdiff/xpatience.c
100644 blob abeb8fb84e6d73086d612b831963a227e35743b8    xdiff/xprepare.c
100644 blob 947d9fc1bb8cf95719284de6563227485907988f    xdiff/xprepare.h
100644 blob 8442bd436efeab81afc25db9d89da082638fcca4    xdiff/xtypes.h
100644 blob cfa6e2220ffd0461ce3293911c86366f6ccb1b05    xdiff/xutils.c
100644 blob fba7bae03c7855ca90aff3f238321581a91a6676    xdiff/xutils.h
100644 blob d594cba3fc9d82d94b9277e886f2bee265e552f6    zlib.c

real    0m0.515s
user    0m0.058s
sys     0m0.017s

処理時間は 0.5 秒、展開後のリポジトリのサイズは 196 KB でした! ファイルの中身を取得しない分高速に処理できています。

$ du -sh git.git
196K    git.git

なお、blob を取得していないファイルに対して git cat-file -p しようとするとこのタイミングで fetch が発生します。

$ git --git-dir git.git cat-file -p v2.23.0:zlib.c | head
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1/1), 1.62 KiB | 1.62 MiB/s, done.
/*
 * zlib wrappers to make sure we don't silently miss errors
 * at init time.
 */
#include "cache.h"

static const char *zerr_to_string(int status)
{
        switch (status) {
        case Z_MEM_ERROR:

--filter について

--filterblob:none を指定すると「blob を一切ダウンロードしない」という挙動になりますが、「特定のサイズ以上のファイルを fetch しない (blob:limit=<n>[kmg])」「特定の path 下にあるファイルのみ fetch する (sparse:oid=<blob-ish>)」といった使い方も可能です。これについては GitLab のドキュメントの以下のページが詳しいです。

uploadpack.allowfilter, uploadpack.allowanysha1inwant とは

uploadpack.allowfilter, uploadpack.allowanysha1inwant はいずれも git upload-pack (簡単に言うと git fetch における通信を司るやつ)の挙動を変更するオプションで、uploadpack.allowfilter はその名の通り --filter を利用することを許可するオプション、uploadpack.allowanysha1inwant は任意の object (tree, blob, commit, tag) を want (fetch) できるようにするオプションです。

「これなんでオプトイン(デフォルトで false)なの?」という疑問が生じますが、おそらくセキュリティ上の都合でしょう(gitnamespaces#uploadpack.allowReachableSHA1InWant の説明を参照)。「任意の object を fetch できる」というのはもちろん強力なオプションであり、object へのフルアクセスを誰からでも許可しているリポジトリのみこれらのオプションを有効にしましょう。まあそもそもセキュリティ上ヤバいファイルはリポジトリごと分離しておけと上記の公式の説明にも書いてあるのですが……。

また、Git v2.22 以前のバージョンでは uploadpack.allowfilter を有効にすることで --filter=sparse:path=<path> というフィルタを使えますが、この処理にはファイルシステムの任意のパスにアクセスできてしまうという問題があるため注意が必要です。

おわりに

というわけで、「結局通常のケースでは ssh して bare リポジトリを直接見るか、ローカルに shallow clone するのが最適解じゃねーか!」ということでした。なんて遠い廻り道………。

でもこれを調べている段階で Git の pack 周りの処理や知られざる Git の機能に触れることができたのでプラスにはなった、たぶん……。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2