--- a/Makefile Fri Mar 13 21:18:59 2015 +0900
+++ b/Makefile Fri Mar 13 17:55:04 2015 -0500
@@ -7,11 +7,14 @@
PREFIX=/usr/local
export PREFIX
PYTHON=python
+$(eval HGROOT := $(shell pwd))
+HGPYTHONS ?= $(HGROOT)/build/pythons
PURE=
PYFILES:=$(shell find mercurial hgext doc -name '*.py')
DOCFILES=mercurial/help/*.txt
export LANGUAGE=C
export LC_ALL=C
+TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
# Set this to e.g. "mingw32" to use a non-default compiler.
COMPILER=
@@ -98,6 +101,13 @@
test-%:
cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
+testpy-%:
+ @echo Looking for Python $* in $(HGPYTHONS)
+ [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
+ cd $$(mktemp --directory --tmpdir) && \
+ $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
+ cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
+
check-code:
hg manifest | xargs python contrib/check-code.py
--- a/contrib/check-code.py Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/check-code.py Fri Mar 13 17:55:04 2015 -0500
@@ -153,7 +153,7 @@
(uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
(uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
"as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
- 'hg pull -q file:../test'), # in test-pull.t which is skipped on windows
+ '# no-msys'), # in test-pull.t which is skipped on windows
(r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
(r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
winglobmsg),
--- a/contrib/check-commit Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/check-commit Fri Mar 13 17:55:04 2015 -0500
@@ -25,6 +25,7 @@
(r"^# .*\n(?!merge with )[^#]\S+[^:] ",
"summary line doesn't start with 'topic: '"),
(r"^# .*\n[A-Z][a-z]\S+", "don't capitalize summary lines"),
+ (r"^# .*\n[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
(r"^# .*\n.*\.\s+$", "don't add trailing period on summary line"),
(r"^# .*\n.{78,}", "summary line too long"),
(r"^\+\n \n", "adds double empty line"),
--- a/contrib/win32/ReadMe.html Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/win32/ReadMe.html Fri Mar 13 17:55:04 2015 -0500
@@ -140,7 +140,7 @@
</p>
<p>
- Mercurial is Copyright 2005-2014 Matt Mackall and others. See
+ Mercurial is Copyright 2005-2015 Matt Mackall and others. See
the <tt>Contributors.txt</tt> file for a list of contributors.
</p>
--- a/contrib/win32/mercurial.iss Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/win32/mercurial.iss Fri Mar 13 17:55:04 2015 -0500
@@ -21,7 +21,7 @@
#endif
[Setup]
-AppCopyright=Copyright 2005-2010 Matt Mackall and others
+AppCopyright=Copyright 2005-2015 Matt Mackall and others
AppName=Mercurial
#if ARCH == "x64"
AppVerName=Mercurial {#VERSION} (64-bit)
@@ -44,7 +44,7 @@
DefaultDirName={pf}\Mercurial
SourceDir=..\..
VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
-VersionInfoCopyright=Copyright 2005-2010 Matt Mackall and others
+VersionInfoCopyright=Copyright 2005-2015 Matt Mackall and others
VersionInfoCompany=Matt Mackall and others
InternalCompressLevel=max
SolidCompression=true
Binary file contrib/wix/COPYING.rtf has changed
--- a/doc/hgmanpage.py Fri Mar 13 21:18:59 2015 +0900
+++ b/doc/hgmanpage.py Fri Mar 13 17:55:04 2015 -0500
@@ -427,7 +427,7 @@
pass
def visit_block_quote(self, node):
- # BUG/HACK: indent alway uses the _last_ indention,
+ # BUG/HACK: indent always uses the _last_ indention,
# thus we need two of them.
self.indent(BLOCKQOUTE_INDENT)
self.indent(0)
--- a/hgext/churn.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/churn.py Fri Mar 13 17:55:04 2015 -0500
@@ -46,7 +46,7 @@
date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
return date.strftime(opts['dateformat'])
else:
- tmpl = opts.get('template', '{author|email}')
+ tmpl = opts.get('oldtemplate') or opts.get('template')
tmpl = maketemplater(ui, repo, tmpl)
def getkey(ctx):
ui.pushbuffer()
@@ -95,7 +95,9 @@
_('count rate for the specified revision or revset'), _('REV')),
('d', 'date', '',
_('count rate for revisions matching date spec'), _('DATE')),
- ('t', 'template', '{author|email}',
+ ('t', 'oldtemplate', '',
+ _('template to group changesets (DEPRECATED)'), _('TEMPLATE')),
+ ('T', 'template', '{author|email}',
_('template to group changesets'), _('TEMPLATE')),
('f', 'dateformat', '',
_('strftime-compatible format for grouping by date'), _('FORMAT')),
--- a/hgext/color.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/color.py Fri Mar 13 17:55:04 2015 -0500
@@ -140,6 +140,17 @@
either using ansi mode (or auto mode), or by using less -r (which will
pass through all terminal control codes, not just color control
codes).
+
+On some systems (such as MSYS in Windows), the terminal may support
+a different color mode than the pager (activated via the "pager"
+extension). It is possible to define separate modes depending on whether
+the pager is active::
+
+ [color]
+ mode = auto
+ pagermode = ansi
+
+If ``pagermode`` is not defined, the ``mode`` will be used.
'''
import os
@@ -213,20 +224,43 @@
formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
mode = ui.config('color', 'mode', 'auto')
+
+ # If pager is active, color.pagermode overrides color.mode.
+ if getattr(ui, 'pageractive', False):
+ mode = ui.config('color', 'pagermode', mode)
+
realmode = mode
if mode == 'auto':
- if os.name == 'nt' and 'TERM' not in os.environ:
- # looks line a cmd.exe console, use win32 API or nothing
- realmode = 'win32'
+ if os.name == 'nt':
+ term = os.environ.get('TERM')
+ # TERM won't be defined in a vanilla cmd.exe environment.
+ if not term:
+ realmode = 'win32'
+
+ # UNIX-like environments on Windows such as Cygwin and MSYS will
+ # set TERM. They appear to make a best effort attempt at setting it
+ # to something appropriate. However, not all environments with TERM
+ # defined support ANSI. Since "ansi" could result in terminal
+ # gibberish, we error on the side of selecting "win32". However, if
+ # w32effects is not defined, we almost certainly don't support
+ # "win32", so don't even try.
+ if 'xterm' in term or not w32effects:
+ realmode = 'ansi'
+ else:
+ realmode = 'win32'
else:
realmode = 'ansi'
+ def modewarn():
+ # only warn if color.mode was explicitly set and we're in
+ # an interactive terminal
+ if mode == realmode and ui.interactive():
+ ui.warn(_('warning: failed to set color mode to %s\n') % mode)
+
if realmode == 'win32':
_terminfo_params = {}
if not w32effects:
- if mode == 'win32':
- # only warn if color.mode is explicitly set to win32
- ui.warn(_('warning: failed to set color mode to %s\n') % mode)
+ modewarn()
return None
_effects.update(w32effects)
elif realmode == 'ansi':
@@ -234,10 +268,8 @@
elif realmode == 'terminfo':
_terminfosetup(ui, mode)
if not _terminfo_params:
- if mode == 'terminfo':
- ## FIXME Shouldn't we return None in this case too?
- # only warn if color.mode is explicitly set to win32
- ui.warn(_('warning: failed to set color mode to %s\n') % mode)
+ ## FIXME Shouldn't we return None in this case too?
+ modewarn()
realmode = 'ansi'
else:
return None
--- a/hgext/convert/monotone.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/convert/monotone.py Fri Mar 13 17:55:04 2015 -0500
@@ -297,7 +297,7 @@
extra = {}
certs = self.mtngetcerts(rev)
if certs.get('suspend') == certs["branch"]:
- extra['close'] = '1'
+ extra['close'] = 1
return commit(
author=certs["author"],
date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
--- a/hgext/extdiff.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/extdiff.py Fri Mar 13 17:55:04 2015 -0500
@@ -276,6 +276,7 @@
def uisetup(ui):
for cmd, path in ui.configitems('extdiff'):
+ path = util.expandpath(path)
if cmd.startswith('cmd.'):
cmd = cmd[4:]
if not path:
--- a/hgext/graphlog.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/graphlog.py Fri Mar 13 17:55:04 2015 -0500
@@ -54,4 +54,5 @@
Nodes printed as an @ character are parents of the working
directory.
"""
- return cmdutil.graphlog(ui, repo, *pats, **opts)
+ opts['graph'] = True
+ return commands.log(ui, repo, *pats, **opts)
--- a/hgext/histedit.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/histedit.py Fri Mar 13 17:55:04 2015 -0500
@@ -142,6 +142,13 @@
as running ``hg histedit 836302820282``. If you need plan to push to a
repository that Mercurial does not detect to be related to the source
repo, you can add a ``--force`` option.
+
+Histedit rule lines are truncated to 80 characters by default. You
+can customise this behaviour by setting a different length in your
+configuration file:
+
+[histedit]
+linelen = 120 # truncate rule lines at 120 characters
"""
try:
@@ -158,6 +165,7 @@
from mercurial import error
from mercurial import copies
from mercurial import context
+from mercurial import extensions
from mercurial import hg
from mercurial import node
from mercurial import repair
@@ -189,13 +197,13 @@
""")
class histeditstate(object):
- def __init__(self, repo, parentctx=None, rules=None, keep=None,
+ def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
topmost=None, replacements=None, lock=None, wlock=None):
self.repo = repo
self.rules = rules
self.keep = keep
self.topmost = topmost
- self.parentctx = parentctx
+ self.parentctxnode = parentctxnode
self.lock = lock
self.wlock = wlock
if replacements is None:
@@ -214,7 +222,7 @@
parentctxnode, rules, keep, topmost, replacements = pickle.load(fp)
- self.parentctx = self.repo[parentctxnode]
+ self.parentctxnode = parentctxnode
self.rules = rules
self.keep = keep
self.topmost = topmost
@@ -222,7 +230,7 @@
def write(self):
fp = self.repo.vfs('histedit-state', 'w')
- pickle.dump((self.parentctx.node(), self.rules, self.keep,
+ pickle.dump((self.parentctxnode, self.rules, self.keep,
self.topmost, self.replacements), fp)
fp.close()
@@ -346,10 +354,11 @@
return repo.commitctx(new)
def pick(ui, state, ha, opts):
- repo, ctx = state.repo, state.parentctx
+ repo, ctxnode = state.repo, state.parentctxnode
+ ctx = repo[ctxnode]
oldctx = repo[ha]
if oldctx.parents()[0] == ctx:
- ui.debug('node %s unchanged\n' % ha)
+ ui.debug('node %s unchanged\n' % ha[:12])
return oldctx, []
hg.update(repo, ctx.node())
stats = applychanges(ui, repo, oldctx, opts)
@@ -361,14 +370,15 @@
n = commit(text=oldctx.description(), user=oldctx.user(),
date=oldctx.date(), extra=oldctx.extra())
if n is None:
- ui.warn(_('%s: empty changeset\n') % node.hex(ha))
+ ui.warn(_('%s: empty changeset\n') % ha[:12])
return ctx, []
new = repo[n]
return new, [(oldctx.node(), (n,))]
def edit(ui, state, ha, opts):
- repo, ctx = state.repo, state.parentctx
+ repo, ctxnode = state.repo, state.parentctxnode
+ ctx = repo[ctxnode]
oldctx = repo[ha]
hg.update(repo, ctx.node())
applychanges(ui, repo, oldctx, opts)
@@ -382,17 +392,18 @@
return fold(ui, state, ha, rollupopts)
def fold(ui, state, ha, opts):
- repo, ctx = state.repo, state.parentctx
+ repo, ctxnode = state.repo, state.parentctxnode
+ ctx = repo[ctxnode]
oldctx = repo[ha]
hg.update(repo, ctx.node())
stats = applychanges(ui, repo, oldctx, opts)
if stats and stats[3] > 0:
raise error.InterventionRequired(
_('Fix up the change and run hg histedit --continue'))
- n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
+ n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(),
date=oldctx.date(), extra=oldctx.extra())
if n is None:
- ui.warn(_('%s: empty changeset') % node.hex(ha))
+ ui.warn(_('%s: empty changeset') % ha[:12])
return ctx, []
return finishfold(ui, repo, ctx, oldctx, n, opts, [])
@@ -438,12 +449,14 @@
return repo[n], replacements
def drop(ui, state, ha, opts):
- repo, ctx = state.repo, state.parentctx
+ repo, ctxnode = state.repo, state.parentctxnode
+ ctx = repo[ctxnode]
return ctx, [(repo[ha].node(), ())]
def message(ui, state, ha, opts):
- repo, ctx = state.repo, state.parentctx
+ repo, ctxnode = state.repo, state.parentctxnode
+ ctx = repo[ctxnode]
oldctx = repo[ha]
hg.update(repo, ctx.node())
stats = applychanges(ui, repo, oldctx, opts)
@@ -501,15 +514,16 @@
@command('histedit',
[('', 'commands', '',
- _('Read history edits from the specified file.')),
+ _('read history edits from the specified file'), _('FILE')),
('c', 'continue', False, _('continue an edit already in progress')),
+ ('', 'edit-plan', False, _('edit remaining actions list')),
('k', 'keep', False,
_("don't strip old nodes after edit is complete")),
('', 'abort', False, _('abort an edit in progress')),
('o', 'outgoing', False, _('changesets not found in destination')),
('f', 'force', False,
_('force outgoing even for unrelated repositories')),
- ('r', 'rev', [], _('first revision to be edited'))],
+ ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
_("ANCESTOR | --outgoing [URL]"))
def histedit(ui, repo, *freeargs, **opts):
"""interactively edit changeset history
@@ -552,6 +566,7 @@
# basic argument incompatibility processing
outg = opts.get('outgoing')
cont = opts.get('continue')
+ editplan = opts.get('edit_plan')
abort = opts.get('abort')
force = opts.get('force')
rules = opts.get('commands', '')
@@ -560,13 +575,18 @@
if force and not outg:
raise util.Abort(_('--force only allowed with --outgoing'))
if cont:
- if util.any((outg, abort, revs, freeargs, rules)):
+ if util.any((outg, abort, revs, freeargs, rules, editplan)):
raise util.Abort(_('no arguments allowed with --continue'))
goal = 'continue'
elif abort:
- if util.any((outg, revs, freeargs, rules)):
+ if util.any((outg, revs, freeargs, rules, editplan)):
raise util.Abort(_('no arguments allowed with --abort'))
goal = 'abort'
+ elif editplan:
+ if util.any((outg, revs, freeargs)):
+ raise util.Abort(_('only --commands argument allowed with'
+ '--edit-plan'))
+ goal = 'edit-plan'
else:
if os.path.exists(os.path.join(repo.path, 'histedit-state')):
raise util.Abort(_('history edit already in progress, try '
@@ -579,6 +599,10 @@
_('only one repo argument allowed with --outgoing'))
else:
revs.extend(freeargs)
+ if len(revs) == 0:
+ histeditdefault = ui.config('histedit', 'defaultrev')
+ if histeditdefault:
+ revs.append(histeditdefault)
if len(revs) != 1:
raise util.Abort(
_('histedit requires exactly one ancestor revision'))
@@ -589,17 +613,33 @@
# rebuild state
if goal == 'continue':
- state = histeditstate(repo)
state.read()
state = bootstrapcontinue(ui, state, opts)
+ elif goal == 'edit-plan':
+ state.read()
+ if not rules:
+ comment = editcomment % (state.parentctx, node.short(state.topmost))
+ rules = ruleeditor(repo, ui, state.rules, comment)
+ else:
+ if rules == '-':
+ f = sys.stdin
+ else:
+ f = open(rules)
+ rules = f.read()
+ f.close()
+ rules = [l for l in (r.strip() for r in rules.splitlines())
+ if l and not l.startswith('#')]
+ rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
+ state.rules = rules
+ state.write()
+ return
elif goal == 'abort':
- state = histeditstate(repo)
state.read()
mapping, tmpnodes, leafs, _ntm = processreplacement(state)
ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
# check whether we should update away
parentnodes = [c.node() for c in repo[None].parents()]
- for n in leafs | set([state.parentctx.node()]):
+ for n in leafs | set([state.parentctxnode]):
if n in parentnodes:
hg.clean(repo, state.topmost)
break
@@ -634,16 +674,8 @@
ctxs = [repo[r] for r in revs]
if not rules:
- rules = '\n'.join([makedesc(c) for c in ctxs])
- rules += '\n\n'
- rules += editcomment % (node.short(root), node.short(topmost))
- rules = ui.edit(rules, ui.username())
- # Save edit rules in .hg/histedit-last-edit.txt in case
- # the user needs to ask for help after something
- # surprising happens.
- f = open(repo.join('histedit-last-edit.txt'), 'w')
- f.write(rules)
- f.close()
+ comment = editcomment % (node.short(root), node.short(topmost))
+ rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
else:
if rules == '-':
f = sys.stdin
@@ -655,9 +687,9 @@
if l and not l.startswith('#')]
rules = verifyrules(rules, repo, ctxs)
- parentctx = repo[root].parents()[0]
+ parentctxnode = repo[root].parents()[0].node()
- state.parentctx = parentctx
+ state.parentctxnode = parentctxnode
state.rules = rules
state.keep = keep
state.topmost = topmost
@@ -666,12 +698,14 @@
while state.rules:
state.write()
action, ha = state.rules.pop(0)
- ui.debug('histedit: processing %s %s\n' % (action, ha))
+ ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
actfunc = actiontable[action]
- state.parentctx, replacement_ = actfunc(ui, state, ha, opts)
+ parentctx, replacement_ = actfunc(ui, state, ha, opts)
+ state.parentctxnode = parentctx.node()
state.replacements.extend(replacement_)
+ state.write()
- hg.update(repo, state.parentctx.node())
+ hg.update(repo, state.parentctxnode)
mapping, tmpnodes, created, ntm = processreplacement(state)
if mapping:
@@ -724,7 +758,8 @@
return newchildren
def bootstrapcontinue(ui, state, opts):
- repo, parentctx = state.repo, state.parentctx
+ repo, parentctxnode = state.repo, state.parentctxnode
+ parentctx = repo[parentctxnode]
action, currentnode = state.rules.pop(0)
ctx = repo[currentnode]
@@ -736,7 +771,7 @@
if s.modified or s.added or s.removed or s.deleted:
# prepare the message for the commit to comes
if action in ('f', 'fold', 'r', 'roll'):
- message = 'fold-temp-revision %s' % currentnode
+ message = 'fold-temp-revision %s' % currentnode[:12]
else:
message = ctx.description()
editopt = action in ('e', 'edit', 'm', 'mess')
@@ -780,7 +815,7 @@
# otherwise update "parentctx" before proceeding to further operation
parentctx = repo[newchildren[-1]]
- state.parentctx = parentctx
+ state.parentctxnode = parentctx.node()
state.replacements.extend(replacements)
return state
@@ -801,19 +836,41 @@
raise util.Abort(_('cannot edit immutable changeset: %s') % root)
return [c.node() for c in ctxs]
-def makedesc(c):
- """build a initial action line for a ctx `c`
+def makedesc(repo, action, rev):
+ """build a initial action line for a ctx
line are in the form:
- pick <hash> <rev> <summary>
+ <action> <hash> <rev> <summary>
"""
+ ctx = repo[rev]
summary = ''
- if c.description():
- summary = c.description().splitlines()[0]
- line = 'pick %s %d %s' % (c, c.rev(), summary)
+ if ctx.description():
+ summary = ctx.description().splitlines()[0]
+ line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
# trim to 80 columns so it's not stupidly wide in my editor
- return util.ellipsis(line, 80)
+ maxlen = repo.ui.configint('histedit', 'linelen', default=80)
+ maxlen = max(maxlen, 22) # avoid truncating hash
+ return util.ellipsis(line, maxlen)
+
+def ruleeditor(repo, ui, rules, editcomment=""):
+ """open an editor to edit rules
+
+ rules are in the format [ [act, ctx], ...] like in state.rules
+ """
+ rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
+ rules += '\n\n'
+ rules += editcomment
+ rules = ui.edit(rules, ui.username())
+
+ # Save edit rules in .hg/histedit-last-edit.txt in case
+ # the user needs to ask for help after something
+ # surprising happens.
+ f = open(repo.join('histedit-last-edit.txt'), 'w')
+ f.write(rules)
+ f.close()
+
+ return rules
def verifyrules(rules, repo, ctxs):
"""Verify that there exists exactly one edit rule per given changeset.
@@ -822,7 +879,7 @@
or a rule on a changeset outside of the user-given range.
"""
parsed = []
- expected = set(str(c) for c in ctxs)
+ expected = set(c.hex() for c in ctxs)
seen = set()
for r in rules:
if ' ' not in r:
@@ -830,22 +887,24 @@
action, rest = r.split(' ', 1)
ha = rest.strip().split(' ', 1)[0]
try:
- ha = str(repo[ha]) # ensure its a short hash
+ ha = repo[ha].hex()
except error.RepoError:
- raise util.Abort(_('unknown changeset %s listed') % ha)
+ raise util.Abort(_('unknown changeset %s listed') % ha[:12])
if ha not in expected:
raise util.Abort(
_('may not use changesets other than the ones listed'))
if ha in seen:
- raise util.Abort(_('duplicated command for changeset %s') % ha)
+ raise util.Abort(_('duplicated command for changeset %s') %
+ ha[:12])
seen.add(ha)
if action not in actiontable:
raise util.Abort(_('unknown action "%s"') % action)
parsed.append([action, ha])
missing = sorted(expected - seen) # sort to stabilize output
if missing:
- raise util.Abort(_('missing rules for changeset %s') % missing[0],
- hint=_('do you want to use the drop action?'))
+ raise util.Abort(_('missing rules for changeset %s') %
+ missing[0][:12],
+ hint=_('do you want to use the drop action?'))
return parsed
def processreplacement(state):
@@ -965,6 +1024,22 @@
finally:
release(lock)
+def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
+ if isinstance(nodelist, str):
+ nodelist = [nodelist]
+ if os.path.exists(os.path.join(repo.path, 'histedit-state')):
+ state = histeditstate(repo)
+ state.read()
+ histedit_nodes = set([ctx for (action, ctx) in state.rules])
+ strip_nodes = set([repo[n].hex() for n in nodelist])
+ common_nodes = histedit_nodes & strip_nodes
+ if common_nodes:
+ raise util.Abort(_("histedit in progress, can't strip %s")
+ % ', '.join(node.short(x) for x in common_nodes))
+ return orig(ui, repo, nodelist, *args, **kwargs)
+
+extensions.wrapfunction(repair, 'strip', stripwrapper)
+
def summaryhook(ui, repo):
if not os.path.exists(repo.join('histedit-state')):
return
--- a/hgext/keyword.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/keyword.py Fri Mar 13 17:55:04 2015 -0500
@@ -737,13 +737,7 @@
extensions.wrapfunction(patch, 'diff', kw_diff)
extensions.wrapfunction(cmdutil, 'amend', kw_amend)
extensions.wrapfunction(cmdutil, 'copy', kw_copy)
+ extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
for c in 'annotate changeset rev filediff diff'.split():
extensions.wrapfunction(webcommands, c, kwweb_skip)
- for name in recordextensions.split():
- try:
- record = extensions.find(name)
- extensions.wrapfunction(record, 'dorecord', kw_dorecord)
- except KeyError:
- pass
-
repo.__class__ = kwrepo
--- a/hgext/largefiles/lfcommands.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/lfcommands.py Fri Mar 13 17:55:04 2015 -0500
@@ -471,9 +471,8 @@
if lfile not in repo[None]: # not switched to normal file
util.unlinkpath(abslfile, ignoremissing=True)
# use normallookup() to allocate an entry in largefiles
- # dirstate, because lack of it misleads
- # lfilesrepo.status() into recognition that such cache
- # missing files are removed.
+ # dirstate to prevent lfilesrepo.status() from reporting
+ # missing files as removed.
lfdirstate.normallookup(lfile)
update[lfile] = expecthash
else:
--- a/hgext/largefiles/overrides.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/overrides.py Fri Mar 13 17:55:04 2015 -0500
@@ -304,17 +304,47 @@
return matchandpats
pats = set(p)
- # TODO: handling of patterns in both cases below
+
+ def fixpats(pat, tostandin=lfutil.standin):
+ kindpat = match_._patsplit(pat, None)
+
+ if kindpat[0] is not None:
+ return kindpat[0] + ':' + tostandin(kindpat[1])
+ return tostandin(kindpat[1])
+
if m._cwd:
- if os.path.isabs(m._cwd):
- # TODO: handle largefile magic when invoked from other cwd
- return matchandpats
- back = (m._cwd.count('/') + 1) * '../'
- pats.update(back + lfutil.standin(m._cwd + '/' + f) for f in p)
+ hglf = lfutil.shortname
+ back = util.pconvert(m.rel(hglf)[:-len(hglf)])
+
+ def tostandin(f):
+ # The file may already be a standin, so trucate the back
+ # prefix and test before mangling it. This avoids turning
+ # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
+ if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
+ return f
+
+ # An absolute path is from outside the repo, so truncate the
+ # path to the root before building the standin. Otherwise cwd
+ # is somewhere in the repo, relative to root, and needs to be
+ # prepended before building the standin.
+ if os.path.isabs(m._cwd):
+ f = f[len(back):]
+ else:
+ f = m._cwd + '/' + f
+ return back + lfutil.standin(f)
+
+ pats.update(fixpats(f, tostandin) for f in p)
else:
- pats.update(lfutil.standin(f) for f in p)
+ def tostandin(f):
+ if lfutil.splitstandin(f):
+ return f
+ return lfutil.standin(f)
+ pats.update(fixpats(f, tostandin) for f in p)
for i in range(0, len(m._files)):
+ # Don't add '.hglf' to m.files, since that is already covered by '.'
+ if m._files[i] == '.':
+ continue
standin = lfutil.standin(m._files[i])
# If the "standin" is a directory, append instead of replace to
# support naming a directory on the command line with only
@@ -325,7 +355,6 @@
elif m._files[i] not in repo[ctx.node()] \
and repo.wvfs.isdir(standin):
m._files.append(standin)
- pats.add(standin)
m._fmap = set(m._files)
m._always = False
@@ -338,6 +367,7 @@
return r
m.matchfn = lfmatchfn
+ ui.debug('updated patterns: %s\n' % sorted(pats))
return m, pats
# For hg log --patch, the match object is used in two different senses:
@@ -548,6 +578,15 @@
repo.wwrite(fcd.path(), fco.data(), fco.flags())
return 0
+def copiespathcopies(orig, ctx1, ctx2):
+ copies = orig(ctx1, ctx2)
+ updated = {}
+
+ for k, v in copies.iteritems():
+ updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
+
+ return updated
+
# Copy first changes the matchers to match standins instead of
# largefiles. Then it overrides util.copyfile in that function it
# checks if the destination largefile already exists. It also keeps a
@@ -559,16 +598,6 @@
# this isn't legal, let the original function deal with it
return orig(ui, repo, pats, opts, rename)
- def makestandin(relpath):
- path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
- return os.path.join(repo.wjoin(lfutil.standin(path)))
-
- fullpats = scmutil.expandpats(pats)
- dest = fullpats[-1]
-
- if os.path.isdir(dest):
- if not os.path.isdir(makestandin(dest)):
- os.makedirs(makestandin(dest))
# This could copy both lfiles and normal files in one command,
# but we don't want to do that. First replace their matcher to
# only match normal files and run it, then replace it to just
@@ -595,6 +624,17 @@
except OSError:
return result
+ def makestandin(relpath):
+ path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
+ return os.path.join(repo.wjoin(lfutil.standin(path)))
+
+ fullpats = scmutil.expandpats(pats)
+ dest = fullpats[-1]
+
+ if os.path.isdir(dest):
+ if not os.path.isdir(makestandin(dest)):
+ os.makedirs(makestandin(dest))
+
try:
try:
# When we call orig below it creates the standins but we don't add
@@ -715,10 +755,17 @@
default='relpath'):
match = oldmatch(ctx, pats, opts, globbed, default)
m = copy.copy(match)
+
+ # revert supports recursing into subrepos, and though largefiles
+ # currently doesn't work correctly in that case, this match is
+ # called, so the lfdirstate above may not be the correct one for
+ # this invocation of match.
+ lfdirstate = lfutil.openlfdirstate(ctx._repo.ui, ctx._repo, False)
+
def tostandin(f):
if lfutil.standin(f) in ctx:
return lfutil.standin(f)
- elif lfutil.standin(f) in repo[None]:
+ elif lfutil.standin(f) in repo[None] or lfdirstate[f] == 'r':
return None
return f
m._files = [tostandin(f) for f in m._files]
@@ -820,6 +867,14 @@
sourcerepo, destrepo = result
repo = destrepo.local()
+ # If largefiles is required for this repo, permanently enable it locally
+ if 'largefiles' in repo.requirements:
+ fp = repo.vfs('hgrc', 'a', text=True)
+ try:
+ fp.write('\n[extensions]\nlargefiles=\n')
+ finally:
+ fp.close()
+
# Caching is implicitly limited to 'rev' option, since the dest repo was
# truncated at that point. The user may expect a download count with
# this option, so attempt whether or not this is a largefile repo.
@@ -845,7 +900,7 @@
repo._lfcommithooks.pop()
def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
- prefix=None, mtime=None, subrepos=None):
+ prefix='', mtime=None, subrepos=None):
# No need to lock because we are only reading history and
# largefile caches, neither of which are modified.
lfcommands.cachelfiles(repo.ui, repo, node)
--- a/hgext/largefiles/reposetup.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/reposetup.py Fri Mar 13 17:55:04 2015 -0500
@@ -10,7 +10,7 @@
import copy
import os
-from mercurial import error, manifest, match as match_, util
+from mercurial import error, match as match_, util
from mercurial.i18n import _
from mercurial import scmutil, localrepo
@@ -38,17 +38,18 @@
def __getitem__(self, changeid):
ctx = super(lfilesrepo, self).__getitem__(changeid)
if self.lfstatus:
- class lfilesmanifestdict(manifest.manifestdict):
- def __contains__(self, filename):
- orig = super(lfilesmanifestdict, self).__contains__
- return orig(filename) or orig(lfutil.standin(filename))
class lfilesctx(ctx.__class__):
def files(self):
filenames = super(lfilesctx, self).files()
return [lfutil.splitstandin(f) or f for f in filenames]
def manifest(self):
man1 = super(lfilesctx, self).manifest()
- man1.__class__ = lfilesmanifestdict
+ class lfilesmanifest(man1.__class):
+ def __contains__(self, filename):
+ orig = super(lfilesmanifest, self).__contains__
+ return (orig(filename) or
+ orig(lfutil.standin(filename)))
+ man1.__class__ = lfilesmanifest
return man1
def filectx(self, path, fileid=None, filelog=None):
orig = super(lfilesctx, self).filectx
@@ -329,10 +330,10 @@
actualfiles.append(lf)
if not matcheddir:
# There may still be normal files in the dir, so
- # make sure a directory is in the list, which
- # forces status to walk and call the match
- # function on the matcher. Windows does NOT
- # require this.
+ # add a directory to the list, which
+ # forces status/dirstate to walk all files and
+ # call the match function on the matcher, even
+ # on case sensitive filesystems.
actualfiles.append('.')
matcheddir = True
# Nothing in dir, so readd it
--- a/hgext/largefiles/uisetup.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/uisetup.py Fri Mar 13 17:55:04 2015 -0500
@@ -9,7 +9,7 @@
'''setup for largefiles extension: uisetup'''
from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
- httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo
+ httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo, copies
from mercurial.i18n import _
from mercurial.hgweb import hgweb_mod, webcommands
@@ -37,6 +37,8 @@
extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
+ extensions.wrapfunction(copies, 'pathcopies', overrides.copiespathcopies)
+
# Subrepos call status function
entry = extensions.wrapcommand(commands.table, 'status',
overrides.overridestatus)
--- a/hgext/pager.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/pager.py Fri Mar 13 17:55:04 2015 -0500
@@ -149,6 +149,8 @@
usepager = True
break
+ setattr(ui, 'pageractive', usepager)
+
if usepager:
ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
ui.setconfig('ui', 'interactive', False, 'pager')
@@ -157,7 +159,12 @@
_runpager(ui, p)
return orig(ui, options, cmd, cmdfunc)
- extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
+ # Wrap dispatch._runcommand after color is loaded so color can see
+ # ui.pageractive. Otherwise, if we loaded first, color's wrapped
+ # dispatch._runcommand would run without having access to ui.pageractive.
+ def afterloaded(loaded):
+ extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
+ extensions.afterloaded('color', afterloaded)
def extsetup(ui):
commands.globalopts.append(
--- a/hgext/record.py Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/record.py Fri Mar 13 17:55:04 2015 -0500
@@ -8,409 +8,18 @@
'''commands to interactively select changes for commit/qrefresh'''
from mercurial.i18n import _
-from mercurial import cmdutil, commands, extensions, hg, patch
+from mercurial import cmdutil, commands, extensions
from mercurial import util
-import copy, cStringIO, errno, os, re, shutil, tempfile
cmdtable = {}
command = cmdutil.command(cmdtable)
testedwith = 'internal'
-lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
-
-def scanpatch(fp):
- """like patch.iterhunks, but yield different events
-
- - ('file', [header_lines + fromfile + tofile])
- - ('context', [context_lines])
- - ('hunk', [hunk_lines])
- - ('range', (-start,len, +start,len, proc))
- """
- lr = patch.linereader(fp)
-
- def scanwhile(first, p):
- """scan lr while predicate holds"""
- lines = [first]
- while True:
- line = lr.readline()
- if not line:
- break
- if p(line):
- lines.append(line)
- else:
- lr.push(line)
- break
- return lines
-
- while True:
- line = lr.readline()
- if not line:
- break
- if line.startswith('diff --git a/') or line.startswith('diff -r '):
- def notheader(line):
- s = line.split(None, 1)
- return not s or s[0] not in ('---', 'diff')
- header = scanwhile(line, notheader)
- fromfile = lr.readline()
- if fromfile.startswith('---'):
- tofile = lr.readline()
- header += [fromfile, tofile]
- else:
- lr.push(fromfile)
- yield 'file', header
- elif line[0] == ' ':
- yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
- elif line[0] in '-+':
- yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
- else:
- m = lines_re.match(line)
- if m:
- yield 'range', m.groups()
- else:
- yield 'other', line
-
-class header(object):
- """patch header
-
- XXX shouldn't we move this to mercurial/patch.py ?
- """
- diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
- diff_re = re.compile('diff -r .* (.*)$')
- allhunks_re = re.compile('(?:index|new file|deleted file) ')
- pretty_re = re.compile('(?:new file|deleted file) ')
- special_re = re.compile('(?:index|new|deleted|copy|rename) ')
-
- def __init__(self, header):
- self.header = header
- self.hunks = []
-
- def binary(self):
- return util.any(h.startswith('index ') for h in self.header)
-
- def pretty(self, fp):
- for h in self.header:
- if h.startswith('index '):
- fp.write(_('this modifies a binary file (all or nothing)\n'))
- break
- if self.pretty_re.match(h):
- fp.write(h)
- if self.binary():
- fp.write(_('this is a binary file\n'))
- break
- if h.startswith('---'):
- fp.write(_('%d hunks, %d lines changed\n') %
- (len(self.hunks),
- sum([max(h.added, h.removed) for h in self.hunks])))
- break
- fp.write(h)
-
- def write(self, fp):
- fp.write(''.join(self.header))
-
- def allhunks(self):
- return util.any(self.allhunks_re.match(h) for h in self.header)
-
- def files(self):
- match = self.diffgit_re.match(self.header[0])
- if match:
- fromfile, tofile = match.groups()
- if fromfile == tofile:
- return [fromfile]
- return [fromfile, tofile]
- else:
- return self.diff_re.match(self.header[0]).groups()
-
- def filename(self):
- return self.files()[-1]
-
- def __repr__(self):
- return '<header %s>' % (' '.join(map(repr, self.files())))
-
- def special(self):
- return util.any(self.special_re.match(h) for h in self.header)
-
-def countchanges(hunk):
- """hunk -> (n+,n-)"""
- add = len([h for h in hunk if h[0] == '+'])
- rem = len([h for h in hunk if h[0] == '-'])
- return add, rem
-
-class hunk(object):
- """patch hunk
-
- XXX shouldn't we merge this with patch.hunk ?
- """
- maxcontext = 3
-
- def __init__(self, header, fromline, toline, proc, before, hunk, after):
- def trimcontext(number, lines):
- delta = len(lines) - self.maxcontext
- if False and delta > 0:
- return number + delta, lines[:self.maxcontext]
- return number, lines
-
- self.header = header
- self.fromline, self.before = trimcontext(fromline, before)
- self.toline, self.after = trimcontext(toline, after)
- self.proc = proc
- self.hunk = hunk
- self.added, self.removed = countchanges(self.hunk)
-
- def write(self, fp):
- delta = len(self.before) + len(self.after)
- if self.after and self.after[-1] == '\\ No newline at end of file\n':
- delta -= 1
- fromlen = delta + self.removed
- tolen = delta + self.added
- fp.write('@@ -%d,%d +%d,%d @@%s\n' %
- (self.fromline, fromlen, self.toline, tolen,
- self.proc and (' ' + self.proc)))
- fp.write(''.join(self.before + self.hunk + self.after))
-
- pretty = write
-
- def filename(self):
- return self.header.filename()
-
- def __repr__(self):
- return '<hunk %r@%d>' % (self.filename(), self.fromline)
-
-def parsepatch(fp):
- """patch -> [] of headers -> [] of hunks """
- class parser(object):
- """patch parsing state machine"""
- def __init__(self):
- self.fromline = 0
- self.toline = 0
- self.proc = ''
- self.header = None
- self.context = []
- self.before = []
- self.hunk = []
- self.headers = []
-
- def addrange(self, limits):
- fromstart, fromend, tostart, toend, proc = limits
- self.fromline = int(fromstart)
- self.toline = int(tostart)
- self.proc = proc
-
- def addcontext(self, context):
- if self.hunk:
- h = hunk(self.header, self.fromline, self.toline, self.proc,
- self.before, self.hunk, context)
- self.header.hunks.append(h)
- self.fromline += len(self.before) + h.removed
- self.toline += len(self.before) + h.added
- self.before = []
- self.hunk = []
- self.proc = ''
- self.context = context
-
- def addhunk(self, hunk):
- if self.context:
- self.before = self.context
- self.context = []
- self.hunk = hunk
-
- def newfile(self, hdr):
- self.addcontext([])
- h = header(hdr)
- self.headers.append(h)
- self.header = h
-
- def addother(self, line):
- pass # 'other' lines are ignored
-
- def finished(self):
- self.addcontext([])
- return self.headers
-
- transitions = {
- 'file': {'context': addcontext,
- 'file': newfile,
- 'hunk': addhunk,
- 'range': addrange},
- 'context': {'file': newfile,
- 'hunk': addhunk,
- 'range': addrange,
- 'other': addother},
- 'hunk': {'context': addcontext,
- 'file': newfile,
- 'range': addrange},
- 'range': {'context': addcontext,
- 'hunk': addhunk},
- 'other': {'other': addother},
- }
-
- p = parser()
-
- state = 'context'
- for newstate, data in scanpatch(fp):
- try:
- p.transitions[state][newstate](p, data)
- except KeyError:
- raise patch.PatchError('unhandled transition: %s -> %s' %
- (state, newstate))
- state = newstate
- return p.finished()
-
-def filterpatch(ui, headers):
- """Interactively filter patch chunks into applied-only chunks"""
-
- def prompt(skipfile, skipall, query, chunk):
- """prompt query, and process base inputs
-
- - y/n for the rest of file
- - y/n for the rest
- - ? (help)
- - q (quit)
-
- Return True/False and possibly updated skipfile and skipall.
- """
- newpatches = None
- if skipall is not None:
- return skipall, skipfile, skipall, newpatches
- if skipfile is not None:
- return skipfile, skipfile, skipall, newpatches
- while True:
- resps = _('[Ynesfdaq?]'
- '$$ &Yes, record this change'
- '$$ &No, skip this change'
- '$$ &Edit this change manually'
- '$$ &Skip remaining changes to this file'
- '$$ Record remaining changes to this &file'
- '$$ &Done, skip remaining changes and files'
- '$$ Record &all changes to all remaining files'
- '$$ &Quit, recording no changes'
- '$$ &? (display help)')
- r = ui.promptchoice("%s %s" % (query, resps))
- ui.write("\n")
- if r == 8: # ?
- for c, t in ui.extractchoices(resps)[1]:
- ui.write('%s - %s\n' % (c, t.lower()))
- continue
- elif r == 0: # yes
- ret = True
- elif r == 1: # no
- ret = False
- elif r == 2: # Edit patch
- if chunk is None:
- ui.write(_('cannot edit patch for whole file'))
- ui.write("\n")
- continue
- if chunk.header.binary():
- ui.write(_('cannot edit patch for binary file'))
- ui.write("\n")
- continue
- # Patch comment based on the Git one (based on comment at end of
- # http://mercurial.selenic.com/wiki/RecordExtension)
- phelp = '---' + _("""
-To remove '-' lines, make them ' ' lines (context).
-To remove '+' lines, delete them.
-Lines starting with # will be removed from the patch.
-
-If the patch applies cleanly, the edited hunk will immediately be
-added to the record list. If it does not apply cleanly, a rejects
-file will be generated: you can use that when you try again. If
-all lines of the hunk are removed, then the edit is aborted and
-the hunk is left unchanged.
-""")
- (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
- suffix=".diff", text=True)
- ncpatchfp = None
- try:
- # Write the initial patch
- f = os.fdopen(patchfd, "w")
- chunk.header.write(f)
- chunk.write(f)
- f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
- f.close()
- # Start the editor and wait for it to complete
- editor = ui.geteditor()
- ui.system("%s \"%s\"" % (editor, patchfn),
- environ={'HGUSER': ui.username()},
- onerr=util.Abort, errprefix=_("edit failed"))
- # Remove comment lines
- patchfp = open(patchfn)
- ncpatchfp = cStringIO.StringIO()
- for line in patchfp:
- if not line.startswith('#'):
- ncpatchfp.write(line)
- patchfp.close()
- ncpatchfp.seek(0)
- newpatches = parsepatch(ncpatchfp)
- finally:
- os.unlink(patchfn)
- del ncpatchfp
- # Signal that the chunk shouldn't be applied as-is, but
- # provide the new patch to be used instead.
- ret = False
- elif r == 3: # Skip
- ret = skipfile = False
- elif r == 4: # file (Record remaining)
- ret = skipfile = True
- elif r == 5: # done, skip remaining
- ret = skipall = False
- elif r == 6: # all
- ret = skipall = True
- elif r == 7: # quit
- raise util.Abort(_('user quit'))
- return ret, skipfile, skipall, newpatches
-
- seen = set()
- applied = {} # 'filename' -> [] of chunks
- skipfile, skipall = None, None
- pos, total = 1, sum(len(h.hunks) for h in headers)
- for h in headers:
- pos += len(h.hunks)
- skipfile = None
- fixoffset = 0
- hdr = ''.join(h.header)
- if hdr in seen:
- continue
- seen.add(hdr)
- if skipall is None:
- h.pretty(ui)
- msg = (_('examine changes to %s?') %
- _(' and ').join("'%s'" % f for f in h.files()))
- r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
- if not r:
- continue
- applied[h.filename()] = [h]
- if h.allhunks():
- applied[h.filename()] += h.hunks
- continue
- for i, chunk in enumerate(h.hunks):
- if skipfile is None and skipall is None:
- chunk.pretty(ui)
- if total == 1:
- msg = _("record this change to '%s'?") % chunk.filename()
- else:
- idx = pos - len(h.hunks) + i
- msg = _("record change %d/%d to '%s'?") % (idx, total,
- chunk.filename())
- r, skipfile, skipall, newpatches = prompt(skipfile,
- skipall, msg, chunk)
- if r:
- if fixoffset:
- chunk = copy.copy(chunk)
- chunk.toline += fixoffset
- applied[chunk.filename()].append(chunk)
- elif newpatches is not None:
- for newpatch in newpatches:
- for newhunk in newpatch.hunks:
- if fixoffset:
- newhunk.toline += fixoffset
- applied[newhunk.filename()].append(newhunk)
- else:
- fixoffset += chunk.removed - chunk.added
- return sum([h for h in applied.itervalues()
- if h[0].special() or len(h) > 1], [])
@command("record",
# same options as commit + white space diff options
- commands.table['^commit|ci'][1][:] + commands.diffwsopts,
+ [c for c in commands.table['^commit|ci'][1][:]
+ if c[1] != "interactive"] + commands.diffwsopts,
_('hg record [OPTION]... [FILE]...'))
def record(ui, repo, *pats, **opts):
'''interactively select changes to commit
@@ -440,7 +49,7 @@
This command is not available when committing a merge.'''
- dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
+ cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
def qrefresh(origfn, ui, repo, *pats, **opts):
if not opts['interactive']:
@@ -456,7 +65,7 @@
mq.refresh(ui, repo, **opts)
# backup all changed files
- dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
+ cmdutil.dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
# This command registration is replaced during uisetup().
@command('qrecord',
@@ -481,162 +90,13 @@
opts['checkname'] = False
mq.new(ui, repo, patch, *pats, **opts)
- dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
+ cmdutil.dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
def qnew(origfn, ui, repo, patch, *args, **opts):
if opts['interactive']:
return qrecord(ui, repo, patch, *args, **opts)
return origfn(ui, repo, patch, *args, **opts)
-def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
- if not ui.interactive():
- raise util.Abort(_('running non-interactively, use %s instead') %
- cmdsuggest)
-
- # make sure username is set before going interactive
- if not opts.get('user'):
- ui.username() # raise exception, username not provided
-
- def recordfunc(ui, repo, message, match, opts):
- """This is generic record driver.
-
- Its job is to interactively filter local changes, and
- accordingly prepare working directory into a state in which the
- job can be delegated to a non-interactive commit command such as
- 'commit' or 'qrefresh'.
-
- After the actual job is done by non-interactive command, the
- working directory is restored to its original state.
-
- In the end we'll record interesting changes, and everything else
- will be left in place, so the user can continue working.
- """
-
- cmdutil.checkunfinished(repo, commit=True)
- merge = len(repo[None].parents()) > 1
- if merge:
- raise util.Abort(_('cannot partially commit a merge '
- '(use "hg commit" instead)'))
-
- status = repo.status(match=match)
- diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
- diffopts.nodates = True
- diffopts.git = True
- chunks = patch.diff(repo, changes=status, opts=diffopts)
- fp = cStringIO.StringIO()
- fp.write(''.join(chunks))
- fp.seek(0)
-
- # 1. filter patch, so we have intending-to apply subset of it
- try:
- chunks = filterpatch(ui, parsepatch(fp))
- except patch.PatchError, err:
- raise util.Abort(_('error parsing patch: %s') % err)
-
- del fp
-
- contenders = set()
- for h in chunks:
- try:
- contenders.update(set(h.files()))
- except AttributeError:
- pass
-
- changed = status.modified + status.added + status.removed
- newfiles = [f for f in changed if f in contenders]
- if not newfiles:
- ui.status(_('no changes to record\n'))
- return 0
-
- modified = set(status.modified)
-
- # 2. backup changed files, so we can restore them in the end
- if backupall:
- tobackup = changed
- else:
- tobackup = [f for f in newfiles if f in modified]
-
- backups = {}
- if tobackup:
- backupdir = repo.join('record-backups')
- try:
- os.mkdir(backupdir)
- except OSError, err:
- if err.errno != errno.EEXIST:
- raise
- try:
- # backup continues
- for f in tobackup:
- fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
- dir=backupdir)
- os.close(fd)
- ui.debug('backup %r as %r\n' % (f, tmpname))
- util.copyfile(repo.wjoin(f), tmpname)
- shutil.copystat(repo.wjoin(f), tmpname)
- backups[f] = tmpname
-
- fp = cStringIO.StringIO()
- for c in chunks:
- if c.filename() in backups:
- c.write(fp)
- dopatch = fp.tell()
- fp.seek(0)
-
- # 3a. apply filtered patch to clean repo (clean)
- if backups:
- hg.revert(repo, repo.dirstate.p1(),
- lambda key: key in backups)
-
- # 3b. (apply)
- if dopatch:
- try:
- ui.debug('applying patch\n')
- ui.debug(fp.getvalue())
- patch.internalpatch(ui, repo, fp, 1, eolmode=None)
- except patch.PatchError, err:
- raise util.Abort(str(err))
- del fp
-
- # 4. We prepared working directory according to filtered
- # patch. Now is the time to delegate the job to
- # commit/qrefresh or the like!
-
- # Make all of the pathnames absolute.
- newfiles = [repo.wjoin(nf) for nf in newfiles]
- commitfunc(ui, repo, *newfiles, **opts)
-
- return 0
- finally:
- # 5. finally restore backed-up files
- try:
- for realname, tmpname in backups.iteritems():
- ui.debug('restoring %r to %r\n' % (tmpname, realname))
- util.copyfile(tmpname, repo.wjoin(realname))
- # Our calls to copystat() here and above are a
- # hack to trick any editors that have f open that
- # we haven't modified them.
- #
- # Also note that this racy as an editor could
- # notice the file's mtime before we've finished
- # writing it.
- shutil.copystat(tmpname, repo.wjoin(realname))
- os.unlink(tmpname)
- if tobackup:
- os.rmdir(backupdir)
- except OSError:
- pass
-
- # wrap ui.write so diff output can be labeled/colorized
- def wrapwrite(orig, *args, **kw):
- label = kw.pop('label', '')
- for chunk, l in patch.difflabel(lambda: args):
- orig(chunk, label=label + l)
- oldwrite = ui.write
- extensions.wrapfunction(ui, 'write', wrapwrite)
- try:
- return cmdutil.commit(ui, repo, recordfunc, pats, opts)
- finally:
- ui.write = oldwrite
def uisetup(ui):
try:
--- a/mercurial/archival.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/archival.py Fri Mar 13 17:55:04 2015 -0500
@@ -230,7 +230,7 @@
}
def archive(repo, dest, node, kind, decode=True, matchfn=None,
- prefix=None, mtime=None, subrepos=False):
+ prefix='', mtime=None, subrepos=False):
'''create archive of repo as it was at node.
dest can be name of directory, name of archive file, or file
--- a/mercurial/bookmarks.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/bookmarks.py Fri Mar 13 17:55:04 2015 -0500
@@ -456,5 +456,5 @@
elif repo.obsstore:
return new.node() in obsolete.foreground(repo, [old.node()])
else:
- # still an independent clause as it is lazyer (and therefore faster)
+ # still an independent clause as it is lazier (and therefore faster)
return old.descendant(new)
--- a/mercurial/branchmap.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/branchmap.py Fri Mar 13 17:55:04 2015 -0500
@@ -411,9 +411,6 @@
try:
if self._rbcnamescount != 0:
f = repo.vfs.open(_rbcnames, 'ab')
- # The position after open(x, 'a') is implementation defined-
- # see issue3543. SEEK_END was added in 2.5
- f.seek(0, 2) #os.SEEK_END
if f.tell() == self._rbcsnameslen:
f.write('\0')
else:
@@ -438,9 +435,6 @@
revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
try:
f = repo.vfs.open(_rbcrevs, 'ab')
- # The position after open(x, 'a') is implementation defined-
- # see issue3543. SEEK_END was added in 2.5
- f.seek(0, 2) #os.SEEK_END
if f.tell() != start:
repo.ui.debug("truncating %s to %s\n" % (_rbcrevs, start))
f.seek(start)
--- a/mercurial/bundle2.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/bundle2.py Fri Mar 13 17:55:04 2015 -0500
@@ -145,6 +145,7 @@
preserve.
"""
+import errno
import sys
import util
import struct
@@ -312,7 +313,7 @@
except Exception, exc:
for part in iterparts:
# consume the bundle content
- part.read()
+ part.seek(0, 2)
# Small hack to let caller code distinguish exceptions from bundle2
# processing from processing the old format. This is mostly
# needed to handle different return codes to unbundle according to the
@@ -364,7 +365,7 @@
outpart.addparam('in-reply-to', str(part.id), mandatory=False)
finally:
# consume the part content to not corrupt the stream.
- part.read()
+ part.seek(0, 2)
def decodecaps(blob):
@@ -484,6 +485,8 @@
def __init__(self, fp):
self._fp = fp
+ self._seekable = (util.safehasattr(fp, 'seek') and
+ util.safehasattr(fp, 'tell'))
def _unpack(self, format):
"""unpack this struct format from the stream"""
@@ -494,6 +497,29 @@
"""read exactly <size> bytes from the stream"""
return changegroup.readexactly(self._fp, size)
+ def seek(self, offset, whence=0):
+ """move the underlying file pointer"""
+ if self._seekable:
+ return self._fp.seek(offset, whence)
+ else:
+ raise NotImplementedError(_('File pointer is not seekable'))
+
+ def tell(self):
+ """return the file offset, or None if file is not seekable"""
+ if self._seekable:
+ try:
+ return self._fp.tell()
+ except IOError, e:
+ if e.errno == errno.ESPIPE:
+ self._seekable = False
+ else:
+ raise
+ return None
+
+ def close(self):
+ """close underlying file"""
+ if util.safehasattr(self._fp, 'close'):
+ return self._fp.close()
class unbundle20(unpackermixin):
"""interpret a bundle2 stream
@@ -564,6 +590,7 @@
while headerblock is not None:
part = unbundlepart(self.ui, headerblock, self._fp)
yield part
+ part.seek(0, 2)
headerblock = self._readpartheader()
self.ui.debug('end of bundle2 stream\n')
@@ -580,6 +607,8 @@
return self._readexact(headersize)
return None
+ def compressed(self):
+ return False
class bundlepart(object):
"""A bundle2 part contains application level payload
@@ -801,6 +830,8 @@
self._payloadstream = None
self._readheader()
self._mandatory = None
+ self._chunkindex = [] #(payload, file) position tuples for chunk starts
+ self._pos = 0
def _fromheader(self, size):
"""return the next <size> byte from the header"""
@@ -826,6 +857,47 @@
self.params.update(dict(self.advisoryparams))
self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
+ def _payloadchunks(self, chunknum=0):
+ '''seek to specified chunk and start yielding data'''
+ if len(self._chunkindex) == 0:
+ assert chunknum == 0, 'Must start with chunk 0'
+ self._chunkindex.append((0, super(unbundlepart, self).tell()))
+ else:
+ assert chunknum < len(self._chunkindex), \
+ 'Unknown chunk %d' % chunknum
+ super(unbundlepart, self).seek(self._chunkindex[chunknum][1])
+
+ pos = self._chunkindex[chunknum][0]
+ payloadsize = self._unpack(_fpayloadsize)[0]
+ self.ui.debug('payload chunk size: %i\n' % payloadsize)
+ while payloadsize:
+ if payloadsize == flaginterrupt:
+ # interruption detection, the handler will now read a
+ # single part and process it.
+ interrupthandler(self.ui, self._fp)()
+ elif payloadsize < 0:
+ msg = 'negative payload chunk size: %i' % payloadsize
+ raise error.BundleValueError(msg)
+ else:
+ result = self._readexact(payloadsize)
+ chunknum += 1
+ pos += payloadsize
+ if chunknum == len(self._chunkindex):
+ self._chunkindex.append((pos,
+ super(unbundlepart, self).tell()))
+ yield result
+ payloadsize = self._unpack(_fpayloadsize)[0]
+ self.ui.debug('payload chunk size: %i\n' % payloadsize)
+
+ def _findchunk(self, pos):
+ '''for a given payload position, return a chunk number and offset'''
+ for chunk, (ppos, fpos) in enumerate(self._chunkindex):
+ if ppos == pos:
+ return chunk, 0
+ elif ppos > pos:
+ return chunk - 1, pos - self._chunkindex[chunk - 1][0]
+ raise ValueError('Unknown chunk')
+
def _readheader(self):
"""read the header and setup the object"""
typesize = self._unpackheader(_fparttypesize)[0]
@@ -857,22 +929,7 @@
advparams.append((self._fromheader(key), self._fromheader(value)))
self._initparams(manparams, advparams)
## part payload
- def payloadchunks():
- payloadsize = self._unpack(_fpayloadsize)[0]
- self.ui.debug('payload chunk size: %i\n' % payloadsize)
- while payloadsize:
- if payloadsize == flaginterrupt:
- # interruption detection, the handler will now read a
- # single part and process it.
- interrupthandler(self.ui, self._fp)()
- elif payloadsize < 0:
- msg = 'negative payload chunk size: %i' % payloadsize
- raise error.BundleValueError(msg)
- else:
- yield self._readexact(payloadsize)
- payloadsize = self._unpack(_fpayloadsize)[0]
- self.ui.debug('payload chunk size: %i\n' % payloadsize)
- self._payloadstream = util.chunkbuffer(payloadchunks())
+ self._payloadstream = util.chunkbuffer(self._payloadchunks())
# we read the data, tell it
self._initialized = True
@@ -886,8 +943,37 @@
data = self._payloadstream.read(size)
if size is None or len(data) < size:
self.consumed = True
+ self._pos += len(data)
return data
+ def tell(self):
+ return self._pos
+
+ def seek(self, offset, whence=0):
+ if whence == 0:
+ newpos = offset
+ elif whence == 1:
+ newpos = self._pos + offset
+ elif whence == 2:
+ if not self.consumed:
+ self.read()
+ newpos = self._chunkindex[-1][0] - offset
+ else:
+ raise ValueError('Unknown whence value: %r' % (whence,))
+
+ if newpos > self._chunkindex[-1][0] and not self.consumed:
+ self.read()
+ if not 0 <= newpos <= self._chunkindex[-1][0]:
+ raise ValueError('Offset out of range')
+
+ if self._pos != newpos:
+ chunk, internaloffset = self._findchunk(newpos)
+ self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
+ adjust = self.read(internaloffset)
+ if len(adjust) != internaloffset:
+ raise util.Abort(_('Seek failed\n'))
+ self._pos = newpos
+
capabilities = {'HG2Y': (),
'b2x:listkeys': (),
'b2x:pushkey': (),
--- a/mercurial/bundlerepo.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/bundlerepo.py Fri Mar 13 17:55:04 2015 -0500
@@ -15,7 +15,7 @@
from i18n import _
import os, tempfile, shutil
import changegroup, util, mdiff, discovery, cmdutil, scmutil, exchange
-import localrepo, changelog, manifest, filelog, revlog, error, phases
+import localrepo, changelog, manifest, filelog, revlog, error, phases, bundle2
class bundlerevlog(revlog.revlog):
def __init__(self, opener, indexfile, bundle, linkmapper):
@@ -177,9 +177,6 @@
def baserevision(self, nodeorrev):
return filelog.filelog.revision(self, nodeorrev)
- def _file(self, f):
- self._repo.file(f)
-
class bundlepeer(localrepo.localpeer):
def canpush(self):
return False
@@ -219,7 +216,7 @@
self.tempfile = None
f = util.posixfile(bundlename, "rb")
- self.bundle = exchange.readbundle(ui, f, bundlename)
+ self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
if self.bundle.compressed():
fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
suffix=".hg10un")
@@ -237,7 +234,27 @@
fptemp.close()
f = self.vfs.open(self.tempfile, mode="rb")
- self.bundle = exchange.readbundle(ui, f, bundlename, self.vfs)
+ self.bundlefile = self.bundle = exchange.readbundle(ui, f,
+ bundlename,
+ self.vfs)
+
+ if isinstance(self.bundle, bundle2.unbundle20):
+ cgparts = [part for part in self.bundle.iterparts()
+ if (part.type == 'b2x:changegroup')
+ and (part.params.get('version', '01')
+ in changegroup.packermap)]
+
+ if not cgparts:
+ raise util.Abort('No changegroups found')
+ version = cgparts[0].params.get('version', '01')
+ cgparts = [p for p in cgparts
+ if p.params.get('version', '01') == version]
+ if len(cgparts) > 1:
+ raise NotImplementedError("Can't process multiple changegroups")
+ part = cgparts[0]
+
+ part.seek(0)
+ self.bundle = changegroup.packermap[version][1](part, 'UN')
# dict with the mapping 'filename' -> position in the bundle
self.bundlefilespos = {}
@@ -303,7 +320,7 @@
def close(self):
"""Close assigned bundle file immediately."""
- self.bundle.close()
+ self.bundlefile.close()
if self.tempfile is not None:
self.vfs.unlink(self.tempfile)
if self._tempparent:
--- a/mercurial/changegroup.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/changegroup.py Fri Mar 13 17:55:04 2015 -0500
@@ -111,7 +111,8 @@
chunkiter = bundle.getchunks()
else:
if cg.version != '01':
- raise util.Abort(_('Bundle1 only supports v1 changegroups\n'))
+ raise util.Abort(_('old bundle types only supports v1 '
+ 'changegroups'))
header, compressor = bundletypes[bundletype]
fh.write(header)
z = compressor()
@@ -481,7 +482,17 @@
base = self.deltaparent(revlog, rev, p1, p2, prev)
prefix = ''
- if base == nullrev:
+ if revlog.iscensored(base) or revlog.iscensored(rev):
+ try:
+ delta = revlog.revision(node)
+ except error.CensoredNodeError, e:
+ delta = e.tombstone
+ if base == nullrev:
+ prefix = mdiff.trivialdiffheader(len(delta))
+ else:
+ baselen = revlog.rawsize(base)
+ prefix = mdiff.replacediffheader(baselen, len(delta))
+ elif base == nullrev:
delta = revlog.revision(node)
prefix = mdiff.trivialdiffheader(len(delta))
else:
@@ -659,8 +670,11 @@
pr()
fl = repo.file(f)
o = len(fl)
- if not fl.addgroup(source, revmap, trp):
- raise util.Abort(_("received file revlog group is empty"))
+ try:
+ if not fl.addgroup(source, revmap, trp):
+ raise util.Abort(_("received file revlog group is empty"))
+ except error.CensoredBaseError, e:
+ raise util.Abort(_("received delta base is censored: %s") % e)
revisions += len(fl) - o
files += 1
if f in needfiles:
--- a/mercurial/changelog.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/changelog.py Fri Mar 13 17:55:04 2015 -0500
@@ -143,6 +143,11 @@
if i not in self.filteredrevs:
return self.node(i)
+ def __contains__(self, rev):
+ """filtered version of revlog.__contains__"""
+ return (revlog.revlog.__contains__(self, rev)
+ and rev not in self.filteredrevs)
+
def __iter__(self):
"""filtered version of revlog.__iter__"""
if len(self.filteredrevs) == 0:
--- a/mercurial/cmdutil.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/cmdutil.py Fri Mar 13 17:55:04 2015 -0500
@@ -7,7 +7,7 @@
from node import hex, nullid, nullrev, short
from i18n import _
-import os, sys, errno, re, tempfile
+import os, sys, errno, re, tempfile, cStringIO, shutil
import util, scmutil, templater, patch, error, templatekw, revlog, copies
import match as matchmod
import context, repair, graphmod, revset, phases, obsolete, pathutil
@@ -19,6 +19,177 @@
def parsealiases(cmd):
return cmd.lstrip("^").split("|")
+def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
+ import merge as mergemod
+ if not ui.interactive():
+ raise util.Abort(_('running non-interactively, use %s instead') %
+ cmdsuggest)
+
+ # make sure username is set before going interactive
+ if not opts.get('user'):
+ ui.username() # raise exception, username not provided
+
+ def recordfunc(ui, repo, message, match, opts):
+ """This is generic record driver.
+
+ Its job is to interactively filter local changes, and
+ accordingly prepare working directory into a state in which the
+ job can be delegated to a non-interactive commit command such as
+ 'commit' or 'qrefresh'.
+
+ After the actual job is done by non-interactive command, the
+ working directory is restored to its original state.
+
+ In the end we'll record interesting changes, and everything else
+ will be left in place, so the user can continue working.
+ """
+
+ checkunfinished(repo, commit=True)
+ merge = len(repo[None].parents()) > 1
+ if merge:
+ raise util.Abort(_('cannot partially commit a merge '
+ '(use "hg commit" instead)'))
+
+ status = repo.status(match=match)
+ diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
+ diffopts.nodates = True
+ diffopts.git = True
+ originalchunks = patch.diff(repo, changes=status, opts=diffopts)
+ fp = cStringIO.StringIO()
+ fp.write(''.join(originalchunks))
+ fp.seek(0)
+
+ # 1. filter patch, so we have intending-to apply subset of it
+ try:
+ chunks = patch.filterpatch(ui, patch.parsepatch(fp))
+ except patch.PatchError, err:
+ raise util.Abort(_('error parsing patch: %s') % err)
+
+ del fp
+
+ contenders = set()
+ for h in chunks:
+ try:
+ contenders.update(set(h.files()))
+ except AttributeError:
+ pass
+
+ changed = status.modified + status.added + status.removed
+ newfiles = [f for f in changed if f in contenders]
+ if not newfiles:
+ ui.status(_('no changes to record\n'))
+ return 0
+
+ newandmodifiedfiles = set()
+ for h in chunks:
+ ishunk = isinstance(h, patch.recordhunk)
+ isnew = h.filename() in status.added
+ if ishunk and isnew and not h in originalchunks:
+ newandmodifiedfiles.add(h.filename())
+
+ modified = set(status.modified)
+
+ # 2. backup changed files, so we can restore them in the end
+
+ if backupall:
+ tobackup = changed
+ else:
+ tobackup = [f for f in newfiles
+ if f in modified or f in newandmodifiedfiles]
+
+ backups = {}
+ if tobackup:
+ backupdir = repo.join('record-backups')
+ try:
+ os.mkdir(backupdir)
+ except OSError, err:
+ if err.errno != errno.EEXIST:
+ raise
+ try:
+ # backup continues
+ for f in tobackup:
+ fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
+ dir=backupdir)
+ os.close(fd)
+ ui.debug('backup %r as %r\n' % (f, tmpname))
+ util.copyfile(repo.wjoin(f), tmpname)
+ shutil.copystat(repo.wjoin(f), tmpname)
+ backups[f] = tmpname
+
+ fp = cStringIO.StringIO()
+ for c in chunks:
+ fname = c.filename()
+ if fname in backups or fname in newandmodifiedfiles:
+ c.write(fp)
+ dopatch = fp.tell()
+ fp.seek(0)
+
+ [os.unlink(c) for c in newandmodifiedfiles]
+
+ # 3a. apply filtered patch to clean repo (clean)
+ if backups:
+ # Equivalent to hg.revert
+ choices = lambda key: key in backups
+ mergemod.update(repo, repo.dirstate.p1(),
+ False, True, choices)
+
+
+ # 3b. (apply)
+ if dopatch:
+ try:
+ ui.debug('applying patch\n')
+ ui.debug(fp.getvalue())
+ patch.internalpatch(ui, repo, fp, 1, eolmode=None)
+ except patch.PatchError, err:
+ raise util.Abort(str(err))
+ del fp
+
+ # 4. We prepared working directory according to filtered
+ # patch. Now is the time to delegate the job to
+ # commit/qrefresh or the like!
+
+ # Make all of the pathnames absolute.
+ newfiles = [repo.wjoin(nf) for nf in newfiles]
+ commitfunc(ui, repo, *newfiles, **opts)
+
+ return 0
+ finally:
+ # 5. finally restore backed-up files
+ try:
+ for realname, tmpname in backups.iteritems():
+ ui.debug('restoring %r to %r\n' % (tmpname, realname))
+ util.copyfile(tmpname, repo.wjoin(realname))
+ # Our calls to copystat() here and above are a
+ # hack to trick any editors that have f open that
+ # we haven't modified them.
+ #
+ # Also note that this racy as an editor could
+ # notice the file's mtime before we've finished
+ # writing it.
+ shutil.copystat(tmpname, repo.wjoin(realname))
+ os.unlink(tmpname)
+ if tobackup:
+ os.rmdir(backupdir)
+ except OSError:
+ pass
+
+ # wrap ui.write so diff output can be labeled/colorized
+ def wrapwrite(orig, *args, **kw):
+ label = kw.pop('label', '')
+ for chunk, l in patch.difflabel(lambda: args):
+ orig(chunk, label=label + l)
+
+ oldwrite = ui.write
+ def wrap(*args, **kwargs):
+ return wrapwrite(oldwrite, *args, **kwargs)
+ setattr(ui, 'write', wrap)
+
+ try:
+ return commit(ui, repo, recordfunc, pats, opts)
+ finally:
+ ui.write = oldwrite
+
+
def findpossible(cmd, table, strict=False):
"""
Return cmd -> (aliases, command table entry)
@@ -34,8 +205,10 @@
else:
keys = table.keys()
+ allcmds = []
for e in keys:
aliases = parsealiases(e)
+ allcmds.extend(aliases)
found = None
if cmd in aliases:
found = cmd
@@ -53,11 +226,11 @@
if not choice and debugchoice:
choice = debugchoice
- return choice
+ return choice, allcmds
def findcmd(cmd, table, strict=True):
"""Return (aliases, command table entry) for command string."""
- choice = findpossible(cmd, table, strict)
+ choice, allcmds = findpossible(cmd, table, strict)
if cmd in choice:
return choice[cmd]
@@ -70,7 +243,7 @@
if choice:
return choice.values()[0]
- raise error.UnknownCommand(cmd)
+ raise error.UnknownCommand(cmd, allcmds)
def findrepo(p):
while not os.path.isdir(os.path.join(p, ".hg")):
@@ -110,22 +283,22 @@
(logfile, inst.strerror))
return message
-def mergeeditform(ctxorbool, baseform):
- """build appropriate editform from ctxorbool and baseform
-
- 'ctxorbool' is one of a ctx to be committed, or a bool whether
+def mergeeditform(ctxorbool, baseformname):
+ """return appropriate editform name (referencing a committemplate)
+
+ 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
merging is committed.
- This returns editform 'baseform' with '.merge' if merging is
- committed, or one with '.normal' suffix otherwise.
+ This returns baseformname with '.merge' appended if it is a merge,
+ otherwise '.normal' is appended.
"""
if isinstance(ctxorbool, bool):
if ctxorbool:
- return baseform + ".merge"
+ return baseformname + ".merge"
elif 1 < len(ctxorbool.parents()):
- return baseform + ".merge"
-
- return baseform + ".normal"
+ return baseformname + ".merge"
+
+ return baseformname + ".normal"
def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
editform='', **opts):
@@ -613,6 +786,7 @@
update = not opts.get('bypass')
strip = opts["strip"]
+ prefix = opts["prefix"]
sim = float(opts.get('similarity') or 0)
if not tmpname:
return (None, None, False)
@@ -672,8 +846,8 @@
partial = opts.get('partial', False)
files = set()
try:
- patch.patch(ui, repo, tmpname, strip=strip, files=files,
- eolmode=None, similarity=sim / 100.0)
+ patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
+ files=files, eolmode=None, similarity=sim / 100.0)
except patch.PatchError, e:
if not partial:
raise util.Abort(str(e))
@@ -710,7 +884,7 @@
try:
files = set()
try:
- patch.patchrepo(ui, repo, p1, store, tmpname, strip,
+ patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
files, eolmode=None)
except patch.PatchError, e:
raise util.Abort(str(e))
@@ -1473,14 +1647,7 @@
function on each context in the window in forward order.'''
follow = opts.get('follow') or opts.get('follow_first')
-
- if opts.get('rev'):
- revs = scmutil.revrange(repo, opts.get('rev'))
- elif follow:
- revs = repo.revs('reverse(:.)')
- else:
- revs = revset.spanset(repo)
- revs.reverse()
+ revs = _logrevs(repo, opts)
if not revs:
return []
wanted = set()
@@ -1822,6 +1989,21 @@
expr = None
return expr, filematcher
+def _logrevs(repo, opts):
+ # Default --rev value depends on --follow but --follow behaviour
+ # depends on revisions resolved from --rev...
+ follow = opts.get('follow') or opts.get('follow_first')
+ if opts.get('rev'):
+ revs = scmutil.revrange(repo, opts['rev'])
+ elif follow and repo.dirstate.p1() == nullid:
+ revs = revset.baseset()
+ elif follow:
+ revs = repo.revs('reverse(:.)')
+ else:
+ revs = revset.spanset(repo)
+ revs.reverse()
+ return revs
+
def getgraphlogrevs(repo, pats, opts):
"""Return (revs, expr, filematcher) where revs is an iterable of
revision numbers, expr is a revset string built from log options
@@ -1830,28 +2012,14 @@
callable taking a revision number and returning a match objects
filtering the files to be detailed when displaying the revision.
"""
- if not len(repo):
- return [], None, None
limit = loglimit(opts)
- # Default --rev value depends on --follow but --follow behaviour
- # depends on revisions resolved from --rev...
- follow = opts.get('follow') or opts.get('follow_first')
- possiblyunsorted = False # whether revs might need sorting
- if opts.get('rev'):
- revs = scmutil.revrange(repo, opts['rev'])
- # Don't sort here because _makelogrevset might depend on the
- # order of revs
- possiblyunsorted = True
- else:
- if follow and len(repo) > 0:
- revs = repo.revs('reverse(:.)')
- else:
- revs = revset.spanset(repo)
- revs.reverse()
+ revs = _logrevs(repo, opts)
if not revs:
return revset.baseset(), None, None
expr, filematcher = _makelogrevset(repo, pats, opts, revs)
- if possiblyunsorted:
+ if opts.get('rev'):
+ # User-specified revs might be unsorted, but don't sort before
+ # _makelogrevset because it might depend on the order of revs
revs.sort(reverse=True)
if expr:
# Revset matchers often operate faster on revisions in changelog
@@ -1882,16 +2050,7 @@
filtering the files to be detailed when displaying the revision.
"""
limit = loglimit(opts)
- # Default --rev value depends on --follow but --follow behaviour
- # depends on revisions resolved from --rev...
- follow = opts.get('follow') or opts.get('follow_first')
- if opts.get('rev'):
- revs = scmutil.revrange(repo, opts['rev'])
- elif follow:
- revs = repo.revs('reverse(:.)')
- else:
- revs = revset.spanset(repo)
- revs.reverse()
+ revs = _logrevs(repo, opts)
if not revs:
return revset.baseset([]), None, None
expr, filematcher = _makelogrevset(repo, pats, opts, revs)
@@ -1930,6 +2089,8 @@
char = '@'
elif ctx.obsolete():
char = 'x'
+ elif ctx.closesbranch():
+ char = '_'
copies = None
if getrenamed and ctx.rev():
copies = []
@@ -2058,6 +2219,24 @@
forgot.extend(f for f in forget if f not in rejected)
return bad, forgot
+def files(ui, ctx, m, fm, fmt):
+ rev = ctx.rev()
+ ret = 1
+ ds = ctx.repo().dirstate
+
+ for f in ctx.matches(m):
+ if rev is None and ds[f] == 'r':
+ continue
+ fm.startitem()
+ if ui.verbose:
+ fc = ctx[f]
+ fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
+ fm.data(abspath=f)
+ fm.write('path', fmt, m.rel(f))
+ ret = 0
+
+ return ret
+
def remove(ui, repo, m, prefix, after, force, subrepos):
join = lambda f: os.path.join(prefix, f)
ret = 0
@@ -2086,6 +2265,7 @@
% join(subpath))
# warn about failure to delete explicit files/dirs
+ deleteddirs = scmutil.dirs(deleted)
for f in m.files():
def insubrepo():
for subpath in wctx.substate:
@@ -2093,7 +2273,8 @@
return True
return False
- if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
+ isdir = f in deleteddirs or f in wctx.dirs()
+ if f in repo.dirstate or isdir or f == '.' or insubrepo():
continue
if repo.wvfs.exists(f):
@@ -2615,9 +2796,8 @@
deladded = _deleted - smf
deleted = _deleted - deladded
- # We need to account for the state of file in the dirstate.
- #
- # Even, when we revert against something else than parent. This will
+ # We need to account for the state of the file in the dirstate,
+ # even when we revert against something else than parent. This will
# slightly alter the behavior of revert (doing back up or not, delete
# or just forget etc).
if parent == node:
@@ -2800,14 +2980,14 @@
_performrevert(repo, parents, ctx, actions)
- # get the list of subrepos that must be reverted
- subrepomatch = scmutil.match(ctx, pats, opts)
- targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
-
- if targetsubs:
- # Revert the subrepos on the revert list
- for sub in targetsubs:
- ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
+ # get the list of subrepos that must be reverted
+ subrepomatch = scmutil.match(ctx, pats, opts)
+ targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
+
+ if targetsubs:
+ # Revert the subrepos on the revert list
+ for sub in targetsubs:
+ ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
finally:
wlock.release()
--- a/mercurial/commands.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/commands.py Fri Mar 13 17:55:04 2015 -0500
@@ -1383,6 +1383,7 @@
('', 'amend', None, _('amend the parent of the working dir')),
('s', 'secret', None, _('use the secret phase for committing')),
('e', 'edit', None, _('invoke editor on commit messages')),
+ ('i', 'interactive', None, _('use interactive mode')),
] + walkopts + commitopts + commitopts2 + subrepoopts,
_('[OPTION]... [FILE]...'),
inferrepo=True)
@@ -1422,6 +1423,11 @@
Returns 0 on success, 1 if nothing changed.
"""
+ if opts.get('interactive'):
+ opts.pop('interactive')
+ cmdutil.dorecord(ui, repo, commit, 'commit', False, *pats, **opts)
+ return
+
if opts.get('subrepos'):
if opts.get('amend'):
raise util.Abort(_('cannot amend with --subrepos'))
@@ -1946,7 +1952,7 @@
ui.write("%s\n" % "\n".join(options))
return
- cmdlist = cmdutil.findpossible(cmd, table)
+ cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
if ui.verbose:
cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
ui.write("%s\n" % "\n".join(sorted(cmdlist)))
@@ -2885,7 +2891,7 @@
weight, optimizedtree = revset.optimize(newtree, True)
ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
func = revset.match(ui, expr)
- for c in func(repo, revset.spanset(repo)):
+ for c in func(repo):
ui.write("%s\n" % c)
@command('debugsetparents', [], _('REV1 [REV2]'))
@@ -2893,7 +2899,8 @@
"""manually set the parents of the current working directory
This is useful for writing repository conversion tools, but should
- be used with care.
+ be used with care. For example, neither the working copy nor the dirstate
+ is updated, so file status may be incorrect after running this command.
Returns 0 on success.
"""
@@ -3257,8 +3264,6 @@
"""
ctx = scmutil.revsingle(repo, opts.get('rev'), None)
- rev = ctx.rev()
- ret = 1
end = '\n'
if opts.get('print0'):
@@ -3267,17 +3272,7 @@
fmt = '%s' + end
m = scmutil.match(ctx, pats, opts)
- ds = repo.dirstate
- for f in ctx.matches(m):
- if rev is None and ds[f] == 'r':
- continue
- fm.startitem()
- if ui.verbose:
- fc = ctx[f]
- fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
- fm.data(abspath=f)
- fm.write('path', fmt, m.rel(f))
- ret = 0
+ ret = cmdutil.files(ui, ctx, m, fm, fmt)
fm.end()
@@ -4056,6 +4051,8 @@
_('commit even if some hunks fail')),
('', 'exact', None,
_('apply patch to the nodes from which it was generated')),
+ ('', 'prefix', '',
+ _('apply patch to directory relative to the root'), _('DIR')),
('', 'import-branch', None,
_('use any branch information in patch (implied by --exact)'))] +
commitopts + commitopts2 + similarityopts,
@@ -4155,6 +4152,8 @@
raise util.Abort(_('cannot use --similarity with --bypass'))
if opts.get('exact') and opts.get('edit'):
raise util.Abort(_('cannot use --exact with --edit'))
+ if opts.get('exact') and opts.get('prefix'):
+ raise util.Abort(_('cannot use --exact with --prefix'))
if update:
cmdutil.checkunfinished(repo)
@@ -4243,13 +4242,13 @@
pull location. These are the changesets that would have been pulled
if a pull at the time you issued this command.
- For remote repository, using --bundle avoids downloading the
- changesets twice if the incoming is followed by a pull.
-
See pull for valid source format details.
.. container:: verbose
+ For remote repository, using --bundle avoids downloading the
+ changesets twice if the incoming is followed by a pull.
+
Examples:
- show incoming changes with patches and full description::
@@ -4477,6 +4476,10 @@
Returns 0 on success.
"""
+ if opts.get('follow') and opts.get('rev'):
+ opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
+ del opts['follow']
+
if opts.get('graph'):
return cmdutil.graphlog(ui, repo, *pats, **opts)
@@ -4821,19 +4824,20 @@
Returns 0 on success.
"""
if search:
- for name, path in ui.configitems("paths"):
+ for name, path in sorted(ui.paths.iteritems()):
if name == search:
- ui.status("%s\n" % util.hidepassword(path))
+ ui.status("%s\n" % util.hidepassword(path.loc))
return
if not ui.quiet:
ui.warn(_("not found!\n"))
return 1
else:
- for name, path in ui.configitems("paths"):
+ for name, path in sorted(ui.paths.iteritems()):
if ui.quiet:
ui.write("%s\n" % name)
else:
- ui.write("%s = %s\n" % (name, util.hidepassword(path)))
+ ui.write("%s = %s\n" % (name,
+ util.hidepassword(path.loc)))
@command('phase',
[('p', 'public', False, _('set changeset phase to public')),
@@ -4984,9 +4988,9 @@
Returns 0 on success, 1 if an update had unresolved files.
"""
source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
+ ui.status(_('pulling from %s\n') % util.hidepassword(source))
other = hg.peer(repo, opts, source)
try:
- ui.status(_('pulling from %s\n') % util.hidepassword(source))
revs, checkout = hg.addbranchrevs(repo, other, branches,
opts.get('rev'))
@@ -5225,7 +5229,7 @@
('m', 'mark', None, _('mark files as resolved')),
('u', 'unmark', None, _('mark files as unresolved')),
('n', 'no-status', None, _('hide status prefix'))]
- + mergetoolopts + walkopts,
+ + mergetoolopts + walkopts + formatteropts,
_('[OPTION]... [FILE]...'),
inferrepo=True)
def resolve(ui, repo, *pats, **opts):
@@ -5277,11 +5281,25 @@
raise util.Abort(_('no files or directories specified'),
hint=('use --all to remerge all files'))
+ if show:
+ fm = ui.formatter('resolve', opts)
+ ms = mergemod.mergestate(repo)
+ m = scmutil.match(repo[None], pats, opts)
+ for f in ms:
+ if not m(f):
+ continue
+ l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved'}[ms[f]]
+ fm.startitem()
+ fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
+ fm.write('path', '%s\n', f, label=l)
+ fm.end()
+ return 0
+
wlock = repo.wlock()
try:
ms = mergemod.mergestate(repo)
- if not (ms.active() or repo.dirstate.p2() != nullid) and not show:
+ if not (ms.active() or repo.dirstate.p2() != nullid):
raise util.Abort(
_('resolve command not applicable when not merging'))
@@ -5295,14 +5313,7 @@
didwork = True
- if show:
- if nostatus:
- ui.write("%s\n" % f)
- else:
- ui.write("%s %s\n" % (ms[f].upper(), f),
- label='resolve.' +
- {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
- elif mark:
+ if mark:
ms.mark(f, "r")
elif unmark:
ms.mark(f, "u")
@@ -5334,10 +5345,8 @@
finally:
wlock.release()
- # Nudge users into finishing an unfinished operation. We don't print
- # this with the list/show operation because we want list/show to remain
- # machine readable.
- if not list(ms.unresolved()) and not show:
+ # Nudge users into finishing an unfinished operation
+ if not list(ms.unresolved()):
ui.status(_('(no more unresolved files)\n'))
return ret
@@ -6303,7 +6312,7 @@
% util.version())
ui.status(_(
"(see http://mercurial.selenic.com for more information)\n"
- "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
+ "\nCopyright (C) 2005-2015 Matt Mackall and others\n"
"This is free software; see the source for copying conditions. "
"There is NO\nwarranty; "
"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
--- a/mercurial/context.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/context.py Fri Mar 13 17:55:04 2015 -0500
@@ -66,8 +66,7 @@
return self.filectx(key)
def __iter__(self):
- for f in sorted(self._manifest):
- yield f
+ return iter(self._manifest)
def _manifestmatches(self, match, s):
"""generate a new manifest filtered by the match argument
@@ -153,6 +152,8 @@
return hex(self.node())
def manifest(self):
return self._manifest
+ def repo(self):
+ return self._repo
def phasestr(self):
return phases.phasenames[self.phase()]
def mutable(self):
@@ -376,10 +377,6 @@
return
if isinstance(changeid, long):
changeid = str(changeid)
- if changeid == '.':
- self._node = repo.dirstate.p1()
- self._rev = repo.changelog.rev(self._node)
- return
if changeid == 'null':
self._node = nullid
self._rev = nullrev
@@ -388,6 +385,12 @@
self._node = repo.changelog.tip()
self._rev = repo.changelog.rev(self._node)
return
+ if changeid == '.' or changeid == repo.dirstate.p1():
+ # this is a hack to delay/avoid loading obsmarkers
+ # when we know that '.' won't be hidden
+ self._node = repo.dirstate.p1()
+ self._rev = repo.unfiltered().changelog.rev(self._node)
+ return
if len(changeid) == 20:
try:
self._node = changeid
@@ -752,7 +755,7 @@
return True
def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
- """return the first ancestor of <srcrev> introducting <fnode>
+ """return the first ancestor of <srcrev> introducing <fnode>
If the linkrev of the file revision does not point to an ancestor of
srcrev, we'll walk down the ancestors until we find one introducing
@@ -824,7 +827,7 @@
# be replaced with the rename information. This parent is -always-
# the first one.
#
- # As null id have alway been filtered out in the previous list
+ # As null id have always been filtered out in the previous list
# comprehension, inserting to 0 will always result in "replacing
# first nullid parent with rename information.
pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
@@ -1287,9 +1290,6 @@
self._repo.dirstate.setparents(node)
self._repo.dirstate.endparentchange()
- def dirs(self):
- return self._repo.dirstate.dirs()
-
class workingctx(committablectx):
"""A workingctx object makes access to data related to
the current working directory convenient.
--- a/mercurial/copies.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/copies.py Fri Mar 13 17:55:04 2015 -0500
@@ -8,10 +8,6 @@
import util
import heapq
-def _nonoverlap(d1, d2, d3):
- "Return list of elements in d1 not in d2 or d3"
- return sorted([d for d in d1 if d not in d3 and d not in d2])
-
def _dirname(f):
s = f.rfind("/")
if s == -1:
@@ -144,6 +140,13 @@
del c[k]
return c
+def _computeforwardmissing(a, b):
+ """Computes which files are in b but not a.
+ This is its own function so extensions can easily wrap this call to see what
+ files _forwardcopies is about to process.
+ """
+ return b.manifest().filesnotin(a.manifest())
+
def _forwardcopies(a, b):
'''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
@@ -167,9 +170,7 @@
# we currently don't try to find where old files went, too expensive
# this means we can miss a case like 'hg rm b; hg cp a b'
cm = {}
- missing = set(b.manifest().iterkeys())
- missing.difference_update(a.manifest().iterkeys())
-
+ missing = _computeforwardmissing(a, b)
ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
for f in missing:
fctx = b[f]
@@ -208,6 +209,25 @@
return _backwardrenames(x, y)
return _chain(x, y, _backwardrenames(x, a), _forwardcopies(a, y))
+def _computenonoverlap(repo, m1, m2, addedinm1, addedinm2):
+ """Computes, based on addedinm1 and addedinm2, the files exclusive to m1
+ and m2. This is its own function so extensions can easily wrap this call
+ to see what files mergecopies is about to process.
+
+ Even though m1 and m2 are not used in this function, they are useful in
+ other extensions for being able to read the file nodes of the changed files.
+ """
+ u1 = sorted(addedinm1 - addedinm2)
+ u2 = sorted(addedinm2 - addedinm1)
+
+ if u1:
+ repo.ui.debug(" unmatched files in local:\n %s\n"
+ % "\n ".join(u1))
+ if u2:
+ repo.ui.debug(" unmatched files in other:\n %s\n"
+ % "\n ".join(u2))
+ return u1, u2
+
def mergecopies(repo, c1, c2, ca):
"""
Find moves and copies between context c1 and c2 that are relevant
@@ -261,15 +281,9 @@
repo.ui.debug(" searching for copies back to rev %d\n" % limit)
- u1 = _nonoverlap(m1, m2, ma)
- u2 = _nonoverlap(m2, m1, ma)
-
- if u1:
- repo.ui.debug(" unmatched files in local:\n %s\n"
- % "\n ".join(u1))
- if u2:
- repo.ui.debug(" unmatched files in other:\n %s\n"
- % "\n ".join(u2))
+ addedinm1 = m1.filesnotin(ma)
+ addedinm2 = m2.filesnotin(ma)
+ u1, u2 = _computenonoverlap(repo, m1, m2, addedinm1, addedinm2)
for f in u1:
checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
@@ -291,7 +305,7 @@
else:
diverge2.update(fl) # reverse map for below
- bothnew = sorted([d for d in m1 if d in m2 and d not in ma])
+ bothnew = sorted(addedinm1 & addedinm2)
if bothnew:
repo.ui.debug(" unmatched files new in both:\n %s\n"
% "\n ".join(bothnew))
--- a/mercurial/default.d/mergetools.rc Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/default.d/mergetools.rc Fri Mar 13 17:55:04 2015 -0500
@@ -102,6 +102,13 @@
bcompare.priority=-1
bcompare.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child
+; OS X version of Beyond Compare
+bcomposx.executable = /Applications/Beyond Compare.app/Contents/MacOS/bcomp
+bcomposx.args=$local $other $base -mergeoutput=$output -ro -lefttitle=parent1 -centertitle=base -righttitle=parent2 -outputtitle=merged -automerge -reviewconflicts -solo
+bcomposx.gui=True
+bcomposx.priority=-1
+bcomposx.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child
+
winmerge.args=/e /x /wl /ub /dl other /dr local $other $local $output
winmerge.regkey=Software\Thingamahoochie\WinMerge
winmerge.regkeyalt=Software\Wow6432Node\Thingamahoochie\WinMerge\
--- a/mercurial/dirstate.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/dirstate.py Fri Mar 13 17:55:04 2015 -0500
@@ -887,9 +887,9 @@
elif time != mtime and time != mtime & _rangemask:
ladd(fn)
elif mtime == lastnormaltime:
- # fn may have been changed in the same timeslot without
- # changing its size. This can happen if we quickly do
- # multiple commits in a single transaction.
+ # fn may have just been marked as normal and it may have
+ # changed in the same second without changing its size.
+ # This can happen if we quickly do multiple commits.
# Force lookup, so we don't miss such a racy file change.
ladd(fn)
elif listclean:
--- a/mercurial/dispatch.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/dispatch.py Fri Mar 13 17:55:04 2015 -0500
@@ -7,6 +7,7 @@
from i18n import _
import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
+import difflib
import util, commands, hg, fancyopts, extensions, hook, error
import cmdutil, encoding
import ui as uimod
@@ -27,6 +28,31 @@
"run the command in sys.argv"
sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
+def _getsimilar(symbols, value):
+ sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
+ # The cutoff for similarity here is pretty arbitrary. It should
+ # probably be investigated and tweaked.
+ return [s for s in symbols if sim(s) > 0.6]
+
+def _formatparse(write, inst):
+ similar = []
+ if isinstance(inst, error.UnknownIdentifier):
+ # make sure to check fileset first, as revset can invoke fileset
+ similar = _getsimilar(inst.symbols, inst.function)
+ if len(inst.args) > 1:
+ write(_("hg: parse error at %s: %s\n") %
+ (inst.args[1], inst.args[0]))
+ if (inst.args[0][0] == ' '):
+ write(_("unexpected leading whitespace\n"))
+ else:
+ write(_("hg: parse error: %s\n") % inst.args[0])
+ if similar:
+ if len(similar) == 1:
+ write(_("(did you mean %r?)\n") % similar[0])
+ else:
+ ss = ", ".join(sorted(similar))
+ write(_("(did you mean one of %s?)\n") % ss)
+
def dispatch(req):
"run the command specified in req.args"
if req.ferr:
@@ -55,13 +81,7 @@
ferr.write(_("(%s)\n") % inst.hint)
return -1
except error.ParseError, inst:
- if len(inst.args) > 1:
- ferr.write(_("hg: parse error at %s: %s\n") %
- (inst.args[1], inst.args[0]))
- if (inst.args[0][0] == ' '):
- ferr.write(_("unexpected leading whitespace\n"))
- else:
- ferr.write(_("hg: parse error: %s\n") % inst.args[0])
+ _formatparse(ferr.write, inst)
return -1
msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
@@ -154,13 +174,7 @@
ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
(inst.args[0], " ".join(inst.args[1])))
except error.ParseError, inst:
- if len(inst.args) > 1:
- ui.warn(_("hg: parse error at %s: %s\n") %
- (inst.args[1], inst.args[0]))
- if (inst.args[0][0] == ' '):
- ui.warn(_("unexpected leading whitespace\n"))
- else:
- ui.warn(_("hg: parse error: %s\n") % inst.args[0])
+ _formatparse(ui.warn, inst)
return -1
except error.LockHeld, inst:
if inst.errno == errno.ETIMEDOUT:
@@ -206,7 +220,15 @@
# (but don't check for extensions themselves)
commands.help_(ui, inst.args[0], unknowncmd=True)
except error.UnknownCommand:
- commands.help_(ui, 'shortlist')
+ suggested = False
+ if len(inst.args) == 2:
+ sim = _getsimilar(inst.args[1], inst.args[0])
+ if sim:
+ ui.warn(_('(did you mean one of %s?)\n') %
+ ', '.join(sorted(sim)))
+ suggested = True
+ if not suggested:
+ commands.help_(ui, 'shortlist')
except error.InterventionRequired, inst:
ui.warn("%s\n" % inst)
return 1
@@ -804,7 +826,7 @@
if cmdoptions.get('insecure', False):
for ui_ in uis:
- ui_.setconfig('web', 'cacerts', '', '--insecure')
+ ui_.setconfig('web', 'cacerts', '!', '--insecure')
if options['version']:
return commands.version_(ui)
--- a/mercurial/error.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/error.py Fri Mar 13 17:55:04 2015 -0500
@@ -22,6 +22,10 @@
class LookupError(RevlogError, KeyError):
def __init__(self, name, index, message):
self.name = name
+ self.index = index
+ # this can't be called 'message' because at least some installs of
+ # Python 2.6+ complain about the 'message' property being deprecated
+ self.lookupmessage = message
if isinstance(name, str) and len(name) == 20:
from node import short
name = short(name)
@@ -61,7 +65,16 @@
"""Exception raised when a remote repo reports failure"""
class ParseError(Exception):
- """Exception raised when parsing config files (msg[, pos])"""
+ """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
+
+class UnknownIdentifier(ParseError):
+ """Exception raised when a {rev,file}set references an unknown identifier"""
+
+ def __init__(self, function, symbols):
+ from i18n import _
+ ParseError.__init__(self, _("unknown identifier: %s") % function)
+ self.function = function
+ self.symbols = symbols
class RepoError(Exception):
def __init__(self, *args, **kw):
@@ -134,8 +147,20 @@
pass
class CensoredNodeError(RevlogError):
- """error raised when content verification fails on a censored node"""
+ """error raised when content verification fails on a censored node
- def __init__(self, filename, node):
+ Also contains the tombstone data substituted for the uncensored data.
+ """
+
+ def __init__(self, filename, node, tombstone):
from node import short
RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
+ self.tombstone = tombstone
+
+class CensoredBaseError(RevlogError):
+ """error raised when a delta is rejected because its base is censored
+
+ A delta based on a censored revision must be formed as single patch
+ operation which replaces the entire base with new content. This ensures
+ the delta may be applied by clones which have not censored the base.
+ """
--- a/mercurial/extensions.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/extensions.py Fri Mar 13 17:55:04 2015 -0500
@@ -10,6 +10,7 @@
from i18n import _, gettext
_extensions = {}
+_aftercallbacks = {}
_order = []
_ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
@@ -87,6 +88,8 @@
mod = importh(name)
_extensions[shortname] = mod
_order.append(shortname)
+ for fn in _aftercallbacks.get(shortname, []):
+ fn(loaded=True)
return mod
def loadall(ui):
@@ -123,7 +126,33 @@
raise
extsetup() # old extsetup with no ui argument
-def wrapcommand(table, command, wrapper):
+ # Call aftercallbacks that were never met.
+ for shortname in _aftercallbacks:
+ if shortname in _extensions:
+ continue
+
+ for fn in _aftercallbacks[shortname]:
+ fn(loaded=False)
+
+def afterloaded(extension, callback):
+ '''Run the specified function after a named extension is loaded.
+
+ If the named extension is already loaded, the callback will be called
+ immediately.
+
+ If the named extension never loads, the callback will be called after
+ all extensions have been loaded.
+
+ The callback receives the named argument ``loaded``, which is a boolean
+ indicating whether the dependent extension actually loaded.
+ '''
+
+ if extension in _extensions:
+ callback(loaded=True)
+ else:
+ _aftercallbacks.setdefault(extension, []).append(callback)
+
+def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
'''Wrap the command named `command' in table
Replace command in the command table with wrapper. The wrapped command will
@@ -135,6 +164,22 @@
where orig is the original (wrapped) function, and *args, **kwargs
are the arguments passed to it.
+
+ Optionally append to the command synopsis and docstring, used for help.
+ For example, if your extension wraps the ``bookmarks`` command to add the
+ flags ``--remote`` and ``--all`` you might call this function like so:
+
+ synopsis = ' [-a] [--remote]'
+ docstring = """
+
+ The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
+ flags to the bookmarks command. Either flag will show the remote bookmarks
+ known to the repository; ``--remote`` will also supress the output of the
+ local bookmarks.
+ """
+
+ extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
+ synopsis, docstring)
'''
assert callable(wrapper)
aliases, entry = cmdutil.findcmd(command, table)
@@ -148,11 +193,17 @@
return util.checksignature(wrapper)(
util.checksignature(origfn), *args, **kwargs)
- wrap.__doc__ = getattr(origfn, '__doc__')
wrap.__module__ = getattr(origfn, '__module__')
+ doc = getattr(origfn, '__doc__')
+ if docstring is not None:
+ doc += docstring
+ wrap.__doc__ = doc
+
newentry = list(entry)
newentry[0] = wrap
+ if synopsis is not None:
+ newentry[2] += synopsis
table[key] = tuple(newentry)
return entry
--- a/mercurial/filelog.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/filelog.py Fri Mar 13 17:55:04 2015 -0500
@@ -5,8 +5,8 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import error, revlog
-import re
+import error, mdiff, revlog
+import re, struct
_mdre = re.compile('\1\n')
def parsemeta(text):
@@ -29,7 +29,7 @@
def _censoredtext(text):
m, offs = parsemeta(text)
- return m and "censored" in m and not text[offs:]
+ return m and "censored" in m
class filelog(revlog.revlog):
def __init__(self, opener, path):
@@ -64,7 +64,7 @@
node = self.node(rev)
if self.renamed(node):
return len(self.read(node))
- if self._iscensored(rev):
+ if self.iscensored(rev):
return 0
# XXX if self.read(node).startswith("\1\n"), this returns (size+4)
@@ -85,7 +85,7 @@
return False
# censored files compare against the empty file
- if self._iscensored(self.rev(node)):
+ if self.iscensored(self.rev(node)):
return text != ''
# renaming a file produces a different hash, even if the data
@@ -101,12 +101,29 @@
super(filelog, self).checkhash(text, p1, p2, node, rev=rev)
except error.RevlogError:
if _censoredtext(text):
- raise error.CensoredNodeError(self.indexfile, node)
+ raise error.CensoredNodeError(self.indexfile, node, text)
raise
- def _file(self, f):
- return filelog(self.opener, f)
-
- def _iscensored(self, rev):
+ def iscensored(self, rev):
"""Check if a file revision is censored."""
return self.flags(rev) & revlog.REVIDX_ISCENSORED
+
+ def _peek_iscensored(self, baserev, delta, flush):
+ """Quickly check if a delta produces a censored revision."""
+ # Fragile heuristic: unless new file meta keys are added alphabetically
+ # preceding "censored", all censored revisions are prefixed by
+ # "\1\ncensored:". A delta producing such a censored revision must be a
+ # full-replacement delta, so we inspect the first and only patch in the
+ # delta for this prefix.
+ hlen = struct.calcsize(">lll")
+ if len(delta) <= hlen:
+ return False
+
+ oldlen = self.rawsize(baserev)
+ newlen = len(delta) - hlen
+ if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
+ return False
+
+ add = "\1\ncensored:"
+ addlen = len(add)
+ return newlen >= addlen and delta[hlen:hlen + addlen] == add
--- a/mercurial/filemerge.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/filemerge.py Fri Mar 13 17:55:04 2015 -0500
@@ -21,6 +21,8 @@
return ui.configlist("merge-tools", tool + "." + part, default)
internals = {}
+# Merge tools to document.
+internalsdoc = {}
def internaltool(name, trymerge, onfailure=None):
'''return a decorator for populating internal merge tool table'''
@@ -29,6 +31,7 @@
func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
internals[fullname] = func
internals['internal:' + name] = func
+ internalsdoc[fullname] = func
func.trymerge = trymerge
func.onfailure = onfailure
return func
--- a/mercurial/fileset.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/fileset.py Fri Mar 13 17:55:04 2015 -0500
@@ -186,7 +186,7 @@
def func(mctx, a, b):
if a[0] == 'symbol' and a[1] in symbols:
return symbols[a[1]](mctx, b)
- raise error.ParseError(_("not a function: %s") % a[1])
+ raise error.UnknownIdentifier(a[1], symbols.keys())
def getlist(x):
if not x:
--- a/mercurial/graphmod.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/graphmod.py Fri Mar 13 17:55:04 2015 -0500
@@ -122,7 +122,7 @@
heappush(pendingheap, -currentrev)
pendingset.add(currentrev)
# iterates on pending rev until after the current rev have been
- # processeed.
+ # processed.
rev = None
while rev != currentrev:
rev = -heappop(pendingheap)
--- a/mercurial/help.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help.py Fri Mar 13 17:55:04 2015 -0500
@@ -6,11 +6,12 @@
# GNU General Public License version 2 or any later version.
from i18n import gettext, _
-import itertools, os
+import itertools, os, textwrap
import error
import extensions, revset, fileset, templatekw, templatefilters, filemerge
import encoding, util, minirst
import cmdutil
+import hgweb.webcommands as webcommands
def listexts(header, exts, indent=1, showdeprecated=False):
'''return a text listing of the given extensions'''
@@ -171,7 +172,7 @@
def addtopichook(topic, rewriter):
helphooks.setdefault(topic, []).append(rewriter)
-def makeitemsdoc(topic, doc, marker, items):
+def makeitemsdoc(topic, doc, marker, items, dedent=False):
"""Extract docstring from the items key to function mapping, build a
.single documentation block and use it to overwrite the marker in doc
"""
@@ -181,27 +182,35 @@
if not text:
continue
text = gettext(text)
+ if dedent:
+ text = textwrap.dedent(text)
lines = text.splitlines()
doclines = [(lines[0])]
for l in lines[1:]:
# Stop once we find some Python doctest
if l.strip().startswith('>>>'):
break
- doclines.append(' ' + l.strip())
+ if dedent:
+ doclines.append(l.rstrip())
+ else:
+ doclines.append(' ' + l.strip())
entries.append('\n'.join(doclines))
entries = '\n\n'.join(entries)
return doc.replace(marker, entries)
-def addtopicsymbols(topic, marker, symbols):
+def addtopicsymbols(topic, marker, symbols, dedent=False):
def add(topic, doc):
- return makeitemsdoc(topic, doc, marker, symbols)
+ return makeitemsdoc(topic, doc, marker, symbols, dedent=dedent)
addtopichook(topic, add)
addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
-addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internals)
+addtopicsymbols('merge-tools', '.. internaltoolsmarker',
+ filemerge.internalsdoc)
addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
+addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
+ dedent=True)
def help_(ui, name, unknowncmd=False, full=True, **opts):
'''
--- a/mercurial/help/config.txt Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/config.txt Fri Mar 13 17:55:04 2015 -0500
@@ -808,6 +808,29 @@
changeset to tag is in ``$HG_NODE``. Name of tag is in ``$HG_TAG``. Tag is
local if ``$HG_LOCAL=1``, in repository if ``$HG_LOCAL=0``.
+``pretxnopen``
+ Run before any new repository transaction is open. The reason for the
+ transaction will be in ``$HG_TXNNAME``. A non-zero status will
+ prevent the transaction from being opened.
+
+``pretxnclose``
+ Run right before the transaction is actually finalized. Any
+ repository change will be visible to the hook program. This lets you
+ validate the transaction content or change it. Exit status 0 allows
+ the commit to proceed. Non-zero status will cause the transaction to
+ be rolled back. The reason for the transaction opening will be in
+ ``$HG_TXNNAME``. The rest of the available data will vary according
+ the transaction type. New changesets will add
+ ``$HG_NODE`` (id of the first added changeset), ``$HG_URL`` and
+ ``$HG_SOURCE`` variables, bookmarks and phases changes will set
+ ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``, etc.
+
+``txnclose``
+ Run after any repository transaction has been commited. At this
+ point, the transaction can no longer be rolled back. The hook will run
+ after the lock is released. see ``pretxnclose`` docs for details about
+ available variables.
+
``pretxnchangegroup``
Run after a changegroup has been added via push, pull or unbundle,
but before the transaction has been committed. Changegroup is
--- a/mercurial/help/hg.1.txt Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hg.1.txt Fri Mar 13 17:55:04 2015 -0500
@@ -112,7 +112,7 @@
Copying
"""""""
-Copyright (C) 2005-2014 Matt Mackall.
+Copyright (C) 2005-2015 Matt Mackall.
Free use of this software is granted under the terms of the GNU General
Public License version 2 or any later version.
--- a/mercurial/help/hgignore.5.txt Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hgignore.5.txt Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
Copying
=======
This manual page is copyright 2006 Vadim Gelfer.
-Mercurial is copyright 2005-2014 Matt Mackall.
+Mercurial is copyright 2005-2015 Matt Mackall.
Free use of this software is granted under the terms of the GNU General
Public License version 2 or any later version.
--- a/mercurial/help/hgrc.5.txt Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hgrc.5.txt Fri Mar 13 17:55:04 2015 -0500
@@ -34,7 +34,7 @@
Copying
=======
This manual page is copyright 2005 Bryan O'Sullivan.
-Mercurial is copyright 2005-2014 Matt Mackall.
+Mercurial is copyright 2005-2015 Matt Mackall.
Free use of this software is granted under the terms of the GNU General
Public License version 2 or any later version.
--- a/mercurial/help/hgweb.txt Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hgweb.txt Fri Mar 13 17:55:04 2015 -0500
@@ -48,3 +48,39 @@
The ``collections`` section is deprecated and has been superseded by
``paths``.
+
+URLs and Common Arguments
+=========================
+
+URLs under each repository have the form ``/{command}[/{arguments}]``
+where ``{command}`` represents the name of a command or handler and
+``{arguments}`` represents any number of additional URL parameters
+to that command.
+
+The web server has a default style associated with it. Styles map to
+a collection of named templates. Each template is used to render a
+specific piece of data, such as a changeset or diff.
+
+The style for the current request can be overwritten two ways. First,
+if ``{command}`` contains a hyphen (``-``), the text before the hyphen
+defines the style. For example, ``/atom-log`` will render the ``log``
+command handler with the ``atom`` style. The second way to set the
+style is with the ``style`` query string argument. For example,
+``/log?style=atom``. The hyphenated URL parameter is preferred.
+
+Not all templates are available for all styles. Attempting to use
+a style that doesn't have all templates defined may result in an error
+rendering the page.
+
+Many commands take a ``{revision}`` URL parameter. This defines the
+changeset to operate on. This is commonly specified as the short,
+12 digit hexidecimal abbreviation for the full 40 character unique
+revision identifier. However, any value described by
+:hg:`help revisions` typically works.
+
+Commands and URLs
+=================
+
+The following web commands and their URLs are available:
+
+ .. webcommandsmarker
--- a/mercurial/help/subrepos.txt Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/subrepos.txt Fri Mar 13 17:55:04 2015 -0500
@@ -78,7 +78,7 @@
:add: add does not recurse in subrepos unless -S/--subrepos is
specified. However, if you specify the full path of a file in a
subrepo, it will be added even without -S/--subrepos specified.
- Git and Subversion subrepositories are currently silently
+ Subversion subrepositories are currently silently
ignored.
:addremove: addremove does not recurse into subrepos unless
@@ -91,7 +91,7 @@
-S/--subrepos is specified.
:cat: cat currently only handles exact file matches in subrepos.
- Git and Subversion subrepositories are currently ignored.
+ Subversion subrepositories are currently ignored.
:commit: commit creates a consistent snapshot of the state of the
entire project and its subrepositories. If any subrepositories
--- a/mercurial/hg.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/hg.py Fri Mar 13 17:55:04 2015 -0500
@@ -672,7 +672,9 @@
for key, val in src.configitems(sect):
dst.setconfig(sect, key, val, 'copied')
v = src.config('web', 'cacerts')
- if v:
+ if v == '!':
+ dst.setconfig('web', 'cacerts', v, 'copied')
+ elif v:
dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
return dst
--- a/mercurial/hgweb/webcommands.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/hgweb/webcommands.py Fri Mar 13 17:55:04 2015 -0500
@@ -13,27 +13,58 @@
from common import paritygen, staticfile, get_contact, ErrorResponse
from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
from mercurial import graphmod, patch
-from mercurial import help as helpmod
from mercurial import scmutil
from mercurial.i18n import _
from mercurial.error import ParseError, RepoLookupError, Abort
from mercurial import revset
-# __all__ is populated with the allowed commands. Be sure to add to it if
-# you're adding a new command, or the new command won't work.
+__all__ = []
+commands = {}
+
+class webcommand(object):
+ """Decorator used to register a web command handler.
+
+ The decorator takes as its positional arguments the name/path the
+ command should be accessible under.
+
+ Usage:
+
+ @webcommand('mycommand')
+ def mycommand(web, req, tmpl):
+ pass
+ """
+
+ def __init__(self, name):
+ self.name = name
-__all__ = [
- 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
- 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
- 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
-]
+ def __call__(self, func):
+ __all__.append(self.name)
+ commands[self.name] = func
+ return func
+
+@webcommand('log')
+def log(web, req, tmpl):
+ """
+ /log[/{revision}[/{path}]]
+ --------------------------
-def log(web, req, tmpl):
+ Show repository or file history.
+
+ For URLs of the form ``/log/{revision}``, a list of changesets starting at
+ the specified changeset identifier is shown. If ``{revision}`` is not
+ defined, the default is ``tip``. This form is equivalent to the
+ ``changelog`` handler.
+
+ For URLs of the form ``/log/{revision}/{file}``, the history for a specific
+ file will be shown. This form is equivalent to the ``filelog`` handler.
+ """
+
if 'file' in req.form and req.form['file'][0]:
return filelog(web, req, tmpl)
else:
return changelog(web, req, tmpl)
+@webcommand('rawfile')
def rawfile(web, req, tmpl):
guessmime = web.configbool('web', 'guessmime', False)
@@ -98,7 +129,26 @@
rename=webutil.renamelink(fctx),
permissions=fctx.manifest().flags(f))
+@webcommand('file')
def file(web, req, tmpl):
+ """
+ /file/{revision}[/{path}]
+ -------------------------
+
+ Show information about a directory or file in the repository.
+
+ Info about the ``path`` given as a URL parameter will be rendered.
+
+ If ``path`` is a directory, information about the entries in that
+ directory will be rendered. This form is equivalent to the ``manifest``
+ handler.
+
+ If ``path`` is a file, information about that file will be shown via
+ the ``filerevision`` template.
+
+ If ``path`` is not defined, information about the root directory will
+ be rendered.
+ """
path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
if not path:
return manifest(web, req, tmpl)
@@ -187,7 +237,7 @@
mfunc = revset.match(web.repo.ui, revdef)
try:
- revs = mfunc(web.repo, revset.baseset(web.repo))
+ revs = mfunc(web.repo)
return MODE_REVSET, revs
# ParseError: wrongly placed tokens, wrongs arguments, etc
# RepoLookupError: no such revision, e.g. in 'revision:'
@@ -267,7 +317,31 @@
modedesc=searchfunc[1],
showforcekw=showforcekw, showunforcekw=showunforcekw)
+@webcommand('changelog')
def changelog(web, req, tmpl, shortlog=False):
+ """
+ /changelog[/{revision}]
+ -----------------------
+
+ Show information about multiple changesets.
+
+ If the optional ``revision`` URL argument is absent, information about
+ all changesets starting at ``tip`` will be rendered. If the ``revision``
+ argument is present, changesets will be shown starting from the specified
+ revision.
+
+ If ``revision`` is absent, the ``rev`` query string argument may be
+ defined. This will perform a search for changesets.
+
+ The argument for ``rev`` can be a single revision, a revision set,
+ or a literal keyword to search for in changeset data (equivalent to
+ :hg:`log -k`.
+
+ The ``revcount`` query string argument defines the maximum numbers of
+ changesets to render.
+
+ For non-searches, the ``changelog`` template will be rendered.
+ """
query = ''
if 'node' in req.form:
@@ -326,63 +400,41 @@
archives=web.archivelist("tip"), revcount=revcount,
morevars=morevars, lessvars=lessvars, query=query)
+@webcommand('shortlog')
def shortlog(web, req, tmpl):
+ """
+ /shortlog
+ ---------
+
+ Show basic information about a set of changesets.
+
+ This accepts the same parameters as the ``changelog`` handler. The only
+ difference is the ``shortlog`` template will be rendered instead of the
+ ``changelog`` template.
+ """
return changelog(web, req, tmpl, shortlog=True)
+@webcommand('changeset')
def changeset(web, req, tmpl):
- ctx = webutil.changectx(web.repo, req)
- basectx = webutil.basechangectx(web.repo, req)
- if basectx is None:
- basectx = ctx.p1()
- showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
- showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
- ctx.node())
- showbranch = webutil.nodebranchnodefault(ctx)
+ """
+ /changeset[/{revision}]
+ -----------------------
- files = []
- parity = paritygen(web.stripecount)
- for blockno, f in enumerate(ctx.files()):
- template = f in ctx and 'filenodelink' or 'filenolink'
- files.append(tmpl(template,
- node=ctx.hex(), file=f, blockno=blockno + 1,
- parity=parity.next()))
-
- style = web.config('web', 'style', 'paper')
- if 'style' in req.form:
- style = req.form['style'][0]
-
- parity = paritygen(web.stripecount)
- diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
+ Show information about a single changeset.
- parity = paritygen(web.stripecount)
- diffstatgen = webutil.diffstatgen(ctx, basectx)
- diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
+ A URL path argument is the changeset identifier to show. See ``hg help
+ revisions`` for possible values. If not defined, the ``tip`` changeset
+ will be shown.
- return tmpl('changeset',
- diff=diffs,
- rev=ctx.rev(),
- node=ctx.hex(),
- parent=tuple(webutil.parents(ctx)),
- child=webutil.children(ctx),
- basenode=basectx.hex(),
- changesettag=showtags,
- changesetbookmark=showbookmarks,
- changesetbranch=showbranch,
- author=ctx.user(),
- desc=ctx.description(),
- extra=ctx.extra(),
- date=ctx.date(),
- files=files,
- diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
- diffstat=diffstat,
- archives=web.archivelist(ctx.hex()),
- tags=webutil.nodetagsdict(web.repo, ctx.node()),
- bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
- branch=webutil.nodebranchnodefault(ctx),
- inbranch=webutil.nodeinbranch(web.repo, ctx),
- branches=webutil.nodebranchdict(web.repo, ctx))
+ The ``changeset`` template is rendered. Contents of the ``changesettag``,
+ ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
+ templates related to diffs may all be used to produce the output.
+ """
+ ctx = webutil.changectx(web.repo, req)
-rev = changeset
+ return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
+
+rev = webcommand('rev')(changeset)
def decodepath(path):
"""Hook for mapping a path in the repository to a path in the
@@ -392,7 +444,23 @@
the virtual file system presented by the manifest command below."""
return path
+@webcommand('manifest')
def manifest(web, req, tmpl):
+ """
+ /manifest[/{revision}[/{path}]]
+ -------------------------------
+
+ Show information about a directory.
+
+ If the URL path arguments are defined, information about the root
+ directory for the ``tip`` changeset will be shown.
+
+ Because this handler can only show information for directories, it
+ is recommended to use the ``file`` handler instead, as it can handle both
+ directories and files.
+
+ The ``manifest`` template will be rendered for this handler.
+ """
ctx = webutil.changectx(web.repo, req)
path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
mf = ctx.manifest()
@@ -474,7 +542,18 @@
inbranch=webutil.nodeinbranch(web.repo, ctx),
branches=webutil.nodebranchdict(web.repo, ctx))
+@webcommand('tags')
def tags(web, req, tmpl):
+ """
+ /tags
+ -----
+
+ Show information about tags.
+
+ No arguments are accepted.
+
+ The ``tags`` template is rendered.
+ """
i = list(reversed(web.repo.tagslist()))
parity = paritygen(web.stripecount)
@@ -496,7 +575,18 @@
entriesnotip=lambda **x: entries(True, False, **x),
latestentry=lambda **x: entries(True, True, **x))
+@webcommand('bookmarks')
def bookmarks(web, req, tmpl):
+ """
+ /bookmarks
+ ----------
+
+ Show information about bookmarks.
+
+ No arguments are accepted.
+
+ The ``bookmarks`` template is rendered.
+ """
i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
parity = paritygen(web.stripecount)
@@ -516,7 +606,20 @@
entries=lambda **x: entries(latestonly=False, **x),
latestentry=lambda **x: entries(latestonly=True, **x))
+@webcommand('branches')
def branches(web, req, tmpl):
+ """
+ /branches
+ ---------
+
+ Show information about branches.
+
+ All known branches are contained in the output, even closed branches.
+
+ No arguments are accepted.
+
+ The ``branches`` template is rendered.
+ """
tips = []
heads = web.repo.heads()
parity = paritygen(web.stripecount)
@@ -547,7 +650,19 @@
entries=lambda **x: entries(0, **x),
latestentry=lambda **x: entries(1, **x))
+@webcommand('summary')
def summary(web, req, tmpl):
+ """
+ /summary
+ --------
+
+ Show a summary of repository state.
+
+ Information about the latest changesets, bookmarks, tags, and branches
+ is captured by this handler.
+
+ The ``summary`` template is rendered.
+ """
i = reversed(web.repo.tagslist())
def tagentries(**map):
@@ -632,7 +747,19 @@
node=tip.hex(),
archives=web.archivelist("tip"))
+@webcommand('filediff')
def filediff(web, req, tmpl):
+ """
+ /diff/{revision}/{path}
+ -----------------------
+
+ Show how a file changed in a particular commit.
+
+ The ``filediff`` template is rendered.
+
+ This hander is registered under both the ``/diff`` and ``/filediff``
+ paths. ``/diff`` is used in modern code.
+ """
fctx, ctx = None, None
try:
fctx = webutil.filectx(web.repo, req)
@@ -672,9 +799,25 @@
child=webutil.children(ctx),
diff=diffs)
-diff = filediff
+diff = webcommand('diff')(filediff)
+
+@webcommand('comparison')
+def comparison(web, req, tmpl):
+ """
+ /comparison/{revision}/{path}
+ -----------------------------
-def comparison(web, req, tmpl):
+ Show a comparison between the old and new versions of a file from changes
+ made on a particular revision.
+
+ This is similar to the ``diff`` handler. However, this form features
+ a split or side-by-side diff rather than a unified diff.
+
+ The ``context`` query string argument can be used to control the lines of
+ context in the diff.
+
+ The ``filecomparison`` template is rendered.
+ """
ctx = webutil.changectx(web.repo, req)
if 'file' not in req.form:
raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
@@ -732,7 +875,16 @@
rightnode=hex(rightnode),
comparison=comparison)
+@webcommand('annotate')
def annotate(web, req, tmpl):
+ """
+ /annotate/{revision}/{path}
+ ---------------------------
+
+ Show changeset information for each line in a file.
+
+ The ``fileannotate`` template is rendered.
+ """
fctx = webutil.filectx(web.repo, req)
f = fctx.path()
parity = paritygen(web.stripecount)
@@ -784,7 +936,19 @@
child=webutil.children(fctx),
permissions=fctx.manifest().flags(f))
+@webcommand('filelog')
def filelog(web, req, tmpl):
+ """
+ /filelog/{revision}/{path}
+ --------------------------
+
+ Show information about the history of a file in the repository.
+
+ The ``revcount`` query string argument can be defined to control the
+ maximum number of entries to show.
+
+ The ``filelog`` template will be rendered.
+ """
try:
fctx = webutil.filectx(web.repo, req)
@@ -862,7 +1026,27 @@
latestentry=latestentry,
revcount=revcount, morevars=morevars, lessvars=lessvars)
+@webcommand('archive')
def archive(web, req, tmpl):
+ """
+ /archive/{revision}.{format}[/{path}]
+ -------------------------------------
+
+ Obtain an archive of repository content.
+
+ The content and type of the archive is defined by a URL path parameter.
+ ``format`` is the file extension of the archive type to be generated. e.g.
+ ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
+ server configuration.
+
+ The optional ``path`` URL parameter controls content to include in the
+ archive. If omitted, every file in the specified revision is present in the
+ archive. If included, only the specified file or contents of the specified
+ directory will be included in the archive.
+
+ No template is used for this handler. Raw, binary content is generated.
+ """
+
type_ = req.form.get('type', [None])[0]
allowed = web.configlist("web", "allow_archive")
key = req.form['node'][0]
@@ -911,6 +1095,7 @@
return []
+@webcommand('static')
def static(web, req, tmpl):
fname = req.form['file'][0]
# a repo owner may set web.static in .hg/hgrc to get any file
@@ -924,7 +1109,24 @@
staticfile(static, fname, req)
return []
+@webcommand('graph')
def graph(web, req, tmpl):
+ """
+ /graph[/{revision}]
+ -------------------
+
+ Show information about the graphical topology of the repository.
+
+ Information rendered by this handler can be used to create visual
+ representations of repository topology.
+
+ The ``revision`` URL parameter controls the starting changeset.
+
+ The ``revcount`` query string argument can define the number of changesets
+ to show information for.
+
+ This handler will render the ``graph`` template.
+ """
ctx = webutil.changectx(web.repo, req)
rev = ctx.rev()
@@ -1047,8 +1249,23 @@
doc = _('(no help text available)')
return doc
+@webcommand('help')
def help(web, req, tmpl):
+ """
+ /help[/{topic}]
+ ---------------
+
+ Render help documentation.
+
+ This web command is roughly equivalent to :hg:`help`. If a ``topic``
+ is defined, that help topic will be rendered. If not, an index of
+ available help topics will be rendered.
+
+ The ``help`` template will be rendered when requesting help for a topic.
+ ``helptopics`` will be rendered for the index of help topics.
+ """
from mercurial import commands # avoid cycle
+ from mercurial import help as helpmod # avoid cycle
topicname = req.form.get('node', [None])[0]
if not topicname:
--- a/mercurial/hgweb/webutil.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/hgweb/webutil.py Fri Mar 13 17:55:04 2015 -0500
@@ -10,7 +10,7 @@
from mercurial import match, patch, error, ui, util, pathutil, context
from mercurial.i18n import _
from mercurial.node import hex, nullid
-from common import ErrorResponse
+from common import ErrorResponse, paritygen
from common import HTTP_NOT_FOUND
import difflib
@@ -138,9 +138,10 @@
yield d
def parents(ctx, hide=None):
- if (isinstance(ctx, context.basefilectx) and
- ctx.changectx().rev() != ctx.linkrev()):
- return _siblings([ctx._repo[ctx.linkrev()]], hide)
+ if isinstance(ctx, context.basefilectx):
+ introrev = ctx.introrev()
+ if ctx.changectx().rev() != introrev:
+ return _siblings([ctx._repo[introrev]], hide)
return _siblings(ctx.parents(), hide)
def children(ctx, hide=None):
@@ -278,6 +279,61 @@
"branches": nodebranchdict(repo, ctx)
}
+def changesetentry(web, req, tmpl, ctx):
+ '''Obtain a dictionary to be used to render the "changeset" template.'''
+
+ showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
+ showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
+ ctx.node())
+ showbranch = nodebranchnodefault(ctx)
+
+ files = []
+ parity = paritygen(web.stripecount)
+ for blockno, f in enumerate(ctx.files()):
+ template = f in ctx and 'filenodelink' or 'filenolink'
+ files.append(tmpl(template,
+ node=ctx.hex(), file=f, blockno=blockno + 1,
+ parity=parity.next()))
+
+ basectx = basechangectx(web.repo, req)
+ if basectx is None:
+ basectx = ctx.p1()
+
+ style = web.config('web', 'style', 'paper')
+ if 'style' in req.form:
+ style = req.form['style'][0]
+
+ parity = paritygen(web.stripecount)
+ diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
+
+ parity = paritygen(web.stripecount)
+ diffstatsgen = diffstatgen(ctx, basectx)
+ diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
+
+ return dict(
+ diff=diff,
+ rev=ctx.rev(),
+ node=ctx.hex(),
+ parent=tuple(parents(ctx)),
+ child=children(ctx),
+ basenode=basectx.hex(),
+ changesettag=showtags,
+ changesetbookmark=showbookmarks,
+ changesetbranch=showbranch,
+ author=ctx.user(),
+ desc=ctx.description(),
+ extra=ctx.extra(),
+ date=ctx.date(),
+ files=files,
+ diffsummary=lambda **x: diffsummary(diffstatsgen),
+ diffstat=diffstats,
+ archives=web.archivelist(ctx.hex()),
+ tags=nodetagsdict(web.repo, ctx.node()),
+ bookmarks=nodebookmarksdict(web.repo, ctx.node()),
+ branch=nodebranchnodefault(ctx),
+ inbranch=nodeinbranch(web.repo, ctx),
+ branches=nodebranchdict(web.repo, ctx))
+
def listfilediffs(tmpl, files, node, max):
for f in files[:max]:
yield tmpl('filedifflink', node=hex(node), file=f)
--- a/mercurial/localrepo.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/localrepo.py Fri Mar 13 17:55:04 2015 -0500
@@ -323,6 +323,9 @@
maxchainlen = self.ui.configint('format', 'maxchainlen')
if maxchainlen is not None:
self.svfs.options['maxchainlen'] = maxchainlen
+ manifestcachesize = self.ui.configint('format', 'manifestcachesize')
+ if manifestcachesize is not None:
+ self.svfs.options['manifestcachesize'] = manifestcachesize
def _writerequirements(self):
reqfile = self.vfs("requires", "w")
@@ -479,7 +482,7 @@
'''Return a list of revisions matching the given revset'''
expr = revset.formatspec(expr, *args)
m = revset.match(None, expr)
- return m(self, revset.spanset(self))
+ return m(self)
def set(self, expr, *args):
'''
@@ -906,19 +909,38 @@
_("abandoned transaction found"),
hint=_("run 'hg recover' to clean up transaction"))
+ self.hook('pretxnopen', throw=True, txnname=desc)
+
self._writejournal(desc)
renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
rp = report and report or self.ui.warn
vfsmap = {'plain': self.vfs} # root of .hg/
- tr = transaction.transaction(rp, self.svfs, vfsmap,
+ # we must avoid cyclic reference between repo and transaction.
+ reporef = weakref.ref(self)
+ def validate(tr):
+ """will run pre-closing hooks"""
+ pending = lambda: tr.writepending() and self.root or ""
+ reporef().hook('pretxnclose', throw=True, pending=pending,
+ xnname=desc)
+
+ tr = transaction.transaction(rp, self.sopener, vfsmap,
"journal",
"undo",
aftertrans(renames),
- self.store.createmode)
+ self.store.createmode,
+ validator=validate)
# note: writing the fncache only during finalize mean that the file is
# outdated when running hooks. As fncache is used for streaming clone,
# this is not expected to break anything that happen during the hooks.
tr.addfinalize('flush-fncache', self.store.write)
+ def txnclosehook(tr2):
+ """To be run if transaction is successful, will schedule a hook run
+ """
+ def hook():
+ reporef().hook('txnclose', throw=False, txnname=desc,
+ **tr2.hookargs)
+ reporef()._afterlock(hook)
+ tr.addfinalize('txnclose-hook', txnclosehook)
self._transref = weakref.ref(tr)
return tr
@@ -1208,7 +1230,7 @@
# Here, we used to search backwards through history to try to find
# where the file copy came from if the source of a copy was not in
- # the parent diretory. However, this doesn't actually make sense to
+ # the parent directory. However, this doesn't actually make sense to
# do (what does a copy from something not in your working copy even
# mean?) and it causes bugs (eg, issue4476). Instead, we will warn
# the user that copy information was dropped, so if they didn't
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/manifest.c Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,926 @@
+/*
+ * manifest.c - manifest type that does on-demand parsing.
+ *
+ * Copyright 2015, Google Inc.
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <Python.h>
+
+/* VC9 doesn't include bool and lacks stdbool.h based on my searching */
+#ifdef _MSC_VER
+#define true 1
+#define false 0
+typedef unsigned char bool;
+#else
+#include <stdbool.h>
+#endif
+
+#define DEFAULT_LINES 100000
+
+typedef struct {
+ char *start;
+ Py_ssize_t len; /* length of line including terminal newline */
+ char hash_suffix;
+ bool from_malloc;
+ bool deleted;
+} line;
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *pydata;
+ line *lines;
+ int numlines; /* number of line entries */
+ int livelines; /* number of non-deleted lines */
+ int maxlines; /* allocated number of lines */
+ bool dirty;
+} lazymanifest;
+
+#define MANIFEST_OOM -1
+#define MANIFEST_NOT_SORTED -2
+#define MANIFEST_MALFORMED -3
+
+/* defined in parsers.c */
+PyObject *unhexlify(const char *str, int len);
+
+/* get the length of the path for a line */
+static size_t pathlen(line *l) {
+ return strlen(l->start);
+}
+
+/* get the node value of a single line */
+static PyObject *nodeof(line *l) {
+ char *s = l->start;
+ ssize_t llen = pathlen(l);
+ PyObject *hash = unhexlify(s + llen + 1, 40);
+ if (!hash) {
+ return NULL;
+ }
+ if (l->hash_suffix != '\0') {
+ char newhash[21];
+ memcpy(newhash, PyString_AsString(hash), 20);
+ Py_DECREF(hash);
+ newhash[20] = l->hash_suffix;
+ hash = PyString_FromStringAndSize(newhash, 21);
+ }
+ return hash;
+}
+
+/* get the node hash and flags of a line as a tuple */
+static PyObject *hashflags(line *l)
+{
+ char *s = l->start;
+ size_t plen = pathlen(l);
+ PyObject *hash = nodeof(l);
+
+ /* 40 for hash, 1 for null byte, 1 for newline */
+ size_t hplen = plen + 42;
+ Py_ssize_t flen = l->len - hplen;
+ PyObject *flags;
+ PyObject *tup;
+
+ if (!hash)
+ return NULL;
+ flags = PyString_FromStringAndSize(s + hplen - 1, flen);
+ if (!flags) {
+ Py_DECREF(hash);
+ return NULL;
+ }
+ tup = PyTuple_Pack(2, hash, flags);
+ Py_DECREF(flags);
+ Py_DECREF(hash);
+ return tup;
+}
+
+/* if we're about to run out of space in the line index, add more */
+static bool realloc_if_full(lazymanifest *self)
+{
+ if (self->numlines == self->maxlines) {
+ self->maxlines *= 2;
+ self->lines = realloc(self->lines, self->maxlines * sizeof(line));
+ }
+ return self->lines;
+}
+
+/*
+ * Find the line boundaries in the manifest that 'data' points to and store
+ * information about each line in 'self'.
+ */
+static int find_lines(lazymanifest *self, char *data, Py_ssize_t len)
+{
+ char *prev = NULL;
+ while (len > 0) {
+ line *l;
+ char *next = memchr(data, '\n', len);
+ if (!next) {
+ return MANIFEST_MALFORMED;
+ }
+ next++; /* advance past newline */
+ if (!realloc_if_full(self)) {
+ return MANIFEST_OOM; /* no memory */
+ }
+ if (prev && strcmp(prev, data) > -1) {
+ /* This data isn't sorted, so we have to abort. */
+ return MANIFEST_NOT_SORTED;
+ }
+ l = self->lines + ((self->numlines)++);
+ l->start = data;
+ l->len = next - data;
+ l->hash_suffix = '\0';
+ l->from_malloc = false;
+ l->deleted = false;
+ len = len - l->len;
+ prev = data;
+ data = next;
+ }
+ self->livelines = self->numlines;
+ return 0;
+}
+
+static int lazymanifest_init(lazymanifest *self, PyObject *args)
+{
+ char *data;
+ Py_ssize_t len;
+ int err, ret;
+ PyObject *pydata;
+ if (!PyArg_ParseTuple(args, "S", &pydata)) {
+ return -1;
+ }
+ err = PyString_AsStringAndSize(pydata, &data, &len);
+
+ self->dirty = false;
+ if (err == -1)
+ return -1;
+ self->pydata = pydata;
+ Py_INCREF(self->pydata);
+ Py_BEGIN_ALLOW_THREADS
+ self->lines = malloc(DEFAULT_LINES * sizeof(line));
+ self->maxlines = DEFAULT_LINES;
+ self->numlines = 0;
+ if (!self->lines)
+ ret = MANIFEST_OOM;
+ else
+ ret = find_lines(self, data, len);
+ Py_END_ALLOW_THREADS
+ switch (ret) {
+ case 0:
+ break;
+ case MANIFEST_OOM:
+ PyErr_NoMemory();
+ break;
+ case MANIFEST_NOT_SORTED:
+ PyErr_Format(PyExc_ValueError,
+ "Manifest lines not in sorted order.");
+ break;
+ case MANIFEST_MALFORMED:
+ PyErr_Format(PyExc_ValueError,
+ "Manifest did not end in a newline.");
+ break;
+ default:
+ PyErr_Format(PyExc_ValueError,
+ "Unknown problem parsing manifest.");
+ }
+ return ret == 0 ? 0 : -1;
+}
+
+static void lazymanifest_dealloc(lazymanifest *self)
+{
+ /* free any extra lines we had to allocate */
+ int i;
+ for (i = 0; i < self->numlines; i++) {
+ if (self->lines[i].from_malloc) {
+ free(self->lines[i].start);
+ }
+ }
+ if (self->lines) {
+ free(self->lines);
+ self->lines = NULL;
+ }
+ if (self->pydata) {
+ Py_DECREF(self->pydata);
+ self->pydata = NULL;
+ }
+ PyObject_Del(self);
+}
+
+/* iteration support */
+
+typedef struct {
+ PyObject_HEAD lazymanifest *m;
+ Py_ssize_t pos;
+} lmIter;
+
+static void lmiter_dealloc(PyObject *o)
+{
+ lmIter *self = (lmIter *)o;
+ Py_DECREF(self->m);
+ PyObject_Del(self);
+}
+
+static line *lmiter_nextline(lmIter *self)
+{
+ do {
+ self->pos++;
+ if (self->pos >= self->m->numlines) {
+ return NULL;
+ }
+ /* skip over deleted manifest entries */
+ } while (self->m->lines[self->pos].deleted);
+ return self->m->lines + self->pos;
+}
+
+static PyObject *lmiter_iterentriesnext(PyObject *o)
+{
+ size_t pl;
+ line *l;
+ Py_ssize_t consumed;
+ PyObject *path = NULL, *hash = NULL, *flags = NULL;
+ l = lmiter_nextline((lmIter *)o);
+ if (!l) {
+ goto bail;
+ }
+ pl = pathlen(l);
+ path = PyString_FromStringAndSize(l->start, pl);
+ hash = nodeof(l);
+ consumed = pl + 41;
+ flags = PyString_FromStringAndSize(l->start + consumed,
+ l->len - consumed - 1);
+ if (!path || !hash || !flags) {
+ goto bail;
+ }
+ return PyTuple_Pack(3, path, hash, flags);
+ bail:
+ Py_XDECREF(path);
+ Py_XDECREF(hash);
+ Py_XDECREF(flags);
+ return NULL;
+}
+
+static PyTypeObject lazymanifestEntriesIterator = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size */
+ "parsers.lazymanifest.entriesiterator", /*tp_name */
+ sizeof(lmIter), /*tp_basicsize */
+ 0, /*tp_itemsize */
+ lmiter_dealloc, /*tp_dealloc */
+ 0, /*tp_print */
+ 0, /*tp_getattr */
+ 0, /*tp_setattr */
+ 0, /*tp_compare */
+ 0, /*tp_repr */
+ 0, /*tp_as_number */
+ 0, /*tp_as_sequence */
+ 0, /*tp_as_mapping */
+ 0, /*tp_hash */
+ 0, /*tp_call */
+ 0, /*tp_str */
+ 0, /*tp_getattro */
+ 0, /*tp_setattro */
+ 0, /*tp_as_buffer */
+ /* tp_flags: Py_TPFLAGS_HAVE_ITER tells python to
+ use tp_iter and tp_iternext fields. */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,
+ "Iterator for 3-tuples in a lazymanifest.", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ PyObject_SelfIter, /* tp_iter: __iter__() method */
+ lmiter_iterentriesnext, /* tp_iternext: next() method */
+};
+
+static PyObject *lmiter_iterkeysnext(PyObject *o)
+{
+ size_t pl;
+ line *l = lmiter_nextline((lmIter *)o);
+ if (!l) {
+ return NULL;
+ }
+ pl = pathlen(l);
+ return PyString_FromStringAndSize(l->start, pl);
+}
+
+static PyTypeObject lazymanifestKeysIterator = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size */
+ "parsers.lazymanifest.keysiterator", /*tp_name */
+ sizeof(lmIter), /*tp_basicsize */
+ 0, /*tp_itemsize */
+ lmiter_dealloc, /*tp_dealloc */
+ 0, /*tp_print */
+ 0, /*tp_getattr */
+ 0, /*tp_setattr */
+ 0, /*tp_compare */
+ 0, /*tp_repr */
+ 0, /*tp_as_number */
+ 0, /*tp_as_sequence */
+ 0, /*tp_as_mapping */
+ 0, /*tp_hash */
+ 0, /*tp_call */
+ 0, /*tp_str */
+ 0, /*tp_getattro */
+ 0, /*tp_setattro */
+ 0, /*tp_as_buffer */
+ /* tp_flags: Py_TPFLAGS_HAVE_ITER tells python to
+ use tp_iter and tp_iternext fields. */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,
+ "Keys iterator for a lazymanifest.", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ PyObject_SelfIter, /* tp_iter: __iter__() method */
+ lmiter_iterkeysnext, /* tp_iternext: next() method */
+};
+
+static lazymanifest *lazymanifest_copy(lazymanifest *self);
+
+static PyObject *lazymanifest_getentriesiter(lazymanifest *self)
+{
+ lmIter *i = NULL;
+ lazymanifest *t = lazymanifest_copy(self);
+ if (!t) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ i = PyObject_New(lmIter, &lazymanifestEntriesIterator);
+ if (i) {
+ i->m = t;
+ i->pos = -1;
+ } else {
+ Py_DECREF(t);
+ PyErr_NoMemory();
+ }
+ return (PyObject *)i;
+}
+
+static PyObject *lazymanifest_getkeysiter(lazymanifest *self)
+{
+ lmIter *i = NULL;
+ lazymanifest *t = lazymanifest_copy(self);
+ if (!t) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ i = PyObject_New(lmIter, &lazymanifestKeysIterator);
+ if (i) {
+ i->m = t;
+ i->pos = -1;
+ } else {
+ Py_DECREF(t);
+ PyErr_NoMemory();
+ }
+ return (PyObject *)i;
+}
+
+/* __getitem__ and __setitem__ support */
+
+static Py_ssize_t lazymanifest_size(lazymanifest *self)
+{
+ return self->livelines;
+}
+
+static int linecmp(const void *left, const void *right)
+{
+ return strcmp(((const line *)left)->start,
+ ((const line *)right)->start);
+}
+
+static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key)
+{
+ line needle;
+ line *hit;
+ if (!PyString_Check(key)) {
+ PyErr_Format(PyExc_TypeError,
+ "getitem: manifest keys must be a string.");
+ return NULL;
+ }
+ needle.start = PyString_AsString(key);
+ hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
+ &linecmp);
+ if (!hit || hit->deleted) {
+ PyErr_Format(PyExc_KeyError, "No such manifest entry.");
+ return NULL;
+ }
+ return hashflags(hit);
+}
+
+static int lazymanifest_delitem(lazymanifest *self, PyObject *key)
+{
+ line needle;
+ line *hit;
+ if (!PyString_Check(key)) {
+ PyErr_Format(PyExc_TypeError,
+ "delitem: manifest keys must be a string.");
+ return -1;
+ }
+ needle.start = PyString_AsString(key);
+ hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
+ &linecmp);
+ if (!hit || hit->deleted) {
+ PyErr_Format(PyExc_KeyError,
+ "Tried to delete nonexistent manifest entry.");
+ return -1;
+ }
+ self->dirty = true;
+ hit->deleted = true;
+ self->livelines--;
+ return 0;
+}
+
+/* Do a binary search for the insertion point for new, creating the
+ * new entry if needed. */
+static int internalsetitem(lazymanifest *self, line *new) {
+ int start = 0, end = self->numlines;
+ while (start < end) {
+ int pos = start + (end - start) / 2;
+ int c = linecmp(new, self->lines + pos);
+ if (c < 0)
+ end = pos;
+ else if (c > 0)
+ start = pos + 1;
+ else {
+ if (self->lines[pos].deleted)
+ self->livelines++;
+ start = pos;
+ goto finish;
+ }
+ }
+ /* being here means we need to do an insert */
+ if (!realloc_if_full(self)) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ memmove(self->lines + start + 1, self->lines + start,
+ (self->numlines - start) * sizeof(line));
+ self->numlines++;
+ self->livelines++;
+finish:
+ self->lines[start] = *new;
+ self->dirty = true;
+ return 0;
+}
+
+static int lazymanifest_setitem(
+ lazymanifest *self, PyObject *key, PyObject *value)
+{
+ char *path;
+ Py_ssize_t plen;
+ PyObject *pyhash;
+ Py_ssize_t hlen;
+ char *hash;
+ PyObject *pyflags;
+ char *flags;
+ Py_ssize_t flen;
+ size_t dlen;
+ char *dest;
+ int i;
+ line new;
+ if (!PyString_Check(key)) {
+ PyErr_Format(PyExc_TypeError,
+ "setitem: manifest keys must be a string.");
+ return -1;
+ }
+ if (!value) {
+ return lazymanifest_delitem(self, key);
+ }
+ if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) {
+ PyErr_Format(PyExc_TypeError,
+ "Manifest values must be a tuple of (node, flags).");
+ return -1;
+ }
+ if (PyString_AsStringAndSize(key, &path, &plen) == -1) {
+ return -1;
+ }
+
+ pyhash = PyTuple_GetItem(value, 0);
+ if (!PyString_Check(pyhash)) {
+ PyErr_Format(PyExc_TypeError,
+ "node must be a 20-byte string");
+ return -1;
+ }
+ hlen = PyString_Size(pyhash);
+ /* Some parts of the codebase try and set 21 or 22
+ * byte "hash" values in order to perturb things for
+ * status. We have to preserve at least the 21st
+ * byte. Sigh. If there's a 22nd byte, we drop it on
+ * the floor, which works fine.
+ */
+ if (hlen != 20 && hlen != 21 && hlen != 22) {
+ PyErr_Format(PyExc_TypeError,
+ "node must be a 20-byte string");
+ return -1;
+ }
+ hash = PyString_AsString(pyhash);
+
+ pyflags = PyTuple_GetItem(value, 1);
+ if (!PyString_Check(pyflags) || PyString_Size(pyflags) > 1) {
+ PyErr_Format(PyExc_TypeError,
+ "flags must a 0 or 1 byte string");
+ return -1;
+ }
+ if (PyString_AsStringAndSize(pyflags, &flags, &flen) == -1) {
+ return -1;
+ }
+ /* one null byte and one newline */
+ dlen = plen + 41 + flen + 1;
+ dest = malloc(dlen);
+ if (!dest) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ memcpy(dest, path, plen + 1);
+ for (i = 0; i < 20; i++) {
+ /* Cast to unsigned, so it will not get sign-extended when promoted
+ * to int (as is done when passing to a variadic function)
+ */
+ sprintf(dest + plen + 1 + (i * 2), "%02x", (unsigned char)hash[i]);
+ }
+ memcpy(dest + plen + 41, flags, flen);
+ dest[plen + 41 + flen] = '\n';
+ new.start = dest;
+ new.len = dlen;
+ new.hash_suffix = '\0';
+ if (hlen > 20) {
+ new.hash_suffix = hash[20];
+ }
+ new.from_malloc = true; /* is `start` a pointer we allocated? */
+ new.deleted = false; /* is this entry deleted? */
+ if (internalsetitem(self, &new)) {
+ return -1;
+ }
+ return 0;
+}
+
+static PyMappingMethods lazymanifest_mapping_methods = {
+ (lenfunc)lazymanifest_size, /* mp_length */
+ (binaryfunc)lazymanifest_getitem, /* mp_subscript */
+ (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */
+};
+
+/* sequence methods (important or __contains__ builds an iterator */
+
+static int lazymanifest_contains(lazymanifest *self, PyObject *key)
+{
+ line needle;
+ line *hit;
+ if (!PyString_Check(key)) {
+ /* Our keys are always strings, so if the contains
+ * check is for a non-string, just return false. */
+ return 0;
+ }
+ needle.start = PyString_AsString(key);
+ hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
+ &linecmp);
+ if (!hit || hit->deleted) {
+ return 0;
+ }
+ return 1;
+}
+
+static PySequenceMethods lazymanifest_seq_meths = {
+ (lenfunc)lazymanifest_size, /* sq_length */
+ 0, /* sq_concat */
+ 0, /* sq_repeat */
+ 0, /* sq_item */
+ 0, /* sq_slice */
+ 0, /* sq_ass_item */
+ 0, /* sq_ass_slice */
+ (objobjproc)lazymanifest_contains, /* sq_contains */
+ 0, /* sq_inplace_concat */
+ 0, /* sq_inplace_repeat */
+};
+
+
+/* Other methods (copy, diff, etc) */
+static PyTypeObject lazymanifestType;
+
+/* If the manifest has changes, build the new manifest text and reindex it. */
+static int compact(lazymanifest *self) {
+ int i;
+ ssize_t need = 0;
+ char *data;
+ line *src, *dst;
+ PyObject *pydata;
+ if (!self->dirty)
+ return 0;
+ for (i = 0; i < self->numlines; i++) {
+ if (!self->lines[i].deleted) {
+ need += self->lines[i].len;
+ }
+ }
+ pydata = PyString_FromStringAndSize(NULL, need);
+ if (!pydata)
+ return -1;
+ data = PyString_AsString(pydata);
+ if (!data) {
+ return -1;
+ }
+ src = self->lines;
+ dst = self->lines;
+ for (i = 0; i < self->numlines; i++, src++) {
+ char *tofree = NULL;
+ if (src->from_malloc) {
+ tofree = src->start;
+ }
+ if (!src->deleted) {
+ memcpy(data, src->start, src->len);
+ *dst = *src;
+ dst->start = data;
+ dst->from_malloc = false;
+ data += dst->len;
+ dst++;
+ }
+ free(tofree);
+ }
+ Py_DECREF(self->pydata);
+ self->pydata = pydata;
+ self->numlines = self->livelines;
+ self->dirty = false;
+ return 0;
+}
+
+static PyObject *lazymanifest_text(lazymanifest *self)
+{
+ if (compact(self) != 0) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ Py_INCREF(self->pydata);
+ return self->pydata;
+}
+
+static lazymanifest *lazymanifest_copy(lazymanifest *self)
+{
+ lazymanifest *copy = NULL;
+ if (compact(self) != 0) {
+ goto nomem;
+ }
+ copy = PyObject_New(lazymanifest, &lazymanifestType);
+ if (!copy) {
+ goto nomem;
+ }
+ copy->numlines = self->numlines;
+ copy->livelines = self->livelines;
+ copy->dirty = false;
+ copy->lines = malloc(self->maxlines *sizeof(line));
+ if (!copy->lines) {
+ goto nomem;
+ }
+ memcpy(copy->lines, self->lines, self->numlines * sizeof(line));
+ copy->maxlines = self->maxlines;
+ copy->pydata = self->pydata;
+ Py_INCREF(copy->pydata);
+ return copy;
+ nomem:
+ PyErr_NoMemory();
+ Py_XDECREF(copy);
+ return NULL;
+}
+
+static lazymanifest *lazymanifest_filtercopy(
+ lazymanifest *self, PyObject *matchfn)
+{
+ lazymanifest *copy = NULL;
+ int i;
+ if (!PyCallable_Check(matchfn)) {
+ PyErr_SetString(PyExc_TypeError, "matchfn must be callable");
+ return NULL;
+ }
+ /* compact ourselves first to avoid double-frees later when we
+ * compact tmp so that it doesn't have random pointers to our
+ * underlying from_malloc-data (self->pydata is safe) */
+ if (compact(self) != 0) {
+ goto nomem;
+ }
+ copy = PyObject_New(lazymanifest, &lazymanifestType);
+ copy->dirty = true;
+ copy->lines = malloc(self->maxlines * sizeof(line));
+ if (!copy->lines) {
+ goto nomem;
+ }
+ copy->maxlines = self->maxlines;
+ copy->numlines = 0;
+ copy->pydata = self->pydata;
+ Py_INCREF(self->pydata);
+ for (i = 0; i < self->numlines; i++) {
+ PyObject *arg = PyString_FromString(self->lines[i].start);
+ PyObject *arglist = PyTuple_Pack(1, arg);
+ PyObject *result = PyObject_CallObject(matchfn, arglist);
+ Py_DECREF(arglist);
+ Py_DECREF(arg);
+ /* if the callback raised an exception, just let it
+ * through and give up */
+ if (!result) {
+ free(copy->lines);
+ Py_DECREF(self->pydata);
+ return NULL;
+ }
+ if (PyObject_IsTrue(result)) {
+ assert(!(self->lines[i].from_malloc));
+ copy->lines[copy->numlines++] = self->lines[i];
+ }
+ Py_DECREF(result);
+ }
+ copy->livelines = copy->numlines;
+ return copy;
+ nomem:
+ PyErr_NoMemory();
+ Py_XDECREF(copy);
+ return NULL;
+}
+
+static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args)
+{
+ lazymanifest *other;
+ PyObject *pyclean = NULL;
+ bool listclean;
+ PyObject *emptyTup = NULL, *ret = NULL;
+ PyObject *es;
+ int sneedle = 0, oneedle = 0;
+ if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) {
+ return NULL;
+ }
+ listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean);
+ es = PyString_FromString("");
+ if (!es) {
+ goto nomem;
+ }
+ emptyTup = PyTuple_Pack(2, Py_None, es);
+ Py_DECREF(es);
+ if (!emptyTup) {
+ goto nomem;
+ }
+ ret = PyDict_New();
+ if (!ret) {
+ goto nomem;
+ }
+ while (sneedle != self->numlines || oneedle != other->numlines) {
+ line *left = self->lines + sneedle;
+ line *right = other->lines + oneedle;
+ int result;
+ PyObject *key;
+ PyObject *outer;
+ /* If we're looking at a deleted entry and it's not
+ * the end of the manifest, just skip it. */
+ if (left->deleted && sneedle < self->numlines) {
+ sneedle++;
+ continue;
+ }
+ if (right->deleted && oneedle < other->numlines) {
+ oneedle++;
+ continue;
+ }
+ /* if we're at the end of either manifest, then we
+ * know the remaining items are adds so we can skip
+ * the strcmp. */
+ if (sneedle == self->numlines) {
+ result = 1;
+ } else if (oneedle == other->numlines) {
+ result = -1;
+ } else {
+ result = linecmp(left, right);
+ }
+ key = result <= 0 ?
+ PyString_FromString(left->start) :
+ PyString_FromString(right->start);
+ if (!key)
+ goto nomem;
+ if (result < 0) {
+ PyObject *l = hashflags(left);
+ if (!l) {
+ goto nomem;
+ }
+ outer = PyTuple_Pack(2, l, emptyTup);
+ Py_DECREF(l);
+ if (!outer) {
+ goto nomem;
+ }
+ PyDict_SetItem(ret, key, outer);
+ Py_DECREF(outer);
+ sneedle++;
+ } else if (result > 0) {
+ PyObject *r = hashflags(right);
+ if (!r) {
+ goto nomem;
+ }
+ outer = PyTuple_Pack(2, emptyTup, r);
+ Py_DECREF(r);
+ if (!outer) {
+ goto nomem;
+ }
+ PyDict_SetItem(ret, key, outer);
+ Py_DECREF(outer);
+ oneedle++;
+ } else {
+ /* file exists in both manifests */
+ if (left->len != right->len
+ || memcmp(left->start, right->start, left->len)
+ || left->hash_suffix != right->hash_suffix) {
+ PyObject *l = hashflags(left);
+ PyObject *r;
+ if (!l) {
+ goto nomem;
+ }
+ r = hashflags(right);
+ if (!r) {
+ Py_DECREF(l);
+ goto nomem;
+ }
+ outer = PyTuple_Pack(2, l, r);
+ Py_DECREF(l);
+ Py_DECREF(r);
+ if (!outer) {
+ goto nomem;
+ }
+ PyDict_SetItem(ret, key, outer);
+ Py_DECREF(outer);
+ } else if (listclean) {
+ PyDict_SetItem(ret, key, Py_None);
+ }
+ sneedle++;
+ oneedle++;
+ }
+ Py_DECREF(key);
+ }
+ Py_DECREF(emptyTup);
+ return ret;
+ nomem:
+ PyErr_NoMemory();
+ Py_XDECREF(ret);
+ Py_XDECREF(emptyTup);
+ return NULL;
+}
+
+static PyMethodDef lazymanifest_methods[] = {
+ {"iterkeys", (PyCFunction)lazymanifest_getkeysiter, METH_NOARGS,
+ "Iterate over file names in this lazymanifest."},
+ {"iterentries", (PyCFunction)lazymanifest_getentriesiter, METH_NOARGS,
+ "Iterate over (path, nodeid, flags) typles in this lazymanifest."},
+ {"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS,
+ "Make a copy of this lazymanifest."},
+ {"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O,
+ "Make a copy of this manifest filtered by matchfn."},
+ {"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS,
+ "Compare this lazymanifest to another one."},
+ {"text", (PyCFunction)lazymanifest_text, METH_NOARGS,
+ "Encode this manifest to text."},
+ {NULL},
+};
+
+static PyTypeObject lazymanifestType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "parsers.lazymanifest", /* tp_name */
+ sizeof(lazymanifest), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)lazymanifest_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ &lazymanifest_seq_meths, /* tp_as_sequence */
+ &lazymanifest_mapping_methods, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN, /* tp_flags */
+ "TODO(augie)", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ (getiterfunc)lazymanifest_getkeysiter, /* tp_iter */
+ 0, /* tp_iternext */
+ lazymanifest_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)lazymanifest_init, /* tp_init */
+ 0, /* tp_alloc */
+};
+
+void manifest_module_init(PyObject * mod)
+{
+ lazymanifestType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&lazymanifestType) < 0)
+ return;
+ Py_INCREF(&lazymanifestType);
+
+ PyModule_AddObject(mod, "lazymanifest",
+ (PyObject *)&lazymanifestType);
+}
--- a/mercurial/manifest.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/manifest.py Fri Mar 13 17:55:04 2015 -0500
@@ -9,37 +9,144 @@
import mdiff, parsers, error, revlog, util
import array, struct
-class manifestdict(dict):
- def __init__(self, mapping=None, flags=None):
- if mapping is None:
- mapping = {}
- if flags is None:
- flags = {}
- dict.__init__(self, mapping)
- self._flags = flags
+
+class _lazymanifest(dict):
+ """This is the pure implementation of lazymanifest.
+
+ It has not been optimized *at all* and is not lazy.
+ """
+
+ def __init__(self, data):
+ # This init method does a little bit of excessive-looking
+ # precondition checking. This is so that the behavior of this
+ # class exactly matches its C counterpart to try and help
+ # prevent surprise breakage for anyone that develops against
+ # the pure version.
+ if data and data[-1] != '\n':
+ raise ValueError('Manifest did not end in a newline.')
+ dict.__init__(self)
+ prev = None
+ for l in data.splitlines():
+ if prev is not None and prev > l:
+ raise ValueError('Manifest lines not in sorted order.')
+ prev = l
+ f, n = l.split('\0')
+ if len(n) > 40:
+ self[f] = revlog.bin(n[:40]), n[40:]
+ else:
+ self[f] = revlog.bin(n), ''
+
def __setitem__(self, k, v):
- assert v is not None
- dict.__setitem__(self, k, v)
- def flags(self, f):
- return self._flags.get(f, "")
- def setflag(self, f, flags):
- """Set the flags (symlink, executable) for path f."""
- self._flags[f] = flags
+ node, flag = v
+ assert node is not None
+ if len(node) > 21:
+ node = node[:21] # match c implementation behavior
+ dict.__setitem__(self, k, (node, flag))
+
+ def __iter__(self):
+ return iter(sorted(dict.keys(self)))
+
+ def iterkeys(self):
+ return iter(sorted(dict.keys(self)))
+
+ def iterentries(self):
+ return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
+
def copy(self):
- return manifestdict(self, dict.copy(self._flags))
+ c = _lazymanifest('')
+ c.update(self)
+ return c
+
+ def diff(self, m2, clean=False):
+ '''Finds changes between the current manifest and m2.'''
+ diff = {}
+
+ for fn, e1 in self.iteritems():
+ if fn not in m2:
+ diff[fn] = e1, (None, '')
+ else:
+ e2 = m2[fn]
+ if e1 != e2:
+ diff[fn] = e1, e2
+ elif clean:
+ diff[fn] = None
+
+ for fn, e2 in m2.iteritems():
+ if fn not in self:
+ diff[fn] = (None, ''), e2
+
+ return diff
+
+ def filtercopy(self, filterfn):
+ c = _lazymanifest('')
+ for f, n, fl in self.iterentries():
+ if filterfn(f):
+ c[f] = n, fl
+ return c
+
+ def text(self):
+ """Get the full data of this manifest as a bytestring."""
+ fl = sorted(self.iterentries())
+
+ _hex = revlog.hex
+ # if this is changed to support newlines in filenames,
+ # be sure to check the templates/ dir again (especially *-raw.tmpl)
+ return ''.join("%s\0%s%s\n" % (
+ f, _hex(n[:20]), flag) for f, n, flag in fl)
+
+try:
+ _lazymanifest = parsers.lazymanifest
+except AttributeError:
+ pass
+
+class manifestdict(object):
+ def __init__(self, data=''):
+ self._lm = _lazymanifest(data)
+
+ def __getitem__(self, key):
+ return self._lm[key][0]
+
+ def find(self, key):
+ return self._lm[key]
+
+ def __len__(self):
+ return len(self._lm)
+
+ def __setitem__(self, key, node):
+ self._lm[key] = node, self.flags(key, '')
+
+ def __contains__(self, key):
+ return key in self._lm
+
+ def __delitem__(self, key):
+ del self._lm[key]
+
+ def __iter__(self):
+ return self._lm.__iter__()
+
+ def iterkeys(self):
+ return self._lm.iterkeys()
+
+ def keys(self):
+ return list(self.iterkeys())
+
def intersectfiles(self, files):
- '''make a new manifestdict with the intersection of self with files
+ '''make a new lazymanifest with the intersection of self with files
The algorithm assumes that files is much smaller than self.'''
ret = manifestdict()
+ lm = self._lm
for fn in files:
- if fn in self:
- ret[fn] = self[fn]
- flags = self._flags.get(fn, None)
- if flags:
- ret._flags[fn] = flags
+ if fn in lm:
+ ret._lm[fn] = self._lm[fn]
return ret
+ def filesnotin(self, m2):
+ '''Set of files in this manifest that are not in the other'''
+ files = set(self)
+ files.difference_update(m2)
+ return files
+
def matches(self, match):
'''generate a new manifest filtered by the match argument'''
if match.always():
@@ -50,11 +157,9 @@
(not match.anypats() and util.all(fn in self for fn in files))):
return self.intersectfiles(files)
- mf = self.copy()
- for fn in mf.keys():
- if not match(fn):
- del mf[fn]
- return mf
+ lm = manifestdict('')
+ lm._lm = self._lm.filtercopy(match)
+ return lm
def diff(self, m2, clean=False):
'''Finds changes between the current manifest and m2.
@@ -71,35 +176,33 @@
the nodeid will be None and the flags will be the empty
string.
'''
- diff = {}
+ return self._lm.diff(m2._lm, clean)
+
+ def setflag(self, key, flag):
+ self._lm[key] = self[key], flag
+
+ def get(self, key, default=None):
+ try:
+ return self._lm[key][0]
+ except KeyError:
+ return default
- for fn, n1 in self.iteritems():
- fl1 = self._flags.get(fn, '')
- n2 = m2.get(fn, None)
- fl2 = m2._flags.get(fn, '')
- if n2 is None:
- fl2 = ''
- if n1 != n2 or fl1 != fl2:
- diff[fn] = ((n1, fl1), (n2, fl2))
- elif clean:
- diff[fn] = None
+ def flags(self, key, default=''):
+ try:
+ return self._lm[key][1]
+ except KeyError:
+ return default
- for fn, n2 in m2.iteritems():
- if fn not in self:
- fl2 = m2._flags.get(fn, '')
- diff[fn] = ((None, ''), (n2, fl2))
+ def copy(self):
+ c = manifestdict('')
+ c._lm = self._lm.copy()
+ return c
- return diff
+ def iteritems(self):
+ return (x[:2] for x in self._lm.iterentries())
def text(self):
- """Get the full data of this manifest as a bytestring."""
- fl = sorted(self)
- _checkforbidden(fl)
-
- hex, flags = revlog.hex, self.flags
- # if this is changed to support newlines in filenames,
- # be sure to check the templates/ dir again (especially *-raw.tmpl)
- return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
+ return self._lm.text()
def fastdelta(self, base, changes):
"""Given a base manifest text as an array.array and a list of changes
@@ -119,7 +222,8 @@
# bs will either be the index of the item or the insert point
start, end = _msearch(addbuf, f, start)
if not todelete:
- l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
+ h, fl = self._lm[f]
+ l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
else:
if start == end:
# item we want to delete was not found, error out
@@ -213,21 +317,22 @@
+ content for start, end, content in x)
return deltatext, newaddlist
-def _parse(lines):
- mfdict = manifestdict()
- parsers.parse_manifest(mfdict, mfdict._flags, lines)
- return mfdict
-
class manifest(revlog.revlog):
def __init__(self, opener):
- # we expect to deal with not more than four revs at a time,
- # during a commit --amend
- self._mancache = util.lrucachedict(4)
+ # During normal operations, we expect to deal with not more than four
+ # revs at a time (such as during commit --amend). When rebasing large
+ # stacks of commits, the number can go up, hence the config knob below.
+ cachesize = 4
+ opts = getattr(opener, 'options', None)
+ if opts is not None:
+ cachesize = opts.get('manifestcachesize', cachesize)
+ self._mancache = util.lrucachedict(cachesize)
revlog.revlog.__init__(self, opener, "00manifest.i")
def readdelta(self, node):
r = self.rev(node)
- return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
+ d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
+ return manifestdict(d)
def readfast(self, node):
'''use the faster of readdelta or read'''
@@ -244,25 +349,20 @@
return self._mancache[node][0]
text = self.revision(node)
arraytext = array.array('c', text)
- mapping = _parse(text)
- self._mancache[node] = (mapping, arraytext)
- return mapping
+ m = manifestdict(text)
+ self._mancache[node] = (m, arraytext)
+ return m
def find(self, node, f):
'''look up entry for a single file efficiently.
return (node, flags) pair if found, (None, None) if not.'''
- if node in self._mancache:
- mapping = self._mancache[node][0]
- return mapping.get(f), mapping.flags(f)
- text = self.revision(node)
- start, end = _msearch(text, f)
- if start == end:
+ m = self.read(node)
+ try:
+ return m.find(f)
+ except KeyError:
return None, None
- l = text[start:end]
- f, n = l.split('\0')
- return revlog.bin(n[:40]), n[40:-1]
- def add(self, map, transaction, link, p1, p2, added, removed):
+ def add(self, m, transaction, link, p1, p2, added, removed):
if p1 in self._mancache:
# If our first parent is in the manifest cache, we can
# compute a delta here using properties we know about the
@@ -277,7 +377,7 @@
# since the lists are already sorted
work.sort()
- arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work)
+ arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
cachedelta = self.rev(p1), deltatext
text = util.buffer(arraytext)
else:
@@ -285,11 +385,11 @@
# just encode a fulltext of the manifest and pass that
# through to the revlog layer, and let it handle the delta
# process.
- text = map.text()
+ text = m.text()
arraytext = array.array('c', text)
cachedelta = None
n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
- self._mancache[n] = (map, arraytext)
+ self._mancache[n] = (m, arraytext)
return n
--- a/mercurial/mdiff.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/mdiff.py Fri Mar 13 17:55:04 2015 -0500
@@ -367,6 +367,9 @@
def trivialdiffheader(length):
return struct.pack(">lll", 0, 0, length)
+def replacediffheader(oldlen, newlen):
+ return struct.pack(">lll", 0, oldlen, newlen)
+
patches = mpatch.patches
patchedsize = mpatch.patchedsize
textdiff = bdiff.bdiff
--- a/mercurial/namespaces.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/namespaces.py Fri Mar 13 17:55:04 2015 -0500
@@ -142,7 +142,7 @@
is used
colorname: the name to use for colored log output; if not specified
logname is used
- logfmt: the format to use for (l10n-ed) log output; if not specified
+ logfmt: the format to use for (i18n-ed) log output; if not specified
it is composed from logname
listnames: function to list all names
namemap: function that inputs a node, output name(s)
--- a/mercurial/obsolete.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/obsolete.py Fri Mar 13 17:55:04 2015 -0500
@@ -68,15 +68,14 @@
"""
import struct
-import util, base85, node
+import util, base85, node, parsers
import phases
from i18n import _
_pack = struct.pack
_unpack = struct.unpack
_calcsize = struct.calcsize
-
-_SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
+propertycache = util.propertycache
# the obsolete feature is not mature enough to be enabled by default.
# you have to rely on third party extension extension to enable this.
@@ -146,7 +145,7 @@
_fm0fsize = _calcsize(_fm0fixed)
_fm0fnodesize = _calcsize(_fm0node)
-def _fm0readmarkers(data, off=0):
+def _fm0readmarkers(data, off):
# Loop on markers
l = len(data)
while off + _fm0fsize <= l:
@@ -285,7 +284,7 @@
_fm1metapair = 'BB'
_fm1metapairsize = _calcsize('BB')
-def _fm1readmarkers(data, off=0):
+def _fm1purereadmarkers(data, off):
# make some global constants local for performance
noneflag = _fm1parentnone
sha2flag = usingsha256
@@ -301,6 +300,7 @@
# Loop on markers
stop = len(data) - _fm1fsize
ufixed = util.unpacker(_fm1fixed)
+
while off <= stop:
# read fixed part
o1 = off + fsize
@@ -395,6 +395,13 @@
data.append(value)
return ''.join(data)
+def _fm1readmarkers(data, off):
+ native = getattr(parsers, 'fm1readmarkers', None)
+ if not native:
+ return _fm1purereadmarkers(data, off)
+ stop = len(data) - _fm1fsize
+ return native(data, off, stop)
+
# mapping to read/write various marker formats
# <version> -> (decoder, encoder)
formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
@@ -462,15 +469,35 @@
"""The flags field of the marker"""
return self._data[2]
-def _checkinvalidmarkers(obsstore):
+@util.nogc
+def _addsuccessors(successors, markers):
+ for mark in markers:
+ successors.setdefault(mark[0], set()).add(mark)
+
+@util.nogc
+def _addprecursors(precursors, markers):
+ for mark in markers:
+ for suc in mark[1]:
+ precursors.setdefault(suc, set()).add(mark)
+
+@util.nogc
+def _addchildren(children, markers):
+ for mark in markers:
+ parents = mark[5]
+ if parents is not None:
+ for p in parents:
+ children.setdefault(p, set()).add(mark)
+
+def _checkinvalidmarkers(markers):
"""search for marker with invalid data and raise error if needed
Exist as a separated function to allow the evolve extension for a more
subtle handling.
"""
- if node.nullid in obsstore.precursors:
- raise util.Abort(_('bad obsolescence marker detected: '
- 'invalid successors nullid'))
+ for mark in markers:
+ if node.nullid in mark[1]:
+ raise util.Abort(_('bad obsolescence marker detected: '
+ 'invalid successors nullid'))
class obsstore(object):
"""Store obsolete markers
@@ -494,16 +521,13 @@
# caches for various obsolescence related cache
self.caches = {}
self._all = []
- self.precursors = {}
- self.successors = {}
- self.children = {}
self.sopener = sopener
data = sopener.tryread('obsstore')
self._version = defaultformat
self._readonly = readonly
if data:
self._version, markers = _readmarkers(data)
- self._load(markers)
+ self._addmarkers(markers)
def __iter__(self):
return iter(self._all)
@@ -566,12 +590,6 @@
if new:
f = self.sopener('obsstore', 'ab')
try:
- # Whether the file's current position is at the begin or at
- # the end after opening a file for appending is implementation
- # defined. So we must seek to the end before calling tell(),
- # or we may get a zero offset for non-zero sized files on
- # some platforms (issue3543).
- f.seek(0, _SEEK_END)
offset = f.tell()
transaction.add('obsstore', offset)
# offset == 0: new file - add the version header
@@ -581,7 +599,7 @@
# XXX: f.close() == filecache invalidation == obsstore rebuilt.
# call 'filecacheentry.refresh()' here
f.close()
- self._load(new)
+ self._addmarkers(new)
# new marker *may* have changed several set. invalidate the cache.
self.caches.clear()
# records the number of new markers for the transaction hooks
@@ -596,19 +614,37 @@
version, markers = _readmarkers(data)
return self.add(transaction, markers)
- @util.nogc
- def _load(self, markers):
- for mark in markers:
- self._all.append(mark)
- pre, sucs = mark[:2]
- self.successors.setdefault(pre, set()).add(mark)
- for suc in sucs:
- self.precursors.setdefault(suc, set()).add(mark)
- parents = mark[5]
- if parents is not None:
- for p in parents:
- self.children.setdefault(p, set()).add(mark)
- _checkinvalidmarkers(self)
+ @propertycache
+ def successors(self):
+ successors = {}
+ _addsuccessors(successors, self._all)
+ return successors
+
+ @propertycache
+ def precursors(self):
+ precursors = {}
+ _addprecursors(precursors, self._all)
+ return precursors
+
+ @propertycache
+ def children(self):
+ children = {}
+ _addchildren(children, self._all)
+ return children
+
+ def _cached(self, attr):
+ return attr in self.__dict__
+
+ def _addmarkers(self, markers):
+ markers = list(markers) # to allow repeated iteration
+ self._all.extend(markers)
+ if self._cached('successors'):
+ _addsuccessors(self.successors, markers)
+ if self._cached('precursors'):
+ _addprecursors(self.precursors, markers)
+ if self._cached('children'):
+ _addchildren(self.children, markers)
+ _checkinvalidmarkers(markers)
def relevantmarkers(self, nodes):
"""return a set of all obsolescence markers relevant to a set of nodes.
--- a/mercurial/parsers.c Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/parsers.c Fri Mar 13 17:55:04 2015 -0500
@@ -71,7 +71,7 @@
/*
* Turn a hex-encoded string into binary.
*/
-static PyObject *unhexlify(const char *str, int len)
+PyObject *unhexlify(const char *str, int len)
{
PyObject *ret;
char *d;
@@ -1676,108 +1676,6 @@
}
/*
- * Given a (possibly overlapping) set of revs, return the greatest
- * common ancestors: those with the longest path to the root.
- */
-static PyObject *index_ancestors(indexObject *self, PyObject *args)
-{
- PyObject *ret = NULL, *gca = NULL;
- Py_ssize_t argcount, i, len;
- bitmask repeat = 0;
- int revcount = 0;
- int *revs;
-
- argcount = PySequence_Length(args);
- revs = malloc(argcount * sizeof(*revs));
- if (argcount > 0 && revs == NULL)
- return PyErr_NoMemory();
- len = index_length(self) - 1;
-
- for (i = 0; i < argcount; i++) {
- static const int capacity = 24;
- PyObject *obj = PySequence_GetItem(args, i);
- bitmask x;
- long val;
-
- if (!PyInt_Check(obj)) {
- PyErr_SetString(PyExc_TypeError,
- "arguments must all be ints");
- Py_DECREF(obj);
- goto bail;
- }
- val = PyInt_AsLong(obj);
- Py_DECREF(obj);
- if (val == -1) {
- ret = PyList_New(0);
- goto done;
- }
- if (val < 0 || val >= len) {
- PyErr_SetString(PyExc_IndexError,
- "index out of range");
- goto bail;
- }
- /* this cheesy bloom filter lets us avoid some more
- * expensive duplicate checks in the common set-is-disjoint
- * case */
- x = 1ull << (val & 0x3f);
- if (repeat & x) {
- int k;
- for (k = 0; k < revcount; k++) {
- if (val == revs[k])
- goto duplicate;
- }
- }
- else repeat |= x;
- if (revcount >= capacity) {
- PyErr_Format(PyExc_OverflowError,
- "bitset size (%d) > capacity (%d)",
- revcount, capacity);
- goto bail;
- }
- revs[revcount++] = (int)val;
- duplicate:;
- }
-
- if (revcount == 0) {
- ret = PyList_New(0);
- goto done;
- }
- if (revcount == 1) {
- PyObject *obj;
- ret = PyList_New(1);
- if (ret == NULL)
- goto bail;
- obj = PyInt_FromLong(revs[0]);
- if (obj == NULL)
- goto bail;
- PyList_SET_ITEM(ret, 0, obj);
- goto done;
- }
-
- gca = find_gca_candidates(self, revs, revcount);
- if (gca == NULL)
- goto bail;
-
- if (PyList_GET_SIZE(gca) <= 1) {
- ret = gca;
- Py_INCREF(gca);
- }
- else ret = find_deepest(self, gca);
-
-done:
- free(revs);
- Py_XDECREF(gca);
-
- return ret;
-
-bail:
- free(revs);
- Py_XDECREF(gca);
- Py_XDECREF(ret);
- return NULL;
-}
-
-/*
* Given a (possibly overlapping) set of revs, return all the
* common ancestors heads: heads(::args[0] and ::a[1] and ...)
*/
@@ -1871,6 +1769,24 @@
}
/*
+ * Given a (possibly overlapping) set of revs, return the greatest
+ * common ancestors: those with the longest path to the root.
+ */
+static PyObject *index_ancestors(indexObject *self, PyObject *args)
+{
+ PyObject *gca = index_commonancestorsheads(self, args);
+ if (gca == NULL)
+ return NULL;
+
+ if (PyList_GET_SIZE(gca) <= 1) {
+ Py_INCREF(gca);
+ return gca;
+ }
+
+ return find_deepest(self, gca);
+}
+
+/*
* Invalidate any trie entries introduced by added revs.
*/
static void nt_invalidate_added(indexObject *self, Py_ssize_t start)
@@ -2230,6 +2146,157 @@
return NULL;
}
+#define BUMPED_FIX 1
+#define USING_SHA_256 2
+
+static PyObject *readshas(
+ const char *source, unsigned char num, Py_ssize_t hashwidth)
+{
+ int i;
+ PyObject *list = PyTuple_New(num);
+ if (list == NULL) {
+ return NULL;
+ }
+ for (i = 0; i < num; i++) {
+ PyObject *hash = PyString_FromStringAndSize(source, hashwidth);
+ if (hash == NULL) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ PyTuple_SetItem(list, i, hash);
+ source += hashwidth;
+ }
+ return list;
+}
+
+static PyObject *fm1readmarker(const char *data, uint32_t *msize)
+{
+ const char *meta;
+
+ double mtime;
+ int16_t tz;
+ uint16_t flags;
+ unsigned char nsuccs, nparents, nmetadata;
+ Py_ssize_t hashwidth = 20;
+
+ PyObject *prec = NULL, *parents = NULL, *succs = NULL;
+ PyObject *metadata = NULL, *ret = NULL;
+ int i;
+
+ *msize = getbe32(data);
+ data += 4;
+ mtime = getbefloat64(data);
+ data += 8;
+ tz = getbeint16(data);
+ data += 2;
+ flags = getbeuint16(data);
+ data += 2;
+
+ if (flags & USING_SHA_256) {
+ hashwidth = 32;
+ }
+
+ nsuccs = (unsigned char)(*data++);
+ nparents = (unsigned char)(*data++);
+ nmetadata = (unsigned char)(*data++);
+
+ prec = PyString_FromStringAndSize(data, hashwidth);
+ data += hashwidth;
+ if (prec == NULL) {
+ goto bail;
+ }
+
+ succs = readshas(data, nsuccs, hashwidth);
+ if (succs == NULL) {
+ goto bail;
+ }
+ data += nsuccs * hashwidth;
+
+ if (nparents == 1 || nparents == 2) {
+ parents = readshas(data, nparents, hashwidth);
+ if (parents == NULL) {
+ goto bail;
+ }
+ data += nparents * hashwidth;
+ } else {
+ parents = Py_None;
+ }
+
+ meta = data + (2 * nmetadata);
+ metadata = PyTuple_New(nmetadata);
+ if (metadata == NULL) {
+ goto bail;
+ }
+ for (i = 0; i < nmetadata; i++) {
+ PyObject *tmp, *left = NULL, *right = NULL;
+ Py_ssize_t metasize = (unsigned char)(*data++);
+ left = PyString_FromStringAndSize(meta, metasize);
+ meta += metasize;
+ metasize = (unsigned char)(*data++);
+ right = PyString_FromStringAndSize(meta, metasize);
+ meta += metasize;
+ if (!left || !right) {
+ Py_XDECREF(left);
+ Py_XDECREF(right);
+ goto bail;
+ }
+ tmp = PyTuple_Pack(2, left, right);
+ Py_DECREF(left);
+ Py_DECREF(right);
+ if (!tmp) {
+ goto bail;
+ }
+ PyTuple_SetItem(metadata, i, tmp);
+ }
+ ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
+ metadata, mtime, (int)tz * 60, parents);
+bail:
+ Py_XDECREF(prec);
+ Py_XDECREF(succs);
+ Py_XDECREF(metadata);
+ if (parents != Py_None)
+ Py_XDECREF(parents);
+ return ret;
+}
+
+
+static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
+ const char *data;
+ Py_ssize_t datalen;
+ /* only unsigned long because python 2.4, should be Py_ssize_t */
+ unsigned long offset, stop;
+ PyObject *markers = NULL;
+
+ /* replace kk with nn when we drop Python 2.4 */
+ if (!PyArg_ParseTuple(args, "s#kk", &data, &datalen, &offset, &stop)) {
+ return NULL;
+ }
+ data += offset;
+ markers = PyList_New(0);
+ if (!markers) {
+ return NULL;
+ }
+ while (offset < stop) {
+ uint32_t msize;
+ int error;
+ PyObject *record = fm1readmarker(data, &msize);
+ if (!record) {
+ goto bail;
+ }
+ error = PyList_Append(markers, record);
+ Py_DECREF(record);
+ if (error) {
+ goto bail;
+ }
+ data += msize;
+ offset += msize;
+ }
+ return markers;
+bail:
+ Py_DECREF(markers);
+ return NULL;
+}
+
static char parsers_doc[] = "Efficient content parsing.";
PyObject *encodedir(PyObject *self, PyObject *args);
@@ -2245,10 +2312,13 @@
{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
+ {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
+ "parse v1 obsolete markers\n"},
{NULL, NULL}
};
void dirs_module_init(PyObject *mod);
+void manifest_module_init(PyObject *mod);
static void module_init(PyObject *mod)
{
@@ -2263,6 +2333,7 @@
PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
dirs_module_init(mod);
+ manifest_module_init(mod);
indexType.tp_new = PyType_GenericNew;
if (PyType_Ready(&indexType) < 0 ||
--- a/mercurial/patch.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/patch.py Fri Mar 13 17:55:04 2015 -0500
@@ -6,7 +6,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import cStringIO, email, os, errno, re, posixpath
+import cStringIO, email, os, errno, re, posixpath, copy
import tempfile, zlib, shutil
# On python2.4 you have to import these by name or they fail to
# load. This was not a problem on Python 2.7.
@@ -804,6 +804,262 @@
self.write_rej()
return len(self.rej)
+class header(object):
+ """patch header
+ """
+ diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
+ diff_re = re.compile('diff -r .* (.*)$')
+ allhunks_re = re.compile('(?:index|deleted file) ')
+ pretty_re = re.compile('(?:new file|deleted file) ')
+ special_re = re.compile('(?:index|new|deleted|copy|rename) ')
+
+ def __init__(self, header):
+ self.header = header
+ self.hunks = []
+
+ def binary(self):
+ return util.any(h.startswith('index ') for h in self.header)
+
+ def pretty(self, fp):
+ for h in self.header:
+ if h.startswith('index '):
+ fp.write(_('this modifies a binary file (all or nothing)\n'))
+ break
+ if self.pretty_re.match(h):
+ fp.write(h)
+ if self.binary():
+ fp.write(_('this is a binary file\n'))
+ break
+ if h.startswith('---'):
+ fp.write(_('%d hunks, %d lines changed\n') %
+ (len(self.hunks),
+ sum([max(h.added, h.removed) for h in self.hunks])))
+ break
+ fp.write(h)
+
+ def write(self, fp):
+ fp.write(''.join(self.header))
+
+ def allhunks(self):
+ return util.any(self.allhunks_re.match(h) for h in self.header)
+
+ def files(self):
+ match = self.diffgit_re.match(self.header[0])
+ if match:
+ fromfile, tofile = match.groups()
+ if fromfile == tofile:
+ return [fromfile]
+ return [fromfile, tofile]
+ else:
+ return self.diff_re.match(self.header[0]).groups()
+
+ def filename(self):
+ return self.files()[-1]
+
+ def __repr__(self):
+ return '<header %s>' % (' '.join(map(repr, self.files())))
+
+ def special(self):
+ return util.any(self.special_re.match(h) for h in self.header)
+
+class recordhunk(object):
+ """patch hunk
+
+ XXX shouldn't we merge this with the other hunk class?
+ """
+ maxcontext = 3
+
+ def __init__(self, header, fromline, toline, proc, before, hunk, after):
+ def trimcontext(number, lines):
+ delta = len(lines) - self.maxcontext
+ if False and delta > 0:
+ return number + delta, lines[:self.maxcontext]
+ return number, lines
+
+ self.header = header
+ self.fromline, self.before = trimcontext(fromline, before)
+ self.toline, self.after = trimcontext(toline, after)
+ self.proc = proc
+ self.hunk = hunk
+ self.added, self.removed = self.countchanges(self.hunk)
+
+ def countchanges(self, hunk):
+ """hunk -> (n+,n-)"""
+ add = len([h for h in hunk if h[0] == '+'])
+ rem = len([h for h in hunk if h[0] == '-'])
+ return add, rem
+
+ def write(self, fp):
+ delta = len(self.before) + len(self.after)
+ if self.after and self.after[-1] == '\\ No newline at end of file\n':
+ delta -= 1
+ fromlen = delta + self.removed
+ tolen = delta + self.added
+ fp.write('@@ -%d,%d +%d,%d @@%s\n' %
+ (self.fromline, fromlen, self.toline, tolen,
+ self.proc and (' ' + self.proc)))
+ fp.write(''.join(self.before + self.hunk + self.after))
+
+ pretty = write
+
+ def filename(self):
+ return self.header.filename()
+
+ def __repr__(self):
+ return '<hunk %r@%d>' % (self.filename(), self.fromline)
+
+def filterpatch(ui, headers):
+ """Interactively filter patch chunks into applied-only chunks"""
+
+ def prompt(skipfile, skipall, query, chunk):
+ """prompt query, and process base inputs
+
+ - y/n for the rest of file
+ - y/n for the rest
+ - ? (help)
+ - q (quit)
+
+ Return True/False and possibly updated skipfile and skipall.
+ """
+ newpatches = None
+ if skipall is not None:
+ return skipall, skipfile, skipall, newpatches
+ if skipfile is not None:
+ return skipfile, skipfile, skipall, newpatches
+ while True:
+ resps = _('[Ynesfdaq?]'
+ '$$ &Yes, record this change'
+ '$$ &No, skip this change'
+ '$$ &Edit this change manually'
+ '$$ &Skip remaining changes to this file'
+ '$$ Record remaining changes to this &file'
+ '$$ &Done, skip remaining changes and files'
+ '$$ Record &all changes to all remaining files'
+ '$$ &Quit, recording no changes'
+ '$$ &? (display help)')
+ r = ui.promptchoice("%s %s" % (query, resps))
+ ui.write("\n")
+ if r == 8: # ?
+ for c, t in ui.extractchoices(resps)[1]:
+ ui.write('%s - %s\n' % (c, t.lower()))
+ continue
+ elif r == 0: # yes
+ ret = True
+ elif r == 1: # no
+ ret = False
+ elif r == 2: # Edit patch
+ if chunk is None:
+ ui.write(_('cannot edit patch for whole file'))
+ ui.write("\n")
+ continue
+ if chunk.header.binary():
+ ui.write(_('cannot edit patch for binary file'))
+ ui.write("\n")
+ continue
+ # Patch comment based on the Git one (based on comment at end of
+ # http://mercurial.selenic.com/wiki/RecordExtension)
+ phelp = '---' + _("""
+To remove '-' lines, make them ' ' lines (context).
+To remove '+' lines, delete them.
+Lines starting with # will be removed from the patch.
+
+If the patch applies cleanly, the edited hunk will immediately be
+added to the record list. If it does not apply cleanly, a rejects
+file will be generated: you can use that when you try again. If
+all lines of the hunk are removed, then the edit is aborted and
+the hunk is left unchanged.
+""")
+ (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
+ suffix=".diff", text=True)
+ ncpatchfp = None
+ try:
+ # Write the initial patch
+ f = os.fdopen(patchfd, "w")
+ chunk.header.write(f)
+ chunk.write(f)
+ f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
+ f.close()
+ # Start the editor and wait for it to complete
+ editor = ui.geteditor()
+ ui.system("%s \"%s\"" % (editor, patchfn),
+ environ={'HGUSER': ui.username()},
+ onerr=util.Abort, errprefix=_("edit failed"))
+ # Remove comment lines
+ patchfp = open(patchfn)
+ ncpatchfp = cStringIO.StringIO()
+ for line in patchfp:
+ if not line.startswith('#'):
+ ncpatchfp.write(line)
+ patchfp.close()
+ ncpatchfp.seek(0)
+ newpatches = parsepatch(ncpatchfp)
+ finally:
+ os.unlink(patchfn)
+ del ncpatchfp
+ # Signal that the chunk shouldn't be applied as-is, but
+ # provide the new patch to be used instead.
+ ret = False
+ elif r == 3: # Skip
+ ret = skipfile = False
+ elif r == 4: # file (Record remaining)
+ ret = skipfile = True
+ elif r == 5: # done, skip remaining
+ ret = skipall = False
+ elif r == 6: # all
+ ret = skipall = True
+ elif r == 7: # quit
+ raise util.Abort(_('user quit'))
+ return ret, skipfile, skipall, newpatches
+
+ seen = set()
+ applied = {} # 'filename' -> [] of chunks
+ skipfile, skipall = None, None
+ pos, total = 1, sum(len(h.hunks) for h in headers)
+ for h in headers:
+ pos += len(h.hunks)
+ skipfile = None
+ fixoffset = 0
+ hdr = ''.join(h.header)
+ if hdr in seen:
+ continue
+ seen.add(hdr)
+ if skipall is None:
+ h.pretty(ui)
+ msg = (_('examine changes to %s?') %
+ _(' and ').join("'%s'" % f for f in h.files()))
+ r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
+ if not r:
+ continue
+ applied[h.filename()] = [h]
+ if h.allhunks():
+ applied[h.filename()] += h.hunks
+ continue
+ for i, chunk in enumerate(h.hunks):
+ if skipfile is None and skipall is None:
+ chunk.pretty(ui)
+ if total == 1:
+ msg = _("record this change to '%s'?") % chunk.filename()
+ else:
+ idx = pos - len(h.hunks) + i
+ msg = _("record change %d/%d to '%s'?") % (idx, total,
+ chunk.filename())
+ r, skipfile, skipall, newpatches = prompt(skipfile,
+ skipall, msg, chunk)
+ if r:
+ if fixoffset:
+ chunk = copy.copy(chunk)
+ chunk.toline += fixoffset
+ applied[chunk.filename()].append(chunk)
+ elif newpatches is not None:
+ for newpatch in newpatches:
+ for newhunk in newpatch.hunks:
+ if fixoffset:
+ newhunk.toline += fixoffset
+ applied[newhunk.filename()].append(newhunk)
+ else:
+ fixoffset += chunk.removed - chunk.added
+ return sum([h for h in applied.itervalues()
+ if h[0].special() or len(h) > 1], [])
class hunk(object):
def __init__(self, desc, num, lr, context):
self.number = num
@@ -1087,7 +1343,105 @@
return s
return s[:i]
-def pathstrip(path, strip):
+def parsepatch(fp):
+ """patch -> [] of headers -> [] of hunks """
+ class parser(object):
+ """patch parsing state machine"""
+ def __init__(self):
+ self.fromline = 0
+ self.toline = 0
+ self.proc = ''
+ self.header = None
+ self.context = []
+ self.before = []
+ self.hunk = []
+ self.headers = []
+
+ def addrange(self, limits):
+ fromstart, fromend, tostart, toend, proc = limits
+ self.fromline = int(fromstart)
+ self.toline = int(tostart)
+ self.proc = proc
+
+ def addcontext(self, context):
+ if self.hunk:
+ h = recordhunk(self.header, self.fromline, self.toline,
+ self.proc, self.before, self.hunk, context)
+ self.header.hunks.append(h)
+ self.fromline += len(self.before) + h.removed
+ self.toline += len(self.before) + h.added
+ self.before = []
+ self.hunk = []
+ self.proc = ''
+ self.context = context
+
+ def addhunk(self, hunk):
+ if self.context:
+ self.before = self.context
+ self.context = []
+ self.hunk = hunk
+
+ def newfile(self, hdr):
+ self.addcontext([])
+ h = header(hdr)
+ self.headers.append(h)
+ self.header = h
+
+ def addother(self, line):
+ pass # 'other' lines are ignored
+
+ def finished(self):
+ self.addcontext([])
+ return self.headers
+
+ transitions = {
+ 'file': {'context': addcontext,
+ 'file': newfile,
+ 'hunk': addhunk,
+ 'range': addrange},
+ 'context': {'file': newfile,
+ 'hunk': addhunk,
+ 'range': addrange,
+ 'other': addother},
+ 'hunk': {'context': addcontext,
+ 'file': newfile,
+ 'range': addrange},
+ 'range': {'context': addcontext,
+ 'hunk': addhunk},
+ 'other': {'other': addother},
+ }
+
+ p = parser()
+
+ state = 'context'
+ for newstate, data in scanpatch(fp):
+ try:
+ p.transitions[state][newstate](p, data)
+ except KeyError:
+ raise PatchError('unhandled transition: %s -> %s' %
+ (state, newstate))
+ state = newstate
+ return p.finished()
+
+def pathtransform(path, strip, prefix):
+ '''turn a path from a patch into a path suitable for the repository
+
+ prefix, if not empty, is expected to be normalized with a / at the end.
+
+ Returns (stripped components, path in repository).
+
+ >>> pathtransform('a/b/c', 0, '')
+ ('', 'a/b/c')
+ >>> pathtransform(' a/b/c ', 0, '')
+ ('', ' a/b/c')
+ >>> pathtransform(' a/b/c ', 2, '')
+ ('a/b/', 'c')
+ >>> pathtransform(' a//b/c ', 2, 'd/e/')
+ ('a//b/', 'd/e/c')
+ >>> pathtransform('a/b/c', 3, '')
+ Traceback (most recent call last):
+ PatchError: unable to strip away 1 of 3 dirs from a/b/c
+ '''
pathlen = len(path)
i = 0
if strip == 0:
@@ -1103,16 +1457,16 @@
while i < pathlen - 1 and path[i] == '/':
i += 1
count -= 1
- return path[:i].lstrip(), path[i:].rstrip()
+ return path[:i].lstrip(), prefix + path[i:].rstrip()
-def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
+def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
nulla = afile_orig == "/dev/null"
nullb = bfile_orig == "/dev/null"
create = nulla and hunk.starta == 0 and hunk.lena == 0
remove = nullb and hunk.startb == 0 and hunk.lenb == 0
- abase, afile = pathstrip(afile_orig, strip)
+ abase, afile = pathtransform(afile_orig, strip, prefix)
gooda = not nulla and backend.exists(afile)
- bbase, bfile = pathstrip(bfile_orig, strip)
+ bbase, bfile = pathtransform(bfile_orig, strip, prefix)
if afile == bfile:
goodb = gooda
else:
@@ -1154,6 +1508,58 @@
gp.op = 'DELETE'
return gp
+def scanpatch(fp):
+ """like patch.iterhunks, but yield different events
+
+ - ('file', [header_lines + fromfile + tofile])
+ - ('context', [context_lines])
+ - ('hunk', [hunk_lines])
+ - ('range', (-start,len, +start,len, proc))
+ """
+ lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
+ lr = linereader(fp)
+
+ def scanwhile(first, p):
+ """scan lr while predicate holds"""
+ lines = [first]
+ while True:
+ line = lr.readline()
+ if not line:
+ break
+ if p(line):
+ lines.append(line)
+ else:
+ lr.push(line)
+ break
+ return lines
+
+ while True:
+ line = lr.readline()
+ if not line:
+ break
+ if line.startswith('diff --git a/') or line.startswith('diff -r '):
+ def notheader(line):
+ s = line.split(None, 1)
+ return not s or s[0] not in ('---', 'diff')
+ header = scanwhile(line, notheader)
+ fromfile = lr.readline()
+ if fromfile.startswith('---'):
+ tofile = lr.readline()
+ header += [fromfile, tofile]
+ else:
+ lr.push(fromfile)
+ yield 'file', header
+ elif line[0] == ' ':
+ yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
+ elif line[0] in '-+':
+ yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
+ else:
+ m = lines_re.match(line)
+ if m:
+ yield 'range', m.groups()
+ else:
+ yield 'other', line
+
def scangitpatch(lr, firstline):
"""
Git patches can emit:
@@ -1335,7 +1741,7 @@
raise PatchError(_('unexpected delta opcode 0'))
return out
-def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
+def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
"""Reads a patch from fp and tries to apply it.
Returns 0 for a clean patch, -1 if any rejects were found and 1 if
@@ -1346,13 +1752,16 @@
patching then normalized according to 'eolmode'.
"""
return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
- eolmode=eolmode)
+ prefix=prefix, eolmode=eolmode)
-def _applydiff(ui, fp, patcher, backend, store, strip=1,
+def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
eolmode='strict'):
+ if prefix:
+ # clean up double slashes, lack of trailing slashes, etc
+ prefix = util.normpath(prefix) + '/'
def pstrip(p):
- return pathstrip(p, strip - 1)[1]
+ return pathtransform(p, strip - 1, prefix)[1]
rejects = 0
err = 0
@@ -1375,7 +1784,8 @@
if gp.oldpath:
gp.oldpath = pstrip(gp.oldpath)
else:
- gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
+ gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
+ prefix)
if gp.op == 'RENAME':
backend.unlink(gp.oldpath)
if not first_hunk:
@@ -1472,7 +1882,8 @@
util.explainexit(code)[0])
return fuzz
-def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
+def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
+ eolmode='strict'):
if files is None:
files = set()
if eolmode is None:
@@ -1487,7 +1898,7 @@
except TypeError:
fp = patchobj
try:
- ret = applydiff(ui, fp, backend, store, strip=strip,
+ ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
eolmode=eolmode)
finally:
if fp != patchobj:
@@ -1498,19 +1909,19 @@
raise PatchError(_('patch failed to apply'))
return ret > 0
-def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
- similarity=0):
+def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
+ eolmode='strict', similarity=0):
"""use builtin patch to apply <patchobj> to the working directory.
returns whether patch was applied with fuzz factor."""
backend = workingbackend(ui, repo, similarity)
- return patchbackend(ui, backend, patchobj, strip, files, eolmode)
+ return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
-def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
+def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
eolmode='strict'):
backend = repobackend(ui, repo, ctx, store)
- return patchbackend(ui, backend, patchobj, strip, files, eolmode)
+ return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
-def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
+def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
similarity=0):
"""Apply <patchname> to the working directory.
@@ -1529,7 +1940,7 @@
if patcher:
return _externalpatch(ui, repo, patcher, patchname, strip,
files, similarity)
- return internalpatch(ui, repo, patchname, strip, files, eolmode,
+ return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
similarity)
def changedfiles(ui, repo, patchpath, strip=1):
@@ -1541,11 +1952,12 @@
if state == 'file':
afile, bfile, first_hunk, gp = values
if gp:
- gp.path = pathstrip(gp.path, strip - 1)[1]
+ gp.path = pathtransform(gp.path, strip - 1, '')[1]
if gp.oldpath:
- gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
+ gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
else:
- gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
+ gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
+ '')
changed.add(gp.path)
if gp.op == 'RENAME':
changed.add(gp.oldpath)
@@ -1736,20 +2148,47 @@
'''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
return difflabel(diff, *args, **kw)
+def _filepairs(ctx1, modified, added, removed, copy, opts):
+ '''generates tuples (f1, f2, copyop), where f1 is the name of the file
+ before and f2 is the the name after. For added files, f1 will be None,
+ and for removed files, f2 will be None. copyop may be set to None, 'copy'
+ or 'rename' (the latter two only if opts.git is set).'''
+ gone = set()
+
+ copyto = dict([(v, k) for k, v in copy.items()])
+
+ addedset, removedset = set(added), set(removed)
+ # Fix up added, since merged-in additions appear as
+ # modifications during merges
+ for f in modified:
+ if f not in ctx1:
+ addedset.add(f)
+
+ for f in sorted(modified + added + removed):
+ copyop = None
+ f1, f2 = f, f
+ if f in addedset:
+ f1 = None
+ if f in copy:
+ if opts.git:
+ f1 = copy[f]
+ if f1 in removedset and f1 not in gone:
+ copyop = 'rename'
+ gone.add(f1)
+ else:
+ copyop = 'copy'
+ elif f in removedset:
+ f2 = None
+ if opts.git:
+ # have we already reported a copy above?
+ if (f in copyto and copyto[f] in addedset
+ and copy[copyto[f]] == f):
+ continue
+ yield f1, f2, copyop
+
def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
copy, getfilectx, opts, losedatafn, prefix):
- def join(f):
- return posixpath.join(prefix, f)
-
- def addmodehdr(header, omode, nmode):
- if omode != nmode:
- header.append('old mode %s\n' % omode)
- header.append('new mode %s\n' % nmode)
-
- def addindexmeta(meta, oindex, nindex):
- meta.append('index %s..%s\n' % (oindex, nindex))
-
def gitindex(text):
if not text:
text = ""
@@ -1764,120 +2203,79 @@
aprefix = 'a/'
bprefix = 'b/'
- def diffline(a, b, revs):
- if opts.git:
- line = 'diff --git %s%s %s%s\n' % (aprefix, a, bprefix, b)
- elif not repo.ui.quiet:
- if revs:
- revinfo = ' '.join(["-r %s" % rev for rev in revs])
- line = 'diff %s %s\n' % (revinfo, a)
- else:
- line = 'diff %s\n' % a
- else:
- line = ''
- return line
+ def diffline(f, revs):
+ revinfo = ' '.join(["-r %s" % rev for rev in revs])
+ return 'diff %s %s' % (revinfo, f)
date1 = util.datestr(ctx1.date())
date2 = util.datestr(ctx2.date())
- gone = set()
gitmode = {'l': '120000', 'x': '100755', '': '100644'}
- copyto = dict([(v, k) for k, v in copy.items()])
-
- if opts.git:
- revs = None
-
- modifiedset, addedset, removedset = set(modified), set(added), set(removed)
- # Fix up modified and added, since merged-in additions appear as
- # modifications during merges
- for f in modifiedset.copy():
- if f not in ctx1:
- addedset.add(f)
- modifiedset.remove(f)
- for f in sorted(modified + added + removed):
- to = None
- tn = None
- binarydiff = False
- header = []
- if f not in addedset:
- to = getfilectx(f, ctx1).data()
- if f not in removedset:
- tn = getfilectx(f, ctx2).data()
- a, b = f, f
+ for f1, f2, copyop in _filepairs(
+ ctx1, modified, added, removed, copy, opts):
+ content1 = None
+ content2 = None
+ flag1 = None
+ flag2 = None
+ if f1:
+ content1 = getfilectx(f1, ctx1).data()
+ if opts.git or losedatafn:
+ flag1 = ctx1.flags(f1)
+ if f2:
+ content2 = getfilectx(f2, ctx2).data()
+ if opts.git or losedatafn:
+ flag2 = ctx2.flags(f2)
+ binary = False
if opts.git or losedatafn:
- if f in addedset:
- mode = gitmode[ctx2.flags(f)]
- if f in copy or f in copyto:
- if opts.git:
- if f in copy:
- a = copy[f]
- else:
- a = copyto[f]
- omode = gitmode[ctx1.flags(a)]
- addmodehdr(header, omode, mode)
- if a in removedset and a not in gone:
- op = 'rename'
- gone.add(a)
- else:
- op = 'copy'
- header.append('%s from %s\n' % (op, join(a)))
- header.append('%s to %s\n' % (op, join(f)))
- to = getfilectx(a, ctx1).data()
- else:
- losedatafn(f)
- else:
- if opts.git:
- header.append('new file mode %s\n' % mode)
- elif ctx2.flags(f):
- losedatafn(f)
- if util.binary(to) or util.binary(tn):
- if opts.git:
- binarydiff = True
- else:
- losedatafn(f)
- if not opts.git and not tn:
- # regular diffs cannot represent new empty file
- losedatafn(f)
- elif f in removedset:
- if opts.git:
- # have we already reported a copy above?
- if ((f in copy and copy[f] in addedset
- and copyto[copy[f]] == f) or
- (f in copyto and copyto[f] in addedset
- and copy[copyto[f]] == f)):
- continue
- else:
- header.append('deleted file mode %s\n' %
- gitmode[ctx1.flags(f)])
- if util.binary(to):
- binarydiff = True
- elif not to or util.binary(to):
- # regular diffs cannot represent empty file deletion
- losedatafn(f)
- else:
- oflag = ctx1.flags(f)
- nflag = ctx2.flags(f)
- binary = util.binary(to) or util.binary(tn)
- if opts.git:
- addmodehdr(header, gitmode[oflag], gitmode[nflag])
- if binary:
- binarydiff = True
- elif binary or nflag != oflag:
- losedatafn(f)
+ binary = util.binary(content1) or util.binary(content2)
+
+ if losedatafn and not opts.git:
+ if (binary or
+ # copy/rename
+ f2 in copy or
+ # empty file creation
+ (not f1 and not content2) or
+ # empty file deletion
+ (not content1 and not f2) or
+ # create with flags
+ (not f1 and flag2) or
+ # change flags
+ (f1 and f2 and flag1 != flag2)):
+ losedatafn(f2 or f1)
- if opts.git or revs:
- header.insert(0, diffline(join(a), join(b), revs))
- if binarydiff and not opts.nobinary:
- text = mdiff.b85diff(to, tn)
- if text and opts.git:
- addindexmeta(header, gitindex(to), gitindex(tn))
+ path1 = posixpath.join(prefix, f1 or f2)
+ path2 = posixpath.join(prefix, f2 or f1)
+ header = []
+ if opts.git:
+ header.append('diff --git %s%s %s%s' %
+ (aprefix, path1, bprefix, path2))
+ if not f1: # added
+ header.append('new file mode %s' % gitmode[flag2])
+ elif not f2: # removed
+ header.append('deleted file mode %s' % gitmode[flag1])
+ else: # modified/copied/renamed
+ mode1, mode2 = gitmode[flag1], gitmode[flag2]
+ if mode1 != mode2:
+ header.append('old mode %s' % mode1)
+ header.append('new mode %s' % mode2)
+ if copyop is not None:
+ header.append('%s from %s' % (copyop, path1))
+ header.append('%s to %s' % (copyop, path2))
+ elif revs and not repo.ui.quiet:
+ header.append(diffline(path1, revs))
+
+ if binary and opts.git and not opts.nobinary:
+ text = mdiff.b85diff(content1, content2)
+ if text:
+ header.append('index %s..%s' %
+ (gitindex(content1), gitindex(content2)))
else:
- text = mdiff.unidiff(to, date1,
- tn, date2,
- join(a), join(b), opts=opts)
+ text = mdiff.unidiff(content1, date1,
+ content2, date2,
+ path1, path2, opts=opts)
if header and (text or len(header) > 1):
- yield ''.join(header)
+ yield '\n'.join(header) + '\n'
if text:
yield text
--- a/mercurial/pure/parsers.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/pure/parsers.py Fri Mar 13 17:55:04 2015 -0500
@@ -5,7 +5,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-from mercurial.node import bin, nullid
+from mercurial.node import nullid
from mercurial import util
import struct, zlib, cStringIO
@@ -21,15 +21,6 @@
# x is a tuple
return x
-def parse_manifest(mfdict, fdict, lines):
- for l in lines.splitlines():
- f, n = l.split('\0')
- if len(n) > 40:
- fdict[f] = n[40:]
- mfdict[f] = bin(n[:40])
- else:
- mfdict[f] = bin(n)
-
def parse_index2(data, inline):
def gettype(q):
return int(q & 0xFFFF)
--- a/mercurial/repair.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/repair.py Fri Mar 13 17:55:04 2015 -0500
@@ -137,6 +137,7 @@
# create a changegroup for all the branches we need to keep
backupfile = None
vfs = repo.vfs
+ node = nodelist[-1]
if backup:
backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
repo.ui.status(_("saved backup bundle to %s\n") %
@@ -181,6 +182,8 @@
repo.ui.pushbuffer()
if isinstance(gen, bundle2.unbundle20):
tr = repo.transaction('strip')
+ tr.hookargs = {'source': 'strip',
+ 'url': 'bundle:' + vfs.join(chgrpfile)}
try:
bundle2.processbundle(repo, gen, lambda: tr)
tr.close()
--- a/mercurial/revlog.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/revlog.py Fri Mar 13 17:55:04 2015 -0500
@@ -277,6 +277,8 @@
def tip(self):
return self.node(len(self.index) - 2)
+ def __contains__(self, rev):
+ return 0 <= rev < len(self)
def __len__(self):
return len(self.index) - 1
def __iter__(self):
@@ -1231,8 +1233,18 @@
if dfh:
dfh.flush()
ifh.flush()
- basetext = self.revision(self.node(cachedelta[0]))
- btext[0] = mdiff.patch(basetext, cachedelta[1])
+ baserev = cachedelta[0]
+ delta = cachedelta[1]
+ # special case deltas which replace entire base; no need to decode
+ # base revision. this neatly avoids censored bases, which throw when
+ # they're decoded.
+ hlen = struct.calcsize(">lll")
+ if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
+ len(delta) - hlen):
+ btext[0] = delta[hlen:]
+ else:
+ basetext = self.revision(self.node(baserev))
+ btext[0] = mdiff.patch(basetext, delta)
try:
self.checkhash(btext[0], p1, p2, node)
if flags & REVIDX_ISCENSORED:
@@ -1249,8 +1261,14 @@
delta = cachedelta[1]
else:
t = buildtext()
- ptext = self.revision(self.node(rev))
- delta = mdiff.textdiff(ptext, t)
+ if self.iscensored(rev):
+ # deltas based on a censored revision must replace the
+ # full content in one patch, so delta works everywhere
+ header = mdiff.replacediffheader(self.rawsize(rev), len(t))
+ delta = header + t
+ else:
+ ptext = self.revision(self.node(rev))
+ delta = mdiff.textdiff(ptext, t)
data = self.compress(delta)
l = len(data[1]) + len(data[0])
if basecache[0] == rev:
@@ -1368,7 +1386,10 @@
transaction.add(self.indexfile, isize, r)
transaction.add(self.datafile, end)
dfh = self.opener(self.datafile, "a")
-
+ def flush():
+ if dfh:
+ dfh.flush()
+ ifh.flush()
try:
# loop through our set of deltas
chain = None
@@ -1401,9 +1422,24 @@
_('unknown delta base'))
baserev = self.rev(deltabase)
+
+ if baserev != nullrev and self.iscensored(baserev):
+ # if base is censored, delta must be full replacement in a
+ # single patch operation
+ hlen = struct.calcsize(">lll")
+ oldlen = self.rawsize(baserev)
+ newlen = len(delta) - hlen
+ if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
+ raise error.CensoredBaseError(self.indexfile,
+ self.node(baserev))
+
+ flags = REVIDX_DEFAULT_FLAGS
+ if self._peek_iscensored(baserev, delta, flush):
+ flags |= REVIDX_ISCENSORED
+
chain = self._addrevision(node, None, transaction, link,
- p1, p2, REVIDX_DEFAULT_FLAGS,
- (baserev, delta), ifh, dfh)
+ p1, p2, flags, (baserev, delta),
+ ifh, dfh)
if not dfh and not self._inline:
# addrevision switched from inline to conventional
# reopen the index
@@ -1417,6 +1453,14 @@
return content
+ def iscensored(self, rev):
+ """Check if a file revision is censored."""
+ return False
+
+ def _peek_iscensored(self, baserev, delta, flush):
+ """Quickly check if a delta produces a censored revision."""
+ return False
+
def getstrippoint(self, minlink):
"""find the minimum rev that must be stripped to strip the linkrev
--- a/mercurial/revset.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/revset.py Fri Mar 13 17:55:04 2015 -0500
@@ -323,8 +323,6 @@
def stringset(repo, subset, x):
x = repo[x].rev()
- if x == -1 and len(subset) == len(repo):
- return baseset([-1])
if x in subset:
return baseset([x])
return baseset()
@@ -349,7 +347,7 @@
return r & subset
def dagrange(repo, subset, x, y):
- r = spanset(repo)
+ r = fullreposet(repo)
xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
return xs & subset
@@ -370,7 +368,7 @@
def func(repo, subset, a, b):
if a[0] == 'symbol' and a[1] in symbols:
return symbols[a[1]](repo, subset, b)
- raise error.ParseError(_("not a function: %s") % a[1])
+ raise error.UnknownIdentifier(a[1], symbols.keys())
# functions
@@ -396,7 +394,7 @@
"""
# i18n: "ancestor" is a keyword
l = getlist(x)
- rl = spanset(repo)
+ rl = fullreposet(repo)
anc = None
# (getset(repo, rl, i) for i in l) generates a list of lists
@@ -412,7 +410,7 @@
return baseset()
def _ancestors(repo, subset, x, followfirst=False):
- heads = getset(repo, spanset(repo), x)
+ heads = getset(repo, fullreposet(repo), x)
if not heads:
return baseset()
s = _revancestors(repo, heads, followfirst)
@@ -544,7 +542,7 @@
else:
return subset.filter(lambda r: matcher(getbi(ucl, r)[0]))
- s = getset(repo, spanset(repo), x)
+ s = getset(repo, fullreposet(repo), x)
b = set()
for r in s:
b.add(getbi(ucl, r)[0])
@@ -708,7 +706,7 @@
return subset.filter(matches)
def _descendants(repo, subset, x, followfirst=False):
- roots = getset(repo, spanset(repo), x)
+ roots = getset(repo, fullreposet(repo), x)
if not roots:
return baseset()
s = _revdescendants(repo, roots, followfirst)
@@ -744,9 +742,9 @@
is the same as passing all().
"""
if x is not None:
- sources = getset(repo, spanset(repo), x)
+ sources = getset(repo, fullreposet(repo), x)
else:
- sources = getall(repo, spanset(repo), x)
+ sources = fullreposet(repo)
dests = set()
@@ -994,7 +992,7 @@
"""
# i18n: "all" is a keyword
getargs(x, 0, 0, _("all takes no arguments"))
- return subset
+ return subset & spanset(repo) # drop "null" if any
def grep(repo, subset, x):
"""``grep(regex)``
@@ -1145,7 +1143,7 @@
# i18n: "limit" is a keyword
raise error.ParseError(_("limit expects a number"))
ss = subset
- os = getset(repo, spanset(repo), l[0])
+ os = getset(repo, fullreposet(repo), l[0])
result = []
it = iter(os)
for x in xrange(lim):
@@ -1172,7 +1170,7 @@
# i18n: "last" is a keyword
raise error.ParseError(_("last expects a number"))
ss = subset
- os = getset(repo, spanset(repo), l[0])
+ os = getset(repo, fullreposet(repo), l[0])
os.reverse()
result = []
it = iter(os)
@@ -1189,7 +1187,7 @@
"""``max(set)``
Changeset with highest revision number in set.
"""
- os = getset(repo, spanset(repo), x)
+ os = getset(repo, fullreposet(repo), x)
if os:
m = os.max()
if m in subset:
@@ -1226,7 +1224,7 @@
"""``min(set)``
Changeset with lowest revision number in set.
"""
- os = getset(repo, spanset(repo), x)
+ os = getset(repo, fullreposet(repo), x)
if os:
m = os.min()
if m in subset:
@@ -1322,7 +1320,7 @@
cl = repo.changelog
# i18n: "only" is a keyword
args = getargs(x, 1, 2, _('only takes one or two arguments'))
- include = getset(repo, spanset(repo), args[0])
+ include = getset(repo, fullreposet(repo), args[0])
if len(args) == 1:
if not include:
return baseset()
@@ -1331,7 +1329,7 @@
exclude = [rev for rev in cl.headrevs()
if not rev in descendants and not rev in include]
else:
- exclude = getset(repo, spanset(repo), args[1])
+ exclude = getset(repo, fullreposet(repo), args[1])
results = set(cl.findmissingrevs(common=exclude, heads=include))
return subset & results
@@ -1345,9 +1343,9 @@
for the first operation is selected.
"""
if x is not None:
- dests = getset(repo, spanset(repo), x)
+ dests = getset(repo, fullreposet(repo), x)
else:
- dests = getall(repo, spanset(repo), x)
+ dests = fullreposet(repo)
def _firstsrc(rev):
src = _getrevsource(repo, rev)
@@ -1400,7 +1398,7 @@
ps = set()
cl = repo.changelog
- for r in getset(repo, spanset(repo), x):
+ for r in getset(repo, fullreposet(repo), x):
ps.add(cl.parentrevs(r)[0])
ps -= set([node.nullrev])
return subset & ps
@@ -1421,7 +1419,7 @@
ps = set()
cl = repo.changelog
- for r in getset(repo, spanset(repo), x):
+ for r in getset(repo, fullreposet(repo), x):
ps.add(cl.parentrevs(r)[1])
ps -= set([node.nullrev])
return subset & ps
@@ -1435,7 +1433,7 @@
else:
ps = set()
cl = repo.changelog
- for r in getset(repo, spanset(repo), x):
+ for r in getset(repo, fullreposet(repo), x):
ps.update(cl.parentrevs(r))
ps -= set([node.nullrev])
return subset & ps
@@ -1548,7 +1546,7 @@
except (TypeError, ValueError):
# i18n: "rev" is a keyword
raise error.ParseError(_("rev expects a number"))
- if l not in fullreposet(repo) and l != node.nullrev:
+ if l not in repo.changelog and l != node.nullrev:
return baseset()
return subset & baseset([l])
@@ -1676,7 +1674,7 @@
"""``roots(set)``
Changesets in set with no parent changeset in set.
"""
- s = getset(repo, spanset(repo), x)
+ s = getset(repo, fullreposet(repo), x)
subset = baseset([r for r in s if r in subset])
cs = _children(repo, subset, s)
return subset - cs
@@ -2153,7 +2151,7 @@
if isinstance(tree, tuple):
arg = _getaliasarg(tree)
if arg is not None and (not known or arg not in known):
- raise error.ParseError(_("not a function: %s") % '_aliasarg')
+ raise error.UnknownIdentifier('_aliasarg', [])
for t in tree:
_checkaliasarg(t, known)
@@ -2243,6 +2241,71 @@
except error.ParseError, inst:
return (decl, None, None, parseerrordetail(inst))
+def _parsealiasdefn(defn, args):
+ """Parse alias definition ``defn``
+
+ This function also replaces alias argument references in the
+ specified definition by ``_aliasarg(ARGNAME)``.
+
+ ``args`` is a list of alias argument names, or None if the alias
+ is declared as a symbol.
+
+ This returns "tree" as parsing result.
+
+ >>> args = ['$1', '$2', 'foo']
+ >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
+ (or
+ (func
+ ('symbol', '_aliasarg')
+ ('string', '$1'))
+ (func
+ ('symbol', '_aliasarg')
+ ('string', 'foo')))
+ >>> try:
+ ... _parsealiasdefn('$1 or $bar', args)
+ ... except error.ParseError, inst:
+ ... print parseerrordetail(inst)
+ at 6: '$' not for alias arguments
+ >>> args = ['$1', '$10', 'foo']
+ >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
+ (or
+ (func
+ ('symbol', '_aliasarg')
+ ('string', '$10'))
+ ('symbol', 'foobar'))
+ >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
+ (or
+ ('string', '$1')
+ ('string', 'foo'))
+ """
+ def tokenizedefn(program, lookup=None):
+ if args:
+ argset = set(args)
+ else:
+ argset = set()
+
+ for t, value, pos in _tokenizealias(program, lookup=lookup):
+ if t == 'symbol':
+ if value in argset:
+ # emulate tokenization of "_aliasarg('ARGNAME')":
+ # "_aliasarg()" is an unknown symbol only used separate
+ # alias argument placeholders from regular strings.
+ yield ('symbol', '_aliasarg', pos)
+ yield ('(', None, pos)
+ yield ('string', value, pos)
+ yield (')', None, pos)
+ continue
+ elif value.startswith('$'):
+ raise error.ParseError(_("'$' not for alias arguments"),
+ pos)
+ yield (t, value, pos)
+
+ p = parser.parser(tokenizedefn, elements)
+ tree, pos = p.parse(defn)
+ if pos != len(defn):
+ raise error.ParseError(_('invalid token'), pos)
+ return tree
+
class revsetalias(object):
# whether own `error` information is already shown or not.
# this avoids showing same warning multiple times at each `findaliases`.
@@ -2260,16 +2323,8 @@
' "%s": %s') % (self.name, self.error)
return
- if self.args:
- for arg in self.args:
- # _aliasarg() is an unknown symbol only used separate
- # alias argument placeholders from regular strings.
- value = value.replace(arg, '_aliasarg(%r)' % (arg,))
-
try:
- self.replacement, pos = parse(value)
- if pos != len(value):
- raise error.ParseError(_('invalid token'), pos)
+ self.replacement = _parsealiasdefn(value, self.args)
# Check for placeholder injection
_checkaliasarg(self.replacement, self.args)
except error.ParseError, inst:
@@ -2392,7 +2447,9 @@
tree = findaliases(ui, tree, showwarning=ui.warn)
tree = foldconcat(tree)
weight, tree = optimize(tree, True)
- def mfunc(repo, subset):
+ def mfunc(repo, subset=None):
+ if subset is None:
+ subset = fullreposet(repo)
if util.safehasattr(subset, 'isascending'):
result = getset(repo, subset, tree)
else:
@@ -3146,18 +3203,7 @@
return it().next()
return None
-def spanset(repo, start=None, end=None):
- """factory function to dispatch between fullreposet and actual spanset
-
- Feel free to update all spanset call sites and kill this function at some
- point.
- """
- if start is None and end is None:
- return fullreposet(repo)
- return _spanset(repo, start, end)
-
-
-class _spanset(abstractsmartset):
+class spanset(abstractsmartset):
"""Duck type for baseset class which represents a range of revisions and
can work lazily and without having all the range in memory
@@ -3261,15 +3307,21 @@
return x
return None
-class fullreposet(_spanset):
+class fullreposet(spanset):
"""a set containing all revisions in the repo
- This class exists to host special optimization.
+ This class exists to host special optimization and magic to handle virtual
+ revisions such as "null".
"""
def __init__(self, repo):
super(fullreposet, self).__init__(repo)
+ def __contains__(self, rev):
+ # assumes the given rev is valid
+ hidden = self._hiddenrevs
+ return not (hidden and rev in hidden)
+
def __and__(self, other):
"""As self contains the whole repo, all of the other set should also be
in self. Therefore `self & other = other`.
--- a/mercurial/scmutil.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/scmutil.py Fri Mar 13 17:55:04 2015 -0500
@@ -628,12 +628,22 @@
return repo[val].rev()
seen, l = set(), revset.baseset([])
+
+ revsetaliases = [alias for (alias, _) in
+ repo.ui.configitems("revsetalias")]
+
for spec in revs:
if l and not seen:
seen = set(l)
# attempt to parse old-style ranges first to deal with
# things like old-tag which contain query metacharacters
try:
+ # ... except for revset aliases without arguments. These
+ # should be parsed as soon as possible, because they might
+ # clash with a hash prefix.
+ if spec in revsetaliases:
+ raise error.RepoLookupError
+
if isinstance(spec, int):
seen.add(spec)
l = l + revset.baseset([spec])
@@ -641,6 +651,9 @@
if _revrangesep in spec:
start, end = spec.split(_revrangesep, 1)
+ if start in revsetaliases or end in revsetaliases:
+ raise error.RepoLookupError
+
start = revfix(repo, start, 0)
end = revfix(repo, end, len(repo) - 1)
if end == nullrev and start < 0:
@@ -672,11 +685,11 @@
# fall through to new-style queries if old-style fails
m = revset.match(repo.ui, spec, repo)
if seen or l:
- dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
+ dl = [r for r in m(repo) if r not in seen]
l = l + revset.baseset(dl)
seen.update(dl)
else:
- l = m(repo, revset.spanset(repo))
+ l = m(repo)
return l
--- a/mercurial/sslutil.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/sslutil.py Fri Mar 13 17:55:04 2015 -0500
@@ -10,12 +10,16 @@
from mercurial import util
from mercurial.i18n import _
+
+_canloaddefaultcerts = False
try:
# avoid using deprecated/broken FakeSocket in python 2.6
import ssl
CERT_REQUIRED = ssl.CERT_REQUIRED
try:
ssl_context = ssl.SSLContext
+ _canloaddefaultcerts = util.safehasattr(ssl_context,
+ 'load_default_certs')
def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
ca_certs=None, serverhostname=None):
@@ -35,6 +39,8 @@
sslcontext.verify_mode = cert_reqs
if ca_certs is not None:
sslcontext.load_verify_locations(cafile=ca_certs)
+ elif _canloaddefaultcerts:
+ sslcontext.load_default_certs()
sslsocket = sslcontext.wrap_socket(sock,
server_hostname=serverhostname)
@@ -129,23 +135,34 @@
return (exe.startswith('/usr/bin/python') or
exe.startswith('/system/library/frameworks/python.framework/'))
+def _defaultcacerts():
+ """return path to CA certificates; None for system's store; ! to disable"""
+ if _plainapplepython():
+ dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
+ if os.path.exists(dummycert):
+ return dummycert
+ if _canloaddefaultcerts:
+ return None
+ return '!'
+
def sslkwargs(ui, host):
kws = {}
hostfingerprint = ui.config('hostfingerprints', host)
if hostfingerprint:
return kws
cacerts = ui.config('web', 'cacerts')
- if cacerts:
+ if cacerts == '!':
+ pass
+ elif cacerts:
cacerts = util.expandpath(cacerts)
if not os.path.exists(cacerts):
raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
- elif cacerts is None and _plainapplepython():
- dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
- if os.path.exists(dummycert):
- ui.debug('using %s to enable OS X system CA\n' % dummycert)
- ui.setconfig('web', 'cacerts', dummycert, 'dummy')
- cacerts = dummycert
- if cacerts:
+ else:
+ cacerts = _defaultcacerts()
+ if cacerts and cacerts != '!':
+ ui.debug('using %s to enable OS X system CA\n' % cacerts)
+ ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
+ if cacerts != '!':
kws.update({'ca_certs': cacerts,
'cert_reqs': CERT_REQUIRED,
})
@@ -194,7 +211,7 @@
hint=_('check hostfingerprint configuration'))
self.ui.debug('%s certificate matched fingerprint %s\n' %
(host, nicefingerprint))
- elif cacerts:
+ elif cacerts != '!':
msg = _verifycert(peercert2, host)
if msg:
raise util.Abort(_('%s certificate error: %s') % (host, msg),
--- a/mercurial/subrepo.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/subrepo.py Fri Mar 13 17:55:04 2015 -0500
@@ -127,7 +127,7 @@
src = src.lstrip() # strip any extra whitespace after ']'
if not util.url(src).isabs():
- parent = _abssource(ctx._repo, abort=False)
+ parent = _abssource(ctx.repo(), abort=False)
if parent:
parent = util.url(parent)
parent.path = posixpath.join(parent.path or '', src)
@@ -332,7 +332,7 @@
import hg as h
hg = h
- pathutil.pathauditor(ctx._repo.root)(path)
+ pathutil.pathauditor(ctx.repo().root)(path)
state = ctx.substate[path]
if state[2] not in types:
raise util.Abort(_('unknown subrepo type %s') % state[2])
@@ -516,10 +516,10 @@
class hgsubrepo(abstractsubrepo):
def __init__(self, ctx, path, state):
- super(hgsubrepo, self).__init__(ctx._repo.ui)
+ super(hgsubrepo, self).__init__(ctx.repo().ui)
self._path = path
self._state = state
- r = ctx._repo
+ r = ctx.repo()
root = r.wjoin(path)
create = not r.wvfs.exists('%s/.hg' % path)
self._repo = hg.repository(r.baseui, root, create=create)
@@ -626,6 +626,7 @@
os.path.join(prefix, self._path), explicitonly,
**opts)
+ @annotatesubrepoerror
def addremove(self, m, prefix, opts, dry_run, similarity):
# In the same way as sub directories are processed, once in a subrepo,
# always entry any of its subrepos. Don't corrupt the options that will
@@ -835,7 +836,7 @@
def files(self):
rev = self._state[1]
ctx = self._repo[rev]
- return ctx.manifest()
+ return ctx.manifest().keys()
def filedata(self, name):
rev = self._state[1]
@@ -877,13 +878,11 @@
opts['date'] = None
opts['rev'] = substate[1]
- pats = []
- if not opts.get('all'):
- pats = ['set:modified()']
self.filerevert(*pats, **opts)
# Update the repo to the revision specified in the given substate
- self.get(substate, overwrite=True)
+ if not opts.get('dry_run'):
+ self.get(substate, overwrite=True)
def filerevert(self, *pats, **opts):
ctx = self._repo[opts['rev']]
@@ -899,7 +898,7 @@
class svnsubrepo(abstractsubrepo):
def __init__(self, ctx, path, state):
- super(svnsubrepo, self).__init__(ctx._repo.ui)
+ super(svnsubrepo, self).__init__(ctx.repo().ui)
self._path = path
self._state = state
self._ctx = ctx
@@ -923,7 +922,7 @@
cmd.append('--non-interactive')
cmd.extend(commands)
if filename is not None:
- path = os.path.join(self._ctx._repo.origroot, self._path, filename)
+ path = os.path.join(self._ctx.repo().origroot, self._path, filename)
cmd.append(path)
env = dict(os.environ)
# Avoid localized output, preserve current locale for everything else.
@@ -1065,7 +1064,7 @@
os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
os.remove(path)
- path = self._ctx._repo.wjoin(self._path)
+ path = self._ctx.repo().wjoin(self._path)
shutil.rmtree(path, onerror=onerror)
try:
os.removedirs(os.path.dirname(path))
@@ -1083,7 +1082,7 @@
# update to a directory which has since been deleted and recreated.
args.append('%s@%s' % (state[0], state[1]))
status, err = self._svncommand(args, failok=True)
- _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
+ _sanitize(self.ui, self._ctx.repo().wjoin(self._path), '.svn')
if not re.search('Checked out revision [0-9]+.', status):
if ('is already a working copy for a different URL' in err
and (self._wcchanged()[:2] == (False, False))):
@@ -1129,13 +1128,13 @@
class gitsubrepo(abstractsubrepo):
def __init__(self, ctx, path, state):
- super(gitsubrepo, self).__init__(ctx._repo.ui)
+ super(gitsubrepo, self).__init__(ctx.repo().ui)
self._state = state
self._ctx = ctx
self._path = path
- self._relpath = os.path.join(reporelpath(ctx._repo), path)
- self._abspath = ctx._repo.wjoin(path)
- self._subparent = ctx._repo
+ self._relpath = os.path.join(reporelpath(ctx.repo()), path)
+ self._abspath = ctx.repo().wjoin(path)
+ self._subparent = ctx.repo()
self._ensuregit()
def _ensuregit(self):
@@ -1524,6 +1523,47 @@
return False
@annotatesubrepoerror
+ def add(self, ui, match, prefix, explicitonly, **opts):
+ if self._gitmissing():
+ return []
+
+ (modified, added, removed,
+ deleted, unknown, ignored, clean) = self.status(None, unknown=True,
+ clean=True)
+
+ tracked = set()
+ # dirstates 'amn' warn, 'r' is added again
+ for l in (modified, added, deleted, clean):
+ tracked.update(l)
+
+ # Unknown files not of interest will be rejected by the matcher
+ files = unknown
+ files.extend(match.files())
+
+ rejected = []
+
+ files = [f for f in sorted(set(files)) if match(f)]
+ for f in files:
+ exact = match.exact(f)
+ command = ["add"]
+ if exact:
+ command.append("-f") #should be added, even if ignored
+ if ui.verbose or not exact:
+ ui.status(_('adding %s\n') % match.rel(f))
+
+ if f in tracked: # hg prints 'adding' even if already tracked
+ if exact:
+ rejected.append(f)
+ continue
+ if not opts.get('dry_run'):
+ self._gitcommand(command + [f])
+
+ for f in rejected:
+ ui.warn(_("%s already tracked!\n") % match.abs(f))
+
+ return rejected
+
+ @annotatesubrepoerror
def remove(self):
if self._gitmissing():
return
@@ -1577,11 +1617,30 @@
@annotatesubrepoerror
+ def cat(self, match, prefix, **opts):
+ rev = self._state[1]
+ if match.anypats():
+ return 1 #No support for include/exclude yet
+
+ if not match.files():
+ return 1
+
+ for f in match.files():
+ output = self._gitcommand(["show", "%s:%s" % (rev, f)])
+ fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
+ self._ctx.node(),
+ pathname=os.path.join(prefix, f))
+ fp.write(output)
+ fp.close()
+ return 0
+
+
+ @annotatesubrepoerror
def status(self, rev2, **opts):
rev1 = self._state[1]
if self._gitmissing() or not rev1:
# if the repo is missing, return no results
- return [], [], [], [], [], [], []
+ return scmutil.status([], [], [], [], [], [], [])
modified, added, removed = [], [], []
self._gitupdatestat()
if rev2:
@@ -1603,13 +1662,42 @@
deleted, unknown, ignored, clean = [], [], [], []
- if not rev2:
- command = ['ls-files', '--others', '--exclude-standard']
- out = self._gitcommand(command)
- for line in out.split('\n'):
- if len(line) == 0:
- continue
- unknown.append(line)
+ command = ['status', '--porcelain', '-z']
+ if opts.get('unknown'):
+ command += ['--untracked-files=all']
+ if opts.get('ignored'):
+ command += ['--ignored']
+ out = self._gitcommand(command)
+
+ changedfiles = set()
+ changedfiles.update(modified)
+ changedfiles.update(added)
+ changedfiles.update(removed)
+ for line in out.split('\0'):
+ if not line:
+ continue
+ st = line[0:2]
+ #moves and copies show 2 files on one line
+ if line.find('\0') >= 0:
+ filename1, filename2 = line[3:].split('\0')
+ else:
+ filename1 = line[3:]
+ filename2 = None
+
+ changedfiles.add(filename1)
+ if filename2:
+ changedfiles.add(filename2)
+
+ if st == '??':
+ unknown.append(filename1)
+ elif st == '!!':
+ ignored.append(filename1)
+
+ if opts.get('clean'):
+ out = self._gitcommand(['ls-files'])
+ for f in out.split('\n'):
+ if not f in changedfiles:
+ clean.append(f)
return scmutil.status(modified, added, removed, deleted,
unknown, ignored, clean)
@@ -1673,7 +1761,8 @@
util.rename(os.path.join(self._abspath, name),
os.path.join(self._abspath, bakname))
- self.get(substate, overwrite=True)
+ if not opts.get('dry_run'):
+ self.get(substate, overwrite=True)
return []
def shortid(self, revid):
--- a/mercurial/tags.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/tags.py Fri Mar 13 17:55:04 2015 -0500
@@ -337,7 +337,7 @@
# them local encoding on input, we would lose info writing them to
# the cache.
cachefile.write('\n')
- for (name, (node, hist)) in cachetags.iteritems():
+ for (name, (node, hist)) in sorted(cachetags.iteritems()):
for n in hist:
cachefile.write("%s %s\n" % (hex(n), name))
cachefile.write("%s %s\n" % (hex(node), name))
--- a/mercurial/templatekw.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templatekw.py Fri Mar 13 17:55:04 2015 -0500
@@ -12,11 +12,15 @@
# This helper class allows us to handle both:
# "{files}" (legacy command-line-specific list hack) and
# "{files % '{file}\n'}" (hgweb-style with inlining and function support)
+# and to access raw values:
+# "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
+# "{get(extras, key)}"
class _hybrid(object):
- def __init__(self, gen, values, joinfmt=None):
+ def __init__(self, gen, values, makemap, joinfmt=None):
self.gen = gen
self.values = values
+ self._makemap = makemap
if joinfmt:
self.joinfmt = joinfmt
else:
@@ -24,16 +28,23 @@
def __iter__(self):
return self.gen
def __call__(self):
+ makemap = self._makemap
for x in self.values:
- yield x
+ yield makemap(x)
+ def __contains__(self, x):
+ return x in self.values
def __len__(self):
return len(self.values)
+ def __getattr__(self, name):
+ if name != 'get':
+ raise AttributeError(name)
+ return getattr(self.values, name)
def showlist(name, values, plural=None, element=None, **args):
if not element:
element = name
f = _showlist(name, values, plural, **args)
- return _hybrid(f, [{element: x} for x in values])
+ return _hybrid(f, values, lambda x: {element: x})
def _showlist(name, values, plural=None, **args):
'''expand set of values.
@@ -200,9 +211,9 @@
repo = args['ctx']._repo
bookmarks = args['ctx'].bookmarks()
current = repo._bookmarkcurrent
- c = [{'bookmark': x, 'current': current} for x in bookmarks]
+ makemap = lambda v: {'bookmark': v, 'current': current}
f = _showlist('bookmark', bookmarks, **args)
- return _hybrid(f, c, lambda x: x['bookmark'])
+ return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
def showchildren(**args):
""":children: List of strings. The children of the changeset."""
@@ -241,9 +252,12 @@
""":extras: List of dicts with key, value entries of the 'extras'
field of this changeset."""
extras = args['ctx'].extra()
- c = [{'key': x[0], 'value': x[1]} for x in sorted(extras.items())]
+ extras = util.sortdict((k, extras[k]) for k in sorted(extras))
+ makemap = lambda k: {'key': k, 'value': extras[k]}
+ c = [makemap(k) for k in extras]
f = _showlist('extra', c, plural='extras', **args)
- return _hybrid(f, c, lambda x: '%s=%s' % (x['key'], x['value']))
+ return _hybrid(f, extras, makemap,
+ lambda x: '%s=%s' % (x['key'], x['value']))
def showfileadds(**args):
""":file_adds: List of strings. Files added by this changeset."""
@@ -267,9 +281,12 @@
if rename:
copies.append((fn, rename[0]))
- c = [{'name': x[0], 'source': x[1]} for x in copies]
+ copies = util.sortdict(copies)
+ makemap = lambda k: {'name': k, 'source': copies[k]}
+ c = [makemap(k) for k in copies]
f = _showlist('file_copy', c, plural='file_copies', **args)
- return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
+ return _hybrid(f, copies, makemap,
+ lambda x: '%s (%s)' % (x['name'], x['source']))
# showfilecopiesswitch() displays file copies only if copy records are
# provided before calling the templater, usually with a --copies
@@ -279,9 +296,12 @@
only if the --copied switch is set.
"""
copies = args['revcache'].get('copies') or []
- c = [{'name': x[0], 'source': x[1]} for x in copies]
+ copies = util.sortdict(copies)
+ makemap = lambda k: {'name': k, 'source': copies[k]}
+ c = [makemap(k) for k in copies]
f = _showlist('file_copy', c, plural='file_copies', **args)
- return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
+ return _hybrid(f, copies, makemap,
+ lambda x: '%s (%s)' % (x['name'], x['source']))
def showfiledels(**args):
""":file_dels: List of strings. Files removed by this changeset."""
--- a/mercurial/templater.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templater.py Fri Mar 13 17:55:04 2015 -0500
@@ -162,8 +162,13 @@
def runfilter(context, mapping, data):
func, data, filt = data
+ # func() may return string, generator of strings or arbitrary object such
+ # as date tuple, but filter does not want generator.
+ thing = func(context, mapping, data)
+ if isinstance(thing, types.GeneratorType):
+ thing = stringify(thing)
try:
- return filt(func(context, mapping, data))
+ return filt(thing)
except (ValueError, AttributeError, TypeError):
if isinstance(data, tuple):
dt = data[1]
@@ -330,10 +335,7 @@
item = stringify(args[0][0](context, mapping, args[0][1]))
items = args[1][0](context, mapping, args[1][1])
- # Iterating over items gives a formatted string, so we iterate
- # directly over the raw values.
- if ((callable(items) and item in [i.values()[0] for i in items()]) or
- (isinstance(items, str) and item in items)):
+ if item in items:
yield _evalifliteral(args[2], context, mapping)
elif len(args) == 4:
yield _evalifliteral(args[3], context, mapping)
@@ -393,7 +395,7 @@
def query(expr):
m = revsetmod.match(repo.ui, expr)
- return m(repo, revsetmod.spanset(repo))
+ return m(repo)
if len(args) > 1:
formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
--- a/mercurial/templates/gitweb/map Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/gitweb/map Fri Mar 13 17:55:04 2015 -0500
@@ -140,7 +140,7 @@
<tr>
<td>parent {rev}</td>
<td style="font-family:monospace">
- {changesetlink} {ifeq(node, basenode, '(current diff)', \'({difffrom})\')}
+ {changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')}
</td>
</tr>'
difffrom = '<a href="{url|urlescape}rev/{node|short}:{originalnode|short}{sessionvars%urlparameter}">diff</a>'
--- a/mercurial/templates/monoblue/bookmarks.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/bookmarks.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li class="current">bookmarks</li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/branches.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/branches.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li class="current">branches</li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/changelog.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/changelog.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -27,7 +27,7 @@
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
{archives%archiveentry}
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/graph.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/graph.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -27,7 +27,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/help.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/help.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li class="current">help</li>
+ <li class="current">help</li>
</ul>
</div>
--- a/mercurial/templates/monoblue/helptopics.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/helptopics.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li class="current">help</li>
+ <li class="current">help</li>
</ul>
</div>
--- a/mercurial/templates/monoblue/manifest.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/manifest.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li class="current">files</li>
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/map Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/map Fri Mar 13 17:55:04 2015 -0500
@@ -93,7 +93,7 @@
<tr class="parity{parity}">
<td class="linenr">
<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}"
- title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
+ title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
</td>
<td class="lineno">
<a href="#{lineid}" id="{lineid}">{linenumber}</a>
@@ -129,7 +129,7 @@
<dd>{changesetlink}</dd>'
changesetparentdiff = '
<dt>parent {rev}</dt>
- <dd>{changesetlink} {ifeq(node, basenode, '(current diff)', \'({difffrom})\')}</dd>'
+ <dd>{changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')}</dd>'
difffrom = '<a href="{url|urlescape}rev/{node|short}:{originalnode|short}{sessionvars%urlparameter}">diff</a>'
filerevbranch = '<dt>branch</dt><dd>{name|escape}</dd>'
filerevparent = '
--- a/mercurial/templates/monoblue/shortlog.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/shortlog.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,8 +26,8 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- {archives%archiveentry}
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ {archives%archiveentry}
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/summary.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/summary.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/monoblue/tags.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/tags.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
<li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
<li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
<li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
- <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
</div>
--- a/mercurial/templates/paper/bookmarks.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/bookmarks.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -23,7 +23,6 @@
<ul>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
-<p></p>
<div class="atom-logo">
<a href="{url|urlescape}atom-bookmarks" title="subscribe to atom feed">
<img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -42,10 +41,12 @@
</form>
<table class="bigtable">
+<thead>
<tr>
<th>bookmark</th>
<th>node</th>
</tr>
+</thead>
<tbody class="stripes2">
{entries%bookmarkentry}
</tbody>
--- a/mercurial/templates/paper/branches.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/branches.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -23,7 +23,6 @@
<ul>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
-<p></p>
<div class="atom-logo">
<a href="{url|urlescape}atom-branches" title="subscribe to atom feed">
<img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -42,10 +41,12 @@
</form>
<table class="bigtable">
+<thead>
<tr>
<th>branch</th>
<th>node</th>
</tr>
+</thead>
<tbody class="stripes2">
{entries % branchentry}
</tbody>
--- a/mercurial/templates/paper/changeset.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/changeset.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -48,7 +48,8 @@
</tr>
<tr>
<th class="date">date</th>
- <td class="date age">{date|rfc822date}</td></tr>
+ <td class="date age">{date|rfc822date}</td>
+</tr>
<tr>
<th class="author">parents</th>
<td class="author">{ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)}</td>
@@ -68,8 +69,7 @@
<a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
<div id="diffstatdetails" style="display:none;">
<a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
- <p></p>
- <table class="stripes2">{diffstat}</table>
+ <table class="diffstat-table stripes2">{diffstat}</table>
</div>
</td>
</tr>
--- a/mercurial/templates/paper/fileannotate.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/fileannotate.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -68,10 +68,12 @@
<div class="overflow">
<table class="bigtable">
+<thead>
<tr>
<th class="annotate">rev</th>
<th class="line"> line source</th>
</tr>
+</thead>
<tbody class="stripes2">
{annotate%annotateline}
</tbody>
--- a/mercurial/templates/paper/filelog.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/filelog.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -35,7 +35,6 @@
<ul>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
-<p></p>
<div class="atom-logo">
<a href="{url|urlescape}atom-log/{node|short}/{file|urlescape}" title="subscribe to atom feed">
<img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -59,11 +58,13 @@
| {nav%filenav}</div>
<table class="bigtable">
+<thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+</thead>
<tbody class="stripes2">
{entries%filelogentry}
</tbody>
--- a/mercurial/templates/paper/graph.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/graph.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -28,7 +28,6 @@
<ul>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
-<p></p>
<div class="atom-logo">
<a href="{url|urlescape}atom-log" title="subscribe to atom feed">
<img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
--- a/mercurial/templates/paper/index.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/index.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -12,6 +12,7 @@
<h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
<table class="bigtable">
+ <thead>
<tr>
<th><a href="?sort={sort_name}">Name</a></th>
<th><a href="?sort={sort_description}">Description</a></th>
@@ -20,6 +21,7 @@
<th> </th>
<th> </th>
</tr>
+ </thead>
<tbody class="stripes2">
{entries%indexentry}
</tbody>
--- a/mercurial/templates/paper/manifest.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/manifest.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -39,11 +39,13 @@
</form>
<table class="bigtable">
+<thead>
<tr>
<th class="name">name</th>
<th class="size">size</th>
<th class="permissions">permissions</th>
</tr>
+</thead>
<tbody class="stripes2">
<tr class="fileline">
<td class="name"><a href="{url|urlescape}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
--- a/mercurial/templates/paper/search.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/search.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -43,11 +43,13 @@
</div>
<table class="bigtable">
+<thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+</thead>
<tbody class="stripes2">
{entries}
</tbody>
--- a/mercurial/templates/paper/shortlog.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/shortlog.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -30,7 +30,6 @@
<ul>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
-<p></p>
<div class="atom-logo">
<a href="{url|urlescape}atom-log" title="subscribe to atom feed">
<img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -55,11 +54,13 @@
</div>
<table class="bigtable">
+<thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+</thead>
<tbody class="stripes2">
{entries%shortlogentry}
</tbody>
--- a/mercurial/templates/paper/tags.tmpl Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/tags.tmpl Fri Mar 13 17:55:04 2015 -0500
@@ -23,7 +23,6 @@
<ul>
<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
</ul>
-<p></p>
<div class="atom-logo">
<a href="{url|urlescape}atom-tags" title="subscribe to atom feed">
<img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -42,10 +41,12 @@
</form>
<table class="bigtable">
+<thead>
<tr>
<th>tag</th>
<th>node</th>
</tr>
+</thead>
<tbody class="stripes2">
{entries%tagentry}
</tbody>
--- a/mercurial/templates/static/style-paper.css Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/static/style-paper.css Fri Mar 13 17:55:04 2015 -0500
@@ -60,6 +60,10 @@
border: 0;
}
+div.atom-logo {
+ margin-top: 10px;
+}
+
.atom-logo img{
width: 14px;
height: 14px;
@@ -104,6 +108,9 @@
.minusline { color: #dc143c; } /* crimson */
.atline { color: purple; }
+.diffstat-table {
+ margin-top: 1em;
+}
.diffstat-file {
white-space: nowrap;
font-size: 90%;
--- a/mercurial/transaction.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/transaction.py Fri Mar 13 17:55:04 2015 -0500
@@ -83,7 +83,7 @@
class transaction(object):
def __init__(self, report, opener, vfsmap, journalname, undoname=None,
- after=None, createmode=None):
+ after=None, createmode=None, validator=None):
"""Begin a new transaction
Begins a new transaction that allows rolling back writes in the event of
@@ -107,6 +107,12 @@
self.journal = journalname
self.undoname = undoname
self._queue = []
+ # A callback to validate transaction content before closing it.
+ # should raise exception is anything is wrong.
+ # target user is repository hooks.
+ if validator is None:
+ validator = lambda tr: None
+ self.validator = validator
# a dict of arguments to be passed to hooks
self.hookargs = {}
self.file = opener.open(self.journal, "w")
@@ -378,6 +384,7 @@
def close(self):
'''commit the transaction'''
if self.count == 1:
+ self.validator(self) # will raise exception if needed
self._generatefiles()
categories = sorted(self._finalizecallback)
for cat in categories:
--- a/mercurial/ui.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/ui.py Fri Mar 13 17:55:04 2015 -0500
@@ -531,10 +531,14 @@
if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
return loc
- path = self.config('paths', loc)
- if not path and default is not None:
- path = self.config('paths', default)
- return path or loc
+ p = self.paths.getpath(loc, default=default)
+ if p:
+ return p.loc
+ return loc
+
+ @util.propertycache
+ def paths(self):
+ return paths(self)
def pushbuffer(self, error=False):
"""install a buffer to capture standard output of the ui object
@@ -923,3 +927,48 @@
ui.write(ui.label(s, 'label')).
'''
return msg
+
+class paths(dict):
+ """Represents a collection of paths and their configs.
+
+ Data is initially derived from ui instances and the config files they have
+ loaded.
+ """
+ def __init__(self, ui):
+ dict.__init__(self)
+
+ for name, loc in ui.configitems('paths'):
+ # No location is the same as not existing.
+ if not loc:
+ continue
+ self[name] = path(name, rawloc=loc)
+
+ def getpath(self, name, default=None):
+ """Return a ``path`` for the specified name, falling back to a default.
+
+ Returns the first of ``name`` or ``default`` that is present, or None
+ if neither is present.
+ """
+ try:
+ return self[name]
+ except KeyError:
+ if default is not None:
+ try:
+ return self[default]
+ except KeyError:
+ pass
+
+ return None
+
+class path(object):
+ """Represents an individual path and its configuration."""
+
+ def __init__(self, name, rawloc=None):
+ """Construct a path from its config options.
+
+ ``name`` is the symbolic name of the path.
+ ``rawloc`` is the raw location, as defined in the config.
+ """
+ self.name = name
+ # We'll do more intelligent things with rawloc in the future.
+ self.loc = rawloc
--- a/mercurial/unionrepo.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/unionrepo.py Fri Mar 13 17:55:04 2015 -0500
@@ -160,8 +160,11 @@
def baserevdiff(self, rev1, rev2):
return filelog.filelog.revdiff(self, rev1, rev2)
- def _file(self, f):
- self._repo.file(f)
+ def iscensored(self, rev):
+ """Check if a revision is censored."""
+ if rev <= self.repotiprev:
+ return filelog.filelog.iscensored(self, rev)
+ return self.revlog2.iscensored(rev)
class unionpeer(localrepo.localpeer):
def canpush(self):
--- a/mercurial/util.h Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/util.h Fri Mar 13 17:55:04 2015 -0500
@@ -172,6 +172,22 @@
(d[3]));
}
+static inline int16_t getbeint16(const char *c)
+{
+ const unsigned char *d = (const unsigned char *)c;
+
+ return ((d[0] << 8) |
+ (d[1]));
+}
+
+static inline uint16_t getbeuint16(const char *c)
+{
+ const unsigned char *d = (const unsigned char *)c;
+
+ return ((d[0] << 8) |
+ (d[1]));
+}
+
static inline void putbe32(uint32_t x, char *c)
{
c[0] = (x >> 24) & 0xff;
@@ -180,4 +196,17 @@
c[3] = (x) & 0xff;
}
+static inline double getbefloat64(const char *c)
+{
+ const unsigned char *d = (const unsigned char *)c;
+ double ret;
+ int i;
+ uint64_t t = 0;
+ for (i = 0; i < 8; i++) {
+ t = (t<<8) + d[i];
+ }
+ memcpy(&ret, &t, sizeof(t));
+ return ret;
+}
+
#endif /* _HG_UTIL_H_ */
--- a/mercurial/util.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/util.py Fri Mar 13 17:55:04 2015 -0500
@@ -359,8 +359,10 @@
def __iter__(self):
return self._list.__iter__()
def update(self, src):
- for k in src:
- self[k] = src[k]
+ if isinstance(src, dict):
+ src = src.iteritems()
+ for k, v in src:
+ self[k] = v
def clear(self):
dict.clear(self)
self._list = []
@@ -1352,11 +1354,11 @@
formats = defaultdateformats
date = date.strip()
- if date == _('now'):
+ if date == 'now' or date == _('now'):
return makedate()
- if date == _('today'):
+ if date == 'today' or date == _('today'):
date = datetime.date.today().strftime('%b %d')
- elif date == _('yesterday'):
+ elif date == 'yesterday' or date == _('yesterday'):
date = (datetime.date.today() -
datetime.timedelta(days=1)).strftime('%b %d')
--- a/mercurial/windows.py Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/windows.py Fri Mar 13 17:55:04 2015 -0500
@@ -26,14 +26,22 @@
unlink = win32.unlink
umask = 0022
+_SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
-# wrap osutil.posixfile to provide friendlier exceptions
def posixfile(name, mode='r', buffering=-1):
+ '''Open a file with even more POSIX-like semantics'''
try:
- return osutil.posixfile(name, mode, buffering)
+ fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
+
+ # The position when opening in append mode is implementation defined, so
+ # make it consistent with other platforms, which position at EOF.
+ if 'a' in mode:
+ fp.seek(0, _SEEK_END)
+
+ return fp
except WindowsError, err:
+ # convert to a friendlier exception
raise IOError(err.errno, '%s: %s' % (name, err.strerror))
-posixfile.__doc__ = osutil.posixfile.__doc__
class winstdout(object):
'''stdout on windows misbehaves if sent through a pipe'''
--- a/setup.py Fri Mar 13 21:18:59 2015 +0900
+++ b/setup.py Fri Mar 13 17:55:04 2015 -0500
@@ -63,6 +63,8 @@
raise SystemExit(
"Couldn't import standard bz2 (incomplete Python install).")
+ispypy = "PyPy" in sys.version
+
import os, stat, subprocess, time
import re
import shutil
@@ -276,7 +278,7 @@
class hgdist(Distribution):
- pure = 0
+ pure = ispypy
global_options = Distribution.global_options + \
[('pure', None, "use pure (slow) Python "
@@ -491,6 +493,7 @@
Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
depends=common_depends),
Extension('mercurial.parsers', ['mercurial/dirs.c',
+ 'mercurial/manifest.c',
'mercurial/parsers.c',
'mercurial/pathencode.c'],
depends=common_depends),
@@ -555,7 +558,7 @@
if py2exeloaded:
extra['console'] = [
{'script':'hg',
- 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
+ 'copyright':'Copyright (C) 2005-2015 Matt Mackall and others',
'product_version':version}]
# sub command of 'build' because 'py2exe' does not handle sub_commands
build.sub_commands.insert(0, ('build_hgextindex', None))
--- a/tests/hghave.py Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/hghave.py Fri Mar 13 17:55:04 2015 -0500
@@ -320,6 +320,11 @@
except ImportError:
return False
+@check("defaultcacerts", "can verify SSL certs by system's CA certs store")
+def has_defaultcacerts():
+ from mercurial import sslutil
+ return sslutil._defaultcacerts() != '!'
+
@check("windows", "Windows")
def has_windows():
return os.name == 'nt'
--- a/tests/run-tests.py Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/run-tests.py Fri Mar 13 17:55:04 2015 -0500
@@ -1863,6 +1863,17 @@
'prefix': self._installdir, 'libdir': self._pythondir,
'bindir': self._bindir,
'nohome': nohome, 'logfile': installerrs})
+
+ # setuptools requires install directories to exist.
+ def makedirs(p):
+ try:
+ os.makedirs(p)
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
+ makedirs(self._pythondir)
+ makedirs(self._bindir)
+
vlog("# Running", cmd)
if os.system(cmd) == 0:
if not self.options.verbose:
@@ -1870,7 +1881,7 @@
else:
f = open(installerrs, 'rb')
for line in f:
- print line
+ sys.stdout.write(line)
f.close()
sys.exit(1)
os.chdir(self._testdir)
--- a/tests/test-addremove.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-addremove.t Fri Mar 13 17:55:04 2015 -0500
@@ -30,12 +30,12 @@
adding foo
$ hg forget foo
#if windows
- $ hg -v addremove nonexistant
- nonexistant: The system cannot find the file specified
+ $ hg -v addremove nonexistent
+ nonexistent: The system cannot find the file specified
[1]
#else
- $ hg -v addremove nonexistant
- nonexistant: No such file or directory
+ $ hg -v addremove nonexistent
+ nonexistent: No such file or directory
[1]
#endif
$ cd ..
@@ -88,13 +88,13 @@
$ rm c
#if windows
- $ hg ci -A -m "c" nonexistant
- nonexistant: The system cannot find the file specified
+ $ hg ci -A -m "c" nonexistent
+ nonexistent: The system cannot find the file specified
abort: failed to mark all new/missing files as added/removed
[255]
#else
- $ hg ci -A -m "c" nonexistant
- nonexistant: No such file or directory
+ $ hg ci -A -m "c" nonexistent
+ nonexistent: No such file or directory
abort: failed to mark all new/missing files as added/removed
[255]
#endif
--- a/tests/test-alias.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-alias.t Fri Mar 13 17:55:04 2015 -0500
@@ -360,9 +360,11 @@
sub
$ hg --cwd .. subalias > /dev/null
hg: unknown command 'subalias'
+ (did you mean one of idalias?)
[255]
$ hg -R .. subalias > /dev/null
hg: unknown command 'subalias'
+ (did you mean one of idalias?)
[255]
@@ -370,12 +372,18 @@
$ hg mainalias > /dev/null
hg: unknown command 'mainalias'
+ (did you mean one of idalias?)
[255]
$ hg -R .. mainalias
main
$ hg --cwd .. mainalias
main
+typos get useful suggestions
+ $ hg --cwd .. manalias
+ hg: unknown command 'manalias'
+ (did you mean one of idalias, mainalias, manifest?)
+ [255]
shell aliases with escaped $ chars
--- a/tests/test-bundle-type.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-bundle-type.t Fri Mar 13 17:55:04 2015 -0500
@@ -87,6 +87,7 @@
$ hg init tgarbage
$ cd tgarbage
$ hg pull ../bgarbage
+ pulling from ../bgarbage
abort: ../bgarbage: not a Mercurial bundle
[255]
$ cd ..
--- a/tests/test-bundle2-exchange.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-bundle2-exchange.t Fri Mar 13 17:55:04 2015 -0500
@@ -25,10 +25,9 @@
> [phases]
> publish=False
> [hooks]
- > changegroup = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" changegroup"
- > b2x-pretransactionclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
- > b2x-transactionclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
- > b2x-transactionclose.env = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" b2x-transactionclose"
+ > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
+ > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
+ > txnclose.env = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" txnclose"
> pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
> EOF
@@ -39,13 +38,19 @@
$ touch a
$ hg add a
$ hg commit -m 'a'
+ pre-close-tip:3903775176ed draft
+ postclose-tip:3903775176ed draft
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=commit
$ hg unbundle $TESTDIR/bundles/rebase.hg
adding changesets
adding manifests
adding file changes
added 8 changesets with 7 changes to 7 files (+3 heads)
- changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=unbundle HG_URL=bundle:*/rebase.hg (glob)
+ pre-close-tip:02de42196ebe draft
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNNAME=unbundle
+ bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ cd ..
@@ -56,11 +61,20 @@
Add more obsolescence information
$ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
+ pre-close-tip:02de42196ebe draft
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
$ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
+ pre-close-tip:02de42196ebe draft
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
clone --pull
$ hg -R main phase --public cd010b8cd998
+ pre-close-tip:000000000000 public
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
$ hg clone main other --pull --rev 9520eea781bc
adding changesets
adding manifests
@@ -69,8 +83,8 @@
1 new obsolescence markers
pre-close-tip:9520eea781bc draft
postclose-tip:9520eea781bc draft
- b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
- changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+ file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg -R other log -G
@@ -84,6 +98,9 @@
pull
$ hg -R main phase --public 9520eea781bc
+ pre-close-tip:000000000000 public
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
$ hg -R other pull -r 24b6387c8c8c
pulling from $TESTTMP/main (glob)
searching for changes
@@ -94,8 +111,8 @@
1 new obsolescence markers
pre-close-tip:24b6387c8c8c draft
postclose-tip:24b6387c8c8c draft
- b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
- changegroup hook: HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+ file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg -R other log -G
o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
@@ -111,12 +128,16 @@
pull empty (with phase movement)
$ hg -R main phase --public 24b6387c8c8c
+ pre-close-tip:000000000000 public
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
$ hg -R other pull -r 24b6387c8c8c
pulling from $TESTTMP/main (glob)
no changes found
pre-close-tip:000000000000 public
postclose-tip:24b6387c8c8c public
- b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+ txnclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+ file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
$ hg -R other log -G
o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
|
@@ -135,7 +156,8 @@
no changes found
pre-close-tip:24b6387c8c8c public
postclose-tip:24b6387c8c8c public
- b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+ txnclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_TXNNAME=pull
+ file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
$ hg -R other log -G
o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
|
@@ -151,14 +173,29 @@
$ hg -R main bookmark --rev eea13746799a book_eea1
$ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
+ pre-close-tip:02de42196ebe draft
+ postclose-tip:02de42196ebe draft
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
$ hg -R main bookmark --rev 02de42196ebe book_02de
$ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
+ pre-close-tip:02de42196ebe draft book_02de
+ postclose-tip:02de42196ebe draft book_02de
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
$ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
$ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
+ pre-close-tip:02de42196ebe draft book_02de
+ postclose-tip:02de42196ebe draft book_02de
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
$ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
$ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
+ pre-close-tip:02de42196ebe draft book_02de
+ postclose-tip:02de42196ebe draft book_02de
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
$ hg -R main bookmark --rev 32af7686d403 book_32af
$ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
+ pre-close-tip:02de42196ebe draft book_02de
+ postclose-tip:02de42196ebe draft book_02de
+ txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
$ hg -R other bookmark --rev cd010b8cd998 book_eea1
$ hg -R other bookmark --rev cd010b8cd998 book_02de
@@ -167,6 +204,9 @@
$ hg -R other bookmark --rev cd010b8cd998 book_32af
$ hg -R main phase --public eea13746799a
+ pre-close-tip:000000000000 public
+ postclose-tip:02de42196ebe draft book_02de
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
push
$ hg -R main push other --rev eea13746799a --bookmark book_eea1
@@ -180,8 +220,7 @@
lock: free
wlock: free
postclose-tip:eea13746799a public book_eea1
- b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_URL=push
- changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_SOURCE=push HG_URL=push
+ txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNNAME=unbundle HG_URL=push
remote: adding changesets
remote: adding manifests
remote: adding file changes
@@ -190,7 +229,8 @@
updating bookmark book_eea1
pre-close-tip:02de42196ebe draft book_02de
postclose-tip:02de42196ebe draft book_02de
- b2x-transactionclose hook: HG_SOURCE=push-response HG_URL=file:$TESTTMP/other
+ txnclose hook: HG_SOURCE=push-response HG_TXNNAME=push-response
+ file:/*/$TESTTMP/other HG_URL=file:$TESTTMP/other (glob)
$ hg -R other log -G
o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
|\
@@ -218,8 +258,8 @@
updating bookmark book_02de
pre-close-tip:02de42196ebe draft book_02de
postclose-tip:02de42196ebe draft book_02de
- b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
- changegroup hook: HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
+ txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+ ssh://user@dummy/main HG_URL=ssh://user@dummy/main
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg -R other debugobsolete
1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -243,8 +283,8 @@
updating bookmark book_42cc
pre-close-tip:42ccdea3bb16 draft book_42cc
postclose-tip:42ccdea3bb16 draft book_42cc
- b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
- changegroup hook: HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
+ txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+ http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
(run 'hg heads .' to see heads, 'hg merge' to merge)
$ cat main-error.log
$ hg -R other debugobsolete
@@ -270,11 +310,11 @@
remote: lock: free
remote: wlock: free
remote: postclose-tip:5fddd98957c8 draft book_5fdd
- remote: b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
- remote: changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
+ remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNNAME=unbundle HG_URL=remote:ssh:127.0.0.1
pre-close-tip:02de42196ebe draft book_02de
postclose-tip:02de42196ebe draft book_02de
- b2x-transactionclose hook: HG_SOURCE=push-response HG_URL=ssh://user@dummy/other
+ txnclose hook: HG_SOURCE=push-response HG_TXNNAME=push-response
+ ssh://user@dummy/other HG_URL=ssh://user@dummy/other
$ hg -R other log -G
o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
|
@@ -304,6 +344,9 @@
$ cat other.pid >> $DAEMON_PIDS
$ hg -R main phase --public 32af7686d403
+ pre-close-tip:000000000000 public
+ postclose-tip:02de42196ebe draft book_02de
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
$ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
pushing to http://localhost:$HGPORT2/
searching for changes
@@ -315,7 +358,8 @@
updating bookmark book_32af
pre-close-tip:02de42196ebe draft book_02de
postclose-tip:02de42196ebe draft book_02de
- b2x-transactionclose hook: HG_SOURCE=push-response HG_URL=http://localhost:$HGPORT2/
+ txnclose hook: HG_SOURCE=push-response HG_TXNNAME=push-response
+ http://localhost:$HGPORT2/ HG_URL=http://localhost:$HGPORT2/
$ cat other-error.log
Check final content.
@@ -400,6 +444,9 @@
$ echo 'I' > I
$ hg add I
$ hg ci -m 'I'
+ pre-close-tip:e7ec4e813ba6 draft
+ postclose-tip:e7ec4e813ba6 draft
+ txnclose hook: HG_TXNNAME=commit
$ hg id
e7ec4e813ba6 tip
$ cd ..
@@ -501,7 +548,7 @@
> [failpush]
> reason =
> [hooks]
- > b2x-pretransactionclose.failpush = false
+ > pretxnclose.failpush = false
> EOF
$ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
@@ -514,13 +561,13 @@
pre-close-tip:e7ec4e813ba6 draft
transaction abort!
rollback completed
- abort: b2x-pretransactionclose.failpush hook exited with status 1
+ abort: pretxnclose.failpush hook exited with status 1
[255]
$ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
pushing to ssh://user@dummy/other
searching for changes
- abort: b2x-pretransactionclose.failpush hook exited with status 1
+ abort: pretxnclose.failpush hook exited with status 1
remote: pre-close-tip:e7ec4e813ba6 draft
remote: transaction abort!
remote: rollback completed
@@ -529,7 +576,7 @@
$ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
pushing to http://localhost:$HGPORT2/
searching for changes
- abort: b2x-pretransactionclose.failpush hook exited with status 1
+ abort: pretxnclose.failpush hook exited with status 1
[255]
(check that no 'pending' files remain)
--- a/tests/test-churn.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-churn.t Fri Mar 13 17:55:04 2015 -0500
@@ -171,4 +171,27 @@
El Ni\xc3\xb1o 1 *************** (esc)
with space 1 ***************
+Test --template argument, with backwards compatiblity
+
+ $ hg churn -t '{author|user}'
+ user1 4 ***************************************************************
+ user3 3 ***********************************************
+ user2 2 ********************************
+ nino 1 ****************
+ with 1 ****************
+ 0
+ user4 0
+ $ hg churn -T '{author|user}'
+ user1 4 ***************************************************************
+ user3 3 ***********************************************
+ user2 2 ********************************
+ nino 1 ****************
+ with 1 ****************
+ 0
+ user4 0
+ $ hg churn -t 'alltogether'
+ alltogether 11 *********************************************************
+ $ hg churn -T 'alltogether'
+ alltogether 11 *********************************************************
+
$ cd ..
--- a/tests/test-command-template.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-command-template.t Fri Mar 13 17:55:04 2015 -0500
@@ -47,6 +47,9 @@
fourth (second)
$ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
second -> fourth
+ $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
+ 8 t
+ 7 f
Quoting for ui.logtemplate
@@ -1898,6 +1901,11 @@
hg: parse error: unknown function 'foo'
[255]
+Pass generator object created by template function to filter
+
+ $ hg log -l 1 --template '{if(author, author)|user}\n'
+ test
+
Test diff function:
$ hg diff -c 8
@@ -2283,6 +2291,14 @@
$ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
no
+Test get function:
+
+ $ hg log -r 0 --template '{get(extras, "branch")}\n'
+ default
+ $ hg log -r 0 --template '{get(files, "should_fail")}\n'
+ hg: parse error: get() expects a dict as first argument
+ [255]
+
Test shortest(node) function:
$ echo b > b
@@ -2386,6 +2402,10 @@
2 bar foo
1 baz
0
+ $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
+ 2 t
+ 1 f
+ 0 f
Test stringify on sub expressions
--- a/tests/test-commit-amend.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-commit-amend.t Fri Mar 13 17:55:04 2015 -0500
@@ -1096,7 +1096,7 @@
$ hg ci -m add
$
$ hg debugrename newdirname/newfile.py
- newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def
+ newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
$ hg status -C --change .
A newdirname/newfile.py
$ hg status -C --rev 1
@@ -1115,7 +1115,7 @@
$ echo a >> newdirname/commonfile.py
$ hg ci --amend -m bug
$ hg debugrename newdirname/newfile.py
- newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def
+ newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
$ hg debugindex newdirname/newfile.py
rev offset length base linkrev nodeid p1 p2
0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-commit-interactive.t Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,1434 @@
+Set up a repo
+
+ $ cat <<EOF >> $HGRCPATH
+ > [ui]
+ > interactive = true
+ > [extensions]
+ > record =
+ > EOF
+
+ $ hg init a
+ $ cd a
+
+Select no files
+
+ $ touch empty-rw
+ $ hg add empty-rw
+
+ $ hg commit -i empty-rw<<EOF
+ > n
+ > EOF
+ diff --git a/empty-rw b/empty-rw
+ new file mode 100644
+ examine changes to 'empty-rw'? [Ynesfdaq?] n
+
+ no changes to record
+
+ $ hg tip -p
+ changeset: -1:000000000000
+ tag: tip
+ user:
+ date: Thu Jan 01 00:00:00 1970 +0000
+
+
+
+Select files but no hunks
+
+ $ hg commit -i empty-rw<<EOF
+ > y
+ > n
+ > EOF
+ diff --git a/empty-rw b/empty-rw
+ new file mode 100644
+ examine changes to 'empty-rw'? [Ynesfdaq?] y
+
+ abort: empty commit message
+ [255]
+
+ $ hg tip -p
+ changeset: -1:000000000000
+ tag: tip
+ user:
+ date: Thu Jan 01 00:00:00 1970 +0000
+
+
+
+Record empty file
+
+ $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/empty-rw b/empty-rw
+ new file mode 100644
+ examine changes to 'empty-rw'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 0:c0708cf4e46e
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: empty
+
+
+
+Summary shows we updated to the new cset
+
+ $ hg summary
+ parent: 0:c0708cf4e46e tip
+ empty
+ branch: default
+ commit: (clean)
+ update: (current)
+
+Rename empty file
+
+ $ hg mv empty-rw empty-rename
+ $ hg commit -i -d '1 0' -m rename<<EOF
+ > y
+ > EOF
+ diff --git a/empty-rw b/empty-rename
+ rename from empty-rw
+ rename to empty-rename
+ examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 1:d695e8dcb197
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:01 1970 +0000
+ summary: rename
+
+
+
+Copy empty file
+
+ $ hg cp empty-rename empty-copy
+ $ hg commit -i -d '2 0' -m copy<<EOF
+ > y
+ > EOF
+ diff --git a/empty-rename b/empty-copy
+ copy from empty-rename
+ copy to empty-copy
+ examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 2:1d4b90bea524
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:02 1970 +0000
+ summary: copy
+
+
+
+Delete empty file
+
+ $ hg rm empty-copy
+ $ hg commit -i -d '3 0' -m delete<<EOF
+ > y
+ > EOF
+ diff --git a/empty-copy b/empty-copy
+ deleted file mode 100644
+ examine changes to 'empty-copy'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 3:b39a238f01a1
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:03 1970 +0000
+ summary: delete
+
+
+
+Add binary file
+
+ $ hg bundle --base -2 tip.bundle
+ 1 changesets found
+ $ hg add tip.bundle
+ $ hg commit -i -d '4 0' -m binary<<EOF
+ > y
+ > EOF
+ diff --git a/tip.bundle b/tip.bundle
+ new file mode 100644
+ this is a binary file
+ examine changes to 'tip.bundle'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 4:ad816da3711e
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:04 1970 +0000
+ summary: binary
+
+ diff -r b39a238f01a1 -r ad816da3711e tip.bundle
+ Binary file tip.bundle has changed
+
+
+Change binary file
+
+ $ hg bundle --base -2 tip.bundle
+ 1 changesets found
+ $ hg commit -i -d '5 0' -m binary-change<<EOF
+ > y
+ > EOF
+ diff --git a/tip.bundle b/tip.bundle
+ this modifies a binary file (all or nothing)
+ examine changes to 'tip.bundle'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 5:dccd6f3eb485
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:05 1970 +0000
+ summary: binary-change
+
+ diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
+ Binary file tip.bundle has changed
+
+
+Rename and change binary file
+
+ $ hg mv tip.bundle top.bundle
+ $ hg bundle --base -2 top.bundle
+ 1 changesets found
+ $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
+ > y
+ > EOF
+ diff --git a/tip.bundle b/top.bundle
+ rename from tip.bundle
+ rename to top.bundle
+ this modifies a binary file (all or nothing)
+ examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 6:7fa44105f5b3
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:06 1970 +0000
+ summary: binary-change-rename
+
+ diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
+ Binary file tip.bundle has changed
+ diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
+ Binary file top.bundle has changed
+
+
+Add plain file
+
+ $ for i in 1 2 3 4 5 6 7 8 9 10; do
+ > echo $i >> plain
+ > done
+
+ $ hg add plain
+ $ hg commit -i -d '7 0' -m plain plain<<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ new file mode 100644
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -0,0 +1,10 @@
+ +1
+ +2
+ +3
+ +4
+ +5
+ +6
+ +7
+ +8
+ +9
+ +10
+ record this change to 'plain'? [Ynesfdaq?] y
+
+ $ hg tip -p
+ changeset: 7:11fb457c1be4
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:07 1970 +0000
+ summary: plain
+
+ diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/plain Thu Jan 01 00:00:07 1970 +0000
+ @@ -0,0 +1,10 @@
+ +1
+ +2
+ +3
+ +4
+ +5
+ +6
+ +7
+ +8
+ +9
+ +10
+
+Modify end of plain file with username unset
+
+ $ echo 11 >> plain
+ $ unset HGUSER
+ $ hg commit -i --config ui.username= -d '8 0' -m end plain
+ abort: no username supplied
+ (use "hg config --edit" to set your username)
+ [255]
+
+
+Modify end of plain file, also test that diffopts are accounted for
+
+ $ HGUSER="test"
+ $ export HGUSER
+ $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 1 hunks, 1 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -8,3 +8,4 @@ 7
+ 8
+ 9
+ 10
+ +11
+ record this change to 'plain'? [Ynesfdaq?] y
+
+
+Modify end of plain file, no EOL
+
+ $ hg tip --template '{node}' >> plain
+ $ hg commit -i -d '9 0' -m noeol plain <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 1 hunks, 1 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -9,3 +9,4 @@
+ 9
+ 10
+ 11
+ +7264f99c5f5ff3261504828afa4fb4d406c3af54
+ \ No newline at end of file
+ record this change to 'plain'? [Ynesfdaq?] y
+
+
+Modify end of plain file, add EOL
+
+ $ echo >> plain
+ $ echo 1 > plain2
+ $ hg add plain2
+ $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
+ > y
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 1 hunks, 1 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -9,4 +9,4 @@
+ 9
+ 10
+ 11
+ -7264f99c5f5ff3261504828afa4fb4d406c3af54
+ \ No newline at end of file
+ +7264f99c5f5ff3261504828afa4fb4d406c3af54
+ record change 1/2 to 'plain'? [Ynesfdaq?] y
+
+ diff --git a/plain2 b/plain2
+ new file mode 100644
+ examine changes to 'plain2'? [Ynesfdaq?] y
+
+ @@ -0,0 +1,1 @@
+ +1
+ record change 2/2 to 'plain2'? [Ynesfdaq?] y
+
+Modify beginning, trim end, record both, add another file to test
+changes numbering
+
+ $ rm plain
+ $ for i in 2 2 3 4 5 6 7 8 9 10; do
+ > echo $i >> plain
+ > done
+ $ echo 2 >> plain2
+
+ $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
+ > y
+ > y
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 2 hunks, 3 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -1,4 +1,4 @@
+ -1
+ +2
+ 2
+ 3
+ 4
+ record change 1/3 to 'plain'? [Ynesfdaq?] y
+
+ @@ -8,5 +8,3 @@
+ 8
+ 9
+ 10
+ -11
+ -7264f99c5f5ff3261504828afa4fb4d406c3af54
+ record change 2/3 to 'plain'? [Ynesfdaq?] y
+
+ diff --git a/plain2 b/plain2
+ 1 hunks, 1 lines changed
+ examine changes to 'plain2'? [Ynesfdaq?] y
+
+ @@ -1,1 +1,2 @@
+ 1
+ +2
+ record change 3/3 to 'plain2'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 11:21df83db12b8
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:10 1970 +0000
+ summary: begin-and-end
+
+ diff -r ddb8b281c3ff -r 21df83db12b8 plain
+ --- a/plain Thu Jan 01 00:00:10 1970 +0000
+ +++ b/plain Thu Jan 01 00:00:10 1970 +0000
+ @@ -1,4 +1,4 @@
+ -1
+ +2
+ 2
+ 3
+ 4
+ @@ -8,5 +8,3 @@
+ 8
+ 9
+ 10
+ -11
+ -7264f99c5f5ff3261504828afa4fb4d406c3af54
+ diff -r ddb8b281c3ff -r 21df83db12b8 plain2
+ --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
+ +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
+ @@ -1,1 +1,2 @@
+ 1
+ +2
+
+
+Trim beginning, modify end
+
+ $ rm plain
+ > for i in 4 5 6 7 8 9 10.new; do
+ > echo $i >> plain
+ > done
+
+Record end
+
+ $ hg commit -i -d '11 0' -m end-only plain <<EOF
+ > y
+ > n
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 2 hunks, 4 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -1,9 +1,6 @@
+ -2
+ -2
+ -3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ record change 1/2 to 'plain'? [Ynesfdaq?] n
+
+ @@ -4,7 +1,7 @@
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ -10
+ +10.new
+ record change 2/2 to 'plain'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 12:99337501826f
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:11 1970 +0000
+ summary: end-only
+
+ diff -r 21df83db12b8 -r 99337501826f plain
+ --- a/plain Thu Jan 01 00:00:10 1970 +0000
+ +++ b/plain Thu Jan 01 00:00:11 1970 +0000
+ @@ -7,4 +7,4 @@
+ 7
+ 8
+ 9
+ -10
+ +10.new
+
+
+Record beginning
+
+ $ hg commit -i -d '12 0' -m begin-only plain <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 1 hunks, 3 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -1,6 +1,3 @@
+ -2
+ -2
+ -3
+ 4
+ 5
+ 6
+ record this change to 'plain'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 13:bbd45465d540
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:12 1970 +0000
+ summary: begin-only
+
+ diff -r 99337501826f -r bbd45465d540 plain
+ --- a/plain Thu Jan 01 00:00:11 1970 +0000
+ +++ b/plain Thu Jan 01 00:00:12 1970 +0000
+ @@ -1,6 +1,3 @@
+ -2
+ -2
+ -3
+ 4
+ 5
+ 6
+
+
+Add to beginning, trim from end
+
+ $ rm plain
+ $ for i in 1 2 3 4 5 6 7 8 9; do
+ > echo $i >> plain
+ > done
+
+Record end
+
+ $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
+ > y
+ > n
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 2 hunks, 4 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -1,6 +1,9 @@
+ +1
+ +2
+ +3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ record change 1/2 to 'plain'? [Ynesfdaq?] n
+
+ @@ -1,7 +4,6 @@
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ -10.new
+ record change 2/2 to 'plain'? [Ynesfdaq?] y
+
+
+Add to beginning, middle, end
+
+ $ rm plain
+ $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
+ > echo $i >> plain
+ > done
+
+Record beginning, middle, and test that format-breaking diffopts are ignored
+
+ $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
+ > y
+ > y
+ > y
+ > n
+ > EOF
+ diff --git a/plain b/plain
+ 3 hunks, 7 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -1,2 +1,5 @@
+ +1
+ +2
+ +3
+ 4
+ 5
+ record change 1/3 to 'plain'? [Ynesfdaq?] y
+
+ @@ -1,6 +4,8 @@
+ 4
+ 5
+ +5.new
+ +5.reallynew
+ 6
+ 7
+ 8
+ 9
+ record change 2/3 to 'plain'? [Ynesfdaq?] y
+
+ @@ -3,4 +8,6 @@
+ 6
+ 7
+ 8
+ 9
+ +10
+ +11
+ record change 3/3 to 'plain'? [Ynesfdaq?] n
+
+
+ $ hg tip -p
+ changeset: 15:f34a7937ec33
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:14 1970 +0000
+ summary: middle-only
+
+ diff -r 82c065d0b850 -r f34a7937ec33 plain
+ --- a/plain Thu Jan 01 00:00:13 1970 +0000
+ +++ b/plain Thu Jan 01 00:00:14 1970 +0000
+ @@ -1,5 +1,10 @@
+ +1
+ +2
+ +3
+ 4
+ 5
+ +5.new
+ +5.reallynew
+ 6
+ 7
+ 8
+
+
+Record end
+
+ $ hg commit -i -d '15 0' -m end-only plain <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/plain b/plain
+ 1 hunks, 2 lines changed
+ examine changes to 'plain'? [Ynesfdaq?] y
+
+ @@ -9,3 +9,5 @@
+ 7
+ 8
+ 9
+ +10
+ +11
+ record this change to 'plain'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 16:f9900b71a04c
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:15 1970 +0000
+ summary: end-only
+
+ diff -r f34a7937ec33 -r f9900b71a04c plain
+ --- a/plain Thu Jan 01 00:00:14 1970 +0000
+ +++ b/plain Thu Jan 01 00:00:15 1970 +0000
+ @@ -9,3 +9,5 @@
+ 7
+ 8
+ 9
+ +10
+ +11
+
+
+ $ mkdir subdir
+ $ cd subdir
+ $ echo a > a
+ $ hg ci -d '16 0' -Amsubdir
+ adding subdir/a
+
+ $ echo a >> a
+ $ hg commit -i -d '16 0' -m subdir-change a <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/a b/subdir/a
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/a'? [Ynesfdaq?] y
+
+ @@ -1,1 +1,2 @@
+ a
+ +a
+ record this change to 'subdir/a'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 18:61be427a9deb
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:16 1970 +0000
+ summary: subdir-change
+
+ diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
+ --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
+ +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
+ @@ -1,1 +1,2 @@
+ a
+ +a
+
+
+ $ echo a > f1
+ $ echo b > f2
+ $ hg add f1 f2
+
+ $ hg ci -mz -d '17 0'
+
+ $ echo a >> f1
+ $ echo b >> f2
+
+Help, quit
+
+ $ hg commit -i <<EOF
+ > ?
+ > q
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] ?
+
+ y - yes, record this change
+ n - no, skip this change
+ e - edit this change manually
+ s - skip remaining changes to this file
+ f - record remaining changes to this file
+ d - done, skip remaining changes and files
+ a - record all changes to all remaining files
+ q - quit, recording no changes
+ ? - ? (display help)
+ examine changes to 'subdir/f1'? [Ynesfdaq?] q
+
+ abort: user quit
+ [255]
+
+Skip
+
+ $ hg commit -i <<EOF
+ > s
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] s
+
+ diff --git a/subdir/f2 b/subdir/f2
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
+ [255]
+
+No
+
+ $ hg commit -i <<EOF
+ > n
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] n
+
+ diff --git a/subdir/f2 b/subdir/f2
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
+ [255]
+
+f, quit
+
+ $ hg commit -i <<EOF
+ > f
+ > q
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] f
+
+ diff --git a/subdir/f2 b/subdir/f2
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f2'? [Ynesfdaq?] q
+
+ abort: user quit
+ [255]
+
+s, all
+
+ $ hg commit -i -d '18 0' -mx <<EOF
+ > s
+ > a
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] s
+
+ diff --git a/subdir/f2 b/subdir/f2
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f2'? [Ynesfdaq?] a
+
+
+ $ hg tip -p
+ changeset: 20:b3df3dda369a
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:18 1970 +0000
+ summary: x
+
+ diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
+ --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
+ +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
+ @@ -1,1 +1,2 @@
+ b
+ +b
+
+
+f
+
+ $ hg commit -i -d '19 0' -my <<EOF
+ > f
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] f
+
+
+ $ hg tip -p
+ changeset: 21:38ec577f126b
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:19 1970 +0000
+ summary: y
+
+ diff -r b3df3dda369a -r 38ec577f126b subdir/f1
+ --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
+ +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
+ @@ -1,1 +1,2 @@
+ a
+ +a
+
+
+#if execbit
+
+Preserve chmod +x
+
+ $ chmod +x f1
+ $ echo a >> f1
+ $ hg commit -i -d '20 0' -mz <<EOF
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ old mode 100644
+ new mode 100755
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -1,2 +1,3 @@
+ a
+ a
+ +a
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip --config diff.git=True -p
+ changeset: 22:3261adceb075
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:20 1970 +0000
+ summary: z
+
+ diff --git a/subdir/f1 b/subdir/f1
+ old mode 100644
+ new mode 100755
+ --- a/subdir/f1
+ +++ b/subdir/f1
+ @@ -1,2 +1,3 @@
+ a
+ a
+ +a
+
+
+Preserve execute permission on original
+
+ $ echo b >> f1
+ $ hg commit -i -d '21 0' -maa <<EOF
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,4 @@
+ a
+ a
+ a
+ +b
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip --config diff.git=True -p
+ changeset: 23:b429867550db
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:21 1970 +0000
+ summary: aa
+
+ diff --git a/subdir/f1 b/subdir/f1
+ --- a/subdir/f1
+ +++ b/subdir/f1
+ @@ -1,3 +1,4 @@
+ a
+ a
+ a
+ +b
+
+
+Preserve chmod -x
+
+ $ chmod -x f1
+ $ echo c >> f1
+ $ hg commit -i -d '22 0' -mab <<EOF
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ old mode 100755
+ new mode 100644
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -2,3 +2,4 @@
+ a
+ a
+ b
+ +c
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip --config diff.git=True -p
+ changeset: 24:0b082130c20a
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:22 1970 +0000
+ summary: ab
+
+ diff --git a/subdir/f1 b/subdir/f1
+ old mode 100755
+ new mode 100644
+ --- a/subdir/f1
+ +++ b/subdir/f1
+ @@ -2,3 +2,4 @@
+ a
+ a
+ b
+ +c
+
+
+#else
+
+Slightly bogus tests to get almost same repo structure as when x bit is used
+- but with different hashes.
+
+Mock "Preserve chmod +x"
+
+ $ echo a >> f1
+ $ hg commit -i -d '20 0' -mz <<EOF
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -1,2 +1,3 @@
+ a
+ a
+ +a
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip --config diff.git=True -p
+ changeset: 22:0d463bd428f5
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:20 1970 +0000
+ summary: z
+
+ diff --git a/subdir/f1 b/subdir/f1
+ --- a/subdir/f1
+ +++ b/subdir/f1
+ @@ -1,2 +1,3 @@
+ a
+ a
+ +a
+
+
+Mock "Preserve execute permission on original"
+
+ $ echo b >> f1
+ $ hg commit -i -d '21 0' -maa <<EOF
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,4 @@
+ a
+ a
+ a
+ +b
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip --config diff.git=True -p
+ changeset: 23:0eab41a3e524
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:21 1970 +0000
+ summary: aa
+
+ diff --git a/subdir/f1 b/subdir/f1
+ --- a/subdir/f1
+ +++ b/subdir/f1
+ @@ -1,3 +1,4 @@
+ a
+ a
+ a
+ +b
+
+
+Mock "Preserve chmod -x"
+
+ $ chmod -x f1
+ $ echo c >> f1
+ $ hg commit -i -d '22 0' -mab <<EOF
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -2,3 +2,4 @@
+ a
+ a
+ b
+ +c
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip --config diff.git=True -p
+ changeset: 24:f4f718f27b7c
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:22 1970 +0000
+ summary: ab
+
+ diff --git a/subdir/f1 b/subdir/f1
+ --- a/subdir/f1
+ +++ b/subdir/f1
+ @@ -2,3 +2,4 @@
+ a
+ a
+ b
+ +c
+
+
+#endif
+
+ $ cd ..
+
+
+Abort early when a merge is in progress
+
+ $ hg up 4
+ 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
+
+ $ touch iwillmergethat
+ $ hg add iwillmergethat
+
+ $ hg branch thatbranch
+ marked working directory as branch thatbranch
+ (branches are permanent and global, did you want a bookmark?)
+
+ $ hg ci -m'new head'
+
+ $ hg up default
+ 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+ $ hg merge thatbranch
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+
+ $ hg commit -i -m'will abort'
+ abort: cannot partially commit a merge (use "hg commit" instead)
+ [255]
+
+ $ hg up -C
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+Editing patch (and ignoring trailing text)
+
+ $ cat > editor.sh << '__EOF__'
+ > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
+ > trailing\nditto' "$1" > tmp
+ > mv tmp "$1"
+ > __EOF__
+ $ cat > editedfile << '__EOF__'
+ > This is the first line
+ > This is the second line
+ > This is the third line
+ > __EOF__
+ $ hg add editedfile
+ $ hg commit -medit-patch-1
+ $ cat > editedfile << '__EOF__'
+ > This line has changed
+ > This change will be committed
+ > This is the third line
+ > __EOF__
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/editedfile b/editedfile
+ 1 hunks, 2 lines changed
+ examine changes to 'editedfile'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,3 @@
+ -This is the first line
+ -This is the second line
+ +This line has changed
+ +This change will be committed
+ This is the third line
+ record this change to 'editedfile'? [Ynesfdaq?] e
+
+ $ cat editedfile
+ This line has changed
+ This change will be committed
+ This is the third line
+ $ hg cat -r tip editedfile
+ This is the first line
+ This change will be committed
+ This is the third line
+ $ hg revert editedfile
+
+Trying to edit patch for whole file
+
+ $ echo "This is the fourth line" >> editedfile
+ $ hg commit -i <<EOF
+ > e
+ > q
+ > EOF
+ diff --git a/editedfile b/editedfile
+ 1 hunks, 1 lines changed
+ examine changes to 'editedfile'? [Ynesfdaq?] e
+
+ cannot edit patch for whole file
+ examine changes to 'editedfile'? [Ynesfdaq?] q
+
+ abort: user quit
+ [255]
+ $ hg revert editedfile
+
+Removing changes from patch
+
+ $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
+ $ mv tmp editedfile
+ $ echo "This line has been added" >> editedfile
+ $ cat > editor.sh << '__EOF__'
+ > sed -e 's/^[-+]/ /' "$1" > tmp
+ > mv tmp "$1"
+ > __EOF__
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/editedfile b/editedfile
+ 1 hunks, 3 lines changed
+ examine changes to 'editedfile'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,3 @@
+ -This is the first line
+ -This change will be committed
+ -This is the third line
+ +This change will not be committed
+ +This is the second line
+ +This line has been added
+ record this change to 'editedfile'? [Ynesfdaq?] e
+
+ no changes to record
+ $ cat editedfile
+ This change will not be committed
+ This is the second line
+ This line has been added
+ $ hg cat -r tip editedfile
+ This is the first line
+ This change will be committed
+ This is the third line
+ $ hg revert editedfile
+
+Invalid patch
+
+ $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
+ $ mv tmp editedfile
+ $ echo "This line has been added" >> editedfile
+ $ cat > editor.sh << '__EOF__'
+ > sed s/This/That/ "$1" > tmp
+ > mv tmp "$1"
+ > __EOF__
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/editedfile b/editedfile
+ 1 hunks, 3 lines changed
+ examine changes to 'editedfile'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,3 @@
+ -This is the first line
+ -This change will be committed
+ -This is the third line
+ +This change will not be committed
+ +This is the second line
+ +This line has been added
+ record this change to 'editedfile'? [Ynesfdaq?] e
+
+ patching file editedfile
+ Hunk #1 FAILED at 0
+ 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
+ abort: patch failed to apply
+ [255]
+ $ cat editedfile
+ This change will not be committed
+ This is the second line
+ This line has been added
+ $ hg cat -r tip editedfile
+ This is the first line
+ This change will be committed
+ This is the third line
+ $ cat editedfile.rej
+ --- editedfile
+ +++ editedfile
+ @@ -1,3 +1,3 @@
+ -That is the first line
+ -That change will be committed
+ -That is the third line
+ +That change will not be committed
+ +That is the second line
+ +That line has been added
+
+Malformed patch - error handling
+
+ $ cat > editor.sh << '__EOF__'
+ > sed -e '/^@/p' "$1" > tmp
+ > mv tmp "$1"
+ > __EOF__
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/editedfile b/editedfile
+ 1 hunks, 3 lines changed
+ examine changes to 'editedfile'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,3 @@
+ -This is the first line
+ -This change will be committed
+ -This is the third line
+ +This change will not be committed
+ +This is the second line
+ +This line has been added
+ record this change to 'editedfile'? [Ynesfdaq?] e
+
+ abort: error parsing patch: unhandled transition: range -> range
+ [255]
+
+random text in random positions is still an error
+
+ $ cat > editor.sh << '__EOF__'
+ > sed -e '/^@/i\
+ > other' "$1" > tmp
+ > mv tmp "$1"
+ > __EOF__
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/editedfile b/editedfile
+ 1 hunks, 3 lines changed
+ examine changes to 'editedfile'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,3 @@
+ -This is the first line
+ -This change will be committed
+ -This is the third line
+ +This change will not be committed
+ +This is the second line
+ +This line has been added
+ record this change to 'editedfile'? [Ynesfdaq?] e
+
+ abort: error parsing patch: unhandled transition: file -> other
+ [255]
+
+ $ hg up -C
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+With win32text
+
+ $ echo '[extensions]' >> .hg/hgrc
+ $ echo 'win32text = ' >> .hg/hgrc
+ $ echo '[decode]' >> .hg/hgrc
+ $ echo '** = cleverdecode:' >> .hg/hgrc
+ $ echo '[encode]' >> .hg/hgrc
+ $ echo '** = cleverencode:' >> .hg/hgrc
+ $ echo '[patch]' >> .hg/hgrc
+ $ echo 'eol = crlf' >> .hg/hgrc
+
+Ignore win32text deprecation warning for now:
+
+ $ echo '[win32text]' >> .hg/hgrc
+ $ echo 'warn = no' >> .hg/hgrc
+
+ $ echo d >> subdir/f1
+ $ hg commit -i -d '24 0' -mw1 <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -3,3 +3,4 @@
+ a
+ b
+ c
+ +d
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+
+ $ hg tip -p
+ changeset: 28:* (glob)
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:24 1970 +0000
+ summary: w1
+
+ diff -r ???????????? -r ???????????? subdir/f1 (glob)
+ --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
+ +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
+ @@ -3,3 +3,4 @@
+ a
+ b
+ c
+ +d
+
+
+
+Test --user when ui.username not set
+ $ unset HGUSER
+ $ echo e >> subdir/f1
+ $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
+ > y
+ > y
+ > EOF
+ diff --git a/subdir/f1 b/subdir/f1
+ 1 hunks, 1 lines changed
+ examine changes to 'subdir/f1'? [Ynesfdaq?] y
+
+ @@ -4,3 +4,4 @@
+ b
+ c
+ d
+ +e
+ record this change to 'subdir/f1'? [Ynesfdaq?] y
+
+ $ hg log --template '{author}\n' -l 1
+ xyz
+ $ HGUSER="test"
+ $ export HGUSER
+
+
+Editing patch of newly added file
+
+ $ cat > editor.sh << '__EOF__'
+ > cat "$1" | sed "s/first/very/g" > tt
+ > mv tt "$1"
+ > __EOF__
+ $ cat > newfile << '__EOF__'
+ > This is the first line
+ > This is the second line
+ > This is the third line
+ > __EOF__
+ $ hg add newfile
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/newfile b/newfile
+ new file mode 100644
+ examine changes to 'newfile'? [Ynesfdaq?] y
+
+ @@ -0,0 +1,3 @@
+ +This is the first line
+ +This is the second line
+ +This is the third line
+ record this change to 'newfile'? [Ynesfdaq?] e
+
+ $ hg cat -r tip newfile
+ This is the very line
+ This is the second line
+ This is the third line
+
+ $ cat newfile
+ This is the first line
+ This is the second line
+ This is the third line
+
+ $ cd ..
--- a/tests/test-completion.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-completion.t Fri Mar 13 17:55:04 2015 -0500
@@ -202,7 +202,7 @@
add: include, exclude, subrepos, dry-run
annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
- commit: addremove, close-branch, amend, secret, edit, include, exclude, message, logfile, date, user, subrepos
+ commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
diff: rev, change, text, git, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
export: output, switch-parent, rev, text, git, nodates
forget: include, exclude
@@ -268,7 +268,7 @@
heads: rev, topo, active, closed, style, template
help: extension, command, keyword
identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
- import: strip, base, edit, force, no-commit, bypass, partial, exact, import-branch, message, logfile, date, user, similarity
+ import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
locate: rev, print0, fullpath, include, exclude
manifest: rev, all, template
@@ -278,7 +278,7 @@
phase: public, draft, secret, force, rev
recover:
rename: after, force, include, exclude, dry-run
- resolve: all, list, mark, unmark, no-status, tool, include, exclude
+ resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
revert: all, date, rev, no-backup, include, exclude, dry-run
rollback: dry-run, force
root:
--- a/tests/test-context.py Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-context.py Fri Mar 13 17:55:04 2015 -0500
@@ -51,7 +51,7 @@
for d in ctxb.diff(ctxa, git=True):
print d
-# test safeness and correctness of "cxt.status()"
+# test safeness and correctness of "ctx.status()"
print '= checking context.status():'
# ancestor "wcctx ~ 2"
--- a/tests/test-convert-datesort.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-convert-datesort.t Fri Mar 13 17:55:04 2015 -0500
@@ -85,9 +85,9 @@
$ hg -R t-datesort log -G --template '{rev} "{desc}"\n'
o 12 "c1"
|\
- | o 11 "b2x"
+ | _ 11 "b2x"
| |
- | | o 10 "a7x"
+ | | _ 10 "a7x"
| | |
o | | 9 "c0"
| | |
@@ -136,9 +136,9 @@
$ hg -R t-sourcesort log -G --template '{rev} "{desc}"\n'
o 12 "c1"
|\
- | o 11 "b2x"
+ | _ 11 "b2x"
| |
- | | o 10 "a7x"
+ | | _ 10 "a7x"
| | |
o | | 9 "c0"
| | |
@@ -189,11 +189,11 @@
|\
| o 11 "c0"
| |
- o | 10 "b2x"
+ _ | 10 "b2x"
| |
o | 9 "b1"
| |
- | | o 8 "a7x"
+ | | _ 8 "a7x"
| | |
| | o 7 "a6"
| | |
--- a/tests/test-convert-filemap.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-convert-filemap.t Fri Mar 13 17:55:04 2015 -0500
@@ -387,15 +387,15 @@
1 addb
0 closedefault
$ glog -R branchpruning-hg1
- o 5 "closedefault" files:
+ _ 5 "closedefault" files:
|
o 4 "addb" files: b
|
- | o 3 "closeempty" files:
+ | _ 3 "closeempty" files:
| |
| o 2 "emptybranch" files:
|/
- | o 1 "closefoo" files:
+ | _ 1 "closefoo" files:
|/
o 0 "adda" files: a
@@ -422,7 +422,7 @@
1 closeempty
0 closedefault
$ glog -R branchpruning-hg2
- o 1 "closedefault" files:
+ _ 1 "closedefault" files:
|
o 0 "addb" files: b
--- a/tests/test-doctest.py Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-doctest.py Fri Mar 13 17:55:04 2015 -0500
@@ -19,6 +19,7 @@
testmod('mercurial.hgweb.hgwebdir_mod')
testmod('mercurial.match')
testmod('mercurial.minirst')
+testmod('mercurial.patch')
testmod('mercurial.pathutil')
testmod('mercurial.revset')
testmod('mercurial.store')
--- a/tests/test-extension.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-extension.t Fri Mar 13 17:55:04 2015 -0500
@@ -946,6 +946,9 @@
Declare the version as supporting this hg version, show regular bts link:
$ hgver=`$PYTHON -c 'from mercurial import util; print util.version().split("+")[0]'`
$ echo 'testedwith = """'"$hgver"'"""' >> throw.py
+ $ if [ -z "$hgver" ]; then
+ > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
+ > fi
$ rm -f throw.pyc throw.pyo
$ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
** unknown exception encountered, please report by visiting
@@ -1140,3 +1143,27 @@
C sub3/3
$ cd ..
+
+Test synopsis and docstring extending
+
+ $ hg init exthelp
+ $ cat > exthelp.py <<EOF
+ > from mercurial import commands, extensions
+ > def exbookmarks(orig, *args, **opts):
+ > return orig(*args, **opts)
+ > def uisetup(ui):
+ > synopsis = ' GREPME [--foo] [-x]'
+ > docstring = '''
+ > GREPME make sure that this is in the help!
+ > '''
+ > extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
+ > synopsis, docstring)
+ > EOF
+ $ abspath=`pwd`/exthelp.py
+ $ echo '[extensions]' >> $HGRCPATH
+ $ echo "exthelp = $abspath" >> $HGRCPATH
+ $ cd exthelp
+ $ hg help bookmarks | grep GREPME
+ hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
+ GREPME make sure that this is in the help!
+
--- a/tests/test-gendoc.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-gendoc.t Fri Mar 13 17:55:04 2015 -0500
@@ -1,4 +1,5 @@
#require docutils
+#require gettext
Test document extraction
--- a/tests/test-glog.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-glog.t Fri Mar 13 17:55:04 2015 -0500
@@ -1541,6 +1541,9 @@
$ testlog --follow
[]
[]
+ $ testlog -rnull
+ ['null']
+ []
$ echo a > a
$ echo aa > aa
$ echo f > f
@@ -1764,6 +1767,13 @@
nodetag 1
nodetag 0
+Test --follow null parent
+
+ $ hg up -q null
+ $ testlog -f
+ []
+ []
+
Test --follow-first
$ hg up -q 3
@@ -2192,13 +2202,6 @@
(func
('symbol', 'rev')
('symbol', '6'))))
- --- log.nodes * (glob)
- +++ glog.nodes * (glob)
- @@ -1,3 +1,3 @@
- -nodetag 6
- nodetag 8
- nodetag 7
- +nodetag 6
Test --follow-first and forward --rev
@@ -2240,6 +2243,14 @@
('symbol', 'rev')
('symbol', '6'))))
+Test --follow with --rev of graphlog extension
+
+ $ hg --config extensions.graphlog= glog -qfr1
+ o 1:216d4c92cf98
+ |
+ o 0:f8035bb17114
+
+
Test subdir
$ hg up -q 3
@@ -2354,4 +2365,14 @@
date: Thu Jan 01 00:00:00 1970 +0000
+should not draw line down to null due to the magic of fullreposet
+
+ $ hg log -G -r 'all()' | tail -6
+ |
+ o changeset: 0:f8035bb17114
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: add a
+
+
$ cd ..
--- a/tests/test-grep.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-grep.t Fri Mar 13 17:55:04 2015 -0500
@@ -82,6 +82,10 @@
port:1:2:+:eggs:export
port:0:1:+:spam:import
+ $ hg up -q null
+ $ hg grep -f port
+ [1]
+
$ cd ..
$ hg init t2
$ cd t2
--- a/tests/test-help.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-help.t Fri Mar 13 17:55:04 2015 -0500
@@ -411,7 +411,7 @@
Mercurial Distributed SCM (version *) (glob)
(see http://mercurial.selenic.com for more information)
- Copyright (C) 2005-2014 Matt Mackall and others
+ Copyright (C) 2005-2015 Matt Mackall and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
@@ -1101,6 +1101,125 @@
abort: help section not found
[255]
+Test dynamic list of merge tools only shows up once
+ $ hg help merge-tools
+ Merge Tools
+ """""""""""
+
+ To merge files Mercurial uses merge tools.
+
+ A merge tool combines two different versions of a file into a merged file.
+ Merge tools are given the two files and the greatest common ancestor of
+ the two file versions, so they can determine the changes made on both
+ branches.
+
+ Merge tools are used both for "hg resolve", "hg merge", "hg update", "hg
+ backout" and in several extensions.
+
+ Usually, the merge tool tries to automatically reconcile the files by
+ combining all non-overlapping changes that occurred separately in the two
+ different evolutions of the same initial base file. Furthermore, some
+ interactive merge programs make it easier to manually resolve conflicting
+ merges, either in a graphical way, or by inserting some conflict markers.
+ Mercurial does not include any interactive merge programs but relies on
+ external tools for that.
+
+ Available merge tools
+ =====================
+
+ External merge tools and their properties are configured in the merge-
+ tools configuration section - see hgrc(5) - but they can often just be
+ named by their executable.
+
+ A merge tool is generally usable if its executable can be found on the
+ system and if it can handle the merge. The executable is found if it is an
+ absolute or relative executable path or the name of an application in the
+ executable search path. The tool is assumed to be able to handle the merge
+ if it can handle symlinks if the file is a symlink, if it can handle
+ binary files if the file is binary, and if a GUI is available if the tool
+ requires a GUI.
+
+ There are some internal merge tools which can be used. The internal merge
+ tools are:
+
+ ":dump"
+ Creates three versions of the files to merge, containing the contents of
+ local, other and base. These files can then be used to perform a merge
+ manually. If the file to be merged is named "a.txt", these files will
+ accordingly be named "a.txt.local", "a.txt.other" and "a.txt.base" and
+ they will be placed in the same directory as "a.txt".
+
+ ":fail"
+ Rather than attempting to merge files that were modified on both
+ branches, it marks them as unresolved. The resolve command must be used
+ to resolve these conflicts.
+
+ ":local"
+ Uses the local version of files as the merged version.
+
+ ":merge"
+ Uses the internal non-interactive simple merge algorithm for merging
+ files. It will fail if there are any conflicts and leave markers in the
+ partially merged file. Markers will have two sections, one for each side
+ of merge.
+
+ ":merge3"
+ Uses the internal non-interactive simple merge algorithm for merging
+ files. It will fail if there are any conflicts and leave markers in the
+ partially merged file. Marker will have three sections, one from each
+ side of the merge and one for the base content.
+
+ ":other"
+ Uses the other version of files as the merged version.
+
+ ":prompt"
+ Asks the user which of the local or the other version to keep as the
+ merged version.
+
+ ":tagmerge"
+ Uses the internal tag merge algorithm (experimental).
+
+ Internal tools are always available and do not require a GUI but will by
+ default not handle symlinks or binary files.
+
+ Choosing a merge tool
+ =====================
+
+ Mercurial uses these rules when deciding which merge tool to use:
+
+ 1. If a tool has been specified with the --tool option to merge or
+ resolve, it is used. If it is the name of a tool in the merge-tools
+ configuration, its configuration is used. Otherwise the specified tool
+ must be executable by the shell.
+ 2. If the "HGMERGE" environment variable is present, its value is used and
+ must be executable by the shell.
+ 3. If the filename of the file to be merged matches any of the patterns in
+ the merge-patterns configuration section, the first usable merge tool
+ corresponding to a matching pattern is used. Here, binary capabilities
+ of the merge tool are not considered.
+ 4. If ui.merge is set it will be considered next. If the value is not the
+ name of a configured tool, the specified value is used and must be
+ executable by the shell. Otherwise the named tool is used if it is
+ usable.
+ 5. If any usable merge tools are present in the merge-tools configuration
+ section, the one with the highest priority is used.
+ 6. If a program named "hgmerge" can be found on the system, it is used -
+ but it will by default not be used for symlinks and binary files.
+ 7. If the file to be merged is not binary and is not a symlink, then
+ internal ":merge" is used.
+ 8. The merge of the file fails and must be resolved before commit.
+
+ Note:
+ After selecting a merge program, Mercurial will by default attempt to
+ merge the files using a simple merge algorithm first. Only if it
+ doesn't succeed because of conflicting changes Mercurial will actually
+ execute the merge program. Whether to use the simple merge algorithm
+ first can be controlled by the premerge setting of the merge tool.
+ Premerge is enabled by default unless the file is binary or a symlink.
+
+ See the merge-tools and ui sections of hgrc(5) for details on the
+ configuration of merge tools.
+
Test usage of section marks in help documents
$ cd "$TESTDIR"/../doc
--- a/tests/test-hgrc.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgrc.t Fri Mar 13 17:55:04 2015 -0500
@@ -71,7 +71,7 @@
Mercurial Distributed SCM (version *) (glob)
(see http://mercurial.selenic.com for more information)
- Copyright (C) 2005-2014 Matt Mackall and others
+ Copyright (C) 2005-2015 Matt Mackall and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ unset FAKEPATH
--- a/tests/test-hgweb-commands.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-commands.t Fri Mar 13 17:55:04 2015 -0500
@@ -726,7 +726,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -752,11 +751,13 @@
</div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
<td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -873,7 +874,8 @@
</tr>
<tr>
<th class="date">date</th>
- <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+ <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+ </tr>
<tr>
<th class="author">parents</th>
<td class="author"></td>
@@ -894,8 +896,7 @@
<a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
<div id="diffstatdetails" style="display:none;">
<a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
- <p></p>
- <table class="stripes2"> <tr>
+ <table class="diffstat-table stripes2"> <tr>
<td class="diffstat-file"><a href="#l1.1">da/foo</a></td>
<td class="diffstat-total" align="right">1</td>
<td class="diffstat-graph">
@@ -1012,11 +1013,13 @@
</div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
<td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
--- a/tests/test-hgweb-descend-empties.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-descend-empties.t Fri Mar 13 17:55:04 2015 -0500
@@ -81,11 +81,13 @@
</form>
<table class="bigtable">
+ <thead>
<tr>
<th class="name">name</th>
<th class="size">size</th>
<th class="permissions">permissions</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr class="fileline">
<td class="name"><a href="/file/9087c84a0f5d/">[up]</a></td>
--- a/tests/test-hgweb-diffs.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-diffs.t Fri Mar 13 17:55:04 2015 -0500
@@ -97,7 +97,8 @@
</tr>
<tr>
<th class="date">date</th>
- <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+ <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+ </tr>
<tr>
<th class="author">parents</th>
<td class="author"></td>
@@ -118,8 +119,7 @@
<a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
<div id="diffstatdetails" style="display:none;">
<a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
- <p></p>
- <table class="stripes2"> <tr>
+ <table class="diffstat-table stripes2"> <tr>
<td class="diffstat-file"><a href="#l1.1">a</a></td>
<td class="diffstat-total" align="right">1</td>
<td class="diffstat-graph">
@@ -369,7 +369,8 @@
</tr>
<tr>
<th class="date">date</th>
- <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+ <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+ </tr>
<tr>
<th class="author">parents</th>
<td class="author"></td>
@@ -390,8 +391,7 @@
<a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
<div id="diffstatdetails" style="display:none;">
<a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
- <p></p>
- <table class="stripes2"> <tr>
+ <table class="diffstat-table stripes2"> <tr>
<td class="diffstat-file"><a href="#l1.1">a</a></td>
<td class="diffstat-total" align="right">1</td>
<td class="diffstat-graph">
--- a/tests/test-hgweb-empty.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-empty.t Fri Mar 13 17:55:04 2015 -0500
@@ -48,7 +48,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -74,11 +73,13 @@
</div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
</tbody>
@@ -158,7 +159,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -184,11 +184,13 @@
</div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
</tbody>
@@ -264,7 +266,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -431,11 +432,13 @@
</form>
<table class="bigtable">
+ <thead>
<tr>
<th class="name">name</th>
<th class="size">size</th>
<th class="permissions">permissions</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr class="fileline">
<td class="name"><a href="/file/000000000000/">[up]</a></td>
--- a/tests/test-hgweb-filelog.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-filelog.t Fri Mar 13 17:55:04 2015 -0500
@@ -156,7 +156,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log/01de2d66a28d/a" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -181,11 +180,13 @@
| <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
<td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -266,7 +267,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log/01de2d66a28d/a" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -291,11 +291,13 @@
| <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
<td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -376,7 +378,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log/5ed941583260/a" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -401,11 +402,13 @@
| <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
<td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -481,7 +484,6 @@
<ul>
<li><a href="/help">help</a></li>
</ul>
- <p></p>
<div class="atom-logo">
<a href="/atom-log/5ed941583260/a" title="subscribe to atom feed">
<img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -506,11 +508,13 @@
| <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
<table class="bigtable">
+ <thead>
<tr>
<th class="age">age</th>
<th class="author">author</th>
<th class="description">description</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
<td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
--- a/tests/test-hgweb-removed.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-removed.t Fri Mar 13 17:55:04 2015 -0500
@@ -78,7 +78,8 @@
</tr>
<tr>
<th class="date">date</th>
- <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+ <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+ </tr>
<tr>
<th class="author">parents</th>
<td class="author"><a href="/rev/cb9a9f314b8b">cb9a9f314b8b</a> </td>
@@ -99,8 +100,7 @@
<a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
<div id="diffstatdetails" style="display:none;">
<a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
- <p></p>
- <table class="stripes2"> <tr>
+ <table class="diffstat-table stripes2"> <tr>
<td class="diffstat-file"><a href="#l1.1">a</a></td>
<td class="diffstat-total" align="right">1</td>
<td class="diffstat-graph">
--- a/tests/test-hgweb.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb.t Fri Mar 13 17:55:04 2015 -0500
@@ -272,11 +272,13 @@
</form>
<table class="bigtable">
+ <thead>
<tr>
<th class="name">name</th>
<th class="size">size</th>
<th class="permissions">permissions</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr class="fileline">
<td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
--- a/tests/test-hgwebdir.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgwebdir.t Fri Mar 13 17:55:04 2015 -0500
@@ -201,6 +201,7 @@
<h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
<table class="bigtable">
+ <thead>
<tr>
<th><a href="?sort=name">Name</a></th>
<th><a href="?sort=description">Description</a></th>
@@ -209,6 +210,7 @@
<th> </th>
<th> </th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
@@ -699,6 +701,7 @@
<h2 class="breadcrumb"><a href="/">Mercurial</a> > <a href="/t">t</a> </h2>
<table class="bigtable">
+ <thead>
<tr>
<th><a href="?sort=name">Name</a></th>
<th><a href="?sort=description">Description</a></th>
@@ -707,6 +710,7 @@
<th> </th>
<th> </th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr>
@@ -1128,6 +1132,7 @@
<h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
<table class="bigtable">
+ <thead>
<tr>
<th><a href="?sort=name">Name</a></th>
<th><a href="?sort=description">Description</a></th>
@@ -1136,6 +1141,7 @@
<th> </th>
<th> </th>
</tr>
+ </thead>
<tbody class="stripes2">
</tbody>
--- a/tests/test-highlight.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-highlight.t Fri Mar 13 17:55:04 2015 -0500
@@ -268,10 +268,12 @@
<div class="overflow">
<table class="bigtable">
+ <thead>
<tr>
<th class="annotate">rev</th>
<th class="line"> line source</th>
</tr>
+ </thead>
<tbody class="stripes2">
<tr id="l1">
--- a/tests/test-histedit-arguments.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-histedit-arguments.t Fri Mar 13 17:55:04 2015 -0500
@@ -103,6 +103,15 @@
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg up --quiet
+Test config specified default
+-----------------------------
+
+ $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
+ > pick c8e68270e35a 3 four
+ > pick 08d98a8350f3 4 five
+ > EOF
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
Run on a revision not descendants of the initial parent
--------------------------------------------------------------------
--- a/tests/test-histedit-drop.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-histedit-drop.t Fri Mar 13 17:55:04 2015 -0500
@@ -96,7 +96,6 @@
Check histedit_source
$ hg log --debug --rev f518305ce889
- invalid branchheads cache (visible): tip differs
changeset: 4:f518305ce889c07cb5bd05522176d75590ef3324
tag: tip
phase: draft
--- a/tests/test-histedit-edit.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-histedit-edit.t Fri Mar 13 17:55:04 2015 -0500
@@ -3,13 +3,14 @@
$ cat >> $HGRCPATH <<EOF
> [extensions]
> histedit=
+ > strip=
> EOF
$ initrepo ()
> {
> hg init r
> cd r
- > for x in a b c d e f ; do
+ > for x in a b c d e f g; do
> echo $x > $x
> hg add $x
> hg ci -m $x
@@ -20,10 +21,15 @@
log before edit
$ hg log --graph
- @ changeset: 5:652413bf663e
+ @ changeset: 6:3c6a8ed2ebe8
| tag: tip
| user: test
| date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: g
+ |
+ o changeset: 5:652413bf663e
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
| summary: f
|
o changeset: 4:e860deea161a
@@ -58,11 +64,19 @@
> pick 055a42cdd887 d
> edit e860deea161a e
> pick 652413bf663e f
+ > pick 3c6a8ed2ebe8 g
> EOF
- 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
Make changes as needed, you may commit or record as needed now.
When you are finished, run hg histedit --continue to resume.
+edit the plan
+ $ hg histedit --edit-plan --commands - 2>&1 << EOF
+ > edit e860deea161a e
+ > pick 652413bf663e f
+ > drop 3c6a8ed2ebe8 g
+ > EOF
+
Go at a random point and try to continue
$ hg id -n
@@ -72,10 +86,22 @@
(use 'hg histedit --continue' or 'hg histedit --abort')
[255]
+Try to delete necessary commit
+ $ hg strip -r 652413bf663e
+ abort: histedit in progress, can't strip 363532343133
+ [255]
+
commit, then edit the revision
$ hg ci -m 'wat'
created new head
$ echo a > e
+
+qnew should fail while we're in the middle of the edit step
+
+ $ hg --config extensions.mq= qnew please-fail
+ abort: histedit in progress
+ (use 'hg histedit --continue' or 'hg histedit --abort')
+ [255]
$ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-hook.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hook.t Fri Mar 13 17:55:04 2015 -0500
@@ -12,13 +12,19 @@
> pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
> pre-cat = python "$TESTDIR/printenv.py" pre-cat
> post-cat = python "$TESTDIR/printenv.py" post-cat
+ > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
+ > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
+ > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
> EOF
$ echo a > a
$ hg add a
$ hg commit -m a
precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
+ pretxnopen hook: HG_TXNNAME=commit
pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
0:cb9a9f314b8b
+ pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+ txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=commit
commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
@@ -42,8 +48,11 @@
$ echo b >> a
$ hg commit -m a1 -d "1 0"
precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+ pretxnopen hook: HG_TXNNAME=commit
pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
1:ab228980c14d
+ pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+ txnclose hook: HG_TXNNAME=commit
commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
$ hg update -C 0
@@ -52,8 +61,11 @@
$ hg add b
$ hg commit -m b -d '1 0'
precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+ pretxnopen hook: HG_TXNNAME=commit
pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
2:ee9deb46ab31
+ pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+ txnclose hook: HG_TXNNAME=commit
commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
created new head
@@ -62,8 +74,11 @@
(branch merge, don't forget to commit)
$ hg commit -m merge -d '2 0'
precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
+ pretxnopen hook: HG_TXNNAME=commit
pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
3:07f3376c1e65
+ pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+ txnclose hook: HG_TXNNAME=commit
commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
@@ -103,9 +118,12 @@
$ hg tag -d '3 0' a
pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
+ pretxnopen hook: HG_TXNNAME=commit
pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
4:539e4b31b6dc
+ pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
+ txnclose hook: HG_TXNNAME=commit
commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
$ hg tag -l la
@@ -137,6 +155,7 @@
4:539e4b31b6dc
$ hg commit -m 'fail' -d '4 0'
precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
+ pretxnopen hook: HG_TXNNAME=commit
pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
5:6f611f8018c1
5:6f611f8018c1
@@ -198,6 +217,9 @@
pushing to ../a
searching for changes
no changes found
+ pretxnopen hook: HG_TXNNAME=bookmarks
+ pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=bookmarks
+ txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNNAME=bookmarks
pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
exporting bookmark foo
[1]
--- a/tests/test-https.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-https.t Fri Mar 13 17:55:04 2015 -0500
@@ -119,12 +119,12 @@
Apple's OpenSSL. This trick do not work with plain OpenSSL.
$ DISABLEOSXDUMMYCERT=
-#if osx
+#if defaultcacerts
$ hg clone https://localhost:$HGPORT/ copy-pull
abort: error: *certificate verify failed* (glob)
[255]
- $ DISABLEOSXDUMMYCERT="--config=web.cacerts="
+ $ DISABLEOSXDUMMYCERT="--config=web.cacerts=!"
#endif
clone via pull
@@ -156,8 +156,8 @@
$ echo '[hooks]' >> .hg/hgrc
$ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
$ hg pull $DISABLEOSXDUMMYCERT
+ pulling from https://localhost:$HGPORT/
warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
- pulling from https://localhost:$HGPORT/
searching for changes
adding changesets
adding manifests
@@ -188,28 +188,30 @@
searching for changes
no changes found
$ P=`pwd` hg -R copy-pull pull --insecure
+ pulling from https://localhost:$HGPORT/
warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
- pulling from https://localhost:$HGPORT/
searching for changes
no changes found
cacert mismatch
$ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
+ pulling from https://127.0.0.1:$HGPORT/
abort: 127.0.0.1 certificate error: certificate is for localhost
(configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
[255]
$ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
+ pulling from https://127.0.0.1:$HGPORT/
warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
- pulling from https://127.0.0.1:$HGPORT/
searching for changes
no changes found
$ hg -R copy-pull pull --config web.cacerts=pub-other.pem
+ pulling from https://localhost:$HGPORT/
abort: error: *certificate verify failed* (glob)
[255]
$ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
+ pulling from https://localhost:$HGPORT/
warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
- pulling from https://localhost:$HGPORT/
searching for changes
no changes found
@@ -218,6 +220,7 @@
$ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
$ cat hg1.pid >> $DAEMON_PIDS
$ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
+ pulling from https://localhost:$HGPORT1/
abort: error: *certificate verify failed* (glob)
[255]
@@ -226,6 +229,7 @@
$ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
$ cat hg2.pid >> $DAEMON_PIDS
$ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
+ pulling from https://localhost:$HGPORT2/
abort: error: *certificate verify failed* (glob)
[255]
@@ -236,7 +240,7 @@
$ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
- works without cacerts
- $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
+ $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=!
5fed3813f7f5
- fails when cert doesn't match hostname (port is ignored)
@@ -267,8 +271,8 @@
Test unvalidated https through proxy
$ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
+ pulling from https://localhost:$HGPORT/
warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
- pulling from https://localhost:$HGPORT/
searching for changes
no changes found
@@ -286,8 +290,10 @@
Test https with cert problems through proxy
$ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
+ pulling from https://localhost:$HGPORT/
abort: error: *certificate verify failed* (glob)
[255]
$ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
+ pulling from https://localhost:$HGPORT2/
abort: error: *certificate verify failed* (glob)
[255]
--- a/tests/test-import-bypass.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-import-bypass.t Fri Mar 13 17:55:04 2015 -0500
@@ -104,6 +104,86 @@
$ hg rollback
repository tip rolled back to revision 1 (undo import)
+Test --strip with --bypass
+
+ $ mkdir -p dir/dir2
+ $ echo bb > dir/dir2/b
+ $ echo cc > dir/dir2/c
+ $ echo d > dir/d
+ $ hg ci -Am 'addabcd'
+ adding dir/d
+ adding dir/dir2/b
+ adding dir/dir2/c
+ $ shortlog
+ @ 2:d805bc8236b6 test 0 0 - default - addabcd
+ |
+ | o 1:4e322f7ce8e3 test 0 0 - foo - changea
+ |/
+ o 0:07f494440405 test 0 0 - default - adda
+
+ $ hg import --bypass --strip 2 --prefix dir/ - <<EOF
+ > # HG changeset patch
+ > # User test
+ > # Date 0 0
+ > # Branch foo
+ > changeabcd
+ >
+ > diff --git a/foo/a b/foo/a
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/foo/a
+ > @@ -0,0 +1 @@
+ > +a
+ > diff --git a/foo/dir2/b b/foo/dir2/b2
+ > rename from foo/dir2/b
+ > rename to foo/dir2/b2
+ > diff --git a/foo/dir2/c b/foo/dir2/c
+ > --- a/foo/dir2/c
+ > +++ b/foo/dir2/c
+ > @@ -0,0 +1 @@
+ > +cc
+ > diff --git a/foo/d b/foo/d
+ > deleted file mode 100644
+ > --- a/foo/d
+ > +++ /dev/null
+ > @@ -1,1 +0,0 @@
+ > -d
+ > EOF
+ applying patch from stdin
+
+ $ shortlog
+ o 3:5bd46886ca3e test 0 0 - default - changeabcd
+ |
+ @ 2:d805bc8236b6 test 0 0 - default - addabcd
+ |
+ | o 1:4e322f7ce8e3 test 0 0 - foo - changea
+ |/
+ o 0:07f494440405 test 0 0 - default - adda
+
+ $ hg diff --change 3 --git
+ diff --git a/dir/a b/dir/a
+ new file mode 100644
+ --- /dev/null
+ +++ b/dir/a
+ @@ -0,0 +1,1 @@
+ +a
+ diff --git a/dir/d b/dir/d
+ deleted file mode 100644
+ --- a/dir/d
+ +++ /dev/null
+ @@ -1,1 +0,0 @@
+ -d
+ diff --git a/dir/dir2/b b/dir/dir2/b2
+ rename from dir/dir2/b
+ rename to dir/dir2/b2
+ diff --git a/dir/dir2/c b/dir/dir2/c
+ --- a/dir/dir2/c
+ +++ b/dir/dir2/c
+ @@ -1,1 +1,2 @@
+ cc
+ +cc
+ $ hg -q --config extensions.strip= strip .
+
Test unsupported combinations
$ hg import --bypass --no-commit ../test.diff
@@ -112,6 +192,9 @@
$ hg import --bypass --similarity 50 ../test.diff
abort: cannot use --similarity with --bypass
[255]
+ $ hg import --exact --prefix dir/ ../test.diff
+ abort: cannot use --exact with --prefix
+ [255]
Test commit editor
(this also tests that editor is invoked, if the patch doesn't contain
--- a/tests/test-import-git.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-import-git.t Fri Mar 13 17:55:04 2015 -0500
@@ -612,12 +612,64 @@
a
R a
-Renames, similarity and git diff
+Prefix with strip, renames, creates etc
$ hg revert -aC
undeleting a
forgetting b
$ rm b
+ $ mkdir -p dir/dir2
+ $ echo b > dir/dir2/b
+ $ echo c > dir/dir2/c
+ $ echo d > dir/d
+ $ hg ci -Am addbcd
+ adding dir/d
+ adding dir/dir2/b
+ adding dir/dir2/c
+(test that prefixes are relative to the root)
+ $ mkdir tmpdir
+ $ cd tmpdir
+ $ hg import --no-commit -p2 --prefix dir/ - <<EOF
+ > diff --git a/foo/a b/foo/a
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/foo/a
+ > @@ -0,0 +1 @@
+ > +a
+ > diff --git a/foo/dir2/b b/foo/dir2/b2
+ > rename from foo/dir2/b
+ > rename to foo/dir2/b2
+ > diff --git a/foo/dir2/c b/foo/dir2/c
+ > --- a/foo/dir2/c
+ > +++ b/foo/dir2/c
+ > @@ -0,0 +1 @@
+ > +cc
+ > diff --git a/foo/d b/foo/d
+ > deleted file mode 100644
+ > --- a/foo/d
+ > +++ /dev/null
+ > @@ -1,1 +0,0 @@
+ > -d
+ > EOF
+ applying patch from stdin
+ $ hg st --copies
+ M dir/dir2/c
+ A dir/a
+ A dir/dir2/b2
+ dir/dir2/b
+ R dir/d
+ R dir/dir2/b
+ $ cd ..
+
+Renames, similarity and git diff
+
+ $ hg revert -aC
+ forgetting dir/a (glob)
+ undeleting dir/d (glob)
+ undeleting dir/dir2/b (glob)
+ forgetting dir/dir2/b2 (glob)
+ reverting dir/dir2/c (glob)
+ $ rm dir/a dir/dir2/b2
$ hg import --similarity 90 --no-commit - <<EOF
> diff --git a/a b/b
> rename from a
--- a/tests/test-import.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-import.t Fri Mar 13 17:55:04 2015 -0500
@@ -670,6 +670,25 @@
$ hg status
$ cat a
bb
+
+test --prefix
+
+ $ mkdir -p dir/dir2
+ $ echo b > dir/dir2/b
+ $ hg ci -Am b
+ adding dir/dir2/b
+ $ hg import -p2 --prefix dir - << EOF
+ > foobar
+ > --- drop1/drop2/dir2/b
+ > +++ drop1/drop2/dir2/b
+ > @@ -1,1 +1,1 @@
+ > -b
+ > +cc
+ > EOF
+ applying patch from stdin
+ $ hg status
+ $ cat dir/dir2/b
+ cc
$ cd ..
--- a/tests/test-keyword.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-keyword.t Fri Mar 13 17:55:04 2015 -0500
@@ -473,18 +473,24 @@
$ hg -v record -l msg -d '12 2' r<<EOF
> y
+ > y
> EOF
diff --git a/r b/r
new file mode 100644
examine changes to 'r'? [Ynesfdaq?] y
+ @@ -0,0 +1,1 @@
+ +$Id$
+ record this change to 'r'? [Ynesfdaq?] y
+
+ resolving manifests
+ patching file r
committing files:
r
committing manifest
committing changelog
committed changeset 3:82a2f715724d
overwriting r expanding keywords
- - status call required for dirstate.normallookup() check
$ hg status r
$ hg --verbose rollback
repository tip rolled back to revision 2 (undo commit)
@@ -501,11 +507,18 @@
$ hg add i
$ hg --verbose record -d '13 1' -m recignored<<EOF
> y
+ > y
> EOF
diff --git a/i b/i
new file mode 100644
examine changes to 'i'? [Ynesfdaq?] y
+ @@ -0,0 +1,1 @@
+ +$Id$
+ record this change to 'i'? [Ynesfdaq?] y
+
+ resolving manifests
+ patching file i
committing files:
i
committing manifest
--- a/tests/test-largefiles-cache.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-largefiles-cache.t Fri Mar 13 17:55:04 2015 -0500
@@ -136,7 +136,7 @@
#endif
Test issue 4053 (remove --after on a deleted, uncommitted file shouldn't say
-it is missing, but a remove on a nonexistant unknown file still should. Same
+it is missing, but a remove on a nonexistent unknown file still should. Same
for a forget.)
$ cd src
--- a/tests/test-largefiles-misc.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-largefiles-misc.t Fri Mar 13 17:55:04 2015 -0500
@@ -362,6 +362,14 @@
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg status -S
+ $ hg forget -v subrepo/large.txt
+ removing subrepo/large.txt (glob)
+
+Test reverting a forgotten file
+ $ hg revert -R subrepo subrepo/large.txt
+ $ hg status -SA subrepo/large.txt
+ C subrepo/large.txt
+
$ hg rm -v subrepo/large.txt
removing subrepo/large.txt (glob)
$ hg revert -R subrepo subrepo/large.txt
@@ -443,6 +451,10 @@
date: Thu Jan 01 00:00:00 1970 +0000
summary: anotherlarge
+ $ hg --debug log -T '{rev}: {desc}\n' ../sub/anotherlarge
+ updated patterns: ['../.hglf/sub/../sub/anotherlarge', '../sub/anotherlarge']
+ 1: anotherlarge
+
$ hg log -G anotherlarge
@ changeset: 1:9627a577c5e9
| tag: tip
@@ -450,6 +462,30 @@
| date: Thu Jan 01 00:00:00 1970 +0000
| summary: anotherlarge
|
+
+ $ hg log glob:another*
+ changeset: 1:9627a577c5e9
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: anotherlarge
+
+ $ hg --debug log -T '{rev}: {desc}\n' -G glob:another*
+ updated patterns: ['glob:../.hglf/sub/another*', 'glob:another*']
+ @ 1: anotherlarge
+ |
+
+#if no-msys
+ $ hg --debug log -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
+ updated patterns: ['glob:../.hglf/sub/another*']
+ 1: anotherlarge
+
+ $ hg --debug log -G -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
+ updated patterns: ['glob:../.hglf/sub/another*']
+ @ 1: anotherlarge
+ |
+#endif
+
$ echo more >> anotherlarge
$ hg st .
M anotherlarge
@@ -460,8 +496,33 @@
? sub/anotherlarge.orig
$ cd ..
+Test glob logging from the root dir
+ $ hg log glob:**another*
+ changeset: 1:9627a577c5e9
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: anotherlarge
+
+ $ hg log -G glob:**another*
+ @ changeset: 1:9627a577c5e9
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: anotherlarge
+ |
+
$ cd ..
+Log from outer space
+ $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/sub/anotherlarge'
+ updated patterns: ['addrm2/.hglf/sub/anotherlarge', 'addrm2/sub/anotherlarge']
+ 1: anotherlarge
+ $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/.hglf/sub/anotherlarge'
+ updated patterns: ['addrm2/.hglf/sub/anotherlarge']
+ 1: anotherlarge
+
+
Check error message while exchange
=========================================================
--- a/tests/test-largefiles.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-largefiles.t Fri Mar 13 17:55:04 2015 -0500
@@ -1183,12 +1183,12 @@
adding manifests
adding file changes
added 1 changesets with 2 changes to 2 files (+1 heads)
- 0 largefiles cached
rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
Invoking status precommit hook
M sub/normal4
M sub2/large6
saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-dd1d9f80-backup.hg (glob)
+ 0 largefiles cached
$ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
$ hg log --template '{rev}:{node|short} {desc|firstline}\n'
9:598410d3eb9a modify normal file largefile in repo d
--- a/tests/test-log.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-log.t Fri Mar 13 17:55:04 2015 -0500
@@ -46,18 +46,31 @@
$ hg ci -me -d '5 0'
Make sure largefiles doesn't interfere with logging a regular file
- $ hg log a --config extensions.largefiles=
- changeset: 0:9161b9aeaf16
- user: test
- date: Thu Jan 01 00:00:01 1970 +0000
- summary: a
-
+ $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
+ updated patterns: ['.hglf/a', 'a']
+ 0: a
$ hg log a
changeset: 0:9161b9aeaf16
user: test
date: Thu Jan 01 00:00:01 1970 +0000
summary: a
+ $ hg log glob:a*
+ changeset: 3:2ca5ba701980
+ user: test
+ date: Thu Jan 01 00:00:04 1970 +0000
+ summary: d
+
+ changeset: 0:9161b9aeaf16
+ user: test
+ date: Thu Jan 01 00:00:01 1970 +0000
+ summary: a
+
+ $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
+ updated patterns: ['glob:.hglf/a*', 'glob:a*']
+ 3: d
+ 0: a
+
log on directory
$ hg log dir
@@ -631,7 +644,7 @@
-log -f -r 1:tip
+log -f -r '1 + 4'
$ hg up -C 0
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -639,25 +652,24 @@
$ hg ci -Amb2 -d '1 0'
adding b2
created new head
- $ hg log -f -r 1:tip
+ $ hg log -f -r '1 + 4'
+ changeset: 4:ddb82e70d1a1
+ tag: tip
+ parent: 0:67e992f2c4f3
+ user: test
+ date: Thu Jan 01 00:00:01 1970 +0000
+ summary: b2
+
changeset: 1:3d5bf5654eda
user: test
date: Thu Jan 01 00:00:01 1970 +0000
summary: r1
- changeset: 2:60c670bf5b30
+ changeset: 0:67e992f2c4f3
user: test
date: Thu Jan 01 00:00:01 1970 +0000
- summary: r2
+ summary: base
- changeset: 3:e62f78d544b4
- parent: 1:3d5bf5654eda
- user: test
- date: Thu Jan 01 00:00:01 1970 +0000
- summary: b1
-
-
-
log -f -r null
$ hg log -f -r null
@@ -672,10 +684,17 @@
+log -f with null parent
+
+ $ hg up -C null
+ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg log -f
+
+
log -r . with two parents
$ hg up -C 3
- 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg merge tip
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
@@ -1339,6 +1358,11 @@
date: Thu Jan 01 00:00:00 1970 +0000
summary: add foo, related
+ changeset: 2:c4c64aedf0f7
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: add unrelated old foo
+
$ cd ..
Issue2383: hg log showing _less_ differences than hg diff
@@ -1652,7 +1676,7 @@
|
o a
-Ensure that largefiles doesn't intefere with following a normal file
+Ensure that largefiles doesn't interfere with following a normal file
$ hg --config extensions.largefiles= log -f d -T '{desc}' -G
@ c
|
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-manifest.py Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,232 @@
+import binascii
+import unittest
+import itertools
+
+import silenttestrunner
+
+from mercurial import manifest as manifestmod
+
+HASH_1 = '1' * 40
+HASH_2 = 'f' * 40
+HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
+A_SHORT_MANIFEST = (
+ 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
+ 'foo\0%(hash1)s%(flag1)s\n'
+ ) % {'hash1': HASH_1,
+ 'flag1': '',
+ 'hash2': HASH_2,
+ 'flag2': 'l',
+ }
+
+HUGE_MANIFEST_ENTRIES = 200001
+
+A_HUGE_MANIFEST = ''.join(sorted(
+ 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
+ itertools.izip(xrange(200001),
+ itertools.cycle((HASH_1, HASH_2)),
+ itertools.cycle(('', 'x', 'l')))))
+
+class testmanifest(unittest.TestCase):
+
+ def assertIn(self, thing, container, msg=None):
+ # assertIn new in 2.7, use it if available, otherwise polyfill
+ sup = getattr(unittest.TestCase, 'assertIn', False)
+ if sup:
+ return sup(self, thing, container, msg=msg)
+ if not msg:
+ msg = 'Expected %r in %r' % (thing, container)
+ self.assert_(thing in container, msg)
+
+ def testEmptyManifest(self):
+ m = manifestmod._lazymanifest('')
+ self.assertEqual(0, len(m))
+ self.assertEqual([], list(m.iterentries()))
+
+ def testManifest(self):
+ m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ want = [
+ ('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
+ ('foo', binascii.unhexlify(HASH_1), ''),
+ ]
+ self.assertEqual(len(want), len(m))
+ self.assertEqual(want, list(m.iterentries()))
+ self.assertEqual((binascii.unhexlify(HASH_1), ''), m['foo'])
+ self.assertRaises(KeyError, lambda : m['wat'])
+ self.assertEqual((binascii.unhexlify(HASH_2), 'l'),
+ m['bar/baz/qux.py'])
+
+ def testSetItem(self):
+ want = binascii.unhexlify(HASH_1), ''
+
+ m = manifestmod._lazymanifest('')
+ m['a'] = want
+ self.assertIn('a', m)
+ self.assertEqual(want, m['a'])
+ self.assertEqual('a\0' + HASH_1 + '\n', m.text())
+
+ m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ m['a'] = want
+ self.assertEqual(want, m['a'])
+ self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
+ m.text())
+ m2 = m.copy()
+ del m
+ del m2 # make sure we don't double free() anything
+
+ def testCompaction(self):
+ unhex = binascii.unhexlify
+ h1, h2 = unhex(HASH_1), unhex(HASH_2)
+ m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ m['alpha'] = h1, ''
+ m['beta'] = h2, ''
+ del m['foo']
+ want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
+ HASH_1, HASH_2, HASH_2)
+ self.assertEqual(want, m.text())
+ self.assertEqual(3, len(m))
+ self.assertEqual((h1, ''), m['alpha'])
+ self.assertEqual((h2, ''), m['beta'])
+ self.assertRaises(KeyError, lambda : m['foo'])
+ w = [('alpha', h1, ''), ('bar/baz/qux.py', h2, 'l'), ('beta', h2, '')]
+ self.assertEqual(w, list(m.iterentries()))
+
+ def testSetGetNodeSuffix(self):
+ clean = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ h, f = m['foo']
+ want = h + 'a', f
+ # Merge code wants to set 21-byte fake hashes at times
+ m['foo'] = want
+ self.assertEqual(want, m['foo'])
+ self.assertEqual([('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
+ ('foo', binascii.unhexlify(HASH_1) + 'a', '')],
+ list(m.iterentries()))
+ # Sometimes it even tries a 22-byte fake hash, but we can
+ # return 21 and it'll work out
+ m['foo'] = want[0] + '+', f
+ self.assertEqual(want, m['foo'])
+ # make sure the suffix survives a copy
+ m2 = m.filtercopy(lambda x: x == 'foo')
+ self.assertEqual(want, m2['foo'])
+ self.assertEqual(1, len(m2))
+ self.assertEqual(('foo\0%s\n' % HASH_1), m2.text())
+ m2 = m.copy()
+ self.assertEqual(want, m2['foo'])
+ # suffix with iteration
+ self.assertEqual([('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
+ ('foo', want[0], '')], list(m.iterentries()))
+ # shows up in diff
+ self.assertEqual({'foo': (want, (h, ''))}, m.diff(clean))
+ self.assertEqual({'foo': ((h, ''), want)}, clean.diff(m))
+
+ def testFilterCopyException(self):
+ m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ def filt(path):
+ if path == 'foo':
+ assert False
+ return True
+ self.assertRaises(AssertionError, m.filtercopy, filt)
+
+ def testRemoveItem(self):
+ m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ del m['foo']
+ self.assertRaises(KeyError, lambda : m['foo'])
+ self.assertEqual(1, len(m))
+ self.assertEqual(1, len(list(m)))
+ # now restore and make sure everything works right
+ m['foo'] = 'a' * 20, ''
+ self.assertEqual(2, len(m))
+ self.assertEqual(2, len(list(m)))
+
+ def testManifestDiff(self):
+ MISSING = (None, '')
+ addl = 'z-only-in-left\0' + HASH_1 + '\n'
+ addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
+ left = manifestmod._lazymanifest(
+ A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
+ right = manifestmod._lazymanifest(A_SHORT_MANIFEST + addr)
+ want = {
+ 'foo': ((binascii.unhexlify(HASH_3), 'x'),
+ (binascii.unhexlify(HASH_1), '')),
+ 'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
+ 'z-only-in-right': (MISSING, (binascii.unhexlify(HASH_2), 'x')),
+ }
+ self.assertEqual(want, left.diff(right))
+
+ want = {
+ 'bar/baz/qux.py': (MISSING, (binascii.unhexlify(HASH_2), 'l')),
+ 'foo': (MISSING, (binascii.unhexlify(HASH_3), 'x')),
+ 'z-only-in-left': (MISSING, (binascii.unhexlify(HASH_1), '')),
+ }
+ self.assertEqual(want, manifestmod._lazymanifest('').diff(left))
+
+ want = {
+ 'bar/baz/qux.py': ((binascii.unhexlify(HASH_2), 'l'), MISSING),
+ 'foo': ((binascii.unhexlify(HASH_3), 'x'), MISSING),
+ 'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
+ }
+ self.assertEqual(want, left.diff(manifestmod._lazymanifest('')))
+ copy = right.copy()
+ del copy['z-only-in-right']
+ del right['foo']
+ want = {
+ 'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
+ 'z-only-in-right': ((binascii.unhexlify(HASH_2), 'x'), MISSING),
+ }
+ self.assertEqual(want, right.diff(copy))
+
+ short = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+ pruned = short.copy()
+ del pruned['foo']
+ want = {
+ 'foo': ((binascii.unhexlify(HASH_1), ''), MISSING),
+ }
+ self.assertEqual(want, short.diff(pruned))
+ want = {
+ 'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
+ }
+ self.assertEqual(want, pruned.diff(short))
+ want = {
+ 'bar/baz/qux.py': None,
+ 'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
+ }
+ self.assertEqual(want, pruned.diff(short, True))
+
+ def testReversedLines(self):
+ backwards = ''.join(
+ l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
+ try:
+ manifestmod._lazymanifest(backwards)
+ self.fail('Should have raised ValueError')
+ except ValueError, v:
+ self.assertIn('Manifest lines not in sorted order.', str(v))
+
+ def testNoTerminalNewline(self):
+ try:
+ manifestmod._lazymanifest(A_SHORT_MANIFEST + 'wat')
+ self.fail('Should have raised ValueError')
+ except ValueError, v:
+ self.assertIn('Manifest did not end in a newline.', str(v))
+
+ def testNoNewLineAtAll(self):
+ try:
+ manifestmod._lazymanifest('wat')
+ self.fail('Should have raised ValueError')
+ except ValueError, v:
+ self.assertIn('Manifest did not end in a newline.', str(v))
+
+ def testHugeManifest(self):
+ m = manifestmod._lazymanifest(A_HUGE_MANIFEST)
+ self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
+ self.assertEqual(len(m), len(list(m)))
+
+ def testIntersectFiles(self):
+ m = manifestmod.manifestdict(A_HUGE_MANIFEST)
+ m2 = m.intersectfiles(['file1', 'file200', 'file300'])
+ w = ('file1\0%sx\n'
+ 'file200\0%sl\n'
+ 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
+ self.assertEqual(w, m2.text())
+
+if __name__ == '__main__':
+ silenttestrunner.main(__name__)
--- a/tests/test-merge-tools.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-merge-tools.t Fri Mar 13 17:55:04 2015 -0500
@@ -603,7 +603,8 @@
true.priority=1
true.executable=cat
# hg update -C 1
- $ hg debugsetparent 0
+ $ hg update -q 0
+ $ hg revert -q -r 1 .
$ hg update -r 2
merging f
revision 1
@@ -628,7 +629,8 @@
true.priority=1
true.executable=cat
# hg update -C 1
- $ hg debugsetparent 0
+ $ hg update -q 0
+ $ hg revert -q -r 1 .
$ hg update -r 2 --tool false
merging f
merging f failed!
--- a/tests/test-mq-subrepo.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-mq-subrepo.t Fri Mar 13 17:55:04 2015 -0500
@@ -295,6 +295,11 @@
new file mode 100644
examine changes to '.hgsub'? [Ynesfdaq?] y
+ @@ -0,0 +1,1 @@
+ +sub = sub
+ record this change to '.hgsub'? [Ynesfdaq?] y
+
+ warning: subrepo spec file .hgsub not found
abort: uncommitted changes in subrepository sub
[255]
% update substate when adding .hgsub w/clean updated subrepo
@@ -304,10 +309,14 @@
new file mode 100644
examine changes to '.hgsub'? [Ynesfdaq?] y
+ @@ -0,0 +1,1 @@
+ +sub = sub
+ record this change to '.hgsub'? [Ynesfdaq?] y
+
+ warning: subrepo spec file .hgsub not found
path sub
source sub
revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
-
$ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
> y
> y
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-tag-cache.t Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,70 @@
+ $ cat >> $HGRCPATH << EOF
+ > [extensions]
+ > rebase=
+ >
+ > [experimental]
+ > evolution = createmarkers
+ > EOF
+
+Create a repo with some tags
+
+ $ hg init repo
+ $ cd repo
+ $ echo initial > foo
+ $ hg -q commit -A -m initial
+ $ hg tag -m 'test tag' test1
+ $ echo first > first
+ $ hg -q commit -A -m first
+ $ hg tag -m 'test2 tag' test2
+ $ hg -q up -r 0
+ $ echo newhead > newhead
+ $ hg commit -A -m newhead
+ adding newhead
+ created new head
+
+Trigger tags cache population by doing something that accesses tags info
+
+ $ hg log -G -T '{rev}:{node|short} {tags} {desc}\n'
+ @ 4:042eb6bfcc49 tip newhead
+ |
+ | o 3:c3cb30f2d2cd test2 tag
+ | |
+ | o 2:d75775ffbc6b test2 first
+ | |
+ | o 1:5f97d42da03f test tag
+ |/
+ o 0:55482a6fb4b1 test1 initial
+
+
+ $ cat .hg/cache/tags
+ 4 042eb6bfcc4909bad84a1cbf6eb1ddf0ab587d41
+ 3 c3cb30f2d2cd0aae008cc91a07876e3c5131fd22 b3bce87817fe7ac9dca2834366c1d7534c095cf1
+
+ 55482a6fb4b1881fa8f746fd52cf6f096bb21c89 test1
+ d75775ffbc6bca1794d300f5571272879bd280da test2
+
+Create some hidden changesets via a rebase and trigger tags cache
+repopulation
+
+ $ hg -q rebase -s 1 -d 4
+ $ hg log -G -T '{rev}:{node|short} {tags} {desc}\n'
+ o 7:eb610439e10e tip test2 tag
+ |
+ o 6:7b4af00c3c83 first
+ |
+ o 5:43ac2a539b3c test tag
+ |
+ @ 4:042eb6bfcc49 newhead
+ |
+ o 0:55482a6fb4b1 test1 initial
+
+
+.hgtags filenodes for hidden heads should be visible (issue4550)
+(currently broken)
+
+ $ cat .hg/cache/tags
+ 7 eb610439e10e0c6b296f97b59624c2e24fc59e30 b3bce87817fe7ac9dca2834366c1d7534c095cf1
+
+ 55482a6fb4b1881fa8f746fd52cf6f096bb21c89 test1
+ d75775ffbc6bca1794d300f5571272879bd280da test2
+
--- a/tests/test-obsolete.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-obsolete.t Fri Mar 13 17:55:04 2015 -0500
@@ -11,7 +11,7 @@
> hg ci -m "add $1"
> }
$ getid() {
- > hg id --debug --hidden -ir "desc('$1')"
+ > hg log -T "{node}\n" --hidden -r "desc('$1')"
> }
$ cat > debugkeys.py <<EOF
@@ -187,6 +187,8 @@
[255]
$ hg debugrevspec 'rev(6)'
$ hg debugrevspec 'rev(4)'
+ $ hg debugrevspec 'null'
+ -1
Check that public changeset are not accounted as obsolete:
@@ -621,7 +623,7 @@
check filelog view
- $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg id --debug --id`/'babar'
+ $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
200 Script output follows
$ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/68'
@@ -753,3 +755,43 @@
$ hg tags
visible 0:193e9254ce7e
tip 0:193e9254ce7e
+
+#if serve
+
+Test issue 4506
+
+ $ cd ..
+ $ hg init repo-issue4506
+ $ cd repo-issue4506
+ $ echo "0" > foo
+ $ hg add foo
+ $ hg ci -m "content-0"
+
+ $ hg up null
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo "1" > bar
+ $ hg add bar
+ $ hg ci -m "content-1"
+ created new head
+ $ hg up 0
+ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg graft 1
+ grafting 1:1c9eddb02162 "content-1" (tip)
+
+ $ hg debugobsolete `hg log -r1 -T'{node}'` `hg log -r2 -T'{node}'`
+
+ $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
+ $ cat hg.pid >> $DAEMON_PIDS
+
+ $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/1'
+ 404 Not Found
+ [1]
+ $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'file/tip/bar'
+ 200 Script output follows
+ $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'annotate/tip/bar'
+ 200 Script output follows
+
+ $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+
+#endif
+
--- a/tests/test-pull.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-pull.t Fri Mar 13 17:55:04 2015 -0500
@@ -76,7 +76,7 @@
abort: file:// URLs can only refer to localhost
[255]
- $ hg pull -q file:../test
+ $ hg pull -q file:../test # no-msys
It's tricky to make file:// URLs working on every platform with
regular shell commands.
--- a/tests/test-rebase-named-branches.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-rebase-named-branches.t Fri Mar 13 17:55:04 2015 -0500
@@ -352,7 +352,7 @@
$ hg ci -qm 'c2 closed' --close
$ hg up -qr 2
$ hg tglog
- o 4: 'c2 closed' c
+ _ 4: 'c2 closed' c
|
o 3: 'b1' b
|
@@ -366,7 +366,7 @@
nothing to rebase - working directory parent is also destination
[1]
$ hg tglog
- o 4: 'c2 closed' c
+ _ 4: 'c2 closed' c
|
o 3: 'b1' b
|
--- a/tests/test-rebase-pull.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-rebase-pull.t Fri Mar 13 17:55:04 2015 -0500
@@ -165,5 +165,47 @@
|
o 0: 'C1'
+pull --rebase works with bundle2 turned on
-
+ $ cd ../a
+ $ echo R4 > R4
+ $ hg ci -Am R4
+ adding R4
+ $ hg tglog
+ @ 5: 'R4'
+ |
+ o 4: 'R3'
+ |
+ o 3: 'R2'
+ |
+ o 2: 'R1'
+ |
+ o 1: 'C2'
+ |
+ o 0: 'C1'
+
+ $ cd ../c
+ $ hg pull --rebase --config experimental.bundle2-exp=True --config experimental.strip-bundle2-version=02
+ pulling from $TESTTMP/a (glob)
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ rebasing 5:518d153c0ba3 "L1"
+ saved backup bundle to $TESTTMP/c/.hg/strip-backup/518d153c0ba3-73407f14-backup.hg (glob)
+ $ hg tglog
+ @ 6: 'L1'
+ |
+ o 5: 'R4'
+ |
+ o 4: 'R3'
+ |
+ o 3: 'R2'
+ |
+ o 2: 'R1'
+ |
+ o 1: 'C2'
+ |
+ o 0: 'C1'
+
--- a/tests/test-record.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-record.t Fri Mar 13 17:55:04 2015 -0500
@@ -235,7 +235,19 @@
new file mode 100644
examine changes to 'plain'? [Ynesfdaq?] y
-
+ @@ -0,0 +1,10 @@
+ +1
+ +2
+ +3
+ +4
+ +5
+ +6
+ +7
+ +8
+ +9
+ +10
+ record this change to 'plain'? [Ynesfdaq?] y
+
$ hg tip -p
changeset: 7:11fb457c1be4
tag: tip
@@ -317,6 +329,7 @@
> y
> y
> y
+ > y
> EOF
diff --git a/plain b/plain
1 hunks, 1 lines changed
@@ -335,7 +348,10 @@
new file mode 100644
examine changes to 'plain2'? [Ynesfdaq?] y
-
+ @@ -0,0 +1,1 @@
+ +1
+ record change 2/2 to 'plain2'? [Ynesfdaq?] y
+
Modify beginning, trim end, record both, add another file to test
changes numbering
@@ -1353,6 +1369,8 @@
c
+d
+
+
Test --user when ui.username not set
$ unset HGUSER
$ echo e >> subdir/f1
@@ -1376,4 +1394,41 @@
$ HGUSER="test"
$ export HGUSER
+
+Editing patch of newly added file
+
+ $ cat > editor.sh << '__EOF__'
+ > cat "$1" | sed "s/first/very/g" > tt
+ > mv tt "$1"
+ > __EOF__
+ $ cat > newfile << '__EOF__'
+ > This is the first line
+ > This is the second line
+ > This is the third line
+ > __EOF__
+ $ hg add newfile
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg record -d '23 0' -medit-patch-new <<EOF
+ > y
+ > e
+ > EOF
+ diff --git a/newfile b/newfile
+ new file mode 100644
+ examine changes to 'newfile'? [Ynesfdaq?] y
+
+ @@ -0,0 +1,3 @@
+ +This is the first line
+ +This is the second line
+ +This is the third line
+ record this change to 'newfile'? [Ynesfdaq?] e
+
+ $ hg cat -r tip newfile
+ This is the very line
+ This is the second line
+ This is the third line
+
+ $ cat newfile
+ This is the first line
+ This is the second line
+ This is the third line
+
$ cd ..
--- a/tests/test-rename.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-rename.t Fri Mar 13 17:55:04 2015 -0500
@@ -620,10 +620,16 @@
$ hg rename d1/d11/a1 .hg
abort: path contains illegal component: .hg/a1 (glob)
[255]
+ $ hg --config extensions.largefiles= rename d1/d11/a1 .hg
+ abort: path contains illegal component: .hg/a1 (glob)
+ [255]
$ hg status -C
$ hg rename d1/d11/a1 ..
abort: ../a1 not under root '$TESTTMP' (glob)
[255]
+ $ hg --config extensions.largefiles= rename d1/d11/a1 ..
+ abort: ../a1 not under root '$TESTTMP' (glob)
+ [255]
$ hg status -C
$ mv d1/d11/a1 .hg
--- a/tests/test-resolve.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-resolve.t Fri Mar 13 17:55:04 2015 -0500
@@ -43,10 +43,15 @@
U file1
U file2
-resolving an unknown path should emit a warning
+ $ hg resolve -l --no-status
+ file1
+ file2
+
+resolving an unknown path should emit a warning, but not for -l
$ hg resolve -m does-not-exist
arguments do not match paths that need resolving
+ $ hg resolve -l does-not-exist
resolve the failure
@@ -59,6 +64,18 @@
R file1
U file2
+ $ hg resolve -l -Tjson
+ [
+ {
+ "path": "file1",
+ "status": "R"
+ },
+ {
+ "path": "file2",
+ "status": "U"
+ }
+ ]
+
resolve -m without paths should mark all resolved
$ hg resolve -m
@@ -69,6 +86,10 @@
$ hg resolve -l
+ $ hg resolve -l -Tjson
+ [
+ ]
+
resolve --all should abort when no merge in progress
$ hg resolve --all
--- a/tests/test-revset.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-revset.t Fri Mar 13 17:55:04 2015 -0500
@@ -478,8 +478,36 @@
[255]
Test null revision
+ $ log '(null)'
+ -1
+ $ log '(null:0)'
+ -1
+ 0
+ $ log '(0:null)'
+ 0
+ -1
+ $ log 'null::0'
+ -1
+ 0
+ $ log 'null:tip - 0:'
+ -1
+ $ log 'null: and null::' | head -1
+ -1
+ $ log 'null: or 0:' | head -2
+ -1
+ 0
$ log 'ancestors(null)'
-1
+ $ log 'reverse(null:)' | tail -2
+ 0
+ -1
+ $ log 'first(null:)'
+ -1
+ $ log 'min(null:)'
+ -1
+ $ log 'tip:null and all()' | tail -2
+ 1
+ 0
$ log 'outgoing()'
8
@@ -860,6 +888,23 @@
hg: parse error: ^ expects a number 0, 1, or 2
[255]
+Bogus function gets suggestions
+ $ log 'add()'
+ hg: parse error: unknown identifier: add
+ (did you mean 'adds'?)
+ [255]
+ $ log 'added()'
+ hg: parse error: unknown identifier: added
+ (did you mean 'adds'?)
+ [255]
+ $ log 'remo()'
+ hg: parse error: unknown identifier: remo
+ (did you mean one of remote, removes?)
+ [255]
+ $ log 'babar()'
+ hg: parse error: unknown identifier: babar
+ [255]
+
multiple revspecs
$ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
@@ -1011,12 +1056,12 @@
(range
('symbol', '2')
('symbol', '5')))
- abort: failed to parse the definition of revset alias "injectparamasstring2": not a function: _aliasarg
+ abort: failed to parse the definition of revset alias "injectparamasstring2": unknown identifier: _aliasarg
[255]
$ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
('symbol', 'tip')
warning: failed to parse the definition of revset alias "anotherbadone": at 7: not a prefix: end
- warning: failed to parse the definition of revset alias "injectparamasstring2": not a function: _aliasarg
+ warning: failed to parse the definition of revset alias "injectparamasstring2": unknown identifier: _aliasarg
9
>>> data = file('.hg/hgrc', 'rb').read()
>>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
@@ -1029,6 +1074,19 @@
('symbol', 'tip')
warning: failed to parse the declaration of revset alias "bad name": at 4: invalid token
9
+ $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
+ $ try 'strictreplacing("foo", tip)'
+ (func
+ ('symbol', 'strictreplacing')
+ (list
+ ('string', 'foo')
+ ('symbol', 'tip')))
+ (or
+ ('symbol', 'tip')
+ (func
+ ('symbol', 'desc')
+ ('string', '$1')))
+ 9
$ try 'd(2:5)'
(func
@@ -1114,6 +1172,62 @@
3
2
+issue4553: check that revset aliases override existing hash prefix
+
+ $ hg log -qr e
+ 6:e0cc66ef77e8
+
+ $ hg log -qr e --config revsetalias.e="all()"
+ 0:2785f51eece5
+ 1:d75937da8da0
+ 2:5ed5505e9f1c
+ 3:8528aa5637f2
+ 4:2326846efdab
+ 5:904fa392b941
+ 6:e0cc66ef77e8
+ 7:013af1973af4
+ 8:d5d0dcbdc4d9
+ 9:24286f4ae135
+
+ $ hg log -qr e: --config revsetalias.e="0"
+ 0:2785f51eece5
+ 1:d75937da8da0
+ 2:5ed5505e9f1c
+ 3:8528aa5637f2
+ 4:2326846efdab
+ 5:904fa392b941
+ 6:e0cc66ef77e8
+ 7:013af1973af4
+ 8:d5d0dcbdc4d9
+ 9:24286f4ae135
+
+ $ hg log -qr :e --config revsetalias.e="9"
+ 0:2785f51eece5
+ 1:d75937da8da0
+ 2:5ed5505e9f1c
+ 3:8528aa5637f2
+ 4:2326846efdab
+ 5:904fa392b941
+ 6:e0cc66ef77e8
+ 7:013af1973af4
+ 8:d5d0dcbdc4d9
+ 9:24286f4ae135
+
+ $ hg log -qr e:
+ 6:e0cc66ef77e8
+ 7:013af1973af4
+ 8:d5d0dcbdc4d9
+ 9:24286f4ae135
+
+ $ hg log -qr :e
+ 0:2785f51eece5
+ 1:d75937da8da0
+ 2:5ed5505e9f1c
+ 3:8528aa5637f2
+ 4:2326846efdab
+ 5:904fa392b941
+ 6:e0cc66ef77e8
+
issue2549 - correct optimizations
$ log 'limit(1 or 2 or 3, 2) and not 2'
--- a/tests/test-shelve.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-shelve.t Fri Mar 13 17:55:04 2015 -0500
@@ -81,11 +81,11 @@
ensure that our shelved changes exist
$ hg shelve -l
- default-01 (*) changes to '[mq]: second.patch' (glob)
- default (*) changes to '[mq]: second.patch' (glob)
+ default-01 (*)* changes to '[mq]: second.patch' (glob)
+ default (*)* changes to '[mq]: second.patch' (glob)
$ hg shelve -l -p default
- default (*) changes to '[mq]: second.patch' (glob)
+ default (*)* changes to '[mq]: second.patch' (glob)
diff --git a/a/a b/a/a
--- a/a/a
--- a/tests/test-ssh.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-ssh.t Fri Mar 13 17:55:04 2015 -0500
@@ -116,6 +116,14 @@
searching for changes
no changes found
+pull from wrong ssh URL
+
+ $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
+ pulling from ssh://user@dummy/doesnotexist
+ remote: abort: there is no Mercurial repository here (.hg not found)!
+ abort: no suitable response from remote hg!
+ [255]
+
local change
$ echo bleah > foo
@@ -446,6 +454,7 @@
Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
Got arguments 1:user@dummy 2:hg -R remote serve --stdio
Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+ Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
Got arguments 1:user@dummy 2:hg -R remote serve --stdio
Got arguments 1:user@dummy 2:hg -R local serve --stdio
Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
--- a/tests/test-status-color.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-status-color.t Fri Mar 13 17:55:04 2015 -0500
@@ -338,8 +338,8 @@
hg resolve with one unresolved, one resolved:
$ hg resolve --color=always -l
- \x1b[0;31;1mU a\x1b[0m (esc)
- \x1b[0;32;1mR b\x1b[0m (esc)
+ \x1b[0;31;1mU \x1b[0m\x1b[0;31;1ma\x1b[0m (esc)
+ \x1b[0;32;1mR \x1b[0m\x1b[0;32;1mb\x1b[0m (esc)
color coding of error message with current availability of curses
--- a/tests/test-strip.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-strip.t Fri Mar 13 17:55:04 2015 -0500
@@ -220,8 +220,69 @@
Stream params: {}
b2x:changegroup -- "{'version': '02'}"
264128213d290d868c54642d13aeaa3675551a78
+ $ hg incoming .hg/strip-backup/*
+ comparing with .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
+ searching for changes
+ changeset: 4:264128213d29
+ tag: tip
+ parent: 1:ef3a871183d7
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: c
+
$ restore
-
+ $ hg up -C 4
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg --config experimental.bundle2-exp=True --config experimental.strip-bundle2-version=02 --traceback strip 4
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/test/.hg/strip-backup/264128213d29-0b39d6bf-backup.hg (glob)
+ $ hg parents
+ changeset: 1:ef3a871183d7
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: b
+
+ $ hg debugbundle .hg/strip-backup/*
+ Stream params: {}
+ b2x:changegroup -- "{'version': '02'}"
+ 264128213d290d868c54642d13aeaa3675551a78
+ $ hg pull .hg/strip-backup/*
+ pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 0 changes to 0 files (+1 heads)
+ (run 'hg heads' to see heads, 'hg merge' to merge)
+ $ rm .hg/strip-backup/*
+ $ hg log --graph
+ o changeset: 4:264128213d29
+ | tag: tip
+ | parent: 1:ef3a871183d7
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: c
+ |
+ | o changeset: 3:443431ffac4f
+ | | user: test
+ | | date: Thu Jan 01 00:00:00 1970 +0000
+ | | summary: e
+ | |
+ | o changeset: 2:65bd5f99a4a3
+ |/ user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: d
+ |
+ @ changeset: 1:ef3a871183d7
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: b
+ |
+ o changeset: 0:9ab35a2d17cb
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: a
+
$ hg up -C 2
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg merge 4
--- a/tests/test-subrepo-deep-nested-change.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-subrepo-deep-nested-change.t Fri Mar 13 17:55:04 2015 -0500
@@ -46,12 +46,29 @@
Clone main
- $ hg clone main cloned
+ $ hg --config extensions.largefiles= clone main cloned
updating to branch default
cloning subrepo sub1 from $TESTTMP/sub1
cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+Largefiles is NOT enabled in the clone if the source repo doesn't require it
+ $ cat cloned/.hg/hgrc
+ # example repository config (see "hg help config" for more info)
+ [paths]
+ default = $TESTTMP/main (glob)
+
+ # path aliases to other clones of this repo in URLs or filesystem paths
+ # (see "hg help config.paths" for more info)
+ #
+ # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+ # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
+ # my-clone = /home/jdoe/jdoes-clone
+
+ [ui]
+ # name and email (local to this repository, optional), e.g.
+ # username = Jane Doe <jdoe@example.com>
+
Checking cloned repo ids
$ printf "cloned " ; hg id -R cloned
@@ -319,6 +336,31 @@
../archive_lf/sub1/sub2/large.bin
$ rm -rf ../archive_lf
+The local repo enables largefiles if a largefiles repo is cloned
+ $ hg showconfig extensions
+ abort: repository requires features unknown to this Mercurial: largefiles!
+ (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
+ [255]
+ $ hg --config extensions.largefiles= clone -qU . ../lfclone
+ $ cat ../lfclone/.hg/hgrc
+ # example repository config (see "hg help config" for more info)
+ [paths]
+ default = $TESTTMP/cloned (glob)
+
+ # path aliases to other clones of this repo in URLs or filesystem paths
+ # (see "hg help config.paths" for more info)
+ #
+ # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+ # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
+ # my-clone = /home/jdoe/jdoes-clone
+
+ [ui]
+ # name and email (local to this repository, optional), e.g.
+ # username = Jane Doe <jdoe@example.com>
+
+ [extensions]
+ largefiles=
+
Find an exact match to a standin (should archive nothing)
$ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
$ find ../archive_lf 2> /dev/null | sort
@@ -377,4 +419,24 @@
A a.dat
A a.txt
+ $ hg ci -m "add a.*"
+ $ hg mv a.dat b.dat
+ $ hg mv foo/bar/abc foo/bar/def
+ $ hg status -C
+ A b.dat
+ a.dat
+ A foo/bar/def
+ foo/bar/abc
+ R a.dat
+ R foo/bar/abc
+
+ $ hg ci -m "move large and normal"
+ $ hg status -C --rev '.^' --rev .
+ A b.dat
+ a.dat
+ A foo/bar/def
+ foo/bar/abc
+ R a.dat
+ R foo/bar/abc
+
$ cd ..
--- a/tests/test-subrepo-git.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-subrepo-git.t Fri Mar 13 17:55:04 2015 -0500
@@ -134,6 +134,7 @@
$ hg status --subrepos
? s/f
$ hg add .
+ adding f
$ git add f
$ cd ..
@@ -174,6 +175,8 @@
pulling subrepo s from $TESTTMP/gitroot
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
+ $ hg st --subrepos s
+ A s/f
$ cat s/f
f
$ cat s/g
@@ -802,4 +805,235 @@
$ hg status --subrepos
? s/barfoo
+show file at specific revision
+ $ cat > s/foobar << EOF
+ > woop woop
+ > fooo bar
+ > EOF
+ $ hg commit --subrepos -m "updated foobar"
+ committing subrepository s
+ $ cat > s/foobar << EOF
+ > current foobar
+ > (should not be visible using hg cat)
+ > EOF
+
+ $ hg cat -r . s/foobar
+ woop woop
+ fooo bar (no-eol)
+ $ hg cat -r "parents(.)" s/foobar > catparents
+
+ $ mkdir -p tmp/s
+
+ $ hg cat -r "parents(.)" --output tmp/%% s/foobar
+ $ diff tmp/% catparents
+
+ $ hg cat -r "parents(.)" --output tmp/%s s/foobar
+ $ diff tmp/foobar catparents
+
+ $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar
+ $ diff tmp/s/otherfoobar catparents
+
+ $ hg cat -r "parents(.)" --output tmp/%p s/foobar
+ $ diff tmp/s/foobar catparents
+
+ $ hg cat -r "parents(.)" --output tmp/%H s/foobar
+ $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents
+
+ $ hg cat -r "parents(.)" --output tmp/%R s/foobar
+ $ diff tmp/10 catparents
+
+ $ hg cat -r "parents(.)" --output tmp/%h s/foobar
+ $ diff tmp/255ee8cf690e catparents
+
+ $ rm tmp/10
+ $ hg cat -r "parents(.)" --output tmp/%r s/foobar
+ $ diff tmp/10 catparents
+
+ $ mkdir tmp/tc
+ $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar
+ $ diff tmp/tc/foobar catparents
+
+cleanup
+ $ rm -r tmp
+ $ rm catparents
+
+add git files, using either files or patterns
+ $ echo "hsss! hsssssssh!" > s/snake.python
+ $ echo "ccc" > s/c.c
+ $ echo "cpp" > s/cpp.cpp
+
+ $ hg add s/snake.python s/c.c s/cpp.cpp
+ $ hg st --subrepos s
+ M s/foobar
+ A s/c.c
+ A s/cpp.cpp
+ A s/snake.python
+ ? s/barfoo
+ $ hg revert s
+ reverting subrepo ../gitroot
+
+ $ hg add --subrepos "glob:**.python"
+ adding s/snake.python (glob)
+ $ hg st --subrepos s
+ A s/snake.python
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+ $ hg revert s
+ reverting subrepo ../gitroot
+
+ $ hg add --subrepos s
+ adding s/barfoo (glob)
+ adding s/c.c (glob)
+ adding s/cpp.cpp (glob)
+ adding s/foobar.orig (glob)
+ adding s/snake.python (glob)
+ $ hg st --subrepos s
+ A s/barfoo
+ A s/c.c
+ A s/cpp.cpp
+ A s/foobar.orig
+ A s/snake.python
+ $ hg revert s
+ reverting subrepo ../gitroot
+make sure everything is reverted correctly
+ $ hg st --subrepos s
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+ ? s/snake.python
+
+ $ hg add --subrepos --exclude "path:s/c.c"
+ adding s/barfoo (glob)
+ adding s/cpp.cpp (glob)
+ adding s/foobar.orig (glob)
+ adding s/snake.python (glob)
+ $ hg st --subrepos s
+ A s/barfoo
+ A s/cpp.cpp
+ A s/foobar.orig
+ A s/snake.python
+ ? s/c.c
+ $ hg revert --all -q
+
+.hgignore should not have influence in subrepos
+ $ cat > .hgignore << EOF
+ > syntax: glob
+ > *.python
+ > EOF
+ $ hg add .hgignore
+ $ hg add --subrepos "glob:**.python" s/barfoo
+ adding s/snake.python (glob)
+ $ hg st --subrepos s
+ A s/barfoo
+ A s/snake.python
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+ $ hg revert --all -q
+
+.gitignore should have influence,
+except for explicitly added files (no patterns)
+ $ cat > s/.gitignore << EOF
+ > *.python
+ > EOF
+ $ hg add s/.gitignore
+ $ hg st --subrepos s
+ A s/.gitignore
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+ $ hg st --subrepos s --all
+ A s/.gitignore
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+ I s/snake.python
+ C s/f
+ C s/foobar
+ C s/g
+ $ hg add --subrepos "glob:**.python"
+ $ hg st --subrepos s
+ A s/.gitignore
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+ $ hg add --subrepos s/snake.python
+ $ hg st --subrepos s
+ A s/.gitignore
+ A s/snake.python
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+
+correctly do a dry run
+ $ hg add --subrepos s --dry-run
+ adding s/barfoo (glob)
+ adding s/c.c (glob)
+ adding s/cpp.cpp (glob)
+ adding s/foobar.orig (glob)
+ $ hg st --subrepos s
+ A s/.gitignore
+ A s/snake.python
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+
+error given when adding an already tracked file
+ $ hg add s/.gitignore
+ s/.gitignore already tracked!
+ [1]
+ $ hg add s/g
+ s/g already tracked!
+ [1]
+
+removed files can be re-added
+removing files using 'rm' or 'git rm' has the same effect,
+since we ignore the staging area
+ $ hg ci --subrepos -m 'snake'
+ committing subrepository s
+ $ cd s
+ $ rm snake.python
+(remove leftover .hg so Mercurial doesn't look for a root here)
+ $ rm -r .hg
+ $ hg status --subrepos --all .
+ R snake.python
+ ? barfoo
+ ? c.c
+ ? cpp.cpp
+ ? foobar.orig
+ C .gitignore
+ C f
+ C foobar
+ C g
+ $ git rm snake.python
+ rm 'snake.python'
+ $ hg status --subrepos --all .
+ R snake.python
+ ? barfoo
+ ? c.c
+ ? cpp.cpp
+ ? foobar.orig
+ C .gitignore
+ C f
+ C foobar
+ C g
+ $ touch snake.python
$ cd ..
+ $ hg add s/snake.python
+ $ hg status -S
+ M s/snake.python
+ ? .hgignore
+ ? s/barfoo
+ ? s/c.c
+ ? s/cpp.cpp
+ ? s/foobar.orig
+
+ $ cd ..
--- a/tests/test-subrepo.t Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-subrepo.t Fri Mar 13 17:55:04 2015 -0500
@@ -50,9 +50,16 @@
Revert subrepo and test subrepo fileset keyword:
$ echo b > s/a
+ $ hg revert --dry-run "set:subrepo('glob:s*')"
+ reverting subrepo s
+ reverting s/a (glob)
+ $ cat s/a
+ b
$ hg revert "set:subrepo('glob:s*')"
reverting subrepo s
reverting s/a (glob)
+ $ cat s/a
+ a
$ rm s/a.orig
Revert subrepo with no backup. The "reverting s/a" line is gone since