git-svnでSVNからGitへ移行:レガシープロジェクトのコミット履歴とブランチを完全保持

Git tutorial - IT technology blog
Git tutorial - IT technology blog

レガシープロジェクトがSVNに「縛られて」いるとき

2016年から稼働しているプロジェクトのメンテナンスを引き継いだとき、コードベースは前の会社のSVNサーバーに置かれていました。7年分のコミット履歴、数十のブランチ——すべてがSVN形式でした。新しいチームはCI/CDを導入するためにGitへ移行したかったものの、その貴重なコミット履歴を捨てたくはありませんでした。

いくつかの方法を試した末に選んだのがgit-svn——Gitに標準搭載されているブリッジツールで、追加の複雑なインストールは不要です。コードをエクスポートして最初からインポートし直すのではなく、リビジョンごとに実際に変換し、各コミット・作成者・ブランチ構造をそのまま保持します。

git-svnはどのように動作するのか

git-svnはGit 1.5.xから存在し、SVNのリビジョンを1つずつ読み込んで対応するGitコミットを作成します。具体的には、次の3つの処理を行います:

  • SVNのリビジョン番号(r1、r2、r3…)をGitのコミットSHAにマッピングする
  • SVNのユーザー名をGitのauthorフォーマット(名前+メール)に変換する
  • SVNのディレクトリ構造(trunk/branches/tags)をGitのブランチとタグに変換する

SVNとGitの構造の違い

SVNは線形のリビジョン番号を使用し、ブランチをサーバー上の実際のディレクトリとして保存します:

svn-repo/
├── trunk/              ← mainブランチに相当
├── branches/
│   ├── feature-login/
│   └── hotfix-payment/
└── tags/
    ├── v1.0/
    └── v2.1/

Gitは異なります。ブランチとタグは単なるポインターであり、実際のディレクトリではありません。git-svnは自動的にtrunkmainに、branches/*をGitブランチに、tags/*をGitタグにマッピングします。クローン後、これらをリモートトラッキングrefではなく、実際のローカルブランチとタグに変換する追加ステップが必要です。

実践:SVNからGitへの段階的な移行手順

以下のデモでは、https://svn.example.com/myprojectにあるSVNリポジトリ(標準的なtrunk/branches/tags構造)を使用します。

ステップ1:git-svnのインストール

多くのディストリビューションでは、git-svnはGitと一緒にデフォルトでインストールされません:

# Ubuntu/Debian
sudo apt-get install git-svn

# CentOS/RHEL
sudo yum install git-svn

# macOS (Homebrew)
brew install git-svn

# インストールを確認
git svn --version

ステップ2:authorマッピングファイルの作成

SVNはユーザー名(例:john_dev)のみを保存しますが、Gitにはフルネームとメールアドレスの両方が必要です。まず、すべてのSVN authorのリストを取得します:

svn log https://svn.example.com/myproject --xml --quiet \
  | grep "<author>" \
  | sort -u \
  | sed 's/.*<author>\(.*\)<\/author>.*/\1/' > /tmp/svn-authors-raw.txt

cat /tmp/svn-authors-raw.txt

そのリストをもとに、authors.txtファイルを作成します:

john_dev = John Nguyen <[email protected]>
mary_tran = Mary Tran <[email protected]>
admin = Admin Bot <[email protected]>

各行のフォーマット:svn_username = Full Name <email>。authorを1人も漏らしてはいけません——git-svnは未定義のユーザー名に初めて遭遇した時点で停止し、エラーを報告して続行できなくなります。

ステップ3:git-svnでSVNリポジトリをクローン

これが最も時間のかかるステップです。1,000リビジョンのリポジトリで約10〜20分、10,000以上のリビジョンではネットワークとサーバーの速度によっては半日かかることもあります。事前にscreentmuxを起動しておくことをおすすめします:

screen -S svn-migration

git svn clone https://svn.example.com/myproject \
  --stdlayout \
  --authors-file=authors.txt \
  --no-metadata \
  myproject-git

# Ctrl+A, D でscreenをdetachする場合

重要なフラグ:

  • --stdlayout:標準的なtrunk/branches/tags構造を自動認識します(省略形は-s
  • --authors-file:ステップ2で作成したマッピングファイル
  • --no-metadata:各コミットメッセージの末尾にgit-svn-id:文字列を追加しません。コミット履歴がすっきりします

SVNリポジトリが標準構造を使用していない場合は、--stdlayoutを省略して手動で指定します:

git svn clone https://svn.example.com/myproject \
  --trunk=code \
  --branches=feature-branches \
  --tags=releases \
  --authors-file=authors.txt \
  myproject-git

ステップ4:ブランチとタグの変換

クローン完了後、SVNのブランチとタグはリモートトラッキングrefrefs/remotes/*)として現れ、実際のGitローカルブランチやタグではありません。変換が必要です:

cd myproject-git

# SVN tagsを実際のGit tagsに変換
git for-each-ref refs/remotes/tags | cut -d / -f 4- | while read tagname; do
  git tag "$tagname" "refs/remotes/tags/$tagname"
  git branch -r -d "tags/$tagname"
done

# SVN branchesをGit local branchesに変換
git for-each-ref refs/remotes | grep -v '@' | grep -v 'tags' | cut -d / -f 3- | while read branchname; do
  git branch "$branchname" "refs/remotes/$branchname"
  git branch -r -d "$branchname"
done

# trunkをmainにリネーム
git branch -m trunk main

結果を確認:

git branch -a          # すべてのlocal branchesを表示
git tag -l             # すべてのtagsを表示
git log --oneline -15  # コミット履歴を確認

ステップ5:リモートGitリポジトリへのpush

GitHub、GitLab、またはGiteaに空のリポジトリを作成してから、すべてをpushします:

git remote add origin https://github.com/yourorg/myproject.git

# すべてのbranchesをpush
git push origin --all

# すべてのtagsをpush
git push origin --tags

このステップで注意すべき点があります:--forceは絶対に使わないでください。以前、他の人が作業中のリポジトリに誤ってforce pushして重要なコードを失った経験があります——それ以来、git push --forceには常に慎重になっています。リモートリポジトリは最初から空なので、forceを使う理由はありません。エラーが出たら--forceでごまかすのではなく、根本的な原因を探しましょう。

実際によく遭遇する問題

マッピングファイルにauthorが不足している

クローンが次のメッセージで停止します:

Author: old_contractor not defined in authors.txt file

authors.txtに不足している行を追加してから再開します——最初からクローンし直す必要はありません:

echo "old_contractor = Old Contractor <[email protected]>" >> authors.txt
cd myproject-git
git svn fetch

リポジトリのリビジョン数が多すぎる

10,000以上のリビジョンで古い履歴が不要な場合は、特定の時点から制限できます:

# revision 8000からHEADまでのみ取得
git svn clone https://svn.example.com/myproject \
  -r 8000:HEAD \
  --stdlayout \
  --authors-file=authors.txt \
  myproject-git

移行後の結果を検証する

完了後、見落としがないよう、コミット数を比較します:

# SVNのrevision数をカウント(svn clientがあるマシンで実行)
svn log https://svn.example.com/myproject | grep -c "^r[0-9]"

# 移行したGit repoのcommit数をカウント
cd myproject-git
git log --all --oneline | wc -l

2つの数値は完全に一致しないのが普通です——SVNにはマージリビジョンやプロパティのみの変更があり、Gitでは個別のコミットを生成しません。10%未満の差異は正常ですが、30%以上の差異がある場合は変換プロセスを確認し直すべきです。

移行後:すぐに行うべきこと

コードはGitに移行済みですが、それは簡単な部分にすぎません。より重要なのは、チーム全員が完全に移行し、誰も誤って古いSVNにコミットしないよう徹底することです:

  • チーム全員に通知し、新しいGitリポジトリのリンクとクローン手順を共有する
  • 誰かが誤ってSVNにコミットしないよう、SVNリポジトリを読み取り専用(または完全ロック)に設定する
  • CI/CDパイプラインをSVN checkoutからGit cloneに更新する
  • .svnディレクトリがGitリポジトリに入らないよう確認する(必要なら.gitignoreに追加する)

最初の1週間は「どうやってcloneするの」「ブランチの切り替えはどうやるの」という質問が出るのは普通です。Gitの基本的なワークフローについて短いドキュメントを用意しておけば十分です。その段階を過ぎると、チームが最初に気づくのは、Gitのブランチ作成がSVNほど「コストがかかる」ものではないということです——サーバー上でディレクトリ全体をコピーするのではなく、数ミリ秒で新しいブランチが作れます

Share: