--- a/contrib/check-commit Mon Mar 02 01:06:31 2015 -0600
+++ b/contrib/check-commit Mon Mar 02 01:20:14 2015 -0600
@@ -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/hgext/churn.py Mon Mar 02 01:06:31 2015 -0600
+++ b/hgext/churn.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/hgext/color.py Mon Mar 02 01:20:14 2015 -0600
@@ -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,11 +224,30 @@
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'
--- a/hgext/histedit.py Mon Mar 02 01:06:31 2015 -0600
+++ b/hgext/histedit.py Mon Mar 02 01:20:14 2015 -0600
@@ -158,6 +158,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 +190,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 +215,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 +223,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 +347,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 +363,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 +385,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 +442,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)
@@ -503,6 +509,7 @@
[('', 'commands', '',
_('Read history edits from the specified 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')),
@@ -552,6 +559,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 +568,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 +592,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 +606,34 @@
# rebuild state
if goal == 'continue':
- state = histeditstate(repo)
state.read()
state = bootstrapcontinue(ui, state, opts)
+ elif goal == 'edit-plan':
+ state = histeditstate(repo)
+ 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 +668,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 +681,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 +692,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 +752,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 +765,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 +809,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,20 +830,40 @@
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)
+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 +871,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 +879,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 +1016,23 @@
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(_('unable to strip %s. Nodes are '
+ 'used by history edit in progress.')
+ % ', '.join(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/largefiles/overrides.py Mon Mar 02 01:06:31 2015 -0600
+++ b/hgext/largefiles/overrides.py Mon Mar 02 01:20:14 2015 -0600
@@ -559,16 +559,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 +585,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 +716,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)
+
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 +828,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.
--- a/hgext/largefiles/reposetup.py Mon Mar 02 01:06:31 2015 -0600
+++ b/hgext/largefiles/reposetup.py Mon Mar 02 01:20:14 2015 -0600
@@ -329,10 +329,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.
+ # make sure _a_ directory is in the list, which
+ # forces status/dirstate to walk all files and
+ # call the match function on the matcher, even
+ # on case sensitive filesytems.
actualfiles.append('.')
matcheddir = True
# Nothing in dir, so readd it
--- a/hgext/pager.py Mon Mar 02 01:06:31 2015 -0600
+++ b/hgext/pager.py Mon Mar 02 01:20:14 2015 -0600
@@ -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/mercurial/branchmap.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/branchmap.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/bundle2.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/bundlerepo.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/changegroup.py Mon Mar 02 01:20:14 2015 -0600
@@ -659,8 +659,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/changelog.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/cmdutil.py Mon Mar 02 01:20:14 2015 -0600
@@ -1473,14 +1473,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 +1815,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 +1838,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 +1876,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)
@@ -2798,14 +2783,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/commands.py Mon Mar 02 01:20:14 2015 -0600
@@ -2885,7 +2885,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]'))
@@ -4984,9 +4984,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 +5225,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 +5277,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 +5309,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 +5341,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
--- a/mercurial/context.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/context.py Mon Mar 02 01:20:14 2015 -0600
@@ -376,10 +376,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 +384,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
--- a/mercurial/copies.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/copies.py Mon Mar 02 01:20:14 2015 -0600
@@ -144,6 +144,15 @@
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.
+ """
+ missing = set(b.manifest().iterkeys())
+ missing.difference_update(a.manifest().iterkeys())
+ return missing
+
def _forwardcopies(a, b):
'''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
@@ -167,9 +176,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 +215,22 @@
return _backwardrenames(x, y)
return _chain(x, y, _backwardrenames(x, a), _forwardcopies(a, y))
+def _computenonoverlap(repo, m1, m2, ma):
+ """Computes 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.
+ """
+ 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))
+ return u1, u2
+
def mergecopies(repo, c1, c2, ca):
"""
Find moves and copies between context c1 and c2 that are relevant
@@ -261,15 +284,7 @@
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))
+ u1, u2 = _computenonoverlap(repo, m1, m2, ma)
for f in u1:
checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
--- a/mercurial/dispatch.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/dispatch.py Mon Mar 02 01:20:14 2015 -0600
@@ -27,6 +27,15 @@
"run the command in sys.argv"
sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
+def _formatparse(write, inst):
+ 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])
+
def dispatch(req):
"run the command specified in req.args"
if req.ferr:
@@ -55,13 +64,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 +157,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:
--- a/mercurial/error.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/error.py Mon Mar 02 01:20:14 2015 -0600
@@ -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,7 @@
"""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 RepoError(Exception):
def __init__(self, *args, **kw):
@@ -139,3 +143,11 @@
def __init__(self, filename, node):
from node import short
RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
+
+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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/extensions.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/filelog.py Mon Mar 02 01:20:14 2015 -0600
@@ -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
@@ -104,9 +104,6 @@
raise error.CensoredNodeError(self.indexfile, node)
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
--- a/mercurial/filemerge.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/filemerge.py Mon Mar 02 01:20:14 2015 -0600
@@ -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/help.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/help.py Mon Mar 02 01:20:14 2015 -0600
@@ -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/hgweb.txt Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/help/hgweb.txt Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/help/subrepos.txt Mon Mar 02 01:20:14 2015 -0600
@@ -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/hgweb/webcommands.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/hgweb/webcommands.py Mon Mar 02 01:20:14 2015 -0600
@@ -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,10 +400,36 @@
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):
+ """
+ /changeset[/{revision}]
+ -----------------------
+
+ Show information about a single changeset.
+
+ 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.
+
+ 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)
basectx = webutil.basechangectx(web.repo, req)
if basectx is None:
@@ -382,7 +482,7 @@
inbranch=webutil.nodeinbranch(web.repo, ctx),
branches=webutil.nodebranchdict(web.repo, ctx))
-rev = changeset
+rev = webcommand('rev')(changeset)
def decodepath(path):
"""Hook for mapping a path in the repository to a path in the
@@ -392,7 +492,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 +590,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 +623,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 +654,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 +698,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 +795,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 +847,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 +923,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 +984,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 +1074,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 +1143,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 +1157,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 +1297,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/hgweb/webutil.py Mon Mar 02 01:20:14 2015 -0600
@@ -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):
--- a/mercurial/localrepo.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/localrepo.py Mon Mar 02 01:20:14 2015 -0600
@@ -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):
'''
@@ -501,7 +504,6 @@
"""
return hook.hook(self.ui, self, name, throw, **args)
- @unfilteredmethod
def _tag(self, names, node, message, local, user, date, extra={},
editor=False):
if isinstance(names, str):
--- a/mercurial/manifest.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/manifest.py Mon Mar 02 01:20:14 2015 -0600
@@ -10,13 +10,8 @@
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
+ def __init__(self):
+ self._flags = {}
def __setitem__(self, k, v):
assert v is not None
dict.__setitem__(self, k, v)
@@ -26,7 +21,10 @@
"""Set the flags (symlink, executable) for path f."""
self._flags[f] = flags
def copy(self):
- return manifestdict(self, dict.copy(self._flags))
+ copy = manifestdict()
+ dict.__init__(copy, self)
+ copy._flags = dict.copy(self._flags)
+ return copy
def intersectfiles(self, files):
'''make a new manifestdict with the intersection of self with files
@@ -50,11 +48,11 @@
(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():
+ m = self.copy()
+ for fn in m.keys():
if not match(fn):
- del mf[fn]
- return mf
+ del m[fn]
+ return m
def diff(self, m2, clean=False):
'''Finds changes between the current manifest and m2.
@@ -220,9 +218,14 @@
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):
@@ -244,16 +247,16 @@
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 = _parse(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)
+ m = self._mancache[node][0]
+ return m.get(f), m.flags(f)
text = self.revision(node)
start, end = _msearch(text, f)
if start == end:
@@ -262,7 +265,7 @@
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 +280,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 +288,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/mdiff.py Mon Mar 02 01:20:14 2015 -0600
@@ -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/obsolete.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/obsolete.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/parsers.c Mon Mar 02 01:20:14 2015 -0600
@@ -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,6 +2312,8 @@
{"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}
};
--- a/mercurial/patch.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/patch.py Mon Mar 02 01:20:14 2015 -0600
@@ -1736,20 +1736,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 +1791,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/revlog.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/revlog.py Mon Mar 02 01:20:14 2015 -0600
@@ -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:
@@ -1401,6 +1419,17 @@
_('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))
+
chain = self._addrevision(node, None, transaction, link,
p1, p2, REVIDX_DEFAULT_FLAGS,
(baserev, delta), ifh, dfh)
@@ -1417,6 +1446,10 @@
return content
+ def iscensored(self, rev):
+ """Check if a file revision is censored."""
+ return False
+
def getstrippoint(self, minlink):
"""find the minimum rev that must be stripped to strip the linkrev
--- a/mercurial/revset.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/revset.py Mon Mar 02 01:20:14 2015 -0600
@@ -349,7 +349,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
@@ -396,7 +396,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 +412,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 +544,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 +708,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 +744,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 = getall(repo, fullreposet(repo), x)
dests = set()
@@ -1145,7 +1145,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 +1172,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 +1189,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 +1226,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 +1322,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 +1331,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 +1345,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 = getall(repo, fullreposet(repo), x)
def _firstsrc(rev):
src = _getrevsource(repo, rev)
@@ -1400,7 +1400,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 +1421,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 +1435,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 +1548,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 +1676,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
@@ -2243,6 +2243,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 +2325,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 +2449,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 +3205,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,7 +3309,7 @@
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.
--- a/mercurial/scmutil.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/scmutil.py Mon Mar 02 01:20:14 2015 -0600
@@ -672,11 +672,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/subrepo.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/subrepo.py Mon Mar 02 01:20:14 2015 -0600
@@ -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
@@ -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']]
@@ -1577,6 +1576,25 @@
@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:
@@ -1673,7 +1691,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/tags.py Mon Mar 02 01:20:14 2015 -0600
@@ -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/templater.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templater.py Mon Mar 02 01:20:14 2015 -0600
@@ -393,7 +393,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/gitweb/map Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/bookmarks.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/branches.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/changelog.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/graph.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/help.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/helptopics.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/manifest.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/map Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/shortlog.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/summary.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/monoblue/tags.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/bookmarks.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/branches.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/changeset.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/fileannotate.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/filelog.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/graph.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/index.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/manifest.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/search.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/shortlog.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/paper/tags.tmpl Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/templates/static/style-paper.css Mon Mar 02 01:20:14 2015 -0600
@@ -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/unionrepo.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/unionrepo.py Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/util.h Mon Mar 02 01:20:14 2015 -0600
@@ -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/windows.py Mon Mar 02 01:06:31 2015 -0600
+++ b/mercurial/windows.py Mon Mar 02 01:20:14 2015 -0600
@@ -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/tests/run-tests.py Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/run-tests.py Mon Mar 02 01:20:14 2015 -0600
@@ -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-bundle-type.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-bundle-type.t Mon Mar 02 01:20:14 2015 -0600
@@ -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-churn.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-churn.t Mon Mar 02 01:20:14 2015 -0600
@@ -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-completion.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-completion.t Mon Mar 02 01:20:14 2015 -0600
@@ -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-extension.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-extension.t Mon Mar 02 01:20:14 2015 -0600
@@ -1140,3 +1140,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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-gendoc.t Mon Mar 02 01:20:14 2015 -0600
@@ -1,4 +1,5 @@
#require docutils
+#require gettext
Test document extraction
--- a/tests/test-glog.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-glog.t Mon Mar 02 01:20:14 2015 -0600
@@ -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
--- a/tests/test-grep.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-grep.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-help.t Mon Mar 02 01:20:14 2015 -0600
@@ -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-hgweb-commands.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb-commands.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb-descend-empties.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb-diffs.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb-empty.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb-filelog.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb-removed.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgweb.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-hgwebdir.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-highlight.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-histedit-arguments.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-histedit-drop.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-histedit-edit.t Mon Mar 02 01:20:14 2015 -0600
@@ -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,6 +86,11 @@
(use 'hg histedit --continue' or 'hg histedit --abort')
[255]
+Try to delete necessary commit
+ $ hg strip -r 652413bf663e
+ abort: unable to strip 652413bf663ef2a641cab26574e46d5f5a64a55a. Nodes are used by history edit in progress.
+ [255]
+
commit, then edit the revision
$ hg ci -m 'wat'
created new head
--- a/tests/test-https.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-https.t Mon Mar 02 01:20:14 2015 -0600
@@ -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]
@@ -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-largefiles-misc.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-largefiles-misc.t Mon Mar 02 01:20:14 2015 -0600
@@ -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
--- a/tests/test-largefiles.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-largefiles.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-log.t Mon Mar 02 01:20:14 2015 -0600
@@ -672,10 +672,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)
--- a/tests/test-merge-tools.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-merge-tools.t Mon Mar 02 01:20:14 2015 -0600
@@ -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!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-tag-cache.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-obsolete.t Mon Mar 02 01:20:14 2015 -0600
@@ -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
@@ -621,7 +621,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 +753,84 @@
$ 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
+
+ $ hg init a
+ $ cd a
+ $ touch foo
+ $ hg add foo
+ $ hg ci -mfoo
+ $ touch bar
+ $ hg add bar
+ $ hg ci -mbar
+ $ hg up 0
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ touch quux
+ $ hg add quux
+ $ hg ci -m quux
+ created new head
+ $ hg up 1
+ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg tag 1.0
+
+ $ hg up 2
+ 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg log -G
+ o 3:bc47fc7e1c1d (draft) [tip ] Added tag 1.0 for changeset 50c889141114
+ |
+ | @ 2:3d7f255a0081 (draft) [ ] quux
+ | |
+ o | 1:50c889141114 (draft) [1.0 ] bar
+ |/
+ o 0:1f7b0de80e11 (draft) [ ] foo
+
+ $ hg debugobsolete `getid bar`
+ $ hg debugobsolete `getid 1.0`
+ $ hg tag 1.0
+ $ hg log -G
+ @ 4:f9f2ab71ffd5 (draft) [tip ] Added tag 1.0 for changeset 3d7f255a0081
+ |
+ o 2:3d7f255a0081 (draft) [1.0 ] quux
+ |
+ o 0:1f7b0de80e11 (draft) [ ] foo
+
+ $ cat .hgtags
+ 3d7f255a008103380aeb2a7d581fe257f40969e7 1.0
--- a/tests/test-rename.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-rename.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-resolve.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-revset.t Mon Mar 02 01:20:14 2015 -0600
@@ -1029,6 +1029,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
--- a/tests/test-ssh.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-ssh.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-status-color.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-strip.t Mon Mar 02 01:20:14 2015 -0600
@@ -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 Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-subrepo-deep-nested-change.t Mon Mar 02 01:20:14 2015 -0600
@@ -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
--- a/tests/test-subrepo-git.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-subrepo-git.t Mon Mar 02 01:20:14 2015 -0600
@@ -802,4 +802,52 @@
$ 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
+
$ cd ..
--- a/tests/test-subrepo.t Mon Mar 02 01:06:31 2015 -0600
+++ b/tests/test-subrepo.t Mon Mar 02 01:20:14 2015 -0600
@@ -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