Subversion から Git へ移行する [wsl]

窓替えのソースコード管理を Subversion から Git へ移行してみました。このページは、その移行手順、 Subversion (SVN) から Git へ移行した手順をメモしたものです。

はじめに

移行のポイント

Git へ移行するにあたってのポイントは以下です。

  • SVN のコミットログを Git でも残す。
  • SVN ブランチは Git ブランチにする。
  • SVN タグは Git タグにする。
  • WSL 上で全てのコマンドを実行する。
    ブランチとタグの移行方法として、PowerShell や Java のスクリプトを使う方法は他サイトにあったのですが、WSL での実行例がなかったため WSL 上で実行できるようにしています。

実行環境

  • Windows 11 23H2
  • WSL 2
  • git version 2.25.1

リポジトリの場所

  • SVN リポジトリの場所:https://svnserver/svn/madokae
  • Git リポジトリを作成する場所:自PC内の %USERPROFILE%/tmp ディレクトリ

移行前と後のイメージ

移行前の SVN のディレクトリ構成と、移行後の Git のブランチとタグの状態です。
左の SVN の構成から、右の Git の構成へと変換しました。

SVN (移行前の状態)

標準レイアウト + doc ディレクトリ構成です。

Git (移行後の状態)

SVN の trunk の内容は master に、branches と tags は Git の branch と tag にします。

コマンドの見方

  • 黄色の文字:固定の文字列。
  • 下線付き文字:自分の環境に合わせて変更する文字列。
  • 白色の文字:ユーザー入力以外のコマンド出力文字。

リポジトリ変換の流れ

ここから SVN リポジトリから Git リポジトリへ変換する手順です。

リポジトリの変換は、次に記載している順にコマンドを実行していきます。各コマンドの説明は、続く「リポジトリ変換の実行」に記載しています。

  1. SVN リポジトリから Author の変換ファイルを作る
  2. SVN リポジトリから Git リポジトリへ変換する
  3. SVN タグを Git タグへ変換する
  4. SVN ブランチを Git ブランチへ変換する

リポジトリ変換の実行

SVN リポジトリから Author の変換ファイルを作る

SVN をコミットしたときの Author を Git に変換したときに取り込めるように「変換ファイル」を作成します。

SVN リポジトリをチェックアウト

SVN のコミットログを参照するために SVN リポジトリをチェックアウトします。

SVN のコミットログから Author を抽出

チェックアウトした SVN リポジトリのワーキングコピーから、Author 部分を抽出して、「authors.txt」に出力します。

Author を抽出したファイルを Git 変換できるように編集

「authors.txt」を編集して、SVN コミットログの Author から Git コミット時の Author に変換できるように編集します。このファイルは、後述の git svn fetch で指定します。


SVN リポジトリから Git リポジトリへ変換する

窓替えの SVN リポジトリは標準レイアウトにない doc というディレクトリを作っていたため、 git svn clone コマンドではなく、git svn init → .git/config 編集 → git svn fetch という手順で実行します。

Git 移行後、Git に doc というブランチがあるのは変ですが、同じような SVN リポジトリから移行する手順として参考になりそうなのでそのまま移行します。

git svn clone と git svn init → git svn fetch は同じ。

git svn clone コマンドの実行と、git svn init に続けて git svn fetch コマンドを実行するのは同じです。(参考:Git – Git をクライアントとして使用する)
SVN 標準レイアウトであれば、git svn clone で十分ですが、窓替えの SVN リポジトリには doc ディレクトリがあるため git svn init と git svn fetch を使います。init の後・fetch の前に .git/config を編集して、doc ディレクトリが処理出来るようにしています。

git svn init コマンドを実行

Git リポジトリを初期化コマンドを実行します。

コマンド引数

-s

SVN リポジトリが trunk、branches、tags の標準レイアウトであると指定しています。–stdlayout と指定しても同じです。

–prefx=svn/

リモートブランチのプレフィックスが “svn/” になるように指定しています。この引数を指定しないで実行すると、.git/config が以下のようになりました。赤字の箇所が、この後の手順で記載している .git/config とは異なる箇所です。–prefx=svn/ を指定することで、最終的に出来あがる Git ブランチ名が “svn/0.9.5” など svn が付いた名前になります。

[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
ignorecase = true
[svn-remote "svn"]
url = https://svnserver/svn/madokae
fetch = trunk:refs/remotes/origin/trunk
branches = branches/*:refs/remotes/origin/*
tags = tags/*:refs/remotes/origin/tags/*

.git/config を編集

窓替えの SVN リポジトリは trunk、branches、tags に加えて doc が在るため、この doc を git svn fetch で処理できるように .git/config に追記します。
.git/config の編集は、SVN リポジトリが標準レイアウトのみだったり、doc が移行不要であれば、飛ばして良い手順です。

 [core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
ignorecase = true
[svn-remote "svn"]
url = https://svnserver/svn/madokae
fetch = trunk:refs/remotes/svn/trunk
+ fetch = doc:refs/remotes/svn/doc
branches = branches/*:refs/remotes/svn/*
tags = tags/*:refs/remotes/svn/tags/*

git svn fetch コマンドを実行

SVN リポジトリからファイルを取得します。

取得した結果を確認

ファイルの状況を確認します。

コミットログを確認します。

ブランチの状況を確認します。

ファイルも取得できて、コミットログも移行できて、SVN の trunk に入っていた内容が master になり、SVN のブランチとタグは remotes/svn/ のブランチとなりました。SVN 標準レイアウトにはない doc も無事に取り込めています。


SVN タグを Git タグへ変換する

この時点では、SVN タグは リモート追跡ブランチとして作成されている状態です。Git のタグにしたいので、Git タグへと変換していきます。

変換コマンドを実行

SVN タグから Git タグへ変換して、変換の済んだ SVN タグを削除するコマンドを実行します。

コマンド説明
refs/remotes/svn/tags/* の一覧からタグ名を取得し、取得したタグ名で Git タグを作成し、SVN ブランチから svn/tags/タグ名 を削除しています。

git for-each-ref –format=’%(refname)’ refs/remotes/svn/tags

パターン: `refs/remotes/svn/tags` に一致する参照名を表示しています。単独で実行すると以下のように表示されます。

refs/remotes/svn/tags/0.9.5.02
refs/remotes/svn/tags/0.9.5.03
refs/remotes/svn/tags/0.9.6.63
refs/remotes/svn/tags/0.9.7.31
refs/remotes/svn/tags/0.9.8.37
sed ‘s/refs\/remotes\/svn\/tags\///g’  

for-each-ref の出力から `refs/remotes/svn/tags/` を除去します。git for-each-ref –format=’%(refname)’ refs/remotes/svn/tags | sed ‘s/refs\/remotes\/svn\/tags\///g’ までを実行すると以下のように表示されます。

0.9.5.02
0.9.5.03
0.9.6.63
0.9.7.31
0.9.8.37
xargs -IXXX sh -c ‘git tag XXX “refs/remotes/svn/tags/XXX”; git branch -r -d “svn/tags/XXX”‘

先の sed で表示された内容から、タグを生成して、同時にリモート追跡ブランチから `svn/tags/*` を削除します。

  • -IXXX : 標準入力の各行を引数として置き換える文字列を指定します。`-IXXX` と指定すると、後ろの `XXX` の部分が sed が出力した各行の内容に置き換わります。一回目は XXX が `0.9.5.02` に置き換えわり、二回目は XXX  `0.9.5.03` に置き換わります。
  • -c : sh として実行するコマンド文字列を指定します。xargs で複数コマンド(この場合は `git tag` と `git branch`)を実行したいときは、この例のようにコマンド文字列を作って sh に渡して実行させます。
  • git tag XXX “refs/remotes/svn/tags/XXX”
    タグ XXX を refs/remotes/XXX の最後のコミットを元に作成します。
    * XXX : 作成するタグ名。前の sed コマンドの出力行です。
    * “refs/remotes/svn/tags/XXX” : 作成元にするブランチを指定しています。このブランチの最後のコミットに対してタグを作成します。
  • git branch -r -d “svn/tags/XXX”
    リモート追跡ブランチを削除します。
    * -r : リモート追跡ブランチを表示する、または削除(`-d`と一緒に指定している時)します。
    * -d : ブランチを削除します。
    * svn/tags/XXX : 削除するブランチ名です。 `refs/remotes` は指定する必要はありません。

変換した結果を確認

git tag コマンドで、タグが生成されていることを確認します。

git branch -a コマンドで、remotes/svn/tags が削除されていることを確認します。

上記のように、Git タグが生成され、リモート追跡ブランチから削除されていたので成功です。


SVN ブランチを Git ブランチへ変換する

SVNブランチもタグと同じようにリモート追跡ブランチとして作成されています。Git のローカルブランチへと変換していきます。

SVN タグも Git タグへ移行する場合は、先にタグの変換をする必要があります。
上から順に実行していれば移行は済んでいますが、飛ばしてブランチの変換だけするとタグは移行できません。タグを残したい場合はタグの変換を先に実行する必要があります。

変換コマンドを実行

SVN ブランチから Git ブランチへ変換して、変換済みの SVN ブランチを削除するコマンドを実行します。

コマンド説明
refs/remotes/* の一覧からブランチ名を取得し、取得したブランチ名で Git ブランチを作成し、SVN ブランチから svn/remotes/ブランチ名 を削除しています。

git for-each-ref –format=’%(refname)’ refs/remotes

パターン: `refs/remotes` に一致する参照名を表示します。単独で実行すると以下のように表示されます。

refs/remotes/svn/0.9.5
refs/remotes/svn/0.9.6
refs/remotes/svn/0.9.7
refs/remotes/svn/0.9.8
refs/remotes/svn/doc
refs/remotes/svn/tags/0.9.5.02
refs/remotes/svn/tags/0.9.5.03
refs/remotes/svn/tags/0.9.6.63
refs/remotes/svn/tags/0.9.7.31
refs/remotes/svn/tags/0.9.8.37
refs/remotes/svn/trunk
sed ‘s/refs\/remotes\///g’

for-each-ref の出力から `refs/remotes/` を除去します。git for-each-ref –format=’%(refname)’ refs/remotes | sed ‘s/refs\/remotes\///g’ までを実行すると以下のように表示されます。

svn/0.9.5
svn/0.9.6
svn/0.9.7
svn/0.9.8
svn/doc
svn/tags/0.9.5.02
svn/tags/0.9.5.03
svn/tags/0.9.6.63
svn/tags/0.9.7.31
svn/tags/0.9.8.37
svn/trunk
xargs -IXXX sh -c ‘git branch “XXX” “refs/remotes/XXX”; git branch -r -d “XXX”

sed された結果から、ブランチを生成して、同時にリモート追跡ブランチからを削除します。

  • -IXXX : 略。「SVN タグを Git タグへ変換する」の説明を参照。
  • -c : 略。「SVN タグを Git タグへ変換する」の説明を参照。
  • git branch “XXX” “refs/remotes/XXX”
    ブランチ XXX を リモートブランチ refs/remotes/XXX から作成します。
    * `XXX` : 作成するブランチ名です。前の sed コマンドの出力行で置き換わります。
    * `”refs/remotes/XXX” ` : 作成元にするブランチを指定しています。このブランチの最後のコミットに対してブランチを作成します。
  • git branch -r -d “svn/tags/XXX”
    リモートブランチを削除します。
    * -r : 追跡リモートブランチを表示する、または削除(-dと一緒に指定している時)します。
    * -d : ブランチを削除します。
    * svn/tags/XXX : 削除するブランチ名です。 refs/remotes は指定する必要はありません。

変換した結果を確認

git branch コマンドで、SVN ブランチが Git ローカルブランチになっていることを確認します。

git branch -a コマンドで、remotes/svn/tags が削除されていることを確認します。

git tag コマンドで、タグに変化が無いことを確認します。


Git リポジトリの内容を確認する

変換した内容を確認します。

ファイルの内容を比較

SVN リポジトリと diff コマンドで比較します。

.git ディレクトリの差だけとなりました。それ以外のファイルは trunk と master のファイルが同じになっていることが確認できました。

Git のブランチとタグを確認

git branch コマンドと git tag コマンドで確認します。

SVN リポジトリの branches が Git ブランチに、tags が Git タグ になっています。


Git サーバーへ移入する

続いて、ローカルで Git リポジトリへ変換したので、Git サーバーへ移入します。
Subversion から Git へ移行したリポジトリを Gitサーバーへ移入する [wsl]

参考情報

コメント