- 引数/戻り値の型が不明瞭: 文字列/changectx 型/内部データ形式(node)
-
1.0 版以前は、内部データ形式を必要とするケースが多かった
現行版では概ね、引数なら文字列/changectx 型が併用可能、
戻り値は changectx/filectx ないし revlog 型になっている
(内部データ形式の直接アクセスは隠蔽)ので、
あまり心配する必要は無い(筈)
- undocumented な API が多数残っている
先述した "The Mercurial API" には主要なクラスの説明は掲載されているが、
実際に履歴情報を参照しようとすると、これだけでは難しい。
類似機能を実現しているコマンドのソースを参照するのが最短。
通常は commands.py での主要コマンド定義を起点に、
cmdutil.py, localrepo.py あたりを参照すれば、
機能実現に必要な手法は概ね獲得できる筈。
----
ボトムアップな実装
----
"Developer Basics" より引用:
from mercurial import ui
from mercurial import hg
u = ui.ui()
repo = hg.repository(u, ".")
----
"Developer Basics" より引用:
from mercurial import ui
from mercurial import hg
u = ui.ui()
repo = hg.repository(u, ".")
利点:
- 気軽に試せる
- try & error の turn around が短くて済む
欠点:
- 上記の u インスタンス経由で取得できる設定は、.hg/hgrc のものを除く
- 複雑な初期化を要するものは、動作確認が難しい
----
基本的な履歴参照
----
特定のリビジョン revname に関する情報取得用オブジェクトの引き当て:
ctx = repo[revname] # Change Context(changectx)
changectx で利用可能な機能の概要は
"The Mercurial API" の "5. Change Contexts" 参照
----
ctx = repo[None] # 作業領域の changectx(workingctx)
親リビジョンの ctx とは以下の点で異なる
str(ctx) => '<parent hash id>+'
ctx.rev() => None
ctx.files() => ※ 作業領域の状況を反映
ctx.description() => ※ 空文字列
----
ctx = repo[None] # 作業領域の changectx(workingctx)
親リビジョンの ctx とは以下の点で異なる
str(ctx) => '<parent hash id>+'
ctx.rev() => None
ctx.files() => ※ 作業領域の状況を反映
ctx.description() => ※ 空文字列
作業領域の親リビジョンの changectx 取得は:
parent = repo['.'] # 第1親固定
parent1, parent2 = repo[None].parents()
----
特定のリビジョン revname 時点における、
ファイル filename に関する情報取得用オブジェクトの引き当て:
ctx = repo[revname]
fctx = ctx[filename] # File context(filectx)
filectx で利用可能な機能の概要は
"The Mercurial API" の "6. File Contexts" 参照
----
履歴参照の実際
----
ctx = repo[revname]
patterns = []
matchopts = { 'include': [], 'exclude': [] }
matcher = cmdutil.match(repo, patterns, matchopts)
for filename in ctx.walk(matcher):
fctx = ctx[filename] # File Context(filectx)
fctx.data() # content of file at that revision
----
ctx = repo[revname]
patterns = []
matchopts = { 'include': [], 'exclude': [] }
matcher = cmdutil.match(repo, patterns, matchopts)
for filename in ctx.walk(matcher):
fctx = ctx[filename] # File Context(filectx)
fctx.data() # content of file at that revision
全ファイルを参照する場合は以下でも可:
for filename in ctx:
fctx = ctx[filename]
fctx.data()
----
以下、"Mercurial API" からの抜粋:
filename in changectx
- tests if the file is in the changeset
そのチェンジセットで、当該ファイルが変更されているか否かの判定
----
以下、"Mercurial API" からの抜粋:
filename in changectx
- tests if the file is in the changeset
そのチェンジセットで、当該ファイルが変更されているか否かの判定
そのリビジョン時点において、
当該ファイルが存在するか否かの判定
----
当該リビジョンにおけるファイル存在の有無の判定:
filename in changectx
----
当該リビジョンにおけるファイル存在の有無の判定:
filename in changectx
当該チェンジセットにおけるファイル改変の有無の判定:
filename in changectx.files().split()
----
当該リビジョンにおけるファイル存在の有無の判定:
filename in changectx
当該チェンジセットにおけるファイル改変の有無の判定:
filename in changectx.files().split()
※ この方法では、ファイル操作の詳細(変更/追加/削除)は判定出来ない
----
localrepository#status(self,
node1='.', node2=None, match=None,
ignored=False, clean=False, unknown=False)
引数:
- node1/node2: リビジョン特定用の文字列、ないし changectx
- match: ファイル名合致判定用の関数(通常は cmdutil.match() 戻り値)
- ignored/clean/unknown: 当該状態ファイルの列挙の有無
戻り値: 以下の tuple
(modified, added, removed, deleted, unknown, ignored, clean)
戻り値 tuple の各要素は、
それぞれの status に該当するファイルのリスト
"removed" と "deleted" の差は hg status で言うところの "R" と "!" の差
----
localrepository#status(self,
node1='.', node2=None, match=None,
ignored=False, clean=False, unknown=False)
引数:
- node1/node2: リビジョン特定用の文字列、ないし changectx
- match: ファイル名合致判定用の関数(通常は cmdutil.match() 戻り値)
- ignored/clean/unknown: 当該状態ファイルの列挙の有無
戻り値: 以下の tuple
(modified, added, removed, deleted, unknown, ignored, clean)
戻り値 tuple の各要素は、
それぞれの status に該当するファイルのリスト
"removed" と "deleted" の差は hg status で言うところの "R" と "!" の差
※ workingctx/memctx クラスは、
状態に応じたファイル一覧返却メソッドを提供しているが…
----
statuses = repo.status(ctx.parents()[0], ctx, clean=True)
files = statuses[0] # for "modified"
# files = statuses[1] # for "added"
# files = statuses[2] # for "removed"
# files = statuses[6] # for "clean"
filename in files
以下のものは、
作業領域(repo[None])に対する判定の場合にのみ意味を持つ
- deleted(statuses[3])
- unknown(statuses[4]) ⇒ 要 "unknown=True" 指定
- ignored(statuses[5]) ⇒ 要 "ignored=True" 指定
----
以下、"Mercurial API" からの抜粋:
fctx.filectx(id)
- the file context for another revision of the file
fctx.filerev()
- the revision at which this file was last changed
fctx.rev()
- the revision from which this file context was extracted
----
以下、"Mercurial API" からの抜粋:
fctx.filectx(id)
- the file context for another revision of the file
fctx.filerev()
- the revision at which this file was last changed
fctx.rev()
- the revision from which this file context was extracted
filectx#rev() における revision は、
changectx 識別子=所謂 revname のこと
----
以下、"Mercurial API" からの抜粋:
fctx.filectx(id)
- the file context for another revision of the file
fctx.filerev()
- the revision at which this file was last changed
fctx.rev()
- the revision from which this file context was extracted
filectx#rev() における revision は、
changectx 識別子=所謂 revname のこと
filectx#filectx(id) および
filectx#filerev() における revision は、
所謂 revname ではなく、
当該ファイルにローカルな通し番号
----
以下、"Mercurial API" からの抜粋:
fctx.filerev()
- the revision at which this file was last changed
----
以下、"Mercurial API" からの抜粋:
fctx.filerev()
- the revision at which this file was last changed
"hg parent -r revname filename" 相当の情報は、以下の実装で取得可能
ctx = repo[revname]
fctx = ctx[filename]
lastchanged_fctx = fctx.filectx(fctx.filerev())
lastchanged_ctx = fctx.changectx()
----
以下、"Mercurial API" からの抜粋:
fctx.filerev()
- the revision at which this file was last changed
"hg parent -r revname filename" 相当の情報は、以下の実装で取得可能
ctx = repo[revname]
fctx = ctx[filename]
lastchanged_fctx = fctx.filectx(fctx.filerev())
lastchanged_ctx = fctx.changectx()
※ revname 時点で filename ファイルが存在していない場合、
ctx[filename] が失敗してしまう
⇒ 次に述べる『履歴全般の走査』を使用する
----
patterns = [ .... ]
get = util.cachefunc(lambda r: repo[r].changeset())
walkopts = { 'rev': [] } # default '0:-1'
changeiter, matchfn =
cmdutil.walkchangerevs(ui, repo, patterns, get, walkopts)
for st, rev, fns in changeiter:
# 反復実装
walkopts での 'rev' 指定は必須
以下の条件選別を walkopts として指定可能
follow
,
follow-first
,
prune
,
removed
,
include
,
exclude
----
patterns = [ .... ]
get = util.cachefunc(lambda r: repo[r].changeset())
walkopts = { 'rev': [] } # default '0:-1'
changeiter, matchfn =
cmdutil.walkchangerevs(ui, repo, patterns, get, walkopts)
for st, rev, fns in changeiter:
# 反復実装
walkopts での 'rev' 指定は必須
以下の条件選別を walkopts として指定可能
follow
,
follow-first
,
prune
,
removed
,
include
,
exclude
"hg log" は以下の条件選別を反復実装において実施
copies
,
date
,
keyword
,
no_merges
,
only_branch
,
only_merges
,
user
----
for st, rev, fns in changeiter:
if 'iter' == st:
# 走査順序に沿った、リビジョン番号の繰り返し
# tuple is: ("iter", rev, None)
print st, rev, fns
if 'add' == st:
# 走査順序とは関わりの無い、ファイル名の繰り返し
# tuple is: ("add", rev, fns)
print st, rev, [f for f in fns]
if 'window' == st:
# 繰り返しにおける区切り(window)の終了
# tuple is: ("window", incrementing, lastrev)
# 'lastrev' 部分は、走査全体で見た場合の最終リビジョン
print st, rev, fns
----
ctx = repo[rev]
parent = str(ctx.parents()[0].rev())
patterns = []
matchopts = {}
matcher = cmdutil.match(repo, patterns, matchopts)
diffopts = {}
chunks = patch.diff(repo, parent, rev, matcher, diffopts)
# chunks is list of diff chunks
----
ctx = repo[rev]
parent = str(ctx.parents()[0].rev())
patterns = []
matchopts = {}
matcher = cmdutil.match(repo, patterns, matchopts)
diffopts = {}
chunks = patch.diff(repo, parent, rev, matcher, diffopts)
# chunks is list of diff chunks
1.1.2 以降であれば、以下の機能を利用可能:
for diffstat in patch.diffstatdata(chunks):
# diffstat is tuple of: (filename, add#, remove#)
----
以下、"Mercurial API" からの抜粋:
fctx.annotate(follow=False, linenumber=None)
- 〜〜
The follow and linenumber parameters are not documented here -
see the source for details
----
以下、"Mercurial API" からの抜粋:
fctx.annotate(follow=False, linenumber=None)
- 〜〜
The follow and linenumber parameters are not documented here -
see the source for details
以下、ソース中のコメントの抜粋:
(This) returns a list of tuples of (ctx, line) for each line
in the file, where ctx is the filectx of the node where
that line was last changed.
This returns tuples of ((ctx, linenumber), line) for each line,
if "linenumber" parameter is NOT "None".
In such tuples, linenumber means one at the first appearance
in the managed file.
To reduce annotation cost,
this returns fixed value(False is used) as linenumber,
if "linenumber" parameter is "False".
----
設定情報の取得
----
- ui.config(section, name)
- ui.configbool(section, name)
>>> ui.ui().config('ui', 'username')
'FUJIWARA Katsunori <foozy@lares.dti.ne.jp>'
----
- ui.configlist(section, name)
>>> ui.ui().configlist('ui', 'username')
['FUJIWARA', 'Katsunori', '<foozy@lares.dti.ne.jp>']
----
>>> ui.ui().configitems('ui')
[('merge', 'diff3'),
('ssh', 'w-ssh'),
('username', 'FUJIWARA Katsunori <foozy@lares.dti.ne.jp>')]
----
コマンド定義の作法
----
コマンド定義のシグネチャの基本形式は以下のもの
def command(ui, repo, a1, a2, ... *rest, **opts):
a1 〜 は必須引数、rest は省略可能引数、opts はオプション
----
コマンド定義のシグネチャの基本形式は以下のもの
def command(ui, repo, a1, a2, ... *rest, **opts):
a1 〜 は必須引数、rest は省略可能引数、opts はオプション
※ e.g.: "hg add" コマンドのシグネチャ
def add(ui, repo, *pats, **opts):
対象特定のための全ての引数をリスト形式の pats で、
オプションは辞書形式の opts で受け取る
----
コマンド定義のシグネチャの基本形式は以下のもの
def command(ui, repo, a1, a2, ... *rest, **opts):
a1 〜 は必須引数、rest は省略可能引数、opts はオプション
※ e.g.: "hg add" コマンドのシグネチャ
def add(ui, repo, *pats, **opts):
対象特定のための全ての引数をリスト形式の pats で、
オプションは辞書形式の opts で受け取る
※ e.g.: "hg merge" コマンドのシグネチャ
def merge(ui, repo, node=None, force=None, rev=None):
node は(省略可能な)第1引数、force/rev はオプション
----
--optname
オプション
⇒ opts['optname']
で取得
----
--optname
オプション
⇒ opts['optname']
で取得
--long-name
オプション
⇒ opts['long_name']
で取得
----
--optname
オプション
⇒ opts['optname']
で取得
--long-name
オプション
⇒ opts['long_name']
で取得
ハイフン⇒アンダースコアの変換に注意!
----
extension の初期化手順
----
load()@extensions.py によるロード契機で実行
----
load()@extensions.py によるロード後に呼び出される
def uisetup(ui):
# do initialization here.
----
load()@extensions.py によるロード後に呼び出される
def uisetup(ui):
# do initialization here.
※ e.g.: "mq" extension
def uisetup(ui):
extensions.wrapcommand(commands.table,
'import',
mqimport)
※ 既存コマンドに対するラッピング
----
全ての extension がロードされた時点で、
_dispatch()@dispatch.py から呼び出される
----
全ての extension がロードされた時点で、
_dispatch()@dispatch.py から呼び出される
def extsetup():
# do initialization here.
他の extension に依存している場合などで有用
----
全ての extension がロードされた時点で、
_dispatch()@dispatch.py から呼び出される
def extsetup():
# do initialization here.
他の extension に依存している場合などで有用
※ e.g.: "record" extension
def extsetup():
try:
mq = extensions.find('mq')
except keyerror:
return
----
_dispatch()@dispatch.py による実施
----
_dispatch()@dispatch.py による実施
以下のような"辞書"形式による定義を
"cmdtable" 変数に格納しておくことで、
自動的にコマンドが追加される
- key: コマンド名(パターン指定可能: e.g. "^commit|ci")
- val: tuple
----
_dispatch()@dispatch.py による実施
以下のような"辞書"形式による定義を
"cmdtable" 変数に格納しておくことで、
自動的にコマンドが追加される
- key: コマンド名(パターン指定可能: e.g. "^commit|ci")
- val: tuple
- コマンド実装関数のシンボル
- オプションリスト(list of tuples)
- synopsis 文字列(i18n 対応)
----
_dispatch()@dispatch.py による実施
以下のような"辞書"形式による定義を
"cmdtable" 変数に格納しておくことで、
自動的にコマンドが追加される
- key: コマンド名(パターン指定可能: e.g. "^commit|ci")
- val: tuple
- コマンド実装関数のシンボル
- オプションリスト(list of tuples)
- short-form(文字列) ※ 「提供無し」なら「空文字列」
- long-form(文字列)
- default value
- オプション説明(i18n 対応)
- synopsis 文字列(i18n 対応)
----
_dispatch()@dispatch.py による実施
以下のような"辞書"形式による定義を
"cmdtable" 変数に格納しておくことで、
自動的にコマンドが追加される
- key: コマンド名(パターン指定可能: e.g. "^commit|ci")
- val: tuple
- コマンド実装関数のシンボル
- オプションリスト(list of tuples)
- short-form(文字列) ※ 「提供無し」なら「空文字列」
- long-form(文字列)
- default value
- フラグオプション(None/False)
- 値オプション(空文字列: ' ')
- 複数値オプション(空リスト: [ ])
- オプション説明(i18n 対応)
- synopsis 文字列(i18n 対応)
----
_dispatch()@dispatch.py による実施
以下のような"辞書"形式による定義を
"cmdtable" 変数に格納しておくことで、
自動的にコマンドが追加される
- key: コマンド名(パターン指定可能: e.g. "^commit|ci")
- val: tuple
- コマンド実装関数のシンボル
- オプションリスト(list of tuples)
- short-form(文字列) ※ 「提供無し」なら「空文字列」
- long-form(文字列)
- default value
- フラグオプション(None/False)
- 値オプション(空文字列: ' ')
- 複数値オプション(空リスト: [ ])
- オプション説明(i18n 対応)
- synopsis 文字列(i18n 対応)
※ 既に定義済みのコマンドと名前が被る場合は、警告が表示される
----
_dispatch()@dispatch.py による実施
以下のような"辞書"形式による定義を
"cmdtable" 変数に格納しておくことで、
自動的にコマンドが追加される
- key: コマンド名(パターン指定可能: e.g. "^commit|ci")
- val: tuple
- コマンド実装関数のシンボル
- オプションリスト(list of tuples)
- short-form(文字列) ※ 「提供無し」なら「空文字列」
- long-form(文字列)
- default value
- フラグオプション(None/False)
- 値オプション(空文字列: ' ')
- 複数値オプション(空リスト: [ ])
- オプション説明(i18n 対応)
- synopsis 文字列(i18n 対応)
※ 既に定義済みのコマンドと名前が被る場合は、警告が表示される
⇒ extensions#wrapcommand() で置き換える
----
リポジトリの初期化(= repository オブジェクトの初期化)
が完了した時点で、
repository()@hg.py 契機で呼び出される
----
リポジトリの初期化(= repository オブジェクトの初期化)
が完了した時点で、
repository()@hg.py 契機で呼び出される
def reposetup(ui, repo):
# do initialization here.
リポジトリの参照が必要な初期化は、
uisetup() や extsetup() ではなくこちらで実施
----
リポジトリの初期化(= repository オブジェクトの初期化)
が完了した時点で、
repository()@hg.py 契機で呼び出される
def reposetup(ui, repo):
# do initialization here.
リポジトリの参照が必要な初期化は、
extsetup() ではなくこちらで実施
※ e.g.: "win32text" extension
def reposetup(ui, repo):
if not repo.local():
return
for name, fn in _filters.iteritems():
repo.adddatafilter(name, fn)
----
END