Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 1/248: 1 2 3 4 5 6 7 8 9 »

GitHubに多数ある自分のリポジトリのデフォルトブランチをmasterからtrunkに切り替えた - Jun 12, 2020

Gitのデフォルトブランチ名は慣習的にmasterとされてるんだけど、これがmaster/slave(主人と奴隷)つまり奴隷制に由来する表現であるとして、2020年5月25日にミネアポリスで起きた白人警官による黒人被疑者の殺人事件を契機に盛り上がりを見せているBlack Lives Matter運動の流れを承けて、別のブランチ名に変更しようという動きがある、という事を知った。実際に、GitHubの公式のコマンドラインツールで既定のブランチ名がtrunkに変更された(……と例に挙げたけど、たまたまタイミングが一致しただけで、このプロジェクトでの変更は事件の前だったみたい)ほか、いくつかのプロジェクトも追従しているという。

結論から先に言えば、僕もこの判断に追従した。GitHubの僕のアカウント配下のリポジトリは現時点で186あって(hub api users/piroor | jq .public_reposで調べたらそう言われた。プルリクエスト用の一時的なforkが結構あるのでそれで多くなってる部分はあると思う)、ひとつひとつ手でやっていると埒が開かないので、それぞれローカルでは1つの作業ディレクトリにcloneされているのをいいことに、WSL1のUbuntuのbashで、GitHubのリポジトリを操作するAPIを叩けるhubを併用して以下のようなワンライナーで一気にやることにした。

export NAMESPACE=piroor;
ls | while read path;
do
  [ -d "$path" -a -d "$path/.git" ] &&
    echo "checking $path";
    (cd "$path";
     export ORIGIN_INFO="$(git remote show origin)";
     (echo "$ORIGIN_INFO" | egrep "Fetch URL: git@github.com:/?$NAMESPACE/[^\\.]+\.git") &&
     (export REPOSITORY="$(echo "$ORIGIN_INFO" | grep 'Fetch URL' | egrep -o "([^/\.']+)\.git" | cut -d . -f 1)";
      echo "updating $NAMESPACE/$REPOSITORY";
      ((git branch | grep '* master' &&
        (git branch --move master trunk;
         git push --set-upstream origin trunk));
       (hub api "repos/$NAMESPACE/$REPOSITORY" | jq .default_branch | grep master &&
        hub api "repos/$NAMESPACE/$REPOSITORY" -X PATCH -F default_branch=trunk);
       (URL_BASE="https://travis-ci.org/$NAMESPACE/$REPOSITORY.svg\\?branch=";
        git grep -E "${URL_BASE}master" |
          cut -d : -f 1 |
          uniq |
          xargs sed -i -r -e "s;${URL_BASE}master;${URL_BASE}trunk;g" &&
        git commit -m 'Migrate master to trunk' $(git grep -E "${URL_BASE}trunk" | cut -d : -f 1 | uniq) &&
        git push))));
done

まずgit remote show originでリモートリポジトリとの対応付けを調べる。移行対象のリポジトリであれば、git branch --move master trunkでブランチ名を変更して、GitHub上のデフォルトブランチもtrunkに切り替えて、Travis CIのビルドステータス画像のURLに含まれているブランチ名もついでに更新する、という感じ。他にもまだ変えないといけない所は残ってると思うけど、それは気付いた時に追々直していこうと思ってる。

同様に各リポジトリをclone済みの他の環境では、ローカルリポジトリの情報を変更するだけなので、以下のようになるか。

export NAMESPACE=piroor;
ls | while read path;
do
  [ -d "$path" -a -d "$path/.git" ] &&
    echo "checking $path";
    (cd "$path";
     export ORIGIN_INFO="$(git remote show origin)";
     (echo "$ORIGIN_INFO" | egrep "Fetch URL: (https://github.com/|git@github.com:/?)$NAMESPACE/[^\\.]+\.git") &&
     (export REPOSITORY="$(echo "$ORIGIN_INFO" | grep 'Fetch URL' | egrep -o "([^/\.']+)\.git" | cut -d . -f 1)";
      echo "updating $NAMESPACE/$REPOSITORY";
      (git branch | grep '* master' &&
       (git branch --move master trunk;
        git branch --set-upstream-to=origin/trunk))));
done

ローカルにcloneされてないリポジトリは、今の所まだ手つかず。hubでどうにかできるだろうとは思ってるので、やったらまた追記する。

(6月15日追記)ローカルにcloneされてないリポジトリも全部やるワンライナーは以下のような感じになった(whileループが二重になってまでワンライナーて……)。

export NAMESPACE=piroor;
export TOTAL_REPOS=$(hub api users/piroor | jq -r .public_repos);
export PER_PAGE=100;
seq 1 $((($TOTAL_REPOS / $PER_PAGE) + 1)) | while read page;
do
  hub api "users/$NAMESPACE/repos?page=$page&per_page=$PER_PAGE" |
    jq -r .[].name |
    while read name;
    do
      [ "$(hub api "repos/$NAMESPACE/$name" | jq -r .default_branch)" = "master" ] &&
        echo "updating $NAMESPACE/$name" &&
        (export workdir="$(mktemp -d)" &&
         echo "  workdir: $workdir" &&
         (cd "$workdir" &&
          git clone "git@github.com:$NAMESPACE/$name" --branch master --single-branch &&
          cd "$name" &&
          git branch --move master trunk &&
          git push --set-upstream origin trunk &&
          hub api "repos/$NAMESPACE/$name" -X PATCH -F default_branch=trunk);
         rm -rf "$workdir");
    done;
done

API叩いて直接リポジトリをリネームするやり方が分からなかった(そういうのはない?)ので、一時的にcloneして作業するという形で解決してみた。プルリク用にforkした物までゴソッとやっちゃうので、気になる人は除外処理を入れて使おう。

自分はこのように移行したけど、既存ツールチェインが壊れる危険とかいろいろあるし、これが本来意図された通りの効果がある変更だという確証もないので、みんながみんなやるべきとまでは思ってない、という事も書き添えておく。

以下、技術的な話から離れて、このような言い換えの必要性と妥当性について今回色々調べた事・考えた事を、記録のために書き残しておく。

続きを表示する ...

書評:わかばちゃんと学ぶ Git使い方入門 - Apr 30, 2017

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉
湊川 あい
シーアンドアール研究所 (2017-04-21)
売り上げランキング: 787

「わかばちゃんと学ぶWeb制作の基本」のレビューを参照しながら書こうと思ったらそもそも書いてなかったので、先にそっちから書いていました。例によって自分は恐らく対象読者ではないのですが、人気の秘密を解き明かしてやんよ!とばかりに自費購入です。

本書は端的に言うと、「チームでGitを使う事になったが、よく分からないという人」「Gitをまだ使ったことがなく、なんだか難しそう……と尻込みしている人」「とりあえずなんとなくで使えてはいるが、決まった操作以外はできずにいる人」「チーム内のITエンジニアがGitを激推ししてくるのだが、使い方を覚えるのが億劫な非ITエンジニアの人」のための本だと思います。「Gitというツールの使い方の入門」+「Gitを使ったワークフローの世界への入門」という、人がGitに初めて触れる場面に即した内容になっています。

わかばちゃんシリーズの2冊目ではありますが、解説の内容は独立しています。一部キャラクターは前作からの登場のため、説明がないキャラは「誰これ?」となってしまうかもしれませんが、解説の理解を妨げるほどではなく、前作から読んでいる人のためのファンサービスと言えるでしょう。

マンガで技術解説、ふたたび

前作同様、本書も「マンガは導入や簡単な説明で、その後のテキストが本編」という構成です。漫画要素は前作より多く、トピックによってはマンガの内容が解説として機能している部分も増えていますが、テキスト部分をすべて読み飛ばして成立する内容ではないので、基本的にはやはり漫画もテキストも全部読みましょうという事になります。

GitやGitを使ったワークフローはそれ自体がそれなりに複雑な物なので、何か基準を定めないと、解説する側も解説される側も混乱してしまうでしょう。本書は主人公の「わかばちゃん」がその基準となり、「これをしたい!」や「こういう事で困った!」という形で話題を展開していきます。まだGitを使ったことがないという段階の人が、「初心者がGitを使い始める時に起こる一通りの事」をわかばちゃんを通して疑似体験できる構成なので、予習としても苦痛を感じず通読できるのではないかと思います。

似た分野で解説漫画を描いている自分としては、「あっ、この1コマで済ませてるやつもうちょっと2~3コマ使って段階的に説明したい……」と感じる箇所もあったのですが、これをやり過ぎるとマンガのページがどんどん膨れあがってしまうので、テキストを本文として置く限りにおいては現状はバランスの良い落とし所と言えると思います。)

純粋にGitの解説としての出来は?

本書の(ページ数的な意味での)前半はGitの基本的な使い方、後半は前半を踏まえてのケーススタディ集です。

Gitには山ほど機能があり、また、それぞれが関連しあっていることがあるため、「そういえば、あれも」「そういえば、これも」と解説し始めるとキリがありません。本書前半は「とりあえずこれだけ覚えれば使える」というレベルに持っていく事を優先して脇の話はざっくりカットしているので、多すぎる情報に惑わされることなく、押さえておかないといけない最も重要なポイントを知る事ができます。

また、そうして基本を分かった上でいざ使い始めてみて起こる様々なトラブルや「これをやりたい」といったニーズに対し、本書後半ではケースごとにその解決方法を解説しています。話題のチョイスとして、Gitの初心者~中級者が遭遇しがちな物がまとめられているため、実用性は高いです。

重要な事として、「やりたくなるけど、やっては駄目」な事は極力解説していないのも好印象です。具体的にはgit push -f絡みの話がそれで、データ喪失などの致命的な事態に陥らないためには「それはできない」という事にしておいた方が安全なのは確かです。この点からも、本書が実際の運用の事を強く意識していることが伺えます。

自分も解説を書く立場の人間ですが、自分も含めて自分から本を書こうというような人は、自分の持つ知識を無意味にひけらかしたがるものです(偏見)。よほど自分を抑制しないと、「Gitの生まれた経緯はこれこれこうで……」というような脇の話や、使用頻度の低い使いどころの難しい機能のように判断力(判断材料となる知識)が不足している初心者には使いこなせない知識など、実際の運用上はどうでもいい情報を盛り込んでしまいがちです。

その点、本書はわかばちゃんという視点を設定することで、「ここは教えなくていい」「今は教えない方がいい」というノイジーな情報をバッサリ切り落としていて、この本を必要としている読者のための本というスタンスを貫いています。読者に寄り添う姿勢を崩さない真摯さと、嘘を書かない真摯さを保ち、その上で必要な情報をきちんと盛り込むという本書のバランス感覚は、見事だと思います。

SourceTreeユーザ以外へのケアが課題か

これはターゲッティングによるものなので仕方がないのですが、基本的にSourceTreeを使っての運用の解説なので、それ以外のツールでのやり方は解説されていません。「SourceTreeを極限まで使いこなす」という観点ではなく「Gitを始めるにあたって最初の道具としてとりあえずSourceTreeを選択する」という観点での解説ですし、pushやpullといった用語はどのツールでも共通なので、本書での解説はほとんどそのまま他のクライアントに読み替えて応用できるのですが、コマンドラインツールのようにまったく操作体系の異なるクライアントの場合はそこそこ苦労しそうな気がします。

希望としては、例えば本書の説明の下にgitコマンドや別のツールでのやり方の説明があればなお良かったのでは?とも思うのですが、それをやり始めると内容が薄いわりに分量だけあるという本になってしまいますし、第一、多すぎる情報の提示は初心者を惑わすだけですので、これらをカットしたのは至極妥当だと自分も思います。電子書籍のように、補足的な内容を視界の中にオーバーレイ(スーパーインポーズ)表示できる媒体の発展に期待したい所です。

なお、gitコマンドを使った操作に関しては、著者の湊川さんご自身の手による以下のコンテンツが存在しています。

副読本として本書と併せて読むと、SourceTreeを通した見え方以外のGitが見えてきて、よりGitへの理解が深まると思います。

<script src="https://source.pixiv.net/source/embed.js" data-id="60527082_db5d3e091bb0cd27ade787226c645edc" data-size="medium" data-border="on" charset="utf-8"></script><noscript>

シス管系女子+マンガでわかるGit コラボ特別編の裏側(想像) by Piro/結城洋志 on pixiv

</noscript>

非エンジニアの人にこそ読んでもらいたい

上では「課題あり」という書き方をしてしまいましたが、コマンド操作に関する説明のような、見た目に抵抗感や苦手意識を持たれそうな内容をばっさりカットしていることで、第一印象でのとっつきやすさは高く保たれていると思います。そこで考えられるのが、非ITエンジニアの人にGitを使ってもらうための入門書としての活用です。

本書の例はWebサイト制作を想定して書かれていますが、Web制作であれば企画やデザイナーといった職種の人も実作業に関わってきます。また、自分が会社の業務として過去に手がけたWEB+DB PRESSの記事執筆でも、複数人で並行して執筆して最後にとりまとめるという事をGitで行っていました。バージョン管理システムや、それを前提としたGitHubのようなサービスの便利さは、ソフトウェア開発に限らずあらゆる場面で実感できるものです。こんな便利なものをITエンジニアだけで使うに留めるのは勿体ないです。

電子化可能なデータを取り扱うプロジェクトに関わるあらゆる人の間で、データを共有しその履歴をトラッキングするインフラとして、Gitを活用する。ファイルの履歴管理や連絡の手間から皆を解放する。そのような働き方の改革にすらなり得る手引き書として、本書は広くおすすめできます。

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉
湊川 あい
シーアンドアール研究所 (2017-04-21)
売り上げランキング: 787

msysGitが動かなくなった - Mar 18, 2011

しばらくぶりに帰宅して、さあ外出先で行った開発の結果を取ってきましょうと思ってgit pullしようとしたら、bashが起動しなくなってた。

背景を説明すると、僕は自宅ではWindowsを使ってて、Gitを使った開発にはTortoiseGitmsysGitの組み合わせを使用しているのですが、普段は全然普通に使えてるのに今日になって急にそれが動かなくなってたという状況でした。どうもTortoiseGitが内部的に呼び出してるCygwinのbashが動いてないようで、bash単体で起動しようとしてもこんな風なエラーが出てしまってすぐにプロセスが落ちてしまいます。

      0 [main] us 0 init_cheap: VirtualAlloc pointer is null, Win32 error 487
AllocationBase 0x0, BaseAddress 0x68540000, RegionSize 0x70000, State 0x10000
C:\Program Files\Git\bin\bash.exe: *** Couldn't reserve space for cygwin's heap,
 Win32 error 0

で、エラーメッセージを頼りに何か情報が無いかとGoogle先生に聞いてみた所、同じようなエラーが起こって困ってる人が結構いるみたいだったんですが、そこで紹介されてた以下の2つの対処法は僕の環境では効果がありませんでした。

諦めずにさらに探してたら、MSYSの謎のエラー - メモ@wantoraというエントリで紹介されてた方法でうまくいきました。

>cd "c:\program files\git\bin"
>rebase -b 0x30000000 msys-1.0.dll

rebaseというのはひょっとしたら普通は入ってないかもしれません。自分はFirefoxのビルドのためにWindows SDKというのを入れてましたが、それに付いてきてたのかもしれません。リンク先のエントリによるとWindows SDK for Windows Server 2008 and .NET Framework 3.5を入れると付いてくるようです。

rebaseってなんやねん、git rebaseとは違うのか、と思って検索してみたら以下のような記事が出てきましたが、読んでもよくわかりませんでした。

msysgitとTortoiseGitの組み合わせでSSHでの接続に失敗する - Nov 10, 2010

SubversionからGitに移行したまとめには書いてなかったけど、TortoiseGitを使うと git@github.com:piroor/treestyletab.git とかのcloneやpullに失敗するという現象に遭遇した。「じゃあ諦めてmsysygitのbashコンソールからやるか……」で今まではなんとかなってたんだけど、これもいつの間にか動かなくなってた。「fatal: the remote end hang up unexpectedly.」とか言われる。

僕は会社でメインで使ってるUbuntuの上で作ったOpenSSHの鍵を使ってて、Windows環境ではそれをPuTTY形式の鍵に変換して使ってる。変換がうまくいってれば、OpenSSHの公開鍵をauthorized_keysに登録してあるサーバに対してもPuTTYで接続できる事を確認済み。pagent.exeが起動していてそいつにPuTTY形式の鍵が読み込まれていれば、パスフレーズの入力も省略できる。

なのにGitではうまくいかない。

TortoiseGitでSSHできない件についてはTortoiseGit 1.5.8付属のTortoisePlink.exeのバグらしい。次のリリース版では直ると書いてあるけど、今使えないんじゃ困る……なので、TortoiseGitの設定ダイアログのNetworkの所でSSH Clientに「C:\Program Files\TortoiseGit\bin\TortoisePlink.exe」の代わりに「C:\Program Files\TortoiseSVN\bin\TortoisePlink.exe」(TortoiseSVN付属のTortoisePlink.exe。こっちはこのバグがない。)を指定して回避することにした。

これでGUI(TortoiseGit)からはcloneしたりpullしたりpushしたりできるようになったんだけど、msysgitのbashからは相変わらずpullも何もできなくてやっぱり「fatal: the remote end hang up unexpectedly.」って言われる。

エラーメッセージで検索してたら、英語圏のフォーラムの記事が引っかかったんだけど、読み進めてるうちにGIT_SSHという環境変数が関係してるらしいという事が分かった。bashコンソール等でSSH経由での接続を試みる時は、Windowsの環境変数でGIT_SSHに入ってる物がSSHのクライアントとして使われるみたいだ。早速システムのプロパティで確認してみたら、大当たりだった。「C:\Program Files\TortoiseGit\bin\TortoisePlink.exe」とバグ持ちのSSHクライアントのパスが入ってたので、「C:\Program Files\TortoiseSVN\bin\TortoisePlink.exe」を指定し直しておいた。bashコンソールを起動し直してもう一度試したら、ちゃんとpullできるようになってた。

TortoiseSVNだとフォルダを複数選択してまとめてチェックアウトでワーキングコピーを最新の物に更新できてたんだけど、TortoiseGitだとそれがうまくいかないようだったので、こういうbashスクリプトで代用しようとして、Windows環境だとgitにパスが通ってない(Cygwinとmsysygit両方入ってる環境だから敢えてmsysgitにはパスを通してない)から

for dirname in *
do
  if [ -d $dirname/.git ]
  then
    cd $dirname
    echo "pull: $dirname"
    "/c/Program Files/Git/bin/git.exe" pull
    echo "submodule update: $dirname"
    "/c/Program Files/Git/bin/git.exe" submodule update --init ""
    "/c/Program Files/Git/bin/git.exe" submodule foreach 'git fetch;git c he cko ut  origin/master'
    cd ..
  fi
done

としてたんだけど、それがうまくいかなかったからムキーッとなってたんだけど、うまくいくようになってよかった。

SubversionからGitに移行した - Nov 01, 2010

アドオンの開発にはずっと須藤さんに用意してもらったSubversionのリポジトリを使ってたんだけど、

  • インターネットに繋がらない状態でコミットできないのが辛い
  • 他の人からの変更を受け取りづらい(そんな事があればの話なんだけど)

と思っていて、Git(あるいは他の分散型バージョン管理システム)ならそれが解消されると期待してて、でもずっと移行できていなかった。

  • コマンドライン操作が嫌いなので、Subversionを使うのにWindowsではTortoiseSVNを、LinuxではRabbitVCS(旧称:NautilusSVN)を使ってるけど、GitではWindowsにはTortoiseGitがあってもLinuxでは相当する物が無いみたい。
  • バージョン管理システムを移行するとそれまでのコミットの履歴も全部なくなっちゃうんじゃないの?
  • git-svnっていうのを使うといいと聞いたけど、試しても空のディレクトリしかできないんですけど……

というのがその理由だった。でも

  • git-svnは時々動作がうまくいかない事があるみたいで、Windows上のmsysgitが特に高確率で失敗するだけで、VirtualPC上のUbuntu 10.04LTSのgit-svnだとそれほどでもなかった。
    • それで成功したケースだとコミット履歴が完全に引き継がれてた。なんだ、ちゃんと動けばちゃんとやってくれるんじゃん!!!
  • 最近、システムモニターの開発でGitをずっと使ってたから、まあLinux上ではコマンドラインでも別にいいかな……という気がしてきた。

という事で、思い切って移行してみる事にしました。具体的な手順はSourceForge.JP のプロジェクトを Subversion から Git へ移行するに従いました。

SubversionとGit

大まかに言うとこういう事だと僕は理解してる。

  • Subversionでは、中央に1つのリポジトリがあって、それを各人がチェックアウトしてワーキングコピーを作り、ワーキングコピーに対して行った変更をリポジトリにコミットする、という形で「バージョンの管理」と「成果の共有」を実現する。
  • Gitでは、リポジトリのコピーを誰でも簡単に手元に作る事ができて(Gitではこれをcloneと言う)、Subversionでワーキングコピーからリポジトリに変更点をコミットするのと同じように、Gitではリポジトリとリポジトリの間で変更点をコミットできる(Gitではこれをpushとかpullとか言う)。
    • どれか1つのリポジトリを「中央のリポジトリ」として運用すれば、Subversionでのバージョン管理と同じような運用もできる。
    • 手元にできるのは「履歴の情報を持たないワーキングコピー」ではなく「元のリポジトリを複製したリポジトリそのもの」なので、インターネットに繋がってない状態でもローカルのリポジトリに対してコミットできる。
    • Subversionではtrunkと呼ばれていた物は、Gitではmasterと呼ばれる。
  • git-svnは、以下のような事をする。
    • Subversionのリポジトリのコミット履歴を全部辿る。
    • ローカルに新しいGitリポジトリを作って、Subversionのコミット履歴に対応するコミットを順番にそのGitリポジトリにコミットする。
    • なので、git-svnでGitリポジトリ化したSubversionリポジトリは、元のリポジトリの変更履歴を完全に引き継いだ物になる。

やってみる

ツリー型タブSubversionリポジトリをgithub上の同名のリポジトリに移行する手順を振り返ってみる。

  1. Subversionのリポジトリをgit-svnでGitリポジトリに変換する。
    $ git svn clone --prefix svn/ -s https://www.cozmixng.org/repos/piro/treestyletab
    これでローカルにtreestyletabという名前のディレクトリができて、これがGitリポジトリになってる。コミットを1件1件取り込むので、コミット数が多いとメチャメチャ時間がかかるけど、黙って待つ。たまに失敗するので、そういう時はできたゴミディレクトリを消してもう一度やり直す。
  2. githubに「treestyletab」という名前でリポジトリを作る。(分かりやすくするためにSubversionの物と同じ名前にするといい。リポジトリ名は後でgithub上で好きなように変更できるので。)これで、読み書き両用のURLとして「git@github.com:piroor/treestyletab.git」みたいなのが使えるようになる。
  3. ローカルにできたtreestyletabのGitリポジトリの「元」として、githubのリポジトリを登録する。
    $ git remote add origin git@github.com:piroor/treestyletab.git
    これによって、「このリポジトリはgithubのリポジトリをcloneした物ですよ」「このリポジトリに行われた変更を元のリポジトリにpushする時は、githubにpushしますよ」ということになる。
  4. pushする。
    $ git push origin master
    これで、Subversionのリポジトリから持ってきたコミット履歴が全部github上のリポジトリに反映される。
  5. Subversionのtagsの内容をGitのtagに変換する。Subversionでtags以下に作ったタグはブランチとして取り込まれているので、これをGitのタグにしてやる。
    $ git branch -r
    これでブランチの一覧を見れるので、
    $ git checkout svn/tags/0.10.2010102501
    という風にしてブランチを切り替えて
    $ git tag 0.10.2010102501
    でタグを打って
    $ git push --tags origin
    でタグの情報をgithubのリポジトリにpushする。タグの数だけこの操作を繰り返す。
  6. Subversionのbranchesにブランチがある場合、Gitのbranchに変換する。Gitだとローカルにあるブランチをgit push origin ローカルリポジトリのブランチ名:リモートリポジトリのブランチ名でリモートリポジトリにpushできるんだけど、git−svnでcloneしたローカルリポジトリにあるブランチはそのままだとpushできない(不可視のブランチになってる?)ようなので、
    $ git checkout svn/my-branch
    でブランチを切り替えて
    $ git branch my-branch
    で普通のブランチとして切り直して
    $ git push origin my-branch:my-branch
    でgithubにpushする。

自分はアドオンの数自体20個以上あるし、それぞれアホみたいに何度もリリースしててタグの数がハンパないことになってるので、全部手動でやることを考えたら気が遠くなりました。なのでこういうスクリプトをRubyで書いてみました。

#!/usr/bin/ruby

SVN_REPOSITORY_PATH = "https://www.cozmixng.org/repos/piro/<%= src_project_name %>"
GIT_REPOSITORY_PATH = "git@github.com:piroor/<%= dest_project_name %>.git"

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "fileutils"
require "erb"
require "shellwords"

def main
  ARGV.each do |arg|
    p "process #{arg}"
    args = arg.split(":")
    src_project_name = args[0]
    dest_project_name = args.size > 1 ? args[1] : args[0]
    svn_to_git(src_project_name, dest_project_name)
  end
end

def svn_to_git(src_project_name, dest_project_name)
  p "svn:#{src_project_name}, git:#{dest_project_name}"
  clone(src_project_name)
  push(src_project_name, dest_project_name)
  push_branches_and_tags(src_project_name)
rescue Exception
  p $!
end

def clone(src_project_name)
  result = run("git", "svn", "clone",
               "--prefix", "svn/",
               "-s", ERB.new(SVN_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
  raise Exception.new("clone of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("Checked out HEAD:")
end

def push(src_project_name, dest_project_name)
  FileUtils.cd(src_project_name) do
    result = run("git", "remote", "add",
                 "origin", ERB.new(GIT_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
    result = run("git", "push", "origin", "master")
    p result.to_s
    raise Exception.new("push of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("master -> master")
  end
end

def push_branches_and_tags(src_project_name)
  FileUtils.cd(src_project_name) do
    branches = run("git", "branch", "-r")
    branches = branches.to_s.split("\n")
    branches.each do |branch|
      next unless branch.include?("svn/")
      branch.strip!

      name = branch.split("svn/")[1]
      next if name == "trunk"

      if name.include?("tags/")
        tag = branch.split("tags/")[1]
        p run("git", "checkout", branch)
        p run("git", "tag", tag)
        p run("git", "push", "--tags", "origin")
      else
        p run("git", "checkout", branch)
        p run("git", "branch", name)
        p run("git", "push", "origin", "#{name}:#{name}")
      end
    end
  end
end

def run(*args)
  command_line = Shellwords.shelljoin(args)
  result = `#{command_line} 2>&1`
  result
end

main

ファイル名は svn-to-git.rb として、

$ ./svn-to-git.rb treestyletab

とやると、ここまでの手順のうちgithubのサイト上でリポジトリを作る所以外を全部自動でやってくれるという物です(ということは、スクリプトの実行前にあらかじめgithubのサイト上でリポジトリを作っておかないといけない)。これでなんとか全部のリポジトリをgithubに持ってくることができましたXUL/Migemoの辞書をSQLiteにしてみようとかそういうブランチを切ってた物が取り込めてなかったりsvn:externalsで参照してた物が入ってなかったり、Subversionに突っ込んでから1回もコミットしてないプロジェクトをまだgithubに持ってきてなかったり(必要あるの? 無いよね?)という課題は残っていますが。→ブランチの取り込み方が分かったので追記しました。→svn:externalsの移行は諦めてsubmoduleにすることにしました。TortoiseGitでメニューから「Submodule Add」を選んでリポジトリにgit@github.com:piroor/makexpi.gitを、パスにbuildscriptを指定する、という手順でだいたい同じような結果になるみたい。更新の時はTortoiseGitだと「Git Sync」から「Submodule Sync」しないといけないようだ。コマンドラインなら一発で更新できるようなんだけど……

今後はgit-svn駆け込み寺あたりを熟読して頑張っていきたいと思っております。あと、今後具体的にコードを提供してくれるような人がもしいれば、githubの方にpull requestっていうんですか?するようにしてもらえたら幸いです。

……というまとめエントリを書こうとしてもうちょっと調べ直してたら、git-svnでタグが自動で取り込まれないとかの問題を解消するラッパーのsvn2gitという物があるということを今更知りました。ギャフン!!!!!

……さらに後から気がついたけど、HTTPでアクセスできる公開のSubversionリポジトリをgithubに移行するだけならtagやbranchの変換も含めてgithubのWebインターフェース上の機能だけでサクッとできてしまうことが分かりました。ギャフンギャフン!!!!!!!!! 手順は以下の通りです。

  1. githubで新しいリポジトリを作る。
  2. 空のリポジトリができたら、最初に表示されてる説明の下の方にある「Subversionのリポジトリを取り込みたい? ここをクリック」というリンク(日本語のUIにしてる場合。英語でも多分似たような文言があると思う。)を辿る。
  3. インポート元のSubversionリポジトリのURLを入力する。
  4. githubのユーザ名( 「piroor <piro.outsider.reflex@gmail.com>」のような、githubのユーザ名と登録済みのメールアドレスの組み合わせ)を作者として入力する。
  5. しばらく待つ。

最初githubのUIを英語で使ってたのと、下までスクロールしてなかったから気がついてなかった。なんということでしょう。丸1日以上を無駄にしてしまいました。

SubversionからGitに移行した - Jan 01, 1970

アドオンの開発にはずっと須藤さんに用意してもらったSubversionのリポジトリを使ってたんだけど、

  • インターネットに繋がらない状態でコミットできないのが辛い
  • 他の人からの変更を受け取りづらい(そんな事があればの話なんだけど)

と思っていて、Git(あるいは他の分散型バージョン管理システム)ならそれが解消されると期待してて、でもずっと移行できていなかった。

  • コマンドライン操作が嫌いなので、Subversionを使うのにWindowsではTortoiseSVNを、LinuxではRabbitVCS(旧称:NautilusSVN)を使ってるけど、GitではWindowsにはTortoiseGitがあってもLinuxでは相当する物が無いみたい。
  • バージョン管理システムを移行するとそれまでのコミットの履歴も全部なくなっちゃうんじゃないの?
  • git-svnっていうのを使うといいと聞いたけど、試しても空のディレクトリしかできないんですけど……

というのがその理由だった。でも

  • git-svnは時々動作がうまくいかない事があるみたいで、Windows上のmsysgitが特に高確率で失敗するだけで、VirtualPC上のUbuntu 10.04LTSのgit-svnだとそれほどでもなかった。
    • それで成功したケースだとコミット履歴が完全に引き継がれてた。なんだ、ちゃんと動けばちゃんとやってくれるんじゃん!!!

  • 最近、システムモニターの開発でGitをずっと使ってたから、まあLinux上ではコマンドラインでも別にいいかな……という気がしてきた。

という事で、思い切って移行してみる事にしました。具体的な手順はSourceForge.JP のプロジェクトを Subversion から Git へ移行するに従いました。

SubversionとGit

大まかに言うとこういう事だと僕は理解してる。

  • Subversionでは、中央に1つのリポジトリがあって、それを各人がチェックアウトしてワーキングコピーを作り、ワーキングコピーに対して行った変更をリポジトリにコミットする、という形で「バージョンの管理」と「成果の共有」を実現する。
  • Gitでは、リポジトリのコピーを誰でも簡単に手元に作る事ができて(Gitではこれをcloneと言う)、Subversionでワーキングコピーからリポジトリに変更点をコミットするのと同じように、Gitではリポジトリとリポジトリの間で変更点をコミットできる(Gitではこれをpushとかpullとか言う)。
    • どれか1つのリポジトリを「中央のリポジトリ」として運用すれば、Subversionでのバージョン管理と同じような運用もできる。
    • 手元にできるのは「履歴の情報を持たないワーキングコピー」ではなく「元のリポジトリを複製したリポジトリそのもの」なので、インターネットに繋がってない状態でもローカルのリポジトリに対してコミットできる。
    • Subversionではtrunkと呼ばれていた物は、Gitではmasterと呼ばれる。

  • git-svnは、以下のような事をする。

    • Subversionのリポジトリのコミット履歴を全部辿る。
    • ローカルに新しいGitリポジトリを作って、Subversionのコミット履歴に対応するコミットを順番にそのGitリポジトリにコミットする。
    • なので、git-svnでGitリポジトリ化したSubversionリポジトリは、元のリポジトリの変更履歴を完全に引き継いだ物になる。

やってみる

ツリー型タブSubversionリポジトリをgithub上の同名のリポジトリに移行する手順を振り返ってみる。

  1. Subversionのリポジトリをgit-svnでGitリポジトリに変換する。
    $ git svn clone --prefix svn/ -s https://www.cozmixng.org/repos/piro/treestyletab
    これでローカルにtreestyletabという名前のディレクトリができて、これがGitリポジトリになってる。コミットを1件1件取り込むので、コミット数が多いとメチャメチャ時間がかかるけど、黙って待つ。たまに失敗するので、そういう時はできたゴミディレクトリを消してもう一度やり直す。
  2. githubに「treestyletab」という名前でリポジトリを作る。(分かりやすくするためにSubversionの物と同じ名前にするといい。リポジトリ名は後でgithub上で好きなように変更できるので。)これで、読み書き両用のURLとして「git@github.com:piroor/treestyletab.git」みたいなのが使えるようになる。
  3. ローカルにできたtreestyletabのGitリポジトリの「元」として、githubのリポジトリを登録する。
    $ git remote add origin git@github.com:piroor/treestyletab.git
    これによって、「このリポジトリはgithubのリポジトリをcloneした物ですよ」「このリポジトリに行われた変更を元のリポジトリにpushする時は、githubにpushしますよ」ということになる。
  4. pushする。
    $ git push origin master
    これで、Subversionのリポジトリから持ってきたコミット履歴が全部github上のリポジトリに反映される。
  5. Subversionのtagsの内容をGitのtagに変換する。Subversionでtags以下に作ったタグはブランチとして取り込まれているので、これをGitのタグにしてやる。
    $ git branch -r
    これでブランチの一覧を見れるので、
    $ git checkout svn/tags/0.10.2010102501
    という風にしてブランチを切り替えて
    $ git tag 0.10.2010102501
    でタグを打って
    $ git push --tags origin
    でタグの情報をgithubのリポジトリにpushする。タグの数だけこの操作を繰り返す。
  6. Subversionのbranchesにブランチがある場合、Gitのbranchに変換する。Gitだとローカルにあるブランチをgit push origin ローカルリポジトリのブランチ名:リモートリポジトリのブランチ名でリモートリポジトリにpushできるんだけど、git−svnでcloneしたローカルリポジトリにあるブランチはそのままだとpushできない(不可視のブランチになってる?)ようなので、
    $ git checkout svn/my-branch
    でブランチを切り替えて
    $ git branch my-branch
    で普通のブランチとして切り直して
    $ git push origin my-branch:my-branch
    でgithubにpushする。

自分はアドオンの数自体20個以上あるし、それぞれアホみたいに何度もリリースしててタグの数がハンパないことになってるので、全部手動でやることを考えたら気が遠くなりました。なのでこういうスクリプトをRubyで書いてみました。

#!/usr/bin/ruby

SVN_REPOSITORY_PATH = "https://www.cozmixng.org/repos/piro/<%= src_project_name %>"
GIT_REPOSITORY_PATH = "git@github.com:piroor/<%= dest_project_name %>.git"

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "fileutils"
require "erb"
require "shellwords"

def main
  ARGV.each do |arg|
    p "process #{arg}"
    args = arg.split(":")
    src_project_name = args[0]
    dest_project_name = args.size > 1 ? args[1] : args[0]
    svn_to_git(src_project_name, dest_project_name)
  end
end

def svn_to_git(src_project_name, dest_project_name)
  p "svn:#{src_project_name}, git:#{dest_project_name}"
  clone(src_project_name)
  push(src_project_name, dest_project_name)
  push_branches_and_tags(src_project_name)
rescue Exception
  p $!
end

def clone(src_project_name)
  result = run("git", "svn", "clone",
               "--prefix", "svn/",
               "-s", ERB.new(SVN_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
  raise Exception.new("clone of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("Checked out HEAD:")
end

def push(src_project_name, dest_project_name)
  FileUtils.cd(src_project_name) do
    result = run("git", "remote", "add",
                 "origin", ERB.new(GIT_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
    result = run("git", "push", "origin", "master")
    p result.to_s
    raise Exception.new("push of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("master -> master")
  end
end

def push_branches_and_tags(src_project_name)
  FileUtils.cd(src_project_name) do
    branches = run("git", "branch", "-r")
    branches = branches.to_s.split("\n")
    branches.each do |branch|
      next unless branch.include?("svn/")
      branch.strip!

      name = branch.split("svn/")[1]
      next if name == "trunk"

      if name.include?("tags/")
        tag = branch.split("tags/")[1]
        p run("git", "checkout", branch)
        p run("git", "tag", tag)
        p run("git", "push", "--tags", "origin")
      else
        p run("git", "checkout", branch)
        p run("git", "branch", name)
        p run("git", "push", "origin", "#{name}:#{name}")
      end
    end
  end
end

def run(*args)
  command_line = Shellwords.shelljoin(args)
  result = `#{command_line} 2>&1`
  result
end

main

ファイル名は svn-to-git.rb として、

$ ./svn-to-git.rb treestyletab

とやると、ここまでの手順のうちgithubのサイト上でリポジトリを作る所以外を全部自動でやってくれるという物です(ということは、スクリプトの実行前にあらかじめgithubのサイト上でリポジトリを作っておかないといけない)。これでなんとか全部のリポジトリをgithubに持ってくることができましたXUL/Migemoの辞書をSQLiteにしてみようとかそういうブランチを切ってた物が取り込めてなかったりsvn:externalsで参照してた物が入ってなかったり、Subversionに突っ込んでから1回もコミットしてないプロジェクトをまだgithubに持ってきてなかったり(必要あるの? 無いよね?)という課題は残っていますが。→ブランチの取り込み方が分かったので追記しました。→svn:externalsの移行は諦めてsubmoduleにすることにしました。TortoiseGitでメニューから「Submodule Add」を選んでリポジトリにgit@github.com:piroor/makexpi.gitを、パスにbuildscriptを指定する、という手順でだいたい同じような結果になるみたい。更新の時はTortoiseGitだと「Git Sync」から「Submodule Sync」しないといけないようだ。コマンドラインなら一発で更新できるようなんだけど……

今後はgit-svn駆け込み寺あたりを熟読して頑張っていきたいと思っております。あと、今後具体的にコードを提供してくれるような人がもしいれば、githubの方にpull requestっていうんですか?するようにしてもらえたら幸いです。

……というまとめエントリを書こうとしてもうちょっと調べ直してたら、git-svnでタグが自動で取り込まれないとかの問題を解消するラッパーのsvn2gitという物があるということを今更知りました。ギャフン!!!!!

……さらに後から気がついたけど、HTTPでアクセスできる公開のSubversionリポジトリをgithubに移行するだけならtagやbranchの変換も含めてgithubのWebインターフェース上の機能だけでサクッとできてしまうことが分かりました。ギャフンギャフン!!!!!!!!! 手順は以下の通りです。

  1. githubで新しいリポジトリを作る。
  2. 空のリポジトリができたら、最初に表示されてる説明の下の方にある「Subversionのリポジトリを取り込みたい? ここをクリック」というリンク(日本語のUIにしてる場合。英語でも多分似たような文言があると思う。)を辿る。
  3. インポート元のSubversionリポジトリのURLを入力する。
  4. githubのユーザ名( 「piroor <piro.outsider.reflex@gmail.com>」のような、githubのユーザ名と登録済みのメールアドレスの組み合わせ)を作者として入力する。
  5. しばらく待つ。

最初githubのUIを英語で使ってたのと、下までスクロールしてなかったから気がついてなかった。なんということでしょう。丸1日以上を無駄にしてしまいました。

SubversionからGitに移行した - Jan 01, 1970

アドオンの開発にはずっと須藤さんに用意してもらったSubversionのリポジトリを使ってたんだけど、

  • インターネットに繋がらない状態でコミットできないのが辛い
  • 他の人からの変更を受け取りづらい(そんな事があればの話なんだけど)

と思っていて、Git(あるいは他の分散型バージョン管理システム)ならそれが解消されると期待してて、でもずっと移行できていなかった。

  • コマンドライン操作が嫌いなので、Subversionを使うのにWindowsではTortoiseSVNを、LinuxではRabbitVCS(旧称:NautilusSVN)を使ってるけど、GitではWindowsにはTortoiseGitがあってもLinuxでは相当する物が無いみたい。
  • バージョン管理システムを移行するとそれまでのコミットの履歴も全部なくなっちゃうんじゃないの?
  • git-svnっていうのを使うといいと聞いたけど、試しても空のディレクトリしかできないんですけど……

というのがその理由だった。でも

  • git-svnは時々動作がうまくいかない事があるみたいで、Windows上のmsysgitが特に高確率で失敗するだけで、VirtualPC上のUbuntu 10.04LTSのgit-svnだとそれほどでもなかった。
    • それで成功したケースだとコミット履歴が完全に引き継がれてた。なんだ、ちゃんと動けばちゃんとやってくれるんじゃん!!!

  • 最近、システムモニターの開発でGitをずっと使ってたから、まあLinux上ではコマンドラインでも別にいいかな……という気がしてきた。

という事で、思い切って移行してみる事にしました。具体的な手順はSourceForge.JP のプロジェクトを Subversion から Git へ移行するに従いました。

SubversionとGit

大まかに言うとこういう事だと僕は理解してる。

  • Subversionでは、中央に1つのリポジトリがあって、それを各人がチェックアウトしてワーキングコピーを作り、ワーキングコピーに対して行った変更をリポジトリにコミットする、という形で「バージョンの管理」と「成果の共有」を実現する。
  • Gitでは、リポジトリのコピーを誰でも簡単に手元に作る事ができて(Gitではこれをcloneと言う)、Subversionでワーキングコピーからリポジトリに変更点をコミットするのと同じように、Gitではリポジトリとリポジトリの間で変更点をコミットできる(Gitではこれをpushとかpullとか言う)。
    • どれか1つのリポジトリを「中央のリポジトリ」として運用すれば、Subversionでのバージョン管理と同じような運用もできる。
    • 手元にできるのは「履歴の情報を持たないワーキングコピー」ではなく「元のリポジトリを複製したリポジトリそのもの」なので、インターネットに繋がってない状態でもローカルのリポジトリに対してコミットできる。
    • Subversionではtrunkと呼ばれていた物は、Gitではmasterと呼ばれる。

  • git-svnは、以下のような事をする。

    • Subversionのリポジトリのコミット履歴を全部辿る。
    • ローカルに新しいGitリポジトリを作って、Subversionのコミット履歴に対応するコミットを順番にそのGitリポジトリにコミットする。
    • なので、git-svnでGitリポジトリ化したSubversionリポジトリは、元のリポジトリの変更履歴を完全に引き継いだ物になる。

やってみる

ツリー型タブSubversionリポジトリをgithub上の同名のリポジトリに移行する手順を振り返ってみる。

  1. Subversionのリポジトリをgit-svnでGitリポジトリに変換する。
    $ git svn clone --prefix svn/ -s https://www.cozmixng.org/repos/piro/treestyletab
    これでローカルにtreestyletabという名前のディレクトリができて、これがGitリポジトリになってる。コミットを1件1件取り込むので、コミット数が多いとメチャメチャ時間がかかるけど、黙って待つ。たまに失敗するので、そういう時はできたゴミディレクトリを消してもう一度やり直す。
  2. githubに「treestyletab」という名前でリポジトリを作る。(分かりやすくするためにSubversionの物と同じ名前にするといい。リポジトリ名は後でgithub上で好きなように変更できるので。)これで、読み書き両用のURLとして「git@github.com:piroor/treestyletab.git」みたいなのが使えるようになる。
  3. ローカルにできたtreestyletabのGitリポジトリの「元」として、githubのリポジトリを登録する。
    $ git remote add origin git@github.com:piroor/treestyletab.git
    これによって、「このリポジトリはgithubのリポジトリをcloneした物ですよ」「このリポジトリに行われた変更を元のリポジトリにpushする時は、githubにpushしますよ」ということになる。
  4. pushする。
    $ git push origin master
    これで、Subversionのリポジトリから持ってきたコミット履歴が全部github上のリポジトリに反映される。
  5. Subversionのtagsの内容をGitのtagに変換する。Subversionでtags以下に作ったタグはブランチとして取り込まれているので、これをGitのタグにしてやる。
    $ git branch -r
    これでブランチの一覧を見れるので、
    $ git checkout svn/tags/0.10.2010102501
    という風にしてブランチを切り替えて
    $ git tag 0.10.2010102501
    でタグを打って
    $ git push --tags origin
    でタグの情報をgithubのリポジトリにpushする。タグの数だけこの操作を繰り返す。
  6. Subversionのbranchesにブランチがある場合、Gitのbranchに変換する。Gitだとローカルにあるブランチをgit push origin ローカルリポジトリのブランチ名:リモートリポジトリのブランチ名でリモートリポジトリにpushできるんだけど、git−svnでcloneしたローカルリポジトリにあるブランチはそのままだとpushできない(不可視のブランチになってる?)ようなので、
    $ git checkout svn/my-branch
    でブランチを切り替えて
    $ git branch my-branch
    で普通のブランチとして切り直して
    $ git push origin my-branch:my-branch
    でgithubにpushする。

自分はアドオンの数自体20個以上あるし、それぞれアホみたいに何度もリリースしててタグの数がハンパないことになってるので、全部手動でやることを考えたら気が遠くなりました。なのでこういうスクリプトをRubyで書いてみました。

#!/usr/bin/ruby

SVN_REPOSITORY_PATH = "https://www.cozmixng.org/repos/piro/<%= src_project_name %>"
GIT_REPOSITORY_PATH = "git@github.com:piroor/<%= dest_project_name %>.git"

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "fileutils"
require "erb"
require "shellwords"

def main
  ARGV.each do |arg|
    p "process #{arg}"
    args = arg.split(":")
    src_project_name = args[0]
    dest_project_name = args.size > 1 ? args[1] : args[0]
    svn_to_git(src_project_name, dest_project_name)
  end
end

def svn_to_git(src_project_name, dest_project_name)
  p "svn:#{src_project_name}, git:#{dest_project_name}"
  clone(src_project_name)
  push(src_project_name, dest_project_name)
  push_branches_and_tags(src_project_name)
rescue Exception
  p $!
end

def clone(src_project_name)
  result = run("git", "svn", "clone",
               "--prefix", "svn/",
               "-s", ERB.new(SVN_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
  raise Exception.new("clone of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("Checked out HEAD:")
end

def push(src_project_name, dest_project_name)
  FileUtils.cd(src_project_name) do
    result = run("git", "remote", "add",
                 "origin", ERB.new(GIT_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
    result = run("git", "push", "origin", "master")
    p result.to_s
    raise Exception.new("push of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("master -> master")
  end
end

def push_branches_and_tags(src_project_name)
  FileUtils.cd(src_project_name) do
    branches = run("git", "branch", "-r")
    branches = branches.to_s.split("\n")
    branches.each do |branch|
      next unless branch.include?("svn/")
      branch.strip!

      name = branch.split("svn/")[1]
      next if name == "trunk"

      if name.include?("tags/")
        tag = branch.split("tags/")[1]
        p run("git", "checkout", branch)
        p run("git", "tag", tag)
        p run("git", "push", "--tags", "origin")
      else
        p run("git", "checkout", branch)
        p run("git", "branch", name)
        p run("git", "push", "origin", "#{name}:#{name}")
      end
    end
  end
end

def run(*args)
  command_line = Shellwords.shelljoin(args)
  result = `#{command_line} 2>&1`
  result
end

main

ファイル名は svn-to-git.rb として、

$ ./svn-to-git.rb treestyletab

とやると、ここまでの手順のうちgithubのサイト上でリポジトリを作る所以外を全部自動でやってくれるという物です(ということは、スクリプトの実行前にあらかじめgithubのサイト上でリポジトリを作っておかないといけない)。これでなんとか全部のリポジトリをgithubに持ってくることができましたXUL/Migemoの辞書をSQLiteにしてみようとかそういうブランチを切ってた物が取り込めてなかったりsvn:externalsで参照してた物が入ってなかったり、Subversionに突っ込んでから1回もコミットしてないプロジェクトをまだgithubに持ってきてなかったり(必要あるの? 無いよね?)という課題は残っていますが。→ブランチの取り込み方が分かったので追記しました。→svn:externalsの移行は諦めてsubmoduleにすることにしました。TortoiseGitでメニューから「Submodule Add」を選んでリポジトリにgit@github.com:piroor/makexpi.gitを、パスにbuildscriptを指定する、という手順でだいたい同じような結果になるみたい。更新の時はTortoiseGitだと「Git Sync」から「Submodule Sync」しないといけないようだ。コマンドラインなら一発で更新できるようなんだけど……

今後はgit-svn駆け込み寺あたりを熟読して頑張っていきたいと思っております。あと、今後具体的にコードを提供してくれるような人がもしいれば、githubの方にpull requestっていうんですか?するようにしてもらえたら幸いです。

……というまとめエントリを書こうとしてもうちょっと調べ直してたら、git-svnでタグが自動で取り込まれないとかの問題を解消するラッパーのsvn2gitという物があるということを今更知りました。ギャフン!!!!!

……さらに後から気がついたけど、HTTPでアクセスできる公開のSubversionリポジトリをgithubに移行するだけならtagやbranchの変換も含めてgithubのWebインターフェース上の機能だけでサクッとできてしまうことが分かりました。ギャフンギャフン!!!!!!!!! 手順は以下の通りです。

  1. githubで新しいリポジトリを作る。
  2. 空のリポジトリができたら、最初に表示されてる説明の下の方にある「Subversionのリポジトリを取り込みたい? ここをクリック」というリンク(日本語のUIにしてる場合。英語でも多分似たような文言があると思う。)を辿る。
  3. インポート元のSubversionリポジトリのURLを入力する。
  4. githubのユーザ名( 「piroor <piro.outsider.reflex@gmail.com>」のような、githubのユーザ名と登録済みのメールアドレスの組み合わせ)を作者として入力する。
  5. しばらく待つ。

最初githubのUIを英語で使ってたのと、下までスクロールしてなかったから気がついてなかった。なんということでしょう。丸1日以上を無駄にしてしまいました。

SubversionからGitに移行した - Jan 01, 1970

アドオンの開発にはずっと須藤さんに用意してもらったSubversionのリポジトリを使ってたんだけど、

  • インターネットに繋がらない状態でコミットできないのが辛い
  • 他の人からの変更を受け取りづらい(そんな事があればの話なんだけど)

と思っていて、Git(あるいは他の分散型バージョン管理システム)ならそれが解消されると期待してて、でもずっと移行できていなかった。

  • コマンドライン操作が嫌いなので、Subversionを使うのにWindowsではTortoiseSVNを、LinuxではRabbitVCS(旧称:NautilusSVN)を使ってるけど、GitではWindowsにはTortoiseGitがあってもLinuxでは相当する物が無いみたい。
  • バージョン管理システムを移行するとそれまでのコミットの履歴も全部なくなっちゃうんじゃないの?
  • git-svnっていうのを使うといいと聞いたけど、試しても空のディレクトリしかできないんですけど……

というのがその理由だった。でも

  • git-svnは時々動作がうまくいかない事があるみたいで、Windows上のmsysgitが特に高確率で失敗するだけで、VirtualPC上のUbuntu 10.04LTSのgit-svnだとそれほどでもなかった。
    • それで成功したケースだとコミット履歴が完全に引き継がれてた。なんだ、ちゃんと動けばちゃんとやってくれるんじゃん!!!

  • 最近、システムモニターの開発でGitをずっと使ってたから、まあLinux上ではコマンドラインでも別にいいかな……という気がしてきた。

という事で、思い切って移行してみる事にしました。具体的な手順はSourceForge.JP のプロジェクトを Subversion から Git へ移行するに従いました。

SubversionとGit

大まかに言うとこういう事だと僕は理解してる。

  • Subversionでは、中央に1つのリポジトリがあって、それを各人がチェックアウトしてワーキングコピーを作り、ワーキングコピーに対して行った変更をリポジトリにコミットする、という形で「バージョンの管理」と「成果の共有」を実現する。
  • Gitでは、リポジトリのコピーを誰でも簡単に手元に作る事ができて(Gitではこれをcloneと言う)、Subversionでワーキングコピーからリポジトリに変更点をコミットするのと同じように、Gitではリポジトリとリポジトリの間で変更点をコミットできる(Gitではこれをpushとかpullとか言う)。
    • どれか1つのリポジトリを「中央のリポジトリ」として運用すれば、Subversionでのバージョン管理と同じような運用もできる。
    • 手元にできるのは「履歴の情報を持たないワーキングコピー」ではなく「元のリポジトリを複製したリポジトリそのもの」なので、インターネットに繋がってない状態でもローカルのリポジトリに対してコミットできる。
    • Subversionではtrunkと呼ばれていた物は、Gitではmasterと呼ばれる。

  • git-svnは、以下のような事をする。

    • Subversionのリポジトリのコミット履歴を全部辿る。
    • ローカルに新しいGitリポジトリを作って、Subversionのコミット履歴に対応するコミットを順番にそのGitリポジトリにコミットする。
    • なので、git-svnでGitリポジトリ化したSubversionリポジトリは、元のリポジトリの変更履歴を完全に引き継いだ物になる。

やってみる

ツリー型タブSubversionリポジトリをgithub上の同名のリポジトリに移行する手順を振り返ってみる。

  1. Subversionのリポジトリをgit-svnでGitリポジトリに変換する。
    $ git svn clone --prefix svn/ -s https://www.cozmixng.org/repos/piro/treestyletab
    これでローカルにtreestyletabという名前のディレクトリができて、これがGitリポジトリになってる。コミットを1件1件取り込むので、コミット数が多いとメチャメチャ時間がかかるけど、黙って待つ。たまに失敗するので、そういう時はできたゴミディレクトリを消してもう一度やり直す。
  2. githubに「treestyletab」という名前でリポジトリを作る。(分かりやすくするためにSubversionの物と同じ名前にするといい。リポジトリ名は後でgithub上で好きなように変更できるので。)これで、読み書き両用のURLとして「git@github.com:piroor/treestyletab.git」みたいなのが使えるようになる。
  3. ローカルにできたtreestyletabのGitリポジトリの「元」として、githubのリポジトリを登録する。
    $ git remote add origin git@github.com:piroor/treestyletab.git
    これによって、「このリポジトリはgithubのリポジトリをcloneした物ですよ」「このリポジトリに行われた変更を元のリポジトリにpushする時は、githubにpushしますよ」ということになる。
  4. pushする。
    $ git push origin master
    これで、Subversionのリポジトリから持ってきたコミット履歴が全部github上のリポジトリに反映される。
  5. Subversionのtagsの内容をGitのtagに変換する。Subversionでtags以下に作ったタグはブランチとして取り込まれているので、これをGitのタグにしてやる。
    $ git branch -r
    これでブランチの一覧を見れるので、
    $ git checkout svn/tags/0.10.2010102501
    という風にしてブランチを切り替えて
    $ git tag 0.10.2010102501
    でタグを打って
    $ git push --tags origin
    でタグの情報をgithubのリポジトリにpushする。タグの数だけこの操作を繰り返す。
  6. Subversionのbranchesにブランチがある場合、Gitのbranchに変換する。Gitだとローカルにあるブランチをgit push origin ローカルリポジトリのブランチ名:リモートリポジトリのブランチ名でリモートリポジトリにpushできるんだけど、git−svnでcloneしたローカルリポジトリにあるブランチはそのままだとpushできない(不可視のブランチになってる?)ようなので、
    $ git checkout svn/my-branch
    でブランチを切り替えて
    $ git branch my-branch
    で普通のブランチとして切り直して
    $ git push origin my-branch:my-branch
    でgithubにpushする。

自分はアドオンの数自体20個以上あるし、それぞれアホみたいに何度もリリースしててタグの数がハンパないことになってるので、全部手動でやることを考えたら気が遠くなりました。なのでこういうスクリプトをRubyで書いてみました。

#!/usr/bin/ruby

SVN_REPOSITORY_PATH = "https://www.cozmixng.org/repos/piro/<%= src_project_name %>"
GIT_REPOSITORY_PATH = "git@github.com:piroor/<%= dest_project_name %>.git"

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "fileutils"
require "erb"
require "shellwords"

def main
  ARGV.each do |arg|
    p "process #{arg}"
    args = arg.split(":")
    src_project_name = args[0]
    dest_project_name = args.size > 1 ? args[1] : args[0]
    svn_to_git(src_project_name, dest_project_name)
  end
end

def svn_to_git(src_project_name, dest_project_name)
  p "svn:#{src_project_name}, git:#{dest_project_name}"
  clone(src_project_name)
  push(src_project_name, dest_project_name)
  push_branches_and_tags(src_project_name)
rescue Exception
  p $!
end

def clone(src_project_name)
  result = run("git", "svn", "clone",
               "--prefix", "svn/",
               "-s", ERB.new(SVN_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
  raise Exception.new("clone of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("Checked out HEAD:")
end

def push(src_project_name, dest_project_name)
  FileUtils.cd(src_project_name) do
    result = run("git", "remote", "add",
                 "origin", ERB.new(GIT_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
    result = run("git", "push", "origin", "master")
    p result.to_s
    raise Exception.new("push of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("master -> master")
  end
end

def push_branches_and_tags(src_project_name)
  FileUtils.cd(src_project_name) do
    branches = run("git", "branch", "-r")
    branches = branches.to_s.split("\n")
    branches.each do |branch|
      next unless branch.include?("svn/")
      branch.strip!

      name = branch.split("svn/")[1]
      next if name == "trunk"

      if name.include?("tags/")
        tag = branch.split("tags/")[1]
        p run("git", "checkout", branch)
        p run("git", "tag", tag)
        p run("git", "push", "--tags", "origin")
      else
        p run("git", "checkout", branch)
        p run("git", "branch", name)
        p run("git", "push", "origin", "#{name}:#{name}")
      end
    end
  end
end

def run(*args)
  command_line = Shellwords.shelljoin(args)
  result = `#{command_line} 2>&1`
  result
end

main

ファイル名は svn-to-git.rb として、

$ ./svn-to-git.rb treestyletab

とやると、ここまでの手順のうちgithubのサイト上でリポジトリを作る所以外を全部自動でやってくれるという物です(ということは、スクリプトの実行前にあらかじめgithubのサイト上でリポジトリを作っておかないといけない)。これでなんとか全部のリポジトリをgithubに持ってくることができましたXUL/Migemoの辞書をSQLiteにしてみようとかそういうブランチを切ってた物が取り込めてなかったりsvn:externalsで参照してた物が入ってなかったり、Subversionに突っ込んでから1回もコミットしてないプロジェクトをまだgithubに持ってきてなかったり(必要あるの? 無いよね?)という課題は残っていますが。→ブランチの取り込み方が分かったので追記しました。→svn:externalsの移行は諦めてsubmoduleにすることにしました。TortoiseGitでメニューから「Submodule Add」を選んでリポジトリにgit@github.com:piroor/makexpi.gitを、パスにbuildscriptを指定する、という手順でだいたい同じような結果になるみたい。更新の時はTortoiseGitだと「Git Sync」から「Submodule Sync」しないといけないようだ。コマンドラインなら一発で更新できるようなんだけど……

今後はgit-svn駆け込み寺あたりを熟読して頑張っていきたいと思っております。あと、今後具体的にコードを提供してくれるような人がもしいれば、githubの方にpull requestっていうんですか?するようにしてもらえたら幸いです。

……というまとめエントリを書こうとしてもうちょっと調べ直してたら、git-svnでタグが自動で取り込まれないとかの問題を解消するラッパーのsvn2gitという物があるということを今更知りました。ギャフン!!!!!

……さらに後から気がついたけど、HTTPでアクセスできる公開のSubversionリポジトリをgithubに移行するだけならtagやbranchの変換も含めてgithubのWebインターフェース上の機能だけでサクッとできてしまうことが分かりました。ギャフンギャフン!!!!!!!!! 手順は以下の通りです。

  1. githubで新しいリポジトリを作る。
  2. 空のリポジトリができたら、最初に表示されてる説明の下の方にある「Subversionのリポジトリを取り込みたい? ここをクリック」というリンク(日本語のUIにしてる場合。英語でも多分似たような文言があると思う。)を辿る。
  3. インポート元のSubversionリポジトリのURLを入力する。
  4. githubのユーザ名( 「piroor <piro.outsider.reflex@gmail.com>」のような、githubのユーザ名と登録済みのメールアドレスの組み合わせ)を作者として入力する。
  5. しばらく待つ。

最初githubのUIを英語で使ってたのと、下までスクロールしてなかったから気がついてなかった。なんということでしょう。丸1日以上を無駄にしてしまいました。

SubversionからGitに移行した - Jan 01, 1970

アドオンの開発にはずっと須藤さんに用意してもらったSubversionのリポジトリを使ってたんだけど、

  • インターネットに繋がらない状態でコミットできないのが辛い
  • 他の人からの変更を受け取りづらい(そんな事があればの話なんだけど)

と思っていて、Git(あるいは他の分散型バージョン管理システム)ならそれが解消されると期待してて、でもずっと移行できていなかった。

  • コマンドライン操作が嫌いなので、Subversionを使うのにWindowsではTortoiseSVNを、LinuxではRabbitVCS(旧称:NautilusSVN)を使ってるけど、GitではWindowsにはTortoiseGitがあってもLinuxでは相当する物が無いみたい。
  • バージョン管理システムを移行するとそれまでのコミットの履歴も全部なくなっちゃうんじゃないの?
  • git-svnっていうのを使うといいと聞いたけど、試しても空のディレクトリしかできないんですけど……

というのがその理由だった。でも

  • git-svnは時々動作がうまくいかない事があるみたいで、Windows上のmsysgitが特に高確率で失敗するだけで、VirtualPC上のUbuntu 10.04LTSのgit-svnだとそれほどでもなかった。
    • それで成功したケースだとコミット履歴が完全に引き継がれてた。なんだ、ちゃんと動けばちゃんとやってくれるんじゃん!!!

  • 最近、システムモニターの開発でGitをずっと使ってたから、まあLinux上ではコマンドラインでも別にいいかな……という気がしてきた。

という事で、思い切って移行してみる事にしました。具体的な手順はSourceForge.JP のプロジェクトを Subversion から Git へ移行するに従いました。

SubversionとGit

大まかに言うとこういう事だと僕は理解してる。

  • Subversionでは、中央に1つのリポジトリがあって、それを各人がチェックアウトしてワーキングコピーを作り、ワーキングコピーに対して行った変更をリポジトリにコミットする、という形で「バージョンの管理」と「成果の共有」を実現する。
  • Gitでは、リポジトリのコピーを誰でも簡単に手元に作る事ができて(Gitではこれをcloneと言う)、Subversionでワーキングコピーからリポジトリに変更点をコミットするのと同じように、Gitではリポジトリとリポジトリの間で変更点をコミットできる(Gitではこれをpushとかpullとか言う)。
    • どれか1つのリポジトリを「中央のリポジトリ」として運用すれば、Subversionでのバージョン管理と同じような運用もできる。
    • 手元にできるのは「履歴の情報を持たないワーキングコピー」ではなく「元のリポジトリを複製したリポジトリそのもの」なので、インターネットに繋がってない状態でもローカルのリポジトリに対してコミットできる。
    • Subversionではtrunkと呼ばれていた物は、Gitではmasterと呼ばれる。

  • git-svnは、以下のような事をする。

    • Subversionのリポジトリのコミット履歴を全部辿る。
    • ローカルに新しいGitリポジトリを作って、Subversionのコミット履歴に対応するコミットを順番にそのGitリポジトリにコミットする。
    • なので、git-svnでGitリポジトリ化したSubversionリポジトリは、元のリポジトリの変更履歴を完全に引き継いだ物になる。

やってみる

ツリー型タブSubversionリポジトリをgithub上の同名のリポジトリに移行する手順を振り返ってみる。

  1. Subversionのリポジトリをgit-svnでGitリポジトリに変換する。
    $ git svn clone --prefix svn/ -s https://www.cozmixng.org/repos/piro/treestyletab
    これでローカルにtreestyletabという名前のディレクトリができて、これがGitリポジトリになってる。コミットを1件1件取り込むので、コミット数が多いとメチャメチャ時間がかかるけど、黙って待つ。たまに失敗するので、そういう時はできたゴミディレクトリを消してもう一度やり直す。
  2. githubに「treestyletab」という名前でリポジトリを作る。(分かりやすくするためにSubversionの物と同じ名前にするといい。リポジトリ名は後でgithub上で好きなように変更できるので。)これで、読み書き両用のURLとして「git@github.com:piroor/treestyletab.git」みたいなのが使えるようになる。
  3. ローカルにできたtreestyletabのGitリポジトリの「元」として、githubのリポジトリを登録する。
    $ git remote add origin git@github.com:piroor/treestyletab.git
    これによって、「このリポジトリはgithubのリポジトリをcloneした物ですよ」「このリポジトリに行われた変更を元のリポジトリにpushする時は、githubにpushしますよ」ということになる。
  4. pushする。
    $ git push origin master
    これで、Subversionのリポジトリから持ってきたコミット履歴が全部github上のリポジトリに反映される。
  5. Subversionのtagsの内容をGitのtagに変換する。Subversionでtags以下に作ったタグはブランチとして取り込まれているので、これをGitのタグにしてやる。
    $ git branch -r
    これでブランチの一覧を見れるので、
    $ git checkout svn/tags/0.10.2010102501
    という風にしてブランチを切り替えて
    $ git tag 0.10.2010102501
    でタグを打って
    $ git push --tags origin
    でタグの情報をgithubのリポジトリにpushする。タグの数だけこの操作を繰り返す。
  6. Subversionのbranchesにブランチがある場合、Gitのbranchに変換する。Gitだとローカルにあるブランチをgit push origin ローカルリポジトリのブランチ名:リモートリポジトリのブランチ名でリモートリポジトリにpushできるんだけど、git−svnでcloneしたローカルリポジトリにあるブランチはそのままだとpushできない(不可視のブランチになってる?)ようなので、
    $ git checkout svn/my-branch
    でブランチを切り替えて
    $ git branch my-branch
    で普通のブランチとして切り直して
    $ git push origin my-branch:my-branch
    でgithubにpushする。

自分はアドオンの数自体20個以上あるし、それぞれアホみたいに何度もリリースしててタグの数がハンパないことになってるので、全部手動でやることを考えたら気が遠くなりました。なのでこういうスクリプトをRubyで書いてみました。

#!/usr/bin/ruby

SVN_REPOSITORY_PATH = "https://www.cozmixng.org/repos/piro/<%= src_project_name %>"
GIT_REPOSITORY_PATH = "git@github.com:piroor/<%= dest_project_name %>.git"

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "fileutils"
require "erb"
require "shellwords"

def main
  ARGV.each do |arg|
    p "process #{arg}"
    args = arg.split(":")
    src_project_name = args[0]
    dest_project_name = args.size > 1 ? args[1] : args[0]
    svn_to_git(src_project_name, dest_project_name)
  end
end

def svn_to_git(src_project_name, dest_project_name)
  p "svn:#{src_project_name}, git:#{dest_project_name}"
  clone(src_project_name)
  push(src_project_name, dest_project_name)
  push_branches_and_tags(src_project_name)
rescue Exception
  p $!
end

def clone(src_project_name)
  result = run("git", "svn", "clone",
               "--prefix", "svn/",
               "-s", ERB.new(SVN_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
  raise Exception.new("clone of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("Checked out HEAD:")
end

def push(src_project_name, dest_project_name)
  FileUtils.cd(src_project_name) do
    result = run("git", "remote", "add",
                 "origin", ERB.new(GIT_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
    result = run("git", "push", "origin", "master")
    p result.to_s
    raise Exception.new("push of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("master -> master")
  end
end

def push_branches_and_tags(src_project_name)
  FileUtils.cd(src_project_name) do
    branches = run("git", "branch", "-r")
    branches = branches.to_s.split("\n")
    branches.each do |branch|
      next unless branch.include?("svn/")
      branch.strip!

      name = branch.split("svn/")[1]
      next if name == "trunk"

      if name.include?("tags/")
        tag = branch.split("tags/")[1]
        p run("git", "checkout", branch)
        p run("git", "tag", tag)
        p run("git", "push", "--tags", "origin")
      else
        p run("git", "checkout", branch)
        p run("git", "branch", name)
        p run("git", "push", "origin", "#{name}:#{name}")
      end
    end
  end
end

def run(*args)
  command_line = Shellwords.shelljoin(args)
  result = `#{command_line} 2>&1`
  result
end

main

ファイル名は svn-to-git.rb として、

$ ./svn-to-git.rb treestyletab

とやると、ここまでの手順のうちgithubのサイト上でリポジトリを作る所以外を全部自動でやってくれるという物です(ということは、スクリプトの実行前にあらかじめgithubのサイト上でリポジトリを作っておかないといけない)。これでなんとか全部のリポジトリをgithubに持ってくることができましたXUL/Migemoの辞書をSQLiteにしてみようとかそういうブランチを切ってた物が取り込めてなかったりsvn:externalsで参照してた物が入ってなかったり、Subversionに突っ込んでから1回もコミットしてないプロジェクトをまだgithubに持ってきてなかったり(必要あるの? 無いよね?)という課題は残っていますが。→ブランチの取り込み方が分かったので追記しました。→svn:externalsの移行は諦めてsubmoduleにすることにしました。TortoiseGitでメニューから「Submodule Add」を選んでリポジトリにgit@github.com:piroor/makexpi.gitを、パスにbuildscriptを指定する、という手順でだいたい同じような結果になるみたい。更新の時はTortoiseGitだと「Git Sync」から「Submodule Sync」しないといけないようだ。コマンドラインなら一発で更新できるようなんだけど……

今後はgit-svn駆け込み寺あたりを熟読して頑張っていきたいと思っております。あと、今後具体的にコードを提供してくれるような人がもしいれば、githubの方にpull requestっていうんですか?するようにしてもらえたら幸いです。

……というまとめエントリを書こうとしてもうちょっと調べ直してたら、git-svnでタグが自動で取り込まれないとかの問題を解消するラッパーのsvn2gitという物があるということを今更知りました。ギャフン!!!!!

……さらに後から気がついたけど、HTTPでアクセスできる公開のSubversionリポジトリをgithubに移行するだけならtagやbranchの変換も含めてgithubのWebインターフェース上の機能だけでサクッとできてしまうことが分かりました。ギャフンギャフン!!!!!!!!! 手順は以下の通りです。

  1. githubで新しいリポジトリを作る。
  2. 空のリポジトリができたら、最初に表示されてる説明の下の方にある「Subversionのリポジトリを取り込みたい? ここをクリック」というリンク(日本語のUIにしてる場合。英語でも多分似たような文言があると思う。)を辿る。
  3. インポート元のSubversionリポジトリのURLを入力する。
  4. githubのユーザ名( 「piroor <piro.outsider.reflex@gmail.com>」のような、githubのユーザ名と登録済みのメールアドレスの組み合わせ)を作者として入力する。
  5. しばらく待つ。

最初githubのUIを英語で使ってたのと、下までスクロールしてなかったから気がついてなかった。なんということでしょう。丸1日以上を無駄にしてしまいました。

SubversionからGitに移行した - Jan 01, 1970

アドオンの開発にはずっと須藤さんに用意してもらったSubversionのリポジトリを使ってたんだけど、

  • インターネットに繋がらない状態でコミットできないのが辛い
  • 他の人からの変更を受け取りづらい(そんな事があればの話なんだけど)

と思っていて、Git(あるいは他の分散型バージョン管理システム)ならそれが解消されると期待してて、でもずっと移行できていなかった。

  • コマンドライン操作が嫌いなので、Subversionを使うのにWindowsではTortoiseSVNを、LinuxではRabbitVCS(旧称:NautilusSVN)を使ってるけど、GitではWindowsにはTortoiseGitがあってもLinuxでは相当する物が無いみたい。
  • バージョン管理システムを移行するとそれまでのコミットの履歴も全部なくなっちゃうんじゃないの?
  • git-svnっていうのを使うといいと聞いたけど、試しても空のディレクトリしかできないんですけど……

というのがその理由だった。でも

  • git-svnは時々動作がうまくいかない事があるみたいで、Windows上のmsysgitが特に高確率で失敗するだけで、VirtualPC上のUbuntu 10.04LTSのgit-svnだとそれほどでもなかった。
    • それで成功したケースだとコミット履歴が完全に引き継がれてた。なんだ、ちゃんと動けばちゃんとやってくれるんじゃん!!!

  • 最近、システムモニターの開発でGitをずっと使ってたから、まあLinux上ではコマンドラインでも別にいいかな……という気がしてきた。

という事で、思い切って移行してみる事にしました。具体的な手順はSourceForge.JP のプロジェクトを Subversion から Git へ移行するに従いました。

SubversionとGit

大まかに言うとこういう事だと僕は理解してる。

  • Subversionでは、中央に1つのリポジトリがあって、それを各人がチェックアウトしてワーキングコピーを作り、ワーキングコピーに対して行った変更をリポジトリにコミットする、という形で「バージョンの管理」と「成果の共有」を実現する。
  • Gitでは、リポジトリのコピーを誰でも簡単に手元に作る事ができて(Gitではこれをcloneと言う)、Subversionでワーキングコピーからリポジトリに変更点をコミットするのと同じように、Gitではリポジトリとリポジトリの間で変更点をコミットできる(Gitではこれをpushとかpullとか言う)。
    • どれか1つのリポジトリを「中央のリポジトリ」として運用すれば、Subversionでのバージョン管理と同じような運用もできる。
    • 手元にできるのは「履歴の情報を持たないワーキングコピー」ではなく「元のリポジトリを複製したリポジトリそのもの」なので、インターネットに繋がってない状態でもローカルのリポジトリに対してコミットできる。
    • Subversionではtrunkと呼ばれていた物は、Gitではmasterと呼ばれる。

  • git-svnは、以下のような事をする。

    • Subversionのリポジトリのコミット履歴を全部辿る。
    • ローカルに新しいGitリポジトリを作って、Subversionのコミット履歴に対応するコミットを順番にそのGitリポジトリにコミットする。
    • なので、git-svnでGitリポジトリ化したSubversionリポジトリは、元のリポジトリの変更履歴を完全に引き継いだ物になる。

やってみる

ツリー型タブSubversionリポジトリをgithub上の同名のリポジトリに移行する手順を振り返ってみる。

  1. Subversionのリポジトリをgit-svnでGitリポジトリに変換する。
    $ git svn clone --prefix svn/ -s https://www.cozmixng.org/repos/piro/treestyletab
    これでローカルにtreestyletabという名前のディレクトリができて、これがGitリポジトリになってる。コミットを1件1件取り込むので、コミット数が多いとメチャメチャ時間がかかるけど、黙って待つ。たまに失敗するので、そういう時はできたゴミディレクトリを消してもう一度やり直す。
  2. githubに「treestyletab」という名前でリポジトリを作る。(分かりやすくするためにSubversionの物と同じ名前にするといい。リポジトリ名は後でgithub上で好きなように変更できるので。)これで、読み書き両用のURLとして「git@github.com:piroor/treestyletab.git」みたいなのが使えるようになる。
  3. ローカルにできたtreestyletabのGitリポジトリの「元」として、githubのリポジトリを登録する。
    $ git remote add origin git@github.com:piroor/treestyletab.git
    これによって、「このリポジトリはgithubのリポジトリをcloneした物ですよ」「このリポジトリに行われた変更を元のリポジトリにpushする時は、githubにpushしますよ」ということになる。
  4. pushする。
    $ git push origin master
    これで、Subversionのリポジトリから持ってきたコミット履歴が全部github上のリポジトリに反映される。
  5. Subversionのtagsの内容をGitのtagに変換する。Subversionでtags以下に作ったタグはブランチとして取り込まれているので、これをGitのタグにしてやる。
    $ git branch -r
    これでブランチの一覧を見れるので、
    $ git checkout svn/tags/0.10.2010102501
    という風にしてブランチを切り替えて
    $ git tag 0.10.2010102501
    でタグを打って
    $ git push --tags origin
    でタグの情報をgithubのリポジトリにpushする。タグの数だけこの操作を繰り返す。
  6. Subversionのbranchesにブランチがある場合、Gitのbranchに変換する。Gitだとローカルにあるブランチをgit push origin ローカルリポジトリのブランチ名:リモートリポジトリのブランチ名でリモートリポジトリにpushできるんだけど、git−svnでcloneしたローカルリポジトリにあるブランチはそのままだとpushできない(不可視のブランチになってる?)ようなので、
    $ git checkout svn/my-branch
    でブランチを切り替えて
    $ git branch my-branch
    で普通のブランチとして切り直して
    $ git push origin my-branch:my-branch
    でgithubにpushする。

自分はアドオンの数自体20個以上あるし、それぞれアホみたいに何度もリリースしててタグの数がハンパないことになってるので、全部手動でやることを考えたら気が遠くなりました。なのでこういうスクリプトをRubyで書いてみました。

#!/usr/bin/ruby

SVN_REPOSITORY_PATH = "https://www.cozmixng.org/repos/piro/<%= src_project_name %>"
GIT_REPOSITORY_PATH = "git@github.com:piroor/<%= dest_project_name %>.git"

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "fileutils"
require "erb"
require "shellwords"

def main
  ARGV.each do |arg|
    p "process #{arg}"
    args = arg.split(":")
    src_project_name = args[0]
    dest_project_name = args.size > 1 ? args[1] : args[0]
    svn_to_git(src_project_name, dest_project_name)
  end
end

def svn_to_git(src_project_name, dest_project_name)
  p "svn:#{src_project_name}, git:#{dest_project_name}"
  clone(src_project_name)
  push(src_project_name, dest_project_name)
  push_branches_and_tags(src_project_name)
rescue Exception
  p $!
end

def clone(src_project_name)
  result = run("git", "svn", "clone",
               "--prefix", "svn/",
               "-s", ERB.new(SVN_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
  raise Exception.new("clone of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("Checked out HEAD:")
end

def push(src_project_name, dest_project_name)
  FileUtils.cd(src_project_name) do
    result = run("git", "remote", "add",
                 "origin", ERB.new(GIT_REPOSITORY_PATH).result(binding).chomp)
    p result.to_s
    result = run("git", "push", "origin", "master")
    p result.to_s
    raise Exception.new("push of #{src_project_name}, #{result.to_s}") unless result.to_s.include?("master -> master")
  end
end

def push_branches_and_tags(src_project_name)
  FileUtils.cd(src_project_name) do
    branches = run("git", "branch", "-r")
    branches = branches.to_s.split("\n")
    branches.each do |branch|
      next unless branch.include?("svn/")
      branch.strip!

      name = branch.split("svn/")[1]
      next if name == "trunk"

      if name.include?("tags/")
        tag = branch.split("tags/")[1]
        p run("git", "checkout", branch)
        p run("git", "tag", tag)
        p run("git", "push", "--tags", "origin")
      else
        p run("git", "checkout", branch)
        p run("git", "branch", name)
        p run("git", "push", "origin", "#{name}:#{name}")
      end
    end
  end
end

def run(*args)
  command_line = Shellwords.shelljoin(args)
  result = `#{command_line} 2>&1`
  result
end

main

ファイル名は svn-to-git.rb として、

$ ./svn-to-git.rb treestyletab

とやると、ここまでの手順のうちgithubのサイト上でリポジトリを作る所以外を全部自動でやってくれるという物です(ということは、スクリプトの実行前にあらかじめgithubのサイト上でリポジトリを作っておかないといけない)。これでなんとか全部のリポジトリをgithubに持ってくることができましたXUL/Migemoの辞書をSQLiteにしてみようとかそういうブランチを切ってた物が取り込めてなかったりsvn:externalsで参照してた物が入ってなかったり、Subversionに突っ込んでから1回もコミットしてないプロジェクトをまだgithubに持ってきてなかったり(必要あるの? 無いよね?)という課題は残っていますが。→ブランチの取り込み方が分かったので追記しました。→svn:externalsの移行は諦めてsubmoduleにすることにしました。TortoiseGitでメニューから「Submodule Add」を選んでリポジトリにgit@github.com:piroor/makexpi.gitを、パスにbuildscriptを指定する、という手順でだいたい同じような結果になるみたい。更新の時はTortoiseGitだと「Git Sync」から「Submodule Sync」しないといけないようだ。コマンドラインなら一発で更新できるようなんだけど……

今後はgit-svn駆け込み寺あたりを熟読して頑張っていきたいと思っております。あと、今後具体的にコードを提供してくれるような人がもしいれば、githubの方にpull requestっていうんですか?するようにしてもらえたら幸いです。

……というまとめエントリを書こうとしてもうちょっと調べ直してたら、git-svnでタグが自動で取り込まれないとかの問題を解消するラッパーのsvn2gitという物があるということを今更知りました。ギャフン!!!!!

……さらに後から気がついたけど、HTTPでアクセスできる公開のSubversionリポジトリをgithubに移行するだけならtagやbranchの変換も含めてgithubのWebインターフェース上の機能だけでサクッとできてしまうことが分かりました。ギャフンギャフン!!!!!!!!! 手順は以下の通りです。

  1. githubで新しいリポジトリを作る。
  2. 空のリポジトリができたら、最初に表示されてる説明の下の方にある「Subversionのリポジトリを取り込みたい? ここをクリック」というリンク(日本語のUIにしてる場合。英語でも多分似たような文言があると思う。)を辿る。
  3. インポート元のSubversionリポジトリのURLを入力する。
  4. githubのユーザ名( 「piroor <piro.outsider.reflex@gmail.com>」のような、githubのユーザ名と登録済みのメールアドレスの組み合わせ)を作者として入力する。
  5. しばらく待つ。

最初githubのUIを英語で使ってたのと、下までスクロールしてなかったから気がついてなかった。なんということでしょう。丸1日以上を無駄にしてしまいました。

Page 1/248: 1 2 3 4 5 6 7 8 9 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき