みなさん、こんにちは。
SOHOや個人事業主として受託開発や自社プロダクトの開発を進めていると、プロジェクトの成長に伴って「リポジトリの整理整頓」が必要になる局面があります。
特に、一つの大きなリポジトリの中で複数の実験的な機能(featureブランチ)を並行開発していたものの、その一部が予想以上にスケールし、「これは単独のプロジェクトとして独立させたほうが管理しやすいな」と判断する場合などです。
今回、私の環境でも「特定の2つのfeatureブランチだけを、履歴を維持したまま別リポジトリに移したい」という要件が発生しました。
一般的にこうした作業には git filter-repo や git filter-branch といったツールが推奨されますが、実際に試してみると、環境によってはエラーの連発で時間を浪費してしまうことも少なくありません。
今回は、強力なツールに頼りすぎず、Gitの基本機能を組み合わせることで「最も安全かつ確実」にリポジトリを切り出す方法をご紹介します。
なぜ git filter-repo でエラーが連発したのか?
当初、私は定石通り git filter-repo を使ってブランチの抽出を試みました。しかし、これが難航の始まりでした。
ミラークローンと参照(refs)の壁
大規模なリポジトリ移行ではよく git clone --mirror を使いますが、このミラークローン特有の挙動が filter-repo と相性が悪い場合があります。
- 参照構造の不整合
→ ミラークローンではrefs/heads/だけでなく全ての参照を同期しますが、filter-repoで--refs指定をする際、このパスの解釈が微妙にズレることで「対象が見つからない」といったエラーに繋がることがあります。 - packed-refs の影響
→ Gitが内部で参照を圧縮して保持するpacked-refsの状態によって、特定のブランチだけがフィルタリング対象から漏れてしまう現象が発生しました。 - fast-import のクラッシュ
→ 履歴を書き換える過程で呼び出されるgit fast-importが、refnameのコールバック処理中に異常終了してしまうケースにも遭遇しました。
もちろん、ツールを使いこなせば解決できる問題かもしれませんが、SOHOの現場で求められるのは「ツールに詳しくなること」ではなく「安全に、早く、確実に移行を完了させること」です。
今回は「移行したい2つのブランチの履歴がほぼ同じ」という状況でした。そのため、あえて履歴を書き換える(フィルタリングする)メリットよりも、壊すリスクの方が大きいと判断しました。
不要なものを削るより「必要なものだけ送る」
考えた挙句、行き着いた結論は、
「新リポジトリには、必要なブランチだけを push する」
これだけです。
「不要なブランチを push しなければ、新リポジトリには存在しないことになる」という、Gitの当たり前の仕様を利用します。この方法なら、複雑な履歴書き換え作業が発生しないため、コミットハッシュの整合性を気にする必要も、ツールがクラッシュする心配もありません。
ステップ・バイ・ステップの移行ガイド
それでは、実際に私が踏んだ手順を解説します。
手順1 – 旧リポジトリを「通常クローン」する
まず、移行元となるリポジトリをローカルに用意します。この際、--mirror や --bare は使わず、通常のクローンを行います。
git clone https://github.com/user/original-project.git
cd original-project
通常のクローンであれば、私たちが普段使い慣れているディレクトリ構造になるため、操作ミスを防ぎやすくなります。
手順2 – リポジトリを origin に設定する
次に、移行先となる新しい空のリポジトリを作成します。その後、ローカルリポジトリのリモートURL(origin)を、新しいリポジトリのURLに書き換えます。
git remote set-url origin https://github.com/user/new-project.git
これにより、このローカルリポジトリは「古い中身を持っているが、送り先は新しいリポジトリ」という状態になります。
手順3 – 必要なブランチをローカルに準備する
クローンした直後、ローカルにはデフォルトブランチ(mainなど)しか存在しない場合があります。移行したい feature ブランチをリモートからローカルにチェックアウトしておきます。
# 移行したいブランチ1
git checkout -b feature/iot-sensor-module origin/feature/iot-sensor-module
# 移行したいブランチ2
git checkout -b feature/nursing-care-system origin/feature/nursing-care-system
手順4 – 必要なブランチだけを Push する
ここが一番のポイントです。一括ですべてを push するのではなく、必要なブランチだけを指定して push します。
git push -u origin feature/iot-sensor-module
git push -u origin feature/nursing-care-system
これで、新しいリポジトリにはこの2つのブランチとその関連履歴だけが転送されました。
手順5 – 新リポジトリ側で main / develop を再構築
この時点では、新リポジトリに main や develop ブランチがなかったり、古い内容のままだったりします。GitHubやGitLabのGUI上で、今回 push したブランチから新しい main を作成するか、あるいは以下のようにローカルで作成して push します。
# feature/iot-sensor-module をベースに新しい main を作る場合
git checkout feature/iot-sensor-module
git checkout -b main
git push -u origin main
これで、新プロジェクトとしての体裁が整いました。
この方法が「最強」である3つの理由
一見すると力技に見えるかもしれませんが、この手法には大きなメリットがあります。
- 履歴を破壊しない
filter-repoは履歴を「書き換える」ツールです。対してこの方法は、既存のコミットを「そのまま運ぶ」だけ。もし途中で作業をミスしても、旧リポジトリには一切影響がないため、何度でもやり直しが効きます。 - 再現性が極めて高い
特殊なツールのインストールや、Pythonの環境構築、複雑なオプション指定は不要です。標準的な Git コマンドだけで完結するため、チームメンバーに手順を共有する際も齟齬が生まれません。 - トラブルの切り分けが容易
もし push 時にエラーが出たとしても、それは単純なネットワークの問題か権限の問題です。「ツールの内部で fast-import がクラッシュした」といった、深入りしたくないデバッグに時間を溶かされることがありません。
シンプルな原則はツールに勝る
エンジニアリングの世界では、時として「高度な専用ツール」を使うこと自体が目的化してしまうことがあります。しかし、医療IoTや介護ITといった、「確実性」と「継続性」が求められる現場において、私は「最もシンプルな方法」を好みます。
今回の教訓は、「Gitの操作は、複雑なツールでフィルタリングするより、シンプルなプッシュ戦略をとる方が安全な場合が多い」ということでした。
もちろん、リポジトリ全体から特定の個人情報を完全に抹消したい、といった「履歴の書き換えそのもの」が目的である場合はツールの力が必要です。しかし、今回のような「プロジェクトの切り出し」であれば、セレクトプッシュで十分対応可能です。
もしリポジトリの移行でツールに拒絶されてしまったら、一度この「基本に立ち返る方法」を試してみてください。驚くほどスムーズに解決するかもしれませんよ。
本日も最後までお読みいただきありがとうございました。
それでは、よいGitライフを!



