GitDownloader
ブログ

GitHub でフォルダだけをダウンロードする方法(リポジトリ全体をクローンせずに)

GitHub でほとんどどのリポジトリを開いても、緑色の Code ボタンがあります。そこでできることは2つだけです。プロジェクト全体をクローンするか、プロジェクト全体の ZIP をダウンロードするかです。src/componentsdocs/examples、あるいは templates フォルダの中に入ってもボタンは同じ場所にありますが、得られるものはやはり全体です。GitHub の公式ドキュメントもこの点ははっきり書いています。できるのはリポジトリのファイルのスナップショットのダウンロード、クローン、フォークの3つだけです。フォルダ単体をダウンロードする第4の選択肢はありません。

そのギャップのせいで、「github フォルダ ダウンロード」は GitHub 関連で最も検索される質問の一つになり、見つかる回答もばらばらになっています。2024年1月に使えなくなった SVN コマンドを今も勧めている記事もあれば、結局はリポジトリ全体をダウンロードしてしまう5行の Git レシピを紹介している記事もあります。本記事では、現在実際に機能するすべての方法と、それぞれに伴うコスト、そしてどの方法を選ぶかを決める具体的なエラー(レート制限、切り捨てられたファイル一覧、LFS ポインタファイル)を解説します。

結論:用途別に方法を選ぶ

方法 インストール要否 プライベートリポジトリ Git 履歴の保持 向いている用途
ブラウザ型フォルダダウンローダー 不要 可能(トークンを使用) 保持しない 単発のダウンロード、フォルダサイズがまちまちな場合
リポジトリ ZIP(公式) 不要 可能(トークンを使用) 保持しない ほとんどのファイルが必要な小規模リポジトリ
git sparse-checkout Git 可能(認証情報を使用) 保持する 今後も更新を取得し続ける場合
REST API + curl curl、jq 可能(トークンを使用) 保持しない スクリプト、CI、繰り返し実行するジョブ
単一ファイルのコピー 不要 可能(API 経由) 保持しない 1つのフォルダから2〜3ファイルだけ欲しい場合

ZIP だけが欲しいなら、ブラウザ経由が最速です。フォルダの URL を GitDownloader のホームページツール に貼り付けると、そのディレクトリだけをまとめてくれます。本記事の残りでは、他の選択肢が存在する理由と、そちらが適している場面を説明します。

GitHub に「このフォルダをダウンロード」ボタンがない理由

これは手抜きではなく、Git のデータ保存方式から必然的に生じる制約です。

Git リポジトリはオブジェクトの有向グラフです。ファイルは blob オブジェクトに存在し、ディレクトリは名前・モード・ハッシュの一覧を持つ tree オブジェクトで、そのハッシュが blob または別の tree を指します。ブランチは1つのコミットへのポインタであり、そのコミットは1つのルート tree を指し、ルート tree がスナップショット全体を推移的に表現します。この構造の中に「src/assets フォルダを独立してダウンロード可能な単位として扱う」ものはありません。サブツリーは親 tree の中でのみ意味を持ちます。

一方、Subversion はディレクトリを第一級のチェックアウト対象として扱っていました。だからこそ古い SVN ブリッジが定番の回避策だったのです。Git のモデルは履歴・ブランチ・完全性をもたらしますが、その代償として部分的な取得はリポジトリの概念ではなくクライアント側の問題になります。

そのため GitHub のインターフェースは、提供コストが低く曖昧さのないものだけを用意しています。

  • 1つの ref の ZIP。 https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip はそのブランチのルート tree のスナップショットをストリーム配信します。便利ですが、常にブランチ全体です。
  • Git Trees API。 これは1回のリクエストでサブツリーを列挙できます。フォルダダウンロードツールは、当サイトのものも含め、すべてこの API を土台にしています。

つまりフォルダのダウンロードは、GitHub が代わりにやってくれる機能ではありません。パス配下のファイルを列挙し、1つずつ取得し、ローカルで ZIP にまとめるという処理を、ツールやスクリプトが GitHub の API を使って行うものなのです。

方法1 — ブラウザベースのフォルダダウンローダー(インストール不要)

ほとんどの人にとって最短の方法であり、インストールもターミナルも不要な唯一の方法です。

  1. GitHub で目的のフォルダを開き、ブランチセレクタが目的のブランチになっていることを確認します。
  2. アドレスバーから URL をコピーします。https://github.com/owner/repo/tree/main/path/to/folder のような形式で、/tree/<branch>/<path> の形が重要です。
  3. URL を ホームページツール の URL 入力欄に貼り付け、Download ZIP を押します。
  4. ファイルはブラウザ内で列挙・取得・圧縮され、ダウンロードフォルダに保存されます。

この段階で優れたツールと壊れたツールを分けるのは、入力欄ではありません。その後に何が起きるかです。

  • スラッシュを含むブランチ名。 release/2.1 は有効なブランチ名です。そのためツールは、最初の / で区切ると決め打ちするのではなく、より長いプレフィックスを順に試して、ブランチ名の終わりとフォルダパスの始まりを特定する必要があります。
  • 非常に大きなディレクトリ。 Trees API は、再帰的な一覧が10万エントリまたは7 MBを超えると "truncated": true を返します。このフラグを無視するツールは、ファイルが欠けた ZIP を黙って渡してきます。正しい動作は、サブツリーを1階層ずつ列挙する方式にフォールバックすることです。
  • レート制限。 未認証の場合、API リクエストは IP アドレスごとに1時間あたり60回、トークンがあれば5,000回です。ディレクトリを1つずつ列挙するツールは、階層の深いフォルダで未認証の枠をすぐに使い切ります。途中でダウンロードに失敗し、1時間後にまた成功するのはこのためです。
  • Git LFS。 Large File Storage を使うリポジトリでは、実体の代わりに小さなポインタファイルが保存されています。このポインタを検出しないと、ZIP は形式的には正しいのにまったく役に立たないものになります。

自分が所有している、またはアクセス権を持つリポジトリであれば、任意のトークン欄に fine-grained パーソナルアクセストークン を貼り付けてください。GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens の順に進み、対象のリポジトリだけに限定して Contents: Read-only を付けて発行します。トークンはご自身のブラウザに保存され、api.github.com にのみ送信されます。途中でファイルを読み取るサーバーは存在しません。詳しくは FAQ をご覧ください。

方法2 — 公式の手段:リポジトリ全体をダウンロードする

意外なほど多くの場面で今も正解であり、UI を完全に迂回できる直接 URL を知っておく価値があります。

UI から行う場合: リポジトリページ → CodeDownload ZIP。ファイルは repo-main.zipmaster など、デフォルトブランチ名に応じて変わります)という名前で保存されます。

ブックマークやスクリプトに使える直接リンク:

# ブランチのアーカイブ(公開リポジトリ、認証不要)
https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip

# 同じファイルを archive ホストから取得
https://codeload.github.com/{owner}/{repo}/zip/refs/heads/{branch}

# API の zipball — トークンを送ればプライベートリポジトリでも動作
https://api.github.com/repos/{owner}/{repo}/zipball/{ref}

リポジトリが小さい場合、そのファイルの大半が本当に必要な場合、Git をインストールしていない場合、リリースタグが指す正確なコミットが欲しい場合にはこれを選びましょう。コストは比例します。クローンで800 MB になるモノレポは ZIP でも約800 MB で、さらに展開して不要な95%を削除する時間もかかります。しかも ZIP はあくまでスナップショットです。履歴もリモートも git pull もありません。

方法3 — git sparse-checkout を正しく使う

フォルダと、動作する Git チェックアウトの両方が欲しい場合、つまり後から更新を取得したい場合は sparse checkout が適切なツールです。多くのチュートリアルは古い手順を載せているので、ここでは現行の方法を示します。

git clone --filter=blob:none --sparse https://github.com/owner/repo.git
cd repo
git sparse-checkout set path/to/folder

重要なポイントは次のとおりです。

  • --filter=blob:none を付けると partial clone(部分クローン) になります。Git はコミットと tree オブジェクトを取得しますが、チェックアウトするまでファイルの中身は取得しません。これがないと、リポジトリ内のすべての blob をダウンロードして、その大半を捨てることになります。
  • --sparse は sparse-checkout ファイルを自動で初期化するので、.git/info/sparse-checkout を手で書く必要はありません。
  • git sparse-checkout setcone モード を使います。これはディレクトリ単位でマッチするため、大きなリポジトリでは格段に高速です。後からパスを追加するには、複数のパスを指定して同じコマンドを再実行します。作業ツリーがずれた場合は git sparse-checkout reapply を実行してください。
  • sparse checkout には Git 2.25 以降、partial clone(--filter)には 2.19 以降が必要です。

記事や Stack Overflow の回答で今も見かける古い手順、つまり git initgit config core.sparseCheckout trueecho "path/" >> .git/info/sparse-checkoutgit pull origin main という流れは、確かに動作しますが、作業ツリーを絞り込む前に完全な履歴とすべての blob をダウンロードしてしまいます。結果として、目的のフォルダに加えて、ファイル本体より何倍も大きい .git ディレクトリが残ります。

本当のトレードオフはここです。sparse checkout で得られるのはアーカイブではなくリポジトリです。別のプロジェクトに取り込むためのクリーンな ZIP が欲しかった場合、手元にはリモートが紐付いたチェックアウトが残ります。コントリビュートする予定ならまさに望みどおりですが、そうでなければ純粋なオーバーヘッドです。

方法4 — GitHub REST API でスクリプト化する

繰り返し実行するジョブ、たとえば共有フォルダを別のリポジトリに取り込む、CI でテンプレートを取得する、毎晩ドキュメントのアセットを更新するといった用途では、ヘッドレスで実行できる手段が必要です。API には2つの構成要素があります。

Trees API(サブツリー全体を1回のリクエストで取得):

GET https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1

レスポンスには、各エントリの pathtype を持つフラットな tree 配列が含まれます。type: "blob" のエントリがファイルです。注意点はドキュメントに記載された上限です。recursive=1 では配列が 10万エントリかつ7 MB に制限され、それを超えるとレスポンスに "truncated": true が設定されます。その場合の正式な対処法は、tree を非再帰で取得し、サブツリーを自分でたどることです。

Contents API(ディレクトリごとに1回のリクエスト):

GET https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}

これは各ファイルの download_url を含む一覧を返し、これまで多くのブラウザツールが使ってきたものです。切り捨ては起きませんが、ディレクトリごとに1リクエストを消費するため、階層が深いとレート制限を使い切ってしまいます。

以下は Trees API と raw.githubusercontent.com を使った、完結した小さなスクリプトです。

OWNER=octocat
REPO=Spoon-Knife
REF=main
PREFIX=src/assets
TOKEN=""   # プライベートリポジトリの場合は設定: export TOKEN=ghp_xxx

AUTH=()
[ -n "$TOKEN" ] && AUTH=(-H "Authorization: Bearer $TOKEN")

# 1. 信頼する前に、一覧が切り捨てられていないか確認する
curl -s "${AUTH[@]}" \
  "https://api.github.com/repos/$OWNER/$REPO/git/trees/$REF?recursive=1" \
  | jq -r '.truncated'

# 2. プレフィックス配下のすべてのファイルパスをリストに書き出す
curl -s "${AUTH[@]}" \
  "https://api.github.com/repos/$OWNER/$REPO/git/trees/$REF?recursive=1" \
  | jq -r --arg p "$PREFIX" \
    '.tree[] | select(.type == "blob") | select(.path | startswith($p + "/")) | .path' \
  > files.txt

# 3. 各ファイルを取得し、必要に応じてディレクトリを作成する
while read -r path; do
  mkdir -p "$(dirname "$path")"
  curl -sL "${AUTH[@]}" -o "$path" \
    "https://raw.githubusercontent.com/$OWNER/$REPO/$REF/$path"
done < files.txt

運用上の注意が2点あります。1つ目はレスポンスヘッダーを確認することです。X-RateLimit-Remaining が残りの枠を、X-RateLimit-Reset が枠が回復する時刻を示します。未認証の枠は IP ごとに1時間あたり60リクエストなので、トークンなしでは200ファイルのフォルダは途中で失敗します。2つ目は、raw.githubusercontent.com はプライベートリポジトリに対して Authorization ヘッダーを尊重しますが、実際に送信する必要があるという点です。送らないと、削除されたファイルと見分けがつかない404が返ります。

方法5 — フォルダから1ファイルだけ取得する

フォルダ自体が不要なこともあります。すべてのファイルには、直接ダウンロードできる raw URL があります。

https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{path}

GitHub の UI でファイルを開き Raw をクリックします(または右クリックして「リンク先を保存…」を選びます)。通常の /blob/ URL に ?raw=true を付けても同じ結果になります。3ファイル程度なら、この方法が本記事のどのツールよりも優れています。

SVN の回避策は終わった — それでもチュートリアルは勧め続けている

約10年間、定番のアドバイスは GitHub の Subversion ブリッジを使うものでした。URL の /tree/main//trunk/ に置き換え、そこに対して svn checkoutsvn export を実行すると、Git に触れずに単一のディレクトリをチェックアウトできました。

そのブリッジはもう存在しません。GitHub は2023年1月に Subversion サポートの終了を告知 し、残っている利用者を洗い出すために2023年11月と12月に2回のブラウンアウトを実施したうえで、2024年1月8日に Subversion プロトコルを完全に削除 しました。GitHub Enterprise Server もバージョン3.13で追随しています。あわせて git archive --remote も使えなくなりました。これはサーバー側の upload-archive サービスを必要としますが、GitHub はそれを有効化したことがありません。パスの書式をどう工夫しても、このコマンドはプロトコルエラーで失敗します。

あるガイドが最初の方法として svn checkout を挙げているなら、そのガイドは削除以前に書かれたものであり、他のアドバイスも吟味する価値があります。代わりに sparse checkout、Trees API、またはブラウザツールを使いましょう。

どの方法を選ぶべきか

方法 最終的に得られるもの プライベートリポジトリ LFS 対応 主なコスト
ブラウザ型フォルダダウンローダー フォルダの ZIP 可能(fine-grained トークンを使用) 可能(ツールが LFS メディアを取得する場合) 正しい URL と、切り捨てを考慮したツールが必要
リポジトリ ZIP ブランチ全体の ZIP 可能(API の zipball 経由) ポインタファイルのみ 通信量と時間がフォルダではなくリポジトリに比例する
git sparse-checkout フォルダの実際の Git チェックアウト 可能(認証情報を使用) 可能(Git LFS のインストールが必要) クリーンなアーカイブではなく、余分な Git オブジェクトが付く
REST API + curl スクリプトで指定したファイルだけ 可能(トークンを使用) 対処しない限りポインタファイル スクリプトとレート制限の管理を自分で行う
Raw ファイル URL 個別のファイル 可能(認証ヘッダーを使用) 対処しない限りポインタファイル 手作業で1ファイルずつ

トラブルシューティング:実際に遭遇する7つの失敗

1. 明らかに存在するリポジトリで404が返る。 そのリポジトリはプライベートで、リクエストが匿名になっています。トークンを送信してください。自動化する場合は、広い repo スコープを持つ classic トークンではなく、特定のリポジトリに限定して Contents を読み取り専用にした fine-grained トークンを使ってください。

2. 長いダウンロードの途中で 403 が返る。 API のレート制限に達しています。未認証で1時間あたり60回、トークンがあれば5,000回です。リセット時刻は X-RateLimit-Reset ヘッダーにあります。認証を行うか、ディレクトリごとに1リクエストではなくサブツリー全体を1回で列挙するツールを使いましょう。

3. 進行状況が終わらない、または ZIP にファイルが欠けている。 再帰的な tree 一覧が切り捨てられたときの典型的な症状です。API レスポンスの "truncated": true で確認し、ルートから再帰するのではなく、サブツリーを1階層ずつ取得してください。

4. ファイルサイズが約130バイトしかない。 それは version https://git-lfs.github.com/spec/v1 で始まる Git LFS のポインタです。実体は https://media.githubusercontent.com/media/{owner}/{repo}/{ref}/{path} にあります。Git LFS をインストールしてクローンするか、エンドポイントを自動で切り替えるツールを使ってください。

5. ZIP に期待したフォルダが入っていない。 想定とは別のブランチを見ているか、公式 ZIP の場合はブランチのルートを見ているので、まずフォルダの中に入る必要があります。URL をコピーする前にブランチセレクタを確認してください。

6. サブモジュールがあるはずの場所が空のフォルダになっている。 サブモジュールは親リポジトリ内のファイルではなく、特別なエントリで参照される別のリポジトリです。中身を取得するにはサブモジュール自身の URL をクローンしてください。

7. ダウンロードがブロックされる、またはファイルが黙ってスキップされる。 コンテンツフィルターがファイル名を検知することがあり、強力な広告ブロッカーやプライバシー拡張機能が並列ダウンロードを壊すこともあります。そのサイトで拡張機能を無効にして再試行し、ツールのステータスログでスキップされたファイル名を確認してください。

FAQ

プライベートリポジトリからフォルダをダウンロードできますか? はい。本記事のどの方法も対応していますが、いずれも認証が必要です。API ベースのツールでは Contents の読み取り権限だけを持つパーソナルアクセストークン、クローンでは通常の Git 認証情報を使います。レビューしていない第三者サイトに、広いスコープのトークンを貼り付けないでください。

フォルダをダウンロードすると Git の履歴は保持されますか? いいえ。ZIP アーカイブと API によるダウンロードは、現在の状態のスナップショットです。履歴が残るのは git clone を使う方法だけで、sparse checkout もこれに含まれます。

目的のフォルダよりダウンロードサイズがずっと大きいのはなぜですか? フォルダではなくリポジトリの ZIP をダウンロードしているか、フォルダに大きなバイナリ資産が含まれているためです。ツールのせいにする前に、GitHub 上でそのフォルダ自体のサイズと比較してください。

特定のブランチ、タグ、コミットからフォルダをダウンロードできますか? はい。/tree/ の後ろのパス要素が ref なので、https://github.com/owner/repo/tree/v2.1.0/path/to/folder は動作しますし、Trees API にタグやコミット SHA を渡すこともできます。

GitHub からコードをダウンロードするのは安全ですか? 通信経路は安全ですが、内容はユーザーがアップロードしたものでありレビューされていません。再利用する前にリポジトリのライセンスを確認し、実行する前にコードを読み、トークンの扱いについては当サイトの FAQ のプライバシーに関する記載をご覧ください。

要点

  • GitHub が単一のフォルダをダウンロードできないのは、Git のオブジェクトモデルがディレクトリをスナップショット内の tree としてしか定義していないためです。フォルダの切り出しはサーバー機能ではなく、クライアント側の組み立て作業です。
  • 単発のダウンロードなら、ブランチ名・切り捨て・LFS・レート制限を正しく扱うブラウザツールであれば、何もインストールせずに数秒で完了します。
  • 今後も更新を取得し続ける作業には、git clone --filter=blob:none --sparsegit sparse-checkout set を使います。最初にすべてをダウンロードする古い .git/info/sparse-checkout の手順は使わないでください。
  • 自動化には Trees API がサブツリー全体を1回のリクエストで列挙します。10万エントリの上限と、1時間あたり60回対5,000回の制限を忘れないでください。
  • 今も svn checkout を最初に挙げているガイドは無視してください。その手段は2024年1月8日に GitHub から削除されました。

準備の手間を省きたいですか?フォルダの URL を GitDownloader ツール に貼り付ければ、公開・プライベートを問わず、LFS も含めてそのディレクトリだけをブラウザ内でまとめます。