窓替えのソースコード管理を 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 ディレクトリ構成です。
$ tree -d -L 2 -n
.
├── trunk
├── branches
│ ├── 0.9.5
│ ├── 0.9.6
│ ├── 0.9.7
│ └── 0.9.8
├── doc
└── tags
├── 0.9.5.02
├── 0.9.5.03
├── 0.9.6.63
├── 0.9.7.31
└── 0.9.8.37
Git (移行後の状態)
SVN の trunk の内容は master に、branches と tags は Git の branch と tag にします。
$ ls
AppBar.cpp PluginApi.cpp ... (略)
$ git branch
* master
svn/0.9.5
svn/0.9.6
svn/0.9.7
svn/0.9.8
svn/doc
svn/trunk
$ git tag
0.9.5.02
0.9.5.03
0.9.6.63
0.9.7.31
0.9.8.37
コマンドの見方
$ svn checkout https://svnserver/svn/madokae madokae_svn
A madokae_svn/trunk
A madokae_svn/trunk/PluginManager.h
...
A madokae_svn/tags/0.7.7.16/FormMain.dfm
Checked out revision 537.
- 黄色の文字:固定の文字列。
- 下線付き文字:自分の環境に合わせて変更する文字列。
- 白色の文字:ユーザー入力以外のコマンド出力文字。
リポジトリ変換の流れ
ここから SVN リポジトリから Git リポジトリへ変換する手順です。
リポジトリの変換は、次に記載している順にコマンドを実行していきます。各コマンドの説明は、続く「リポジトリ変換の実行」に記載しています。
- SVN リポジトリから Author の変換ファイルを作る
$ svn checkout https://svnserver/svn/madokae madokae_svn
$ cd madokae_svn
$ svn log -q | sed '/^-/d' | sed 's/^[^|]*|\ \(.*\)\ |.*$/\1/g' | sort | uniq > ../authors.txt
$ cd ..
$ vi authors.txt - SVN リポジトリから Git リポジトリへ変換する
$ mkdir madokae_git; cd madokae_git
$ git svn init -s --prefix=svn/ https://svnserver/svn/madokae
$ vi .git/config
$ git svn fetch --authors-file="../authors.txt" - SVN タグを Git タグへ変換する
$ git for-each-ref --format='%(refname)' refs/remotes/svn/tags | sed 's/refs\/remotes\/svn\/tags\///g' | xargs -IXXX sh -c 'git tag XXX "refs/remotes/svn/tags/XXX"; git branch -r -d "svn/tags/XXX"'
- SVN ブランチを Git ブランチへ変換する
$ git for-each-ref --format='%(refname)' refs/remotes | sed 's/refs\/remotes\///g' | xargs -IXXX sh -c 'git branch "XXX" "refs/remotes/XXX"; git branch -r -d "XXX"'
リポジトリ変換の実行
SVN リポジトリから Author の変換ファイルを作る
SVN をコミットしたときの Author を Git に変換したときに取り込めるように「変換ファイル」を作成します。
SVN リポジトリをチェックアウト
SVN のコミットログを参照するために SVN リポジトリをチェックアウトします。
$ svn checkout https://svnserver/svn/madokae madokae_svn
$ cd madokae_svn
SVN のコミットログから Author を抽出
チェックアウトした SVN リポジトリのワーキングコピーから、Author 部分を抽出して、「authors.txt」に出力します。
$ svn log -q | sed '/^-/d' | sed 's/^[^|]*|\ \(.*\)\ |.*$/\1/g' | sort | uniq > ../authors.txt
Author を抽出したファイルを Git 変換できるように編集
「authors.txt」を編集して、SVN コミットログの Author から Git コミット時の Author に変換できるように編集します。このファイルは、後述の git svn fetch で指定します。
$ cd ..
$ vi authors.txt
-takla
+takla = takla <xxxxx@xxxx.xxx>
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 リポジトリを初期化コマンドを実行します。
$ mkdir madokae_git; cd madokae_git
$ git svn init -s --prefix=svn/ https://svnserver/svn/madokae
Initialized empty Git repository in /mnt/c/tmp/madokae_git/.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 が移行不要であれば、飛ばして良い手順です。
$ vi .git/config
[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 リポジトリからファイルを取得します。
$ git svn fetch --authors-file="../authors.txt"
...
r537 = 93b6ca628cdf676e210fd77e2f4c4debc26eee83 (refs/remotes/svn/trunk)
Auto packing the repository in background for optimum performance.
See "git help gc" for manual housekeeping.
Updating files: 100% (332/332), done.
Checked out HEAD:
https://svnserver/svn/madokae/trunk r537
取得した結果を確認
ファイルの状況を確認します。
$ ls
AppBar.cpp PluginApi.cpp ... (略)
コミットログを確認します。
$ git log
commit 93b6ca628cdf676e210fd77e2f4c4debc26eee83 (HEAD -> master, svn/trunk)
Author: takla <xxxxx@xxxx.xxx>
Date: Sun Aug 18 13:34:03 2024 +0000
madokae ver.1.0.0.1
* バージョン番号の変更。 1.0.0.0 → 1.0.0.1
* 新プラグイン ClipTitle の追加
git-svn-id: https://svnserver/svn/madokae/trunk@537 0f5df85e-1135-0410-a0de-a3c8b8909916
...
ブランチの状況を確認します。
$ git branch
* master
$ git branch -a
* master
remotes/svn/0.9.5
remotes/svn/0.9.6
remotes/svn/0.9.7
remotes/svn/0.9.8
remotes/svn/doc
remotes/svn/tags/0.9.5.02
remotes/svn/tags/0.9.5.03
remotes/svn/tags/0.9.6.63
remotes/svn/tags/0.9.7.31
remotes/svn/tags/0.9.8.37
remotes/svn/trunk
ファイルも取得できて、コミットログも移行できて、SVN の trunk に入っていた内容が master になり、SVN のブランチとタグは remotes/svn/ のブランチとなりました。SVN 標準レイアウトにはない doc も無事に取り込めています。
SVN タグを Git タグへ変換する
この時点では、SVN タグは リモート追跡ブランチとして作成されている状態です。Git のタグにしたいので、Git タグへと変換していきます。
変換コマンドを実行
SVN タグから Git タグへ変換して、変換の済んだ SVN タグを削除するコマンドを実行します。
$ git for-each-ref --format='%(refname)' refs/remotes/svn/tags | sed 's/refs\/remotes\/svn\/tags\///g' | xargs -IXXX sh -c 'git tag XXX "refs/remotes/svn/tags/XXX"; git branch -r -d "svn/tags/XXX"'
Deleted remote-tracking branch svn/tags/0.9.5.02 (was 9f0d0a6).
Deleted remote-tracking branch svn/tags/0.9.5.03 (was abf76d4).
Deleted remote-tracking branch svn/tags/0.9.6.63 (was ebf1b16).
Deleted remote-tracking branch svn/tags/0.9.7.31 (was a472c8c).
Deleted remote-tracking branch svn/tags/0.9.8.37 (was a73b4d3).
コマンド説明
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 tag
0.9.5.02
0.9.5.03
0.9.6.63
0.9.7.31
0.9.8.37
git branch -a コマンドで、remotes/svn/tags が削除されていることを確認します。
$ git branch -a
* master
remotes/svn/0.9.5
remotes/svn/0.9.6
remotes/svn/0.9.7
remotes/svn/0.9.8
remotes/svn/doc
remotes/svn/trunk
上記のように、Git タグが生成され、リモート追跡ブランチから削除されていたので成功です。
SVN ブランチを Git ブランチへ変換する
SVNブランチもタグと同じようにリモート追跡ブランチとして作成されています。Git のローカルブランチへと変換していきます。
SVN タグも Git タグへ移行する場合は、先にタグの変換をする必要があります。
上から順に実行していれば移行は済んでいますが、飛ばしてブランチの変換だけするとタグは移行できません。タグを残したい場合はタグの変換を先に実行する必要があります。
変換コマンドを実行
SVN ブランチから Git ブランチへ変換して、変換済みの SVN ブランチを削除するコマンドを実行します。
$ git for-each-ref --format='%(refname)' refs/remotes | sed 's/refs\/remotes\///g' | xargs -IXXX sh -c 'git branch "XXX" "refs/remotes/XXX"; git branch -r -d "XXX"'
Deleted remote-tracking branch svn/0.9.5 (was c53cf29).
Deleted remote-tracking branch svn/0.9.6 (was 08a555f).
Deleted remote-tracking branch svn/0.9.7 (was d87443e).
Deleted remote-tracking branch svn/0.9.8 (was ed9cf9b).
Deleted remote-tracking branch svn/doc (was 3c30bfd).
Deleted remote-tracking branch svn/trunk (was 93b6ca6).
コマンド説明
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
* master
svn/0.9.5
svn/0.9.6
svn/0.9.7
svn/0.9.8
svn/doc
svn/trunk
git branch -a コマンドで、remotes/svn/tags が削除されていることを確認します。
$ git branch -a
* master
svn/0.9.5
svn/0.9.6
svn/0.9.7
svn/0.9.8
svn/doc
svn/trunk
git tag コマンドで、タグに変化が無いことを確認します。
$ git tag
0.9.5.02
0.9.5.03
0.9.6.63
0.9.7.31
0.9.8.37
Git リポジトリの内容を確認する
変換した内容を確認します。
ファイルの内容を比較
SVN リポジトリと diff コマンドで比較します。
$ cd ..
$ diff madokae_svn/trunk madokae_git
Only in test/madokae_git: .git
$
.git ディレクトリの差だけとなりました。それ以外のファイルは trunk と master のファイルが同じになっていることが確認できました。
Git のブランチとタグを確認
git branch コマンドと git tag コマンドで確認します。
$ git branch
* master
svn/0.9.5
svn/0.9.6
svn/0.9.7
svn/0.9.8
svn/doc
svn/trunk
$ git tag
0.9.5.02
0.9.5.03
0.9.6.63
0.9.7.31
0.9.8.37
SVN リポジトリの branches が Git ブランチに、tags が Git タグ になっています。
Git サーバーへ移入する
続いて、ローカルで Git リポジトリへ変換したので、Git サーバーへ移入します。
Subversion から Git へ移行したリポジトリを Gitサーバーへ移入する [wsl]
コメント