-
1. 使い始める
- 1.1 バージョン管理に関して
- 1.2 Git略史
- 1.3 Gitの基本
- 1.4 コマンドライン
- 1.5 Gitのインストール
- 1.6 最初のGitの構成
- 1.7 ヘルプを見る
- 1.8 まとめ
-
2. Git の基本
- 2.1 Git リポジトリの取得
- 2.2 変更内容のリポジトリへの記録
- 2.3 コミット履歴の閲覧
- 2.4 作業のやり直し
- 2.5 リモートでの作業
- 2.6 タグ
- 2.7 Git エイリアス
- 2.8 まとめ
-
3. Git のブランチ機能
- 3.1 ブランチとは
- 3.2 ブランチとマージの基本
- 3.3 ブランチの管理
- 3.4 ブランチでの作業の流れ
- 3.5 リモートブランチ
- 3.6 リベース
- 3.7 まとめ
-
4. Gitサーバー
- 4.1 プロトコル
- 4.2 サーバー用の Git の取得
- 4.3 SSH 公開鍵の作成
- 4.4 サーバーのセットアップ
- 4.5 Git デーモン
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 サードパーティによる Git ホスティング
- 4.10 まとめ
-
5. Git での分散作業
- 5.1 分散作業の流れ
- 5.2 プロジェクトへの貢献
- 5.3 プロジェクトの運営
- 5.4 まとめ
-
6. GitHub
- 6.1 アカウントの準備と設定
- 6.2 プロジェクトへの貢献
- 6.3 プロジェクトのメンテナンス
- 6.4 組織の管理
- 6.5 スクリプトによる GitHub の操作
- 6.6 まとめ
-
7. Git のさまざまなツール
- 7.1 リビジョンの選択
- 7.2 対話的なステージング
- 7.3 作業の隠しかたと消しかた
- 7.4 作業内容への署名
- 7.5 検索
- 7.6 歴史の書き換え
- 7.7 リセットコマンド詳説
- 7.8 高度なマージ手法
- 7.9 Rerere
- 7.10 Git によるデバッグ
- 7.11 サブモジュール
- 7.12 バンドルファイルの作成
- 7.13 Git オブジェクトの置き換え
- 7.14 認証情報の保存
- 7.15 まとめ
-
8. Git のカスタマイズ
- 8.1 Git の設定
- 8.2 Git の属性
- 8.3 Git フック
- 8.4 Git ポリシーの実施例
- 8.5 まとめ
-
9. Gitとその他のシステムの連携
- 9.1 Git をクライアントとして使用する
- 9.2 Git へ移行する
- 9.3 まとめ
-
10. Gitの内側
- 10.1 配管(Plumbing)と磁器(Porcelain)
- 10.2 Gitオブジェクト
- 10.3 Gitの参照
- 10.4 Packfile
- 10.5 Refspec
- 10.6 転送プロトコル
- 10.7 メンテナンスとデータリカバリ
- 10.8 環境変数
- 10.9 まとめ
-
A1. 付録 A: その他の環境でのGit
- A1.1 グラフィカルインタフェース
- A1.2 Visual StudioでGitを使う
- A1.3 EclipseでGitを使う
- A1.4 BashでGitを使う
- A1.5 ZshでGitを使う
- A1.6 PowershellでGitを使う
- A1.7 まとめ
-
A2. 付録 B: Gitをあなたのアプリケーションに組み込む
- A2.1 Gitのコマンドラインツールを使う方法
- A2.2 Libgit2を使う方法
- A2.3 JGit
-
A3. 付録 C: Gitのコマンド
- A3.1 セットアップと設定
- A3.2 プロジェクトの取得と作成
- A3.3 基本的なスナップショット
- A3.4 ブランチとマージ
- A3.5 プロジェクトの共有とアップデート
- A3.6 検査と比較
- A3.7 デバッグ
- A3.8 パッチの適用
- A3.9 メール
- A3.10 外部システム
- A3.11 システム管理
- A3.12 配管コマンド
9.2 Gitとその他のシステムの連携 - Git へ移行する
Git へ移行する
Git 以外のバージョン管理システムで管理しているコードベースがあるけれど、Git を使いはじめることにした、という場合、どうにかしてプロジェクトを移行する必要があります。 この節では、主要なバージョン管理システム用のインポーターについて触れた後、独自のインポーターを自前で開発する方法を実際に見ていきます。 ここでは、いくつかの大きくてプロ仕様のソースコード管理システムからデータをインポートする方法を学びます。これは、移行するユーザの多くがそういったシステムのユーザであるのと、そういったシステムでは高品質なツールが簡単に手に入るためです。
Subversion
先ほどの節で git svn
の使い方を読んでいれば、話は簡単です。まず git svn clone
でリポジトリを作り、そして Subversion サーバーを使うのをやめ、新しい Git サーバーにプッシュし、あとはそれを使い始めればいいのです。これまでの歴史が欲しいのなら、それも Subversion サーバーからプルすることができます (多少時間がかかります)。
しかし、インポートは完全ではありません。また時間もかかるので、正しくやるのがいいでしょう。まず最初に問題になるのが作者 (author) の情報です。Subversion ではコミットした人すべてがシステム上にユーザーを持っており、それがコミット情報として記録されます。たとえば先ほどの節のサンプルで言うと schacon
がそれで、blame
の出力や git svn log
の出力に含まれています。これをうまく Git の作者データとしてマップするには、Subversion のユーザーと Git の作者のマッピングが必要です。users.txt
という名前のファイルを作り、このような書式でマッピングを記述します。
schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>
SVN で使っている作者の一覧を取得するには、このようにします。
$ svn log --xml | grep author | sort -u | \
perl -pe 's/.*>(.*?)<.*/$1 = /'
これは、まずログを XML フォーマットで生成します。その中から作者の情報を含む行だけを抽出し、重複を削除して、XML タグを除去します。
(ちょっと見ればわかりますが、これは grep
や sort
、そして perl
といったコマンドが使える環境でないと動きません)
この出力を users.txt
にリダイレクトし、そこに Git のユーザーデータを書き足していきます。
このファイルを git svn
に渡せば、作者のデータをより正確にマッピングできるようになります。また、Subversion が通常インポートするメタデータを含めないよう git svn
に指示することもできます。そのためには --no-metadata
を clone
コマンドあるいは init
コマンドに渡します。そうすると、 import
コマンドは次のようになります。
$ git svn clone http://my-project.googlecode.com/svn/ \
--authors-file=users.txt --no-metadata -s my_project
これで、Subversion をちょっとマシにインポートした my_project
ディレクトリができあがりました。コミットがこんなふうに記録されるのではなく、
commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-
be05-5f7a86268029
次のように記録されています。
commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
Author フィールドの見た目がずっとよくなっただけではなく、git-svn-id
もなくなっています。
インポートした後には、ちょっとした後始末も行ったほうがよいでしょう。
たとえば、git svn
が作成した変な参照は削除しておくべきです。
まずはタグを移動して、奇妙なリモートブランチではなくちゃんとしたタグとして扱えるようにします。そして、残りのブランチを移動してローカルで扱えるようにします。
タグを Git のタグとして扱うには、次のコマンドを実行します。
$ cp -Rf .git/refs/remotes/origin/tags/* .git/refs/tags/
$ rm -Rf .git/refs/remotes/origin/tags
これは、リモートブランチのうち remotes/origin/tags/
で始まる名前のものを、実際の (軽量な) タグに変えます。
次に、refs/remotes
以下にあるそれ以外の参照をローカルブランチに移動します。
$ cp -Rf .git/refs/remotes/origin/* .git/refs/heads/
$ rm -Rf .git/refs/remotes/origin
このとき、Subversionではブランチが1つだったのにもかかわらず、名前が`@xxx`(xxxは数字)で終わる余分なブランチがいくつか出来てしまうことがあります。Subversionの「ペグ・リビジョン」という機能が原因なのですが、Gitにはこれと同等の機能は存在しません。よって、`git svn`コマンドはブランチ名にsvnのバージョン番号をそのまま追加します。svnでペグ・リビジョンをブランチに設定するときとまさに同じやり方です。もうペグ・リビジョンがいらないのであれば、`git branch -d`コマンドで削除してしまいましょう。
インポートが終わり、過去のブランチはGitのブランチへ、過去のタグはGitのタグへと変換できました。
最後に後始末についてです。残念なことに、`git svn`は`trunk`という名前の余計なブランチを生成してしまいます。Subversionにおけるデフォルトブランチではあるのですが、`trunk`の参照が指す場所は`master`と同じです。`master`のほうが用語としてもGitらしいですから、余分なブランチは削除してしまいましょう。
$ git branch -d trunk
これで、今まであった古いブランチはすべて Git のブランチとなり、古いタグもすべて Git のタグになりました。最後に残る作業は、新しい Git サーバーをリモートに追加してプッシュすることです。自分のサーバーをリモートとして追加するには以下のようにします。
$ git remote add origin git@my-git-server:myrepository.git
すべてのブランチやタグを一緒にプッシュするには、このようにします。
$ git push origin --all
$ git push origin --tags
これで、ブランチやタグも含めたすべてを、新しい Git サーバーにきれいにインポートできました。
Mercurial
Mercurial と Git は、バージョンの表現方法がよく似ており、また Git の方が少し柔軟性が高いので、Mercurial から Git へのリポジトリの変換は非常に素直に行えます。変換には "hg-fast-export" というツールを使用します。このツールは次のコマンドで取得できます。
$ git clone http://repo.or.cz/r/fast-export.git /tmp/fast-export
変換の最初のステップとして、変換の対象となる Mercurial リポジトリのクローンを取得します。
$ hg clone <remote repo URL> /tmp/hg-repo
次のステップでは、author マッピングファイルを作成します。
チェンジセットの author フィールドへ指定できる内容は、Git より Mercurial の方が制限がゆるいので、これを機に内容を見直すのがよいでしょう。
author マッピングファイルは、bash
シェルなら次のワンライナーで生成できます。
$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors
プロジェクトの歴史の長さによりますが、このコマンドの実行には数秒かかります。実行後には、 /tmp/authors
ファイルが次のような内容で作成されているはずです。
bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>
この例では、同じ人(Bob)がチェンジセットを作成したときの名前が4パターンあり、そのうち1つだけが標準に合った書き方で、また別の1つは Git のコミットとしては完全に無効なように見えます。
hg-fast-export では、このような状態を修正する場合、修正したい行の末尾に ={修正後の氏名とメールアドレス}
を追加し、変更したくないユーザ名の行はすべて削除します。
すべてのユーザ名が正しいなら、このファイルは必要ありません。
この例では、ファイルの内容を次のようにします。
bob=Bob Jones <bob@company.com>
bob@localhost=Bob Jones <bob@company.com>
bob <bob@company.com>=Bob Jones <bob@company.com>
bob jones <bob <AT> company <DOT> com>=Bob Jones <bob@company.com>
次のステップでは、新しい Git リポジトリを作成して、エクスポート用スクリプトを実行します。
$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
hg-fast-export に対して、 -r
フラグで、変換の対象となる Mercurial リポジトリの場所を指定しています。また、 -A
フラグで、author マッピングファイルの場所を指定しています。
このスクリプトは、Mercurial のチェンジセットを解析して、Git の "fast-import" 機能(詳細はまた後で説明します)用のスクリプトへ変換します。
これには少し時間がかかります(ネットワーク経由の場合と比べれば かなり 速いですが)。出力は非常に長くなります。
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 120000
Total objects: 115032 ( 208171 duplicates )
blobs : 40504 ( 205320 duplicates 26117 deltas of 39602 attempts)
trees : 52320 ( 2851 duplicates 47467 deltas of 47599 attempts)
commits: 22208 ( 0 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 109 ( 2 loads )
marks: 1048576 ( 22208 unique )
atoms: 1952
Memory total: 7860 KiB
pools: 2235 KiB
objects: 5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 90430
pack_report: pack_mmap_calls = 46771
pack_report: pack_open_windows = 1 / 1
pack_report: pack_mapped = 340852700 / 340852700
---------------------------------------------------------------------
$ git shortlog -sn
369 Bob Jones
365 Joe Smith
作業はこれだけです。 すべての Mercurial のタグは Git のタグに変換され、 Mercurial のブランチとブックマークは Git のブランチに変換されています。 これで、リポジトリを新しいサーバ側へプッシュする準備が整いました。
$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all
Perforce
次のインポート元としてとりあげるのは Perforce です。 前述の通り、 Git と Perforce を相互接続するには2つの方法があります。 git-p4 と Perforce Git Fusion です。
Perforce Git Fusion
Git Fusion を使えば、移行のプロセスに労力はほぼかかりません。( Git Fusion で述べた通り)設定ファイルで、プロジェクトの設定、ユーザのマッピング、およびブランチの設定を行った後、リポジトリをクローンすれば完了です。 Git Fusion がネイティブな Git リポジトリと類似の環境を提供してくれるので、お望みとあればいつでも、本物のネイティブな Git リポジトリへプッシュする準備はできているというわけです。 また、お望みなら、 Perforce を Git のホストとして使用することもできます。
git-p4
git-p4 はインポート用ツールとしても使えます。 例として、 Perforce Public Depot から Jam プロジェクトをインポートしてみましょう。 クライアントをセットアップするには、環境変数 P4PORT をエクスポートして Perforce ディポの場所を指すようにしなければなりません。
$ export P4PORT=public.perforce.com:1666
注記
|
以降の手順に従うには、アクセスできる Perforce のディポが必要です。 この例では public.perforce.com にある公開ディポを使用していますが、アクセス権があればどんなディポでも使用できます。 |
git p4 clone
コマンドを実行して、 Perforce サーバから Jam プロジェクトをインポートします。ディポとプロジェクトのパス、およびプロジェクトのインポート先のパスを指定します。
$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)
このプロジェクトにはブランチがひとつしかありませんが、ブランチビューで設定されたブランチ(またはディレクトリ)があるなら、 git p4 clone
に --detect-branches
フラグを指定すれば、プロジェクトのブランチすべてをインポートできます。
この詳細については ブランチ を参照してください。
この時点で作業はおおむね完了です。
p4import
ディレクトリへ移動して git log
を実行すると、インポートした成果物を確認できます。
$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
[git-p4: depot-paths = "//public/jam/src/": change = 8068]
commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
[git-p4: depot-paths = "//public/jam/src/": change = 7304]
git-p4
が各コミットメッセージに識別子を追加しているのが分かると思います。
この識別子はそのままにしておいてもかまいません。後で万一 Perforce のチェンジ番号を参照しなければならなくなったときのために使えます。しかし、もし削除したいのなら、新しいリポジトリ上で何か作業を始める前の、この段階で消しておきましょう。
git filter-branch
を使えば、この識別子を一括削除することができます。
$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten
git log
を実行すると、コミットの SHA-1 チェックサムは変わりましたが、 git-p4
という文字列がコミットメッセージから消えたことが分かると思います。
$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
これで、インポート結果を新しい Git サーバへプッシュする準備ができました。
A Custom Importer
前述した以外のシステムを使っている場合は、それ用のインポートツールをオンラインで探さなければなりません。CVS、Clear Case、Visual Source Safe、あるいはアーカイブのディレクトリなど、多くのバージョン管理システムについて、品質の高いインポーターが公開されています。
これらのツールがうまく動かなかったり、もっとマイナーなバージョン管理ツールを使っていたり、あるいはインポート処理で特殊な操作をしたりしたい場合は git fast-import
を使います。
このコマンドはシンプルな指示を標準入力から受け取って、特定の Git データを書き出します。
git fast-import
を使えば、生の Git コマンドを使ったり、生のオブジェクトを書きだそうとしたりする(詳細は [ch10-git-internals] を参照してください)よりは、ずっと簡単に Git オブジェクトを作ることができます。
この方法を使えばインポートスクリプトを自作することができます。必要な情報を元のシステムから読み込み、単純な指示を標準出力に出せばよいのです。
そして、このスクリプトの出力をパイプで git fast-import
に送ります。
手軽に試してみるために、シンプルなインポーターを書いてみましょう。
current
で作業をしており、プロジェクトのバックアップは時々ディレクトリまるごとのコピーで行っているものとします。バックアップディレクトリの名前は、タイムスタンプをもとに back_YYYY_MM_DD
としています。これらを Git にインポートしてみましょう。
ディレクトリの構造は、このようになっています。
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current
Git のディレクトリにインポートするため、まず Git がどのようにデータを格納しているかをおさらいしましょう。
覚えているかもしれませんが、 Git は基本的にはコミットオブジェクトのリンクリストであり、コミットオブジェクトがコンテンツのスナップショットを指しています。
fast-import
に指示しなければならないのは、コンテンツのスナップショットが何でどのコミットデータがそれを指しているのかということと、コミットデータを取り込む順番だけです。
ここでは、スナップショットをひとつずつたどって各ディレクトリの中身を含むコミットオブジェクトを作り、それらを日付順にリンクさせるものとします。
Git ポリシーの実施例 と同様、ここでも Ruby を使って書きます。Ruby を使うのは、我々が普段使っている言語であり、読みやすくしやすいためです。
このサンプルをあなたの使いなれた言語で書き換えるのも簡単でしょう。単に適切な情報を標準出力に送るだけなのだから。
また、Windows を使っている場合は、行末にキャリッジリターンを含めないように注意が必要です。git fast-import
が想定している行末は LF だけであり、Windows で使われている CRLF は想定していません。
まず最初に対象ディレクトリに移動し、そのサブディレクトリを認識させます。各サブディレクトリがコミットとしてインポートすべきスナップショットとなります。 続いて各サブディレクトリへ移動し、そのサブディレクトリをエクスポートするためのコマンドを出力します。 基本的なメインループは、このようになります。
last_mark = nil
# loop through the directories
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
各ディレクトリ内で実行している print_export
は、前のスナップショットの内容一覧とマークを受け取って、このディレクトリの内容一覧とマークを返します。このようにして、それぞれを適切にリンクさせます。
“マーク” とは fast-import
用語で、コミットに対する識別子を意味します。コミットを作成するときにマークをつけ、それを使って他のコミットとリンクさせます。
つまり、print_export
メソッドで最初にやることは、ディレクトリ名からマークを生成することです。
mark = convert_dir_to_mark(dir)
これを行うには、まずディレクトリの配列を作り、そのインデックスの値をマークとして使います。マークは整数値でなければならないからです。 メソッドの中身はこのようになります。
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
これで各コミットを表す整数値が取得できました。次に必要なのは、コミットのメタデータ用の日付です。
日付はディレクトリ名に現れているので、ここから取得します。print_export
ファイルで次にすることは、これです。
date = convert_dir_to_date(dir)
convert_dir_to_date
の定義は次のようになります。
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
これは、各ディレクトリの日付に対応する整数値を返します。 コミットのメタ情報として必要な最後の情報はコミッターのデータで、これはグローバル変数にハードコードします。
$author = 'John Doe <john@example.com>'
これで、コミットのデータをインポーターに流せるようになりました。 最初の情報では、今定義しているのがコミットオブジェクトであることと、どのブランチにいるのかを示しています。その後に先ほど生成したマークが続き、さらにコミッターの情報とコミットメッセージが続いた後にひとつ前のコミットが (もし存在すれば) 続きます。 コードはこのようになります。
# print the import information
puts 'commit refs/heads/master'
puts 'mark :' + mark
puts "committer #{$author} #{date} -0700"
export_data('imported from ' + dir)
puts 'from :' + last_mark if last_mark
タイムゾーン (-0700) をハードコードしているのは、そのほうがお手軽だったからです。 別のシステムからインポートする場合は、タイムゾーンをオフセットとして指定しなければなりません。 コミットメッセージは、次のような特殊な書式にする必要があります。
data (size)\n(contents)
まず最初に「data」という単語、そして読み込むデータのサイズ、改行、最後にデータがきます。
同じ書式は後でファイルのコンテンツを指定するときにも使うので、ヘルパーメソッド export_data
を作ります。
def export_data(string)
print "data #{string.size}\n#{string}"
end
残っているのは、各スナップショットが持つファイルのコンテンツを指定することです。
今回の場合はどれも一つのディレクトリにまとまっているので簡単です。deleteall
コマンドを出力し、それに続けてディレクトリ内の各ファイルの中身を出力すればよいのです。
そうすれば、Git が各スナップショットを適切に記録します。
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
注意:多くのシステムではリビジョンを「あるコミットと別のコミットの差分」と考えているので、fast-importでもその形式でコマンドを受け取ることができます。つまりコミットを指定するときに、追加/削除/変更されたファイルと新しいコンテンツの中身で指定できるということです。
各スナップショットの差分を算出してそのデータだけを渡すこともできますが、処理が複雑になります。すべてのデータを渡して、Git に差分を算出させたほうがよいでしょう。
もし差分を渡すほうが手元のデータに適しているようなら、fast-import
のマニュアルで詳細な方法を調べましょう。
新しいファイルの内容、あるいは変更されたファイルと変更後の内容を表す書式は次のようになります。
M 644 inline path/to/file
data (size)
(file contents)
この 644 はモード (実行可能ファイルがある場合は、そのファイルについては 755 を指定する必要があります) を表し、inline とはファイルの内容をこの次の行に続けて指定するという意味です。inline_data
メソッドは、このようになります。
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
先ほど定義した export_data
メソッドを再利用することができます。この書式はコミットメッセージの書式と同じだからです。
最後に必要となるのは、現在のマークを返して次の処理に渡せるようにすることです。
return mark
注記
|
Windows 上で動かす場合はさらにもう一手間必要です。
先述したように、Windows の改行文字は CRLF ですが
|
これで終わりです。 スクリプト全体を以下に示します。
#!/usr/bin/env ruby
$stdout.binmode
$author = "John Doe <john@example.com>"
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir)+1).to_s
end
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
def export_data(string)
print "data #{string.size}\n#{string}"
end
def inline_data(file, code='M', mode='644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
def print_export(dir, last_mark)
date = convert_dir_to_date(dir)
mark = convert_dir_to_mark(dir)
puts 'commit refs/heads/master'
puts "mark :#{mark}"
puts "committer #{$author} #{date} -0700"
export_data("imported from #{dir}")
puts "from :#{last_mark}" if last_mark
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
mark
end
# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
このスクリプトを実行すれば、次のような結果が得られます。
$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello
This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby
puts "Hey there"
M 644 inline README.md
(...)
インポーターを動かすには、インポート先の Git レポジトリにおいて、インポーターの出力をパイプで git fast-import
に渡す必要があります。
インポート先に新しいディレクトリを作成したら、以下のように git init
を実行し、そしてスクリプトを実行してみましょう。
$ git init
Initialized empty Git repository in /opt/import_to/.git/
$ ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 5000
Total objects: 13 ( 6 duplicates )
blobs : 5 ( 4 duplicates 3 deltas of 5 attempts)
trees : 4 ( 1 duplicates 0 deltas of 4 attempts)
commits: 4 ( 1 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 1 ( 1 loads )
marks: 1024 ( 5 unique )
atoms: 2
Memory total: 2344 KiB
pools: 2110 KiB
objects: 234 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 10
pack_report: pack_mmap_calls = 5
pack_report: pack_open_windows = 2 / 2
pack_report: pack_mapped = 1457 / 1457
---------------------------------------------------------------------
ご覧のとおり、処理が正常に完了すると、処理内容に関する統計情報が表示されます。
この場合は、全部で 13 のオブジェクトからなる 4 つのコミットが 1 つのブランチにインポートされたことがわかります。
では、git log
で新しい歴史を確認しましょう。
$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date: Tue Jul 29 19:39:04 2014 -0700
imported from current
commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date: Mon Feb 3 01:00:00 2014 -0700
imported from back_2014_02_03
きれいな Git リポジトリができていますね。
ここで重要なのは、この時点ではまだ何もチェックアウトされていないということです。作業ディレクトリには何もファイルがありません。
ファイルを取得するには、ブランチをリセットして master
の現在の状態にしなければなりません。
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb
fast-import
ツールにはさらに多くの機能があります。さまざまなモードを処理したりバイナリデータを扱ったり、複数のブランチやそのマージ、タグ、進捗状況表示などです。
より複雑なシナリオのサンプルは Git のソースコードの contrib/fast-import
ディレクトリにあります。