2011年1月22日土曜日

Repo と Git の使い方 (Using Repo and Git)


Android コードで作業するには, Git と Repo の両方を使う必要が出てくるでしょう。

  • Git は, 複数のリポジトリに分散した非常に大きなプロジェクト群を扱うために設計された, オープンソースのバージョン管理システムです. Android では, ローカルでのブランチ作成, コミット, 編集等のローカル操作で Git を使用します.

  • Repo は Git 上に構築したツールです. Repo により, 多くの Git リポジトリ群の管理が容易になり, 私たちのリビジョン管理システムへアップロードが行え, Android 開発の作業手順が自動化できます. Repo は Git を置き換えることを意図したものではなく, Android 開発において, Git での作業をより容易にするためのものです. repo コマンドは, 実行可能 Python スクリプトで, 検索パスのどこにでも置くことができます.

Android ソースファイルでの作業中, ネットワーク越しの操作では Repo を使うことになるでしょう. 例えば, 一回の repo コマンドで, ローカルの作業ディレクトリへ複数のリポジトリからファイル群をダウンロードすることができます.


Git について

なぜ Git なのか?

Android プロジェクトの立ち上げ時の課題の一つは, 外部コミュニティー(趣味の一環で活動する集団から大衆市場の家電機器を製造する大企業にいたるまで)をサポートする最も良い方法を考え出すことでした. 私たちは, コンポーネント群を置き換え可能なものにしたかったですし, 良いコンポーネントは Android から離れて独自に成長していけるようにもしたかったのです. 私たちは始めに分散バージョン管理システムを選択し, そして Git へと絞り込んでいきました.

既に Git ユーザーですか?

ほとんどの状況では Repo のかわりに Git を使用することができます. また, Repo コマンドと Git コマンドを混ぜて複雑なコマンドを作ることもできます. しかし, 基本的なネットワーク越しの操作においては Repo を使うようにすれば, 作業はかなり単純になります.


作業リファレンス

下記の作業リストは, Repo および Git でのよくある作業のやり方をまとめたものです. すぐに作業を始めるための完全な情報と例については, 「Get source」を参照してください.

Repo のインストール

  $ curl http://android.git.kernel.org/repo > ~/bin/repo
  $ chmod a+x ~/bin/repo
  $ mkdir 作業ディレクトリ名
  $ cd 作業ディレクトリ名
  $ repo init-u git://android.git.kernel.org/platform/manifest.git

クライアントの同期

入手可能な全てのプロジェクトのファイルを同期する:

  $ repo sync

選択したプロジェクトのファイルを同期する:

  $ repo sync プロジェクト1 プロジェクト2 ...

なぜトピックブランチを使うのか?

変更を始めるとき, 例えばデバッグ作業や新機能の作業を始めるとき, 常にローカル作業環境でトピックブランチを開始してください.

トピックブランチは元ファイル群のコピーではなく, ある時点のコミットを指すものです. これにより, ローカルブランチの作成や, ローカルブランチ間での切替え操作が軽くなります. ブランチを使用することにより, 自分の作業を他から分離することができます. トピックブランチの使用に関する良い文章をお探しなら, 「Separating topic branches」を参照してください.

トピックブランチの作成

Repo を使用してトピックブランチを開始する:

  $ repo start ブランチ名

新しいブランチが作成されたことを確認する:

  $ repo status

トピックブランチの使用

あるプロジェクトにブランチを割り当てる:

  $ repo start ブランチ 名称 プロジェクト

ローカル作業環境に作成してあるブランチ間の移動を行う:

  $ git checkout ブランチ名

存在するブランチのリストを見る:

  $ git branch

もしくは

  $ repo branches

現在のブランチの名前の前にはアスタリスクが付きます.

注意:
不具合のせいで repo sync がローカルのトピックブランチをリセットしてしまうかもしれません. repo sync 実行後の git branch の出力が「* (no branch)」の場合は git checkout を再度実行してください.

クライアントの状態確認

ファイルの状態を見る:

  $ repo status

まだコミットされていない変更を見る:

  $ repo diff

repo diff コマンドが表示するのは, 全ての変更のうち, もし現時点でコミットを実行するとした場合に, そのコミットには含まれないもののリストです.

コミットを今実行するとした場合に, そのコミットに含まれることになる全ての変更を見るためには Git コマンド git diff が必要です. このコマンドを実行する前に, プロジェクトのディレクトリに移動しておいてください:

  $ cd ~/作業ディレクトリ/プロジェクト
  $ git diff --cached

同期による衝突からの回復

もしも repo sync が同期による衝突を示した場合,

  1. マージされていない (状態コード = U) ファイルを見て,

  2. 必要に応じて衝突部分を編集し,

  3. 該当プロジェクトのディレクトリへ移動し, 対象ファイルに対して
     git add と git commit を実行し, それからその変更を rebase
     してください. 例えば次のようにします.

       $ cd bionic
       $ git add bionic/*
       $ git commit
       $ git rebase --continue

  4. rebase が完了したら, 再度全ての同期を開始してください.

       $ repo sync bionic プロジェクト2 プロジェクト3 ... プロジェクトN

クライアント側ファイル群のクリーンアップ

変更を Gerrit へマージした後にローカル作業ディレクトリを更新する:

  $ repo sync

古いトピックブランチを安全に削除する:

  $ repo prune

クライアントの削除

クライアントを削除すると, まだレビューにあげていない全ての変更を恒久的に削除することになります. 全ての状態情報はクライアント側に保持されているので, 手元のファイルシステムからディレクトリを削除するだけで済みます.

  $ cd ~
  $ rm -rf 作業ディレクトリ名

共通タスクのスクリプト化

全てのプロジェクトに対して同じプログラムを実行するために Repo を使用することができます.

  $ repo forall [プロジェクト1 プロジェクト2 ... プロジェクトN] \
                -c 'echo $REPO_PROJECT $@' [引数1 引数2 ... 引数N]

-c 引数は /bin/sh により解釈され, それより後の引数群はシェルの位置パラメーターとして渡されます.


Repo コマンドリファレンス

Repo の使用方法は次の形式を取ります.

  repo コマンド オプション群

任意指定の要素は角括弧 [ ] で囲んで示します. Repo をインストールすれば, 下記のように実行することでコマンドに関する情報を得られます.

  repo help コマンド

init

repo init -u url [オプション群]

カレントディレクトリに Repo をインストールします. これにより, Repo ソースコードと標準 Android マニフェストファイル群を含む .repo/ ディレクトリが作成されます. また, .repo/ ディレクトリには manifest.xml が含まれ, これは .repo/manifests/ ディレクトリ内にあるマニフェストファイルのうち選択されたものへのシンボリックリンクになっています.

-u 引数には, マニフェストリポジトリの取得先の URL を指定します. 例えば次のようになります.

  $ repo init -u git://android.git.kernel.org/platform/manifest.git

リポジトリ内のマニフェストファイルを選択するには, -m オプションを使います. (マニフェスト名が選択されていない場合, デフォルトは default.xml です.) 例えば次のようになります.

  $ repo init -u git://android.git.kernel.org/platform/manifest.git -m dalkvik-plus.xml

リビジョンを指定するには, つまり, 特定のマニフェストブランチを指定するには, -b オプションを使います. 例えば次のようになります.

  $ repo init -u git://android.git.kernel.org/platform/manifest.git -b release-1.0

その他の repo init のオプションを見るには, 次のコマンドを実行します.

  $ repo help init

注意: 以降の全ての Repo コマンドについては, カレント作業ディレクトリは .repo/ ディレクトリの親ディレクトリか, もしくはその親ディレクトリのサブディレクトリのいずれかでなければなりません.

sync

repo sync [プロジェクトリスト]

新しい変更をダウンロードし, ローカル環境の作業ファイル群を更新します. repo sync が成功すると, 指定されたプロジェクト内のコードはリモートリポジトリのコードで更新されています.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo sync [プロジェクト1 プロジェクト2 ... プロジェクトN]

引数を指定せずに repo sync を実行した場合は全てのプロジェクトの同期を行います.

Repo による同期の動作

repo sync を実行したときに起こることは下記の通りです.

  1. プロジェクトが過去に一度も同期されていなければ, repo sync は
     git clone と同じ動作となります. リモートリポジトリ内の全ての
     ブランチがローカルプロジェクトディレクトリにコピーされます.

  2. プロジェクトが過去に一度でも同期されていれば, repo sync は
     次のコマンドと同じです.

       git remote update
       git rebase origin/ブランチ

     ここで「ブランチ」は, ローカルプロジェクトディレクトリに
     現在チェックアウトされているブランチです. 対象のローカル
     ブランチがリモートリポジトリ内のブランチを追跡するものでは
     ない場合, そのプロジェクトに対して同期は行われません.

     git rebase 操作がマージで衝突を起こした場合, 衝突を解決する
     ため, (例えば git rebase --continue などの) 通常の Git
     コマンドを使う必要があります.

repo sync コマンドは, .repo/ ディレクトリ内のプライベートなリポジトリも更新します.

upload

repo upload [プロジェクトリスト]

Repo は, 指定されたプロジェクトについて, ローカルブランチと, 最後の repo sync の間に更新されたリモートブランチとを比較します. Repo は, まだレビュー用にアップロードされていないブランチを一つ以上選択するよう要求してきます.

一つ以上のブランチを選択すると, 選択されたブランチの全てのコミットが, SSH コネクションを通じて Gerrit に転送されます. アップロードの認証を有効にするため, SSH キーを設定する必要があるでしょう. 自分の公開キーを Gerrit に登録するには, ユーザー設定パネルの「SSH Keys」を開いてください. パスワード無しでのアップロードを有効にするには, クライアント側で SSH エージェントの使用を検討してください.

Gerrit は, SSH サーバを通じてオブジェクトデータを受け取ると, 各コミットをチェンジに変換し, レビューワーが各コミットに対して個別にコメントできるようにします.

複数のチェックポイントコミットを一つのコミットに統合するには, repo upload を実行する前に git rebase -i を使用してください.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo upload [プロジェクト1 プロジェクト2 ... プロジェクトN]

引数を付けずに repo upload を実行すると, アップロードする変更を見つけるために全てのプロジェクトが検索されます.

編集したものをアップロード後にチェンジへと変換するためには, ローカルコミットを更新するときに git rebase -i もしくは git commit --amend といったツールを使用してください.

編集が完了したら,

  1. 更新したブランチが現在チェックアウトされているブランチで
     あることを確認し,

  2. チェンジマッチングエディターを開くために「repo upload
     --replace プロジェクト」を使用し,

  3. 一連の各コミットに対して, 角括弧内に Gerrit チェンジ ID を
     入力してください.

# ブランチ foo を置き換える
[ 3021 ] 35f2596c Refactor part of GetUploadableBranches to lookup one specific...
[ 2829 ] ec18b4ba Update proto client to support patch set replacments
[ 3022 ] c99883fe Teach 'repo upload --replace' how to add replacement patch se...
# 新しいパッチセットを追加するには角括弧内にチェンジ番号を挿入する.
# 新しいチェンジレコードを作成するには, 角括弧は空のままにする.

アップロードが完了すると, 新しいパッチセット (例: Patch Set 2, Patch Set 3, ...) がチェンジに追加されます.

diff

repo diff [プロジェクトリスト]

コミットと作業ツリー間の変更を表示します.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo diff [プロジェクト1 プロジェクト2 ... プロジェクトN]

オプション:

-h, --help はヘルプメッセージを表示して終了します.

download

repo download ターゲット チェンジ

指定したチェンジを指定したローカルディレクトリにダウンロードします. (バージョン 1.0.4 の Repo から追加されました.)

例えば, チェンジ 1241 を platform/frameworks/base ディレクトリにダウンロードするには次のようにします.

  $ repo download platform/frameworks/base 1241

repo download 経由で取得されたコミットは repo sync により取り除かれます. リモートブランチをチェックアウトすることもできます. 例: git checkout m/master

注意: 2009 年 1 月 26 日以降, Gerrit のウェブサイトで変更が可視になるのと repo download がそれを見つけられるようになるまでの間には, 約 5 分のミラーリングラグがあります. なぜなら, チェンジは実際には git://android.git.kernel.org/ ミラーからダウンロードされるからです. Gerrit は新しくアップロードされたチェンジをミラーにプッシュするので, 若干のミラーリングラグが常に発生します.

forall

repo forall [プロジェクトリスト] -c コマンド [引数 ...]

各プロジェクトに対してシェルコマンドを実行します.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

help

repo help [コマンド]

コマンドに関する詳細なヘルプを表示します.

prune

repo prune [プロジェクトリスト]

マージ済みのトピックを削除します.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo prune [プロジェクト1 プロジェクト2 ... プロジェクトN]

start

repo start 新しいブランチ名 [プロジェクトリスト]

開発用の新しいブランチを開始します.

新しいブランチ名」引数は, プロジェクトに加えようとしている変更の簡易説明とすべきでしょう. 分からなければ, 名前として default を使用することを検討してください.

プロジェクトリスト」には, 対象のトピックブランチに参加させるプロジェクト群を指定します. プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo start default [プロジェクト1 プロジェクト2 ... プロジェクトN]

"." は, カレント作業ディレクトリのプロジェクトを指し示すための速記法です.

status

repo status [プロジェクトリスト]

カレント作業ディレクトリの状態を表示します. プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo status [プロジェクト1 プロジェクト2 ... プロジェクトN]

現在のブランチの状態のみを見たい場合は, repo status . を実行します.

状態情報はプロジェクトごとにリストされます. プロジェクト内の各ファイルごとに二文字のコードが使用されます.

左のカラムは, 最後のコミット状態と比較したときにインデックス (ステージされたファイル群) で何が起こっているかを大文字で示します.

次のカラムは, インデックス (ステージされたもの) と比較したときに作業ディレクトリで何が起こっているかを小文字で示します.

  文字: A
  意味: ファイルは追加されている (新規). 一番目のカラムにしか表示されない.

  文字: M もしくは m
  意味: ファイルは既に存在するが, 何らかの変更がなされている.

  文字: D もしくは d
  意味: ファイルは削除されている.

  文字: R
  意味: ファイル名が変更されている. 一番目のカラムにしか表示されない.
        新しい名前もライン上に表示される.

  文字: C
  意味: ファイルは他のファイルからコピーされたものである. 一番目のカラムに
        しか表示されない. 元ファイルも表示される.

  文字: T
  意味: ファイルのモード (実行可能であるか否か) のみが変更されている.
        一番目のカラムにしか表示されない.

  文字: U
  意味: ファイルにはマージによる衝突が含まれていて, まだマージができて
        いない. 一番目のカラムにしか表示されない.

  文字: -
  意味: ファイルの状態に変更はない. ハイフンが両方のカラムに表示されて
        いる場合, 新しいファイルだが Git により認識されていないという
        ことを意味する. このファイルに対して git add を実行すると,
        repo status は A- と表示し, そのファイルが追加されたことを示す.

例えば, appeng プロジェクト内の main.py というファイルを変更し, その変更をステージせずにいる場合, repo status は次のように表示します.

  project appeng/
  -m main.py

git add を実行して main.py に対する変更をステージすると, repo status は次のように表示します.

  project appeng/
  M- main.py

続けて, 既にステージされた main.py と, プロジェクト内の他のファイル app.yaml を編集すると, repo status は次のように表示します.

  project appeng/
  -m app.yaml
  Mm main.py


Git と Repo の早見表





用語

ステージされた変更

次回のコミットのスナップショットに含めるよう, git add でマークされた変更.

コミット

ステージされたファイル群のスナップショットと, 変更内容を説明するログメッセージを保存するため, 間隔を置いて git commit を使用します.

マニフェスト

リポジトリのリストと, それらのリポジトリのファイル群を作業ディレクトリのどこに配置するかの対応情報を含むマニフェストファイル. ファイルの同期をおこなうと, マニフェストにリストされたリポジトリに含まれるファイルは作業ディレクトリへとプルされます.