# HG changeset patch # User Matt Mackall # Date 1425592327 21600 # Node ID 4ef4e3c3c00693868ba428e21ac092e559e4fcea # Parent 1e3e064c16a12b4a2d67a5168fb3948e42884e85# Parent 3cc630be5f09ab586e1ca3f015456fe5611e6333 merge with stable diff -r 3cc630be5f09 -r 4ef4e3c3c006 Makefile --- a/Makefile Fri Mar 06 00:14:22 2015 +0900 +++ b/Makefile Thu Mar 05 15:52:07 2015 -0600 @@ -7,11 +7,14 @@ PREFIX=/usr/local export PREFIX PYTHON=python +$(eval HGROOT := $(shell pwd)) +HGPYTHONS ?= $(HGROOT)/build/pythons PURE= PYFILES:=$(shell find mercurial hgext doc -name '*.py') DOCFILES=mercurial/help/*.txt export LANGUAGE=C export LC_ALL=C +TESTFLAGS ?= $(shell echo $$HGTESTFLAGS) # Set this to e.g. "mingw32" to use a non-default compiler. COMPILER= @@ -98,6 +101,13 @@ test-%: cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@ +testpy-%: + @echo Looking for Python $* in $(HGPYTHONS) + [ -e $(HGPYTHONS)/$*/bin/python ] || ( \ + cd $$(mktemp --directory --tmpdir) && \ + $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python ) + cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS) + check-code: hg manifest | xargs python contrib/check-code.py diff -r 3cc630be5f09 -r 4ef4e3c3c006 contrib/check-code.py --- a/contrib/check-code.py Fri Mar 06 00:14:22 2015 +0900 +++ b/contrib/check-code.py Thu Mar 05 15:52:07 2015 -0600 @@ -153,7 +153,7 @@ (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"), (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite " "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx - 'hg pull -q file:../test'), # in test-pull.t which is skipped on windows + '# no-msys'), # in test-pull.t which is skipped on windows (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg), (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$', winglobmsg), diff -r 3cc630be5f09 -r 4ef4e3c3c006 contrib/check-commit --- a/contrib/check-commit Fri Mar 06 00:14:22 2015 +0900 +++ b/contrib/check-commit Thu Mar 05 15:52:07 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"), diff -r 3cc630be5f09 -r 4ef4e3c3c006 contrib/win32/ReadMe.html --- a/contrib/win32/ReadMe.html Fri Mar 06 00:14:22 2015 +0900 +++ b/contrib/win32/ReadMe.html Thu Mar 05 15:52:07 2015 -0600 @@ -140,7 +140,7 @@

- Mercurial is Copyright 2005-2014 Matt Mackall and others. See + Mercurial is Copyright 2005-2015 Matt Mackall and others. See the Contributors.txt file for a list of contributors.

diff -r 3cc630be5f09 -r 4ef4e3c3c006 contrib/win32/mercurial.iss --- a/contrib/win32/mercurial.iss Fri Mar 06 00:14:22 2015 +0900 +++ b/contrib/win32/mercurial.iss Thu Mar 05 15:52:07 2015 -0600 @@ -21,7 +21,7 @@ #endif [Setup] -AppCopyright=Copyright 2005-2010 Matt Mackall and others +AppCopyright=Copyright 2005-2015 Matt Mackall and others AppName=Mercurial #if ARCH == "x64" AppVerName=Mercurial {#VERSION} (64-bit) @@ -44,7 +44,7 @@ DefaultDirName={pf}\Mercurial SourceDir=..\.. VersionInfoDescription=Mercurial distributed SCM (version {#VERSION}) -VersionInfoCopyright=Copyright 2005-2010 Matt Mackall and others +VersionInfoCopyright=Copyright 2005-2015 Matt Mackall and others VersionInfoCompany=Matt Mackall and others InternalCompressLevel=max SolidCompression=true diff -r 3cc630be5f09 -r 4ef4e3c3c006 contrib/wix/COPYING.rtf Binary file contrib/wix/COPYING.rtf has changed diff -r 3cc630be5f09 -r 4ef4e3c3c006 doc/hgmanpage.py --- a/doc/hgmanpage.py Fri Mar 06 00:14:22 2015 +0900 +++ b/doc/hgmanpage.py Thu Mar 05 15:52:07 2015 -0600 @@ -427,7 +427,7 @@ pass def visit_block_quote(self, node): - # BUG/HACK: indent alway uses the _last_ indention, + # BUG/HACK: indent always uses the _last_ indention, # thus we need two of them. self.indent(BLOCKQOUTE_INDENT) self.indent(0) diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/churn.py --- a/hgext/churn.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/churn.py Thu Mar 05 15:52:07 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')), diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/color.py --- a/hgext/color.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/color.py Thu Mar 05 15:52:07 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' diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/convert/monotone.py --- a/hgext/convert/monotone.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/convert/monotone.py Thu Mar 05 15:52:07 2015 -0600 @@ -297,7 +297,7 @@ extra = {} certs = self.mtngetcerts(rev) if certs.get('suspend') == certs["branch"]: - extra['close'] = '1' + extra['close'] = 1 return commit( author=certs["author"], date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")), diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/extdiff.py --- a/hgext/extdiff.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/extdiff.py Thu Mar 05 15:52:07 2015 -0600 @@ -276,6 +276,7 @@ def uisetup(ui): for cmd, path in ui.configitems('extdiff'): + path = util.expandpath(path) if cmd.startswith('cmd.'): cmd = cmd[4:] if not path: diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/graphlog.py --- a/hgext/graphlog.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/graphlog.py Thu Mar 05 15:52:07 2015 -0600 @@ -54,4 +54,5 @@ Nodes printed as an @ character are parents of the working directory. """ - return cmdutil.graphlog(ui, repo, *pats, **opts) + opts['graph'] = True + return commands.log(ui, repo, *pats, **opts) diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/histedit.py --- a/hgext/histedit.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/histedit.py Thu Mar 05 15:52:07 2015 -0600 @@ -142,6 +142,13 @@ as running ``hg histedit 836302820282``. If you need plan to push to a repository that Mercurial does not detect to be related to the source repo, you can add a ``--force`` option. + +Histedit rule lines are truncated to 80 characters by default. You +can customise this behaviour by setting a different length in your +configuration file: + +[histedit] +linelen = 120 # truncate rule lines at 120 characters """ try: @@ -158,6 +165,7 @@ from mercurial import error from mercurial import copies from mercurial import context +from mercurial import extensions from mercurial import hg from mercurial import node from mercurial import repair @@ -189,13 +197,13 @@ """) class histeditstate(object): - def __init__(self, repo, parentctx=None, rules=None, keep=None, + def __init__(self, repo, parentctxnode=None, rules=None, keep=None, topmost=None, replacements=None, lock=None, wlock=None): self.repo = repo self.rules = rules self.keep = keep self.topmost = topmost - self.parentctx = parentctx + self.parentctxnode = parentctxnode self.lock = lock self.wlock = wlock if replacements is None: @@ -214,7 +222,7 @@ parentctxnode, rules, keep, topmost, replacements = pickle.load(fp) - self.parentctx = self.repo[parentctxnode] + self.parentctxnode = parentctxnode self.rules = rules self.keep = keep self.topmost = topmost @@ -222,7 +230,7 @@ def write(self): fp = self.repo.vfs('histedit-state', 'w') - pickle.dump((self.parentctx.node(), self.rules, self.keep, + pickle.dump((self.parentctxnode, self.rules, self.keep, self.topmost, self.replacements), fp) fp.close() @@ -346,10 +354,11 @@ return repo.commitctx(new) def pick(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] if oldctx.parents()[0] == ctx: - ui.debug('node %s unchanged\n' % ha) + ui.debug('node %s unchanged\n' % ha[:12]) return oldctx, [] hg.update(repo, ctx.node()) stats = applychanges(ui, repo, oldctx, opts) @@ -361,14 +370,15 @@ n = commit(text=oldctx.description(), user=oldctx.user(), date=oldctx.date(), extra=oldctx.extra()) if n is None: - ui.warn(_('%s: empty changeset\n') % node.hex(ha)) + ui.warn(_('%s: empty changeset\n') % ha[:12]) return ctx, [] new = repo[n] return new, [(oldctx.node(), (n,))] def edit(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] hg.update(repo, ctx.node()) applychanges(ui, repo, oldctx, opts) @@ -382,17 +392,18 @@ return fold(ui, state, ha, rollupopts) def fold(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] hg.update(repo, ctx.node()) stats = applychanges(ui, repo, oldctx, opts) if stats and stats[3] > 0: raise error.InterventionRequired( _('Fix up the change and run hg histedit --continue')) - n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), + n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(), date=oldctx.date(), extra=oldctx.extra()) if n is None: - ui.warn(_('%s: empty changeset') % node.hex(ha)) + ui.warn(_('%s: empty changeset') % ha[:12]) return ctx, [] return finishfold(ui, repo, ctx, oldctx, n, opts, []) @@ -438,12 +449,14 @@ return repo[n], replacements def drop(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] return ctx, [(repo[ha].node(), ())] def message(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] hg.update(repo, ctx.node()) stats = applychanges(ui, repo, oldctx, opts) @@ -503,6 +516,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 +566,7 @@ # basic argument incompatibility processing outg = opts.get('outgoing') cont = opts.get('continue') + editplan = opts.get('edit_plan') abort = opts.get('abort') force = opts.get('force') rules = opts.get('commands', '') @@ -560,13 +575,18 @@ if force and not outg: raise util.Abort(_('--force only allowed with --outgoing')) if cont: - if util.any((outg, abort, revs, freeargs, rules)): + if util.any((outg, abort, revs, freeargs, rules, editplan)): raise util.Abort(_('no arguments allowed with --continue')) goal = 'continue' elif abort: - if util.any((outg, revs, freeargs, rules)): + if util.any((outg, revs, freeargs, rules, editplan)): raise util.Abort(_('no arguments allowed with --abort')) goal = 'abort' + elif editplan: + if util.any((outg, revs, freeargs)): + raise util.Abort(_('only --commands argument allowed with' + '--edit-plan')) + goal = 'edit-plan' else: if os.path.exists(os.path.join(repo.path, 'histedit-state')): raise util.Abort(_('history edit already in progress, try ' @@ -579,6 +599,10 @@ _('only one repo argument allowed with --outgoing')) else: revs.extend(freeargs) + if len(revs) == 0: + histeditdefault = ui.config('histedit', 'defaultrev') + if histeditdefault: + revs.append(histeditdefault) if len(revs) != 1: raise util.Abort( _('histedit requires exactly one ancestor revision')) @@ -589,17 +613,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 +675,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 +688,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 +699,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 +759,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 +772,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 +816,7 @@ # otherwise update "parentctx" before proceeding to further operation parentctx = repo[newchildren[-1]] - state.parentctx = parentctx + state.parentctxnode = parentctx.node() state.replacements.extend(replacements) return state @@ -801,19 +837,41 @@ raise util.Abort(_('cannot edit immutable changeset: %s') % root) return [c.node() for c in ctxs] -def makedesc(c): - """build a initial action line for a ctx `c` +def makedesc(repo, action, rev): + """build a initial action line for a ctx line are in the form: - pick + """ + ctx = repo[rev] summary = '' - if c.description(): - summary = c.description().splitlines()[0] - line = 'pick %s %d %s' % (c, c.rev(), summary) + if ctx.description(): + summary = ctx.description().splitlines()[0] + line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary) # trim to 80 columns so it's not stupidly wide in my editor - return util.ellipsis(line, 80) + maxlen = repo.ui.configint('histedit', 'linelen', default=80) + maxlen = max(maxlen, 22) # avoid truncating hash + return util.ellipsis(line, maxlen) + +def ruleeditor(repo, ui, rules, editcomment=""): + """open an editor to edit rules + + rules are in the format [ [act, ctx], ...] like in state.rules + """ + rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules]) + rules += '\n\n' + rules += editcomment + rules = ui.edit(rules, ui.username()) + + # Save edit rules in .hg/histedit-last-edit.txt in case + # the user needs to ask for help after something + # surprising happens. + f = open(repo.join('histedit-last-edit.txt'), 'w') + f.write(rules) + f.close() + + return rules def verifyrules(rules, repo, ctxs): """Verify that there exists exactly one edit rule per given changeset. @@ -822,7 +880,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 +888,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 +1025,22 @@ finally: release(lock) +def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs): + if isinstance(nodelist, str): + nodelist = [nodelist] + if os.path.exists(os.path.join(repo.path, 'histedit-state')): + state = histeditstate(repo) + state.read() + histedit_nodes = set([ctx for (action, ctx) in state.rules]) + strip_nodes = set([repo[n].hex() for n in nodelist]) + common_nodes = histedit_nodes & strip_nodes + if common_nodes: + raise util.Abort(_("histedit in progress, can't strip %s") + % ', '.join(node.short(x) for x in common_nodes)) + return orig(ui, repo, nodelist, *args, **kwargs) + +extensions.wrapfunction(repair, 'strip', stripwrapper) + def summaryhook(ui, repo): if not os.path.exists(repo.join('histedit-state')): return diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/largefiles/lfcommands.py --- a/hgext/largefiles/lfcommands.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/largefiles/lfcommands.py Thu Mar 05 15:52:07 2015 -0600 @@ -471,9 +471,8 @@ if lfile not in repo[None]: # not switched to normal file util.unlinkpath(abslfile, ignoremissing=True) # use normallookup() to allocate an entry in largefiles - # dirstate, because lack of it misleads - # lfilesrepo.status() into recognition that such cache - # missing files are removed. + # dirstate to prevent lfilesrepo.status() from reporting + # missing files as removed. lfdirstate.normallookup(lfile) update[lfile] = expecthash else: diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/largefiles/overrides.py Thu Mar 05 15:52:07 2015 -0600 @@ -304,17 +304,47 @@ return matchandpats pats = set(p) - # TODO: handling of patterns in both cases below + + def fixpats(pat, tostandin=lfutil.standin): + kindpat = match_._patsplit(pat, None) + + if kindpat[0] is not None: + return kindpat[0] + ':' + tostandin(kindpat[1]) + return tostandin(kindpat[1]) + if m._cwd: - if os.path.isabs(m._cwd): - # TODO: handle largefile magic when invoked from other cwd - return matchandpats - back = (m._cwd.count('/') + 1) * '../' - pats.update(back + lfutil.standin(m._cwd + '/' + f) for f in p) + hglf = lfutil.shortname + back = util.pconvert(m.rel(hglf)[:-len(hglf)]) + + def tostandin(f): + # The file may already be a standin, so trucate the back + # prefix and test before mangling it. This avoids turning + # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'. + if f.startswith(back) and lfutil.splitstandin(f[len(back):]): + return f + + # An absolute path is from outside the repo, so truncate the + # path to the root before building the standin. Otherwise cwd + # is somewhere in the repo, relative to root, and needs to be + # prepended before building the standin. + if os.path.isabs(m._cwd): + f = f[len(back):] + else: + f = m._cwd + '/' + f + return back + lfutil.standin(f) + + pats.update(fixpats(f, tostandin) for f in p) else: - pats.update(lfutil.standin(f) for f in p) + def tostandin(f): + if lfutil.splitstandin(f): + return f + return lfutil.standin(f) + pats.update(fixpats(f, tostandin) for f in p) for i in range(0, len(m._files)): + # Don't add '.hglf' to m.files, since that is already covered by '.' + if m._files[i] == '.': + continue standin = lfutil.standin(m._files[i]) # If the "standin" is a directory, append instead of replace to # support naming a directory on the command line with only @@ -325,7 +355,6 @@ elif m._files[i] not in repo[ctx.node()] \ and repo.wvfs.isdir(standin): m._files.append(standin) - pats.add(standin) m._fmap = set(m._files) m._always = False @@ -338,6 +367,7 @@ return r m.matchfn = lfmatchfn + ui.debug('updated patterns: %s\n' % sorted(pats)) return m, pats # For hg log --patch, the match object is used in two different senses: @@ -559,16 +589,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 +615,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 +746,17 @@ default='relpath'): match = oldmatch(ctx, pats, opts, globbed, default) m = copy.copy(match) + + # revert supports recursing into subrepos, and though largefiles + # currently doesn't work correctly in that case, this match is + # called, so the lfdirstate above may not be the correct one for + # this invocation of match. + lfdirstate = lfutil.openlfdirstate(ctx._repo.ui, ctx._repo, False) + def tostandin(f): if lfutil.standin(f) in ctx: return lfutil.standin(f) - elif lfutil.standin(f) in repo[None]: + elif lfutil.standin(f) in repo[None] or lfdirstate[f] == 'r': return None return f m._files = [tostandin(f) for f in m._files] @@ -820,6 +858,14 @@ sourcerepo, destrepo = result repo = destrepo.local() + # If largefiles is required for this repo, permanently enable it locally + if 'largefiles' in repo.requirements: + fp = repo.vfs('hgrc', 'a', text=True) + try: + fp.write('\n[extensions]\nlargefiles=\n') + finally: + fp.close() + # Caching is implicitly limited to 'rev' option, since the dest repo was # truncated at that point. The user may expect a download count with # this option, so attempt whether or not this is a largefile repo. @@ -845,7 +891,7 @@ repo._lfcommithooks.pop() def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None, - prefix=None, mtime=None, subrepos=None): + prefix='', mtime=None, subrepos=None): # No need to lock because we are only reading history and # largefile caches, neither of which are modified. lfcommands.cachelfiles(repo.ui, repo, node) diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/largefiles/reposetup.py Thu Mar 05 15:52:07 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. + # add a directory to the list, which + # forces status/dirstate to walk all files and + # call the match function on the matcher, even + # on case sensitive filesystems. actualfiles.append('.') matcheddir = True # Nothing in dir, so readd it diff -r 3cc630be5f09 -r 4ef4e3c3c006 hgext/pager.py --- a/hgext/pager.py Fri Mar 06 00:14:22 2015 +0900 +++ b/hgext/pager.py Thu Mar 05 15:52:07 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( diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/archival.py --- a/mercurial/archival.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/archival.py Thu Mar 05 15:52:07 2015 -0600 @@ -230,7 +230,7 @@ } def archive(repo, dest, node, kind, decode=True, matchfn=None, - prefix=None, mtime=None, subrepos=False): + prefix='', mtime=None, subrepos=False): '''create archive of repo as it was at node. dest can be name of directory, name of archive file, or file diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/bookmarks.py --- a/mercurial/bookmarks.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/bookmarks.py Thu Mar 05 15:52:07 2015 -0600 @@ -456,5 +456,5 @@ elif repo.obsstore: return new.node() in obsolete.foreground(repo, [old.node()]) else: - # still an independent clause as it is lazyer (and therefore faster) + # still an independent clause as it is lazier (and therefore faster) return old.descendant(new) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/branchmap.py --- a/mercurial/branchmap.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/branchmap.py Thu Mar 05 15:52:07 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) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/bundle2.py --- a/mercurial/bundle2.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/bundle2.py Thu Mar 05 15:52:07 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 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 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': (), diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/bundlerepo.py Thu Mar 05 15:52:07 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: diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/changegroup.py --- a/mercurial/changegroup.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/changegroup.py Thu Mar 05 15:52:07 2015 -0600 @@ -111,7 +111,8 @@ chunkiter = bundle.getchunks() else: if cg.version != '01': - raise util.Abort(_('Bundle1 only supports v1 changegroups\n')) + raise util.Abort(_('old bundle types only supports v1 ' + 'changegroups')) header, compressor = bundletypes[bundletype] fh.write(header) z = compressor() @@ -481,7 +482,17 @@ base = self.deltaparent(revlog, rev, p1, p2, prev) prefix = '' - if base == nullrev: + if revlog.iscensored(base) or revlog.iscensored(rev): + try: + delta = revlog.revision(node) + except error.CensoredNodeError, e: + delta = e.tombstone + if base == nullrev: + prefix = mdiff.trivialdiffheader(len(delta)) + else: + baselen = revlog.rawsize(base) + prefix = mdiff.replacediffheader(baselen, len(delta)) + elif base == nullrev: delta = revlog.revision(node) prefix = mdiff.trivialdiffheader(len(delta)) else: @@ -659,8 +670,11 @@ pr() fl = repo.file(f) o = len(fl) - if not fl.addgroup(source, revmap, trp): - raise util.Abort(_("received file revlog group is empty")) + try: + if not fl.addgroup(source, revmap, trp): + raise util.Abort(_("received file revlog group is empty")) + except error.CensoredBaseError, e: + raise util.Abort(_("received delta base is censored: %s") % e) revisions += len(fl) - o files += 1 if f in needfiles: diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/changelog.py --- a/mercurial/changelog.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/changelog.py Thu Mar 05 15:52:07 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: diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/cmdutil.py Thu Mar 05 15:52:07 2015 -0600 @@ -110,22 +110,22 @@ (logfile, inst.strerror)) return message -def mergeeditform(ctxorbool, baseform): - """build appropriate editform from ctxorbool and baseform - - 'ctxorbool' is one of a ctx to be committed, or a bool whether +def mergeeditform(ctxorbool, baseformname): + """return appropriate editform name (referencing a committemplate) + + 'ctxorbool' is either a ctx to be committed, or a bool indicating whether merging is committed. - This returns editform 'baseform' with '.merge' if merging is - committed, or one with '.normal' suffix otherwise. + This returns baseformname with '.merge' appended if it is a merge, + otherwise '.normal' is appended. """ if isinstance(ctxorbool, bool): if ctxorbool: - return baseform + ".merge" + return baseformname + ".merge" elif 1 < len(ctxorbool.parents()): - return baseform + ".merge" - - return baseform + ".normal" + return baseformname + ".merge" + + return baseformname + ".normal" def getcommiteditor(edit=False, finishdesc=None, extramsg=None, editform='', **opts): @@ -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) @@ -2615,9 +2600,8 @@ deladded = _deleted - smf deleted = _deleted - deladded - # We need to account for the state of file in the dirstate. - # - # Even, when we revert against something else than parent. This will + # We need to account for the state of the file in the dirstate, + # even when we revert against something else than parent. This will # slightly alter the behavior of revert (doing back up or not, delete # or just forget etc). if parent == node: @@ -2800,14 +2784,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() diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/commands.py --- a/mercurial/commands.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/commands.py Thu Mar 05 15:52:07 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]')) @@ -2893,7 +2893,8 @@ """manually set the parents of the current working directory This is useful for writing repository conversion tools, but should - be used with care. + be used with care. For example, neither the working copy nor the dirstate + is updated, so file status may be incorrect after running this command. Returns 0 on success. """ @@ -4477,6 +4478,10 @@ Returns 0 on success. """ + if opts.get('follow') and opts.get('rev'): + opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))] + del opts['follow'] + if opts.get('graph'): return cmdutil.graphlog(ui, repo, *pats, **opts) @@ -4984,9 +4989,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 +5230,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 +5282,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 +5314,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 +5346,8 @@ finally: wlock.release() - # Nudge users into finishing an unfinished operation. We don't print - # this with the list/show operation because we want list/show to remain - # machine readable. - if not list(ms.unresolved()) and not show: + # Nudge users into finishing an unfinished operation + if not list(ms.unresolved()): ui.status(_('(no more unresolved files)\n')) return ret @@ -6303,7 +6313,7 @@ % util.version()) ui.status(_( "(see http://mercurial.selenic.com for more information)\n" - "\nCopyright (C) 2005-2014 Matt Mackall and others\n" + "\nCopyright (C) 2005-2015 Matt Mackall and others\n" "This is free software; see the source for copying conditions. " "There is NO\nwarranty; " "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/context.py --- a/mercurial/context.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/context.py Thu Mar 05 15:52:07 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 @@ -752,7 +754,7 @@ return True def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False): - """return the first ancestor of introducting + """return the first ancestor of introducing If the linkrev of the file revision does not point to an ancestor of srcrev, we'll walk down the ancestors until we find one introducing @@ -824,7 +826,7 @@ # be replaced with the rename information. This parent is -always- # the first one. # - # As null id have alway been filtered out in the previous list + # As null id have always been filtered out in the previous list # comprehension, inserting to 0 will always result in "replacing # first nullid parent with rename information. pl.insert(0, (r[0], r[1], self._repo.file(r[0]))) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/copies.py --- a/mercurial/copies.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/copies.py Thu Mar 05 15:52:07 2015 -0600 @@ -8,10 +8,6 @@ import util import heapq -def _nonoverlap(d1, d2, d3): - "Return list of elements in d1 not in d2 or d3" - return sorted([d for d in d1 if d not in d3 and d not in d2]) - def _dirname(f): s = f.rfind("/") if s == -1: @@ -144,6 +140,13 @@ del c[k] return c +def _computeforwardmissing(a, b): + """Computes which files are in b but not a. + This is its own function so extensions can easily wrap this call to see what + files _forwardcopies is about to process. + """ + return b.manifest().filesnotin(a.manifest()) + def _forwardcopies(a, b): '''find {dst@b: src@a} copy mapping where a is an ancestor of b''' @@ -167,9 +170,7 @@ # we currently don't try to find where old files went, too expensive # this means we can miss a case like 'hg rm b; hg cp a b' cm = {} - missing = set(b.manifest().iterkeys()) - missing.difference_update(a.manifest().iterkeys()) - + missing = _computeforwardmissing(a, b) ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True) for f in missing: fctx = b[f] @@ -208,6 +209,22 @@ return _backwardrenames(x, y) return _chain(x, y, _backwardrenames(x, a), _forwardcopies(a, y)) +def _computenonoverlap(repo, addedinm1, addedinm2): + """Computes, based on addedinm1 and addedinm2, the files exclusive to m1 + and m2. This is its own function so extensions can easily wrap this call + to see what files mergecopies is about to process. + """ + u1 = sorted(addedinm1 - addedinm2) + u2 = sorted(addedinm2 - addedinm1) + + if u1: + repo.ui.debug(" unmatched files in local:\n %s\n" + % "\n ".join(u1)) + if u2: + repo.ui.debug(" unmatched files in other:\n %s\n" + % "\n ".join(u2)) + return u1, u2 + def mergecopies(repo, c1, c2, ca): """ Find moves and copies between context c1 and c2 that are relevant @@ -261,15 +278,9 @@ repo.ui.debug(" searching for copies back to rev %d\n" % limit) - u1 = _nonoverlap(m1, m2, ma) - u2 = _nonoverlap(m2, m1, ma) - - if u1: - repo.ui.debug(" unmatched files in local:\n %s\n" - % "\n ".join(u1)) - if u2: - repo.ui.debug(" unmatched files in other:\n %s\n" - % "\n ".join(u2)) + addedinm1 = m1.filesnotin(ma) + addedinm2 = m2.filesnotin(ma) + u1, u2 = _computenonoverlap(repo, addedinm1, addedinm2) for f in u1: checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy) @@ -291,7 +302,7 @@ else: diverge2.update(fl) # reverse map for below - bothnew = sorted([d for d in m1 if d in m2 and d not in ma]) + bothnew = sorted(addedinm1 & addedinm2) if bothnew: repo.ui.debug(" unmatched files new in both:\n %s\n" % "\n ".join(bothnew)) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/default.d/mergetools.rc --- a/mercurial/default.d/mergetools.rc Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/default.d/mergetools.rc Thu Mar 05 15:52:07 2015 -0600 @@ -102,6 +102,13 @@ bcompare.priority=-1 bcompare.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child +; OS X version of Beyond Compare +bcomposx.executable = /Applications/Beyond Compare.app/Contents/MacOS/bcomp +bcomposx.args=$local $other $base -mergeoutput=$output -ro -lefttitle=parent1 -centertitle=base -righttitle=parent2 -outputtitle=merged -automerge -reviewconflicts -solo +bcomposx.gui=True +bcomposx.priority=-1 +bcomposx.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child + winmerge.args=/e /x /wl /ub /dl other /dr local $other $local $output winmerge.regkey=Software\Thingamahoochie\WinMerge winmerge.regkeyalt=Software\Wow6432Node\Thingamahoochie\WinMerge\ diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/dirstate.py --- a/mercurial/dirstate.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/dirstate.py Thu Mar 05 15:52:07 2015 -0600 @@ -887,9 +887,9 @@ elif time != mtime and time != mtime & _rangemask: ladd(fn) elif mtime == lastnormaltime: - # fn may have been changed in the same timeslot without - # changing its size. This can happen if we quickly do - # multiple commits in a single transaction. + # fn may have just been marked as normal and it may have + # changed in the same second without changing its size. + # This can happen if we quickly do multiple commits. # Force lookup, so we don't miss such a racy file change. ladd(fn) elif listclean: diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/dispatch.py --- a/mercurial/dispatch.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/dispatch.py Thu Mar 05 15:52:07 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: diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/error.py --- a/mercurial/error.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/error.py Thu Mar 05 15:52:07 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): @@ -134,8 +138,20 @@ pass class CensoredNodeError(RevlogError): - """error raised when content verification fails on a censored node""" + """error raised when content verification fails on a censored node - def __init__(self, filename, node): + Also contains the tombstone data substituted for the uncensored data. + """ + + def __init__(self, filename, node, tombstone): from node import short RevlogError.__init__(self, '%s:%s' % (filename, short(node))) + self.tombstone = tombstone + +class CensoredBaseError(RevlogError): + """error raised when a delta is rejected because its base is censored + + A delta based on a censored revision must be formed as single patch + operation which replaces the entire base with new content. This ensures + the delta may be applied by clones which have not censored the base. + """ diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/extensions.py --- a/mercurial/extensions.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/extensions.py Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/filelog.py --- a/mercurial/filelog.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/filelog.py Thu Mar 05 15:52:07 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 @@ -101,12 +101,9 @@ super(filelog, self).checkhash(text, p1, p2, node, rev=rev) except error.RevlogError: if _censoredtext(text): - raise error.CensoredNodeError(self.indexfile, node) + raise error.CensoredNodeError(self.indexfile, node, text) raise - def _file(self, f): - return filelog(self.opener, f) - - def _iscensored(self, rev): + def iscensored(self, rev): """Check if a file revision is censored.""" return self.flags(rev) & revlog.REVIDX_ISCENSORED diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/filemerge.py --- a/mercurial/filemerge.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/filemerge.py Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/graphmod.py --- a/mercurial/graphmod.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/graphmod.py Thu Mar 05 15:52:07 2015 -0600 @@ -122,7 +122,7 @@ heappush(pendingheap, -currentrev) pendingset.add(currentrev) # iterates on pending rev until after the current rev have been - # processeed. + # processed. rev = None while rev != currentrev: rev = -heappop(pendingheap) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/help.py --- a/mercurial/help.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/help.py Thu Mar 05 15:52:07 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): ''' diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/help/hg.1.txt --- a/mercurial/help/hg.1.txt Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/help/hg.1.txt Thu Mar 05 15:52:07 2015 -0600 @@ -112,7 +112,7 @@ Copying """"""" -Copyright (C) 2005-2014 Matt Mackall. +Copyright (C) 2005-2015 Matt Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/help/hgignore.5.txt --- a/mercurial/help/hgignore.5.txt Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/help/hgignore.5.txt Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@ Copying ======= This manual page is copyright 2006 Vadim Gelfer. -Mercurial is copyright 2005-2014 Matt Mackall. +Mercurial is copyright 2005-2015 Matt Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/help/hgrc.5.txt --- a/mercurial/help/hgrc.5.txt Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/help/hgrc.5.txt Thu Mar 05 15:52:07 2015 -0600 @@ -34,7 +34,7 @@ Copying ======= This manual page is copyright 2005 Bryan O'Sullivan. -Mercurial is copyright 2005-2014 Matt Mackall. +Mercurial is copyright 2005-2015 Matt Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/help/hgweb.txt --- a/mercurial/help/hgweb.txt Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/help/hgweb.txt Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/help/subrepos.txt --- a/mercurial/help/subrepos.txt Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/help/subrepos.txt Thu Mar 05 15:52:07 2015 -0600 @@ -78,7 +78,7 @@ :add: add does not recurse in subrepos unless -S/--subrepos is specified. However, if you specify the full path of a file in a subrepo, it will be added even without -S/--subrepos specified. - Git and Subversion subrepositories are currently silently + Subversion subrepositories are currently silently ignored. :addremove: addremove does not recurse into subrepos unless @@ -91,7 +91,7 @@ -S/--subrepos is specified. :cat: cat currently only handles exact file matches in subrepos. - Git and Subversion subrepositories are currently ignored. + Subversion subrepositories are currently ignored. :commit: commit creates a consistent snapshot of the state of the entire project and its subrepositories. If any subrepositories diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/hgweb/webcommands.py Thu Mar 05 15:52:07 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,63 +400,41 @@ archives=web.archivelist("tip"), revcount=revcount, morevars=morevars, lessvars=lessvars, query=query) +@webcommand('shortlog') def shortlog(web, req, tmpl): + """ + /shortlog + --------- + + Show basic information about a set of changesets. + + This accepts the same parameters as the ``changelog`` handler. The only + difference is the ``shortlog`` template will be rendered instead of the + ``changelog`` template. + """ return changelog(web, req, tmpl, shortlog=True) +@webcommand('changeset') def changeset(web, req, tmpl): - ctx = webutil.changectx(web.repo, req) - basectx = webutil.basechangectx(web.repo, req) - if basectx is None: - basectx = ctx.p1() - showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node()) - showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark', - ctx.node()) - showbranch = webutil.nodebranchnodefault(ctx) + """ + /changeset[/{revision}] + ----------------------- - files = [] - parity = paritygen(web.stripecount) - for blockno, f in enumerate(ctx.files()): - template = f in ctx and 'filenodelink' or 'filenolink' - files.append(tmpl(template, - node=ctx.hex(), file=f, blockno=blockno + 1, - parity=parity.next())) - - style = web.config('web', 'style', 'paper') - if 'style' in req.form: - style = req.form['style'][0] - - parity = paritygen(web.stripecount) - diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style) + Show information about a single changeset. - parity = paritygen(web.stripecount) - diffstatgen = webutil.diffstatgen(ctx, basectx) - diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity) + A URL path argument is the changeset identifier to show. See ``hg help + revisions`` for possible values. If not defined, the ``tip`` changeset + will be shown. - return tmpl('changeset', - diff=diffs, - rev=ctx.rev(), - node=ctx.hex(), - parent=tuple(webutil.parents(ctx)), - child=webutil.children(ctx), - basenode=basectx.hex(), - changesettag=showtags, - changesetbookmark=showbookmarks, - changesetbranch=showbranch, - author=ctx.user(), - desc=ctx.description(), - extra=ctx.extra(), - date=ctx.date(), - files=files, - diffsummary=lambda **x: webutil.diffsummary(diffstatgen), - diffstat=diffstat, - archives=web.archivelist(ctx.hex()), - tags=webutil.nodetagsdict(web.repo, ctx.node()), - bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()), - branch=webutil.nodebranchnodefault(ctx), - inbranch=webutil.nodeinbranch(web.repo, ctx), - branches=webutil.nodebranchdict(web.repo, ctx)) + The ``changeset`` template is rendered. Contents of the ``changesettag``, + ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many + templates related to diffs may all be used to produce the output. + """ + ctx = webutil.changectx(web.repo, req) -rev = changeset + return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx)) + +rev = webcommand('rev')(changeset) def decodepath(path): """Hook for mapping a path in the repository to a path in the @@ -392,7 +444,23 @@ the virtual file system presented by the manifest command below.""" return path +@webcommand('manifest') def manifest(web, req, tmpl): + """ + /manifest[/{revision}[/{path}]] + ------------------------------- + + Show information about a directory. + + If the URL path arguments are defined, information about the root + directory for the ``tip`` changeset will be shown. + + Because this handler can only show information for directories, it + is recommended to use the ``file`` handler instead, as it can handle both + directories and files. + + The ``manifest`` template will be rendered for this handler. + """ ctx = webutil.changectx(web.repo, req) path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) mf = ctx.manifest() @@ -474,7 +542,18 @@ inbranch=webutil.nodeinbranch(web.repo, ctx), branches=webutil.nodebranchdict(web.repo, ctx)) +@webcommand('tags') def tags(web, req, tmpl): + """ + /tags + ----- + + Show information about tags. + + No arguments are accepted. + + The ``tags`` template is rendered. + """ i = list(reversed(web.repo.tagslist())) parity = paritygen(web.stripecount) @@ -496,7 +575,18 @@ entriesnotip=lambda **x: entries(True, False, **x), latestentry=lambda **x: entries(True, True, **x)) +@webcommand('bookmarks') def bookmarks(web, req, tmpl): + """ + /bookmarks + ---------- + + Show information about bookmarks. + + No arguments are accepted. + + The ``bookmarks`` template is rendered. + """ i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] parity = paritygen(web.stripecount) @@ -516,7 +606,20 @@ entries=lambda **x: entries(latestonly=False, **x), latestentry=lambda **x: entries(latestonly=True, **x)) +@webcommand('branches') def branches(web, req, tmpl): + """ + /branches + --------- + + Show information about branches. + + All known branches are contained in the output, even closed branches. + + No arguments are accepted. + + The ``branches`` template is rendered. + """ tips = [] heads = web.repo.heads() parity = paritygen(web.stripecount) @@ -547,7 +650,19 @@ entries=lambda **x: entries(0, **x), latestentry=lambda **x: entries(1, **x)) +@webcommand('summary') def summary(web, req, tmpl): + """ + /summary + -------- + + Show a summary of repository state. + + Information about the latest changesets, bookmarks, tags, and branches + is captured by this handler. + + The ``summary`` template is rendered. + """ i = reversed(web.repo.tagslist()) def tagentries(**map): @@ -632,7 +747,19 @@ node=tip.hex(), archives=web.archivelist("tip")) +@webcommand('filediff') def filediff(web, req, tmpl): + """ + /diff/{revision}/{path} + ----------------------- + + Show how a file changed in a particular commit. + + The ``filediff`` template is rendered. + + This hander is registered under both the ``/diff`` and ``/filediff`` + paths. ``/diff`` is used in modern code. + """ fctx, ctx = None, None try: fctx = webutil.filectx(web.repo, req) @@ -672,9 +799,25 @@ child=webutil.children(ctx), diff=diffs) -diff = filediff +diff = webcommand('diff')(filediff) + +@webcommand('comparison') +def comparison(web, req, tmpl): + """ + /comparison/{revision}/{path} + ----------------------------- -def comparison(web, req, tmpl): + Show a comparison between the old and new versions of a file from changes + made on a particular revision. + + This is similar to the ``diff`` handler. However, this form features + a split or side-by-side diff rather than a unified diff. + + The ``context`` query string argument can be used to control the lines of + context in the diff. + + The ``filecomparison`` template is rendered. + """ ctx = webutil.changectx(web.repo, req) if 'file' not in req.form: raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') @@ -732,7 +875,16 @@ rightnode=hex(rightnode), comparison=comparison) +@webcommand('annotate') def annotate(web, req, tmpl): + """ + /annotate/{revision}/{path} + --------------------------- + + Show changeset information for each line in a file. + + The ``fileannotate`` template is rendered. + """ fctx = webutil.filectx(web.repo, req) f = fctx.path() parity = paritygen(web.stripecount) @@ -784,7 +936,19 @@ child=webutil.children(fctx), permissions=fctx.manifest().flags(f)) +@webcommand('filelog') def filelog(web, req, tmpl): + """ + /filelog/{revision}/{path} + -------------------------- + + Show information about the history of a file in the repository. + + The ``revcount`` query string argument can be defined to control the + maximum number of entries to show. + + The ``filelog`` template will be rendered. + """ try: fctx = webutil.filectx(web.repo, req) @@ -862,7 +1026,27 @@ latestentry=latestentry, revcount=revcount, morevars=morevars, lessvars=lessvars) +@webcommand('archive') def archive(web, req, tmpl): + """ + /archive/{revision}.{format}[/{path}] + ------------------------------------- + + Obtain an archive of repository content. + + The content and type of the archive is defined by a URL path parameter. + ``format`` is the file extension of the archive type to be generated. e.g. + ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your + server configuration. + + The optional ``path`` URL parameter controls content to include in the + archive. If omitted, every file in the specified revision is present in the + archive. If included, only the specified file or contents of the specified + directory will be included in the archive. + + No template is used for this handler. Raw, binary content is generated. + """ + type_ = req.form.get('type', [None])[0] allowed = web.configlist("web", "allow_archive") key = req.form['node'][0] @@ -911,6 +1095,7 @@ return [] +@webcommand('static') def static(web, req, tmpl): fname = req.form['file'][0] # a repo owner may set web.static in .hg/hgrc to get any file @@ -924,7 +1109,24 @@ staticfile(static, fname, req) return [] +@webcommand('graph') def graph(web, req, tmpl): + """ + /graph[/{revision}] + ------------------- + + Show information about the graphical topology of the repository. + + Information rendered by this handler can be used to create visual + representations of repository topology. + + The ``revision`` URL parameter controls the starting changeset. + + The ``revcount`` query string argument can define the number of changesets + to show information for. + + This handler will render the ``graph`` template. + """ ctx = webutil.changectx(web.repo, req) rev = ctx.rev() @@ -1047,8 +1249,23 @@ doc = _('(no help text available)') return doc +@webcommand('help') def help(web, req, tmpl): + """ + /help[/{topic}] + --------------- + + Render help documentation. + + This web command is roughly equivalent to :hg:`help`. If a ``topic`` + is defined, that help topic will be rendered. If not, an index of + available help topics will be rendered. + + The ``help`` template will be rendered when requesting help for a topic. + ``helptopics`` will be rendered for the index of help topics. + """ from mercurial import commands # avoid cycle + from mercurial import help as helpmod # avoid cycle topicname = req.form.get('node', [None])[0] if not topicname: diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/hgweb/webutil.py Thu Mar 05 15:52:07 2015 -0600 @@ -10,7 +10,7 @@ from mercurial import match, patch, error, ui, util, pathutil, context from mercurial.i18n import _ from mercurial.node import hex, nullid -from common import ErrorResponse +from common import ErrorResponse, paritygen from common import HTTP_NOT_FOUND import difflib @@ -138,9 +138,10 @@ yield d def parents(ctx, hide=None): - if (isinstance(ctx, context.basefilectx) and - ctx.changectx().rev() != ctx.linkrev()): - return _siblings([ctx._repo[ctx.linkrev()]], hide) + if isinstance(ctx, context.basefilectx): + introrev = ctx.introrev() + if ctx.changectx().rev() != introrev: + return _siblings([ctx._repo[introrev]], hide) return _siblings(ctx.parents(), hide) def children(ctx, hide=None): @@ -278,6 +279,61 @@ "branches": nodebranchdict(repo, ctx) } +def changesetentry(web, req, tmpl, ctx): + '''Obtain a dictionary to be used to render the "changeset" template.''' + + showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node()) + showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark', + ctx.node()) + showbranch = nodebranchnodefault(ctx) + + files = [] + parity = paritygen(web.stripecount) + for blockno, f in enumerate(ctx.files()): + template = f in ctx and 'filenodelink' or 'filenolink' + files.append(tmpl(template, + node=ctx.hex(), file=f, blockno=blockno + 1, + parity=parity.next())) + + basectx = basechangectx(web.repo, req) + if basectx is None: + basectx = ctx.p1() + + style = web.config('web', 'style', 'paper') + if 'style' in req.form: + style = req.form['style'][0] + + parity = paritygen(web.stripecount) + diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style) + + parity = paritygen(web.stripecount) + diffstatsgen = diffstatgen(ctx, basectx) + diffstats = diffstat(tmpl, ctx, diffstatsgen, parity) + + return dict( + diff=diff, + rev=ctx.rev(), + node=ctx.hex(), + parent=tuple(parents(ctx)), + child=children(ctx), + basenode=basectx.hex(), + changesettag=showtags, + changesetbookmark=showbookmarks, + changesetbranch=showbranch, + author=ctx.user(), + desc=ctx.description(), + extra=ctx.extra(), + date=ctx.date(), + files=files, + diffsummary=lambda **x: diffsummary(diffstatsgen), + diffstat=diffstats, + archives=web.archivelist(ctx.hex()), + tags=nodetagsdict(web.repo, ctx.node()), + bookmarks=nodebookmarksdict(web.repo, ctx.node()), + branch=nodebranchnodefault(ctx), + inbranch=nodeinbranch(web.repo, ctx), + branches=nodebranchdict(web.repo, ctx)) + def listfilediffs(tmpl, files, node, max): for f in files[:max]: yield tmpl('filedifflink', node=hex(node), file=f) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/localrepo.py --- a/mercurial/localrepo.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/localrepo.py Thu Mar 05 15:52:07 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): @@ -1208,7 +1210,7 @@ # Here, we used to search backwards through history to try to find # where the file copy came from if the source of a copy was not in - # the parent diretory. However, this doesn't actually make sense to + # the parent directory. However, this doesn't actually make sense to # do (what does a copy from something not in your working copy even # mean?) and it causes bugs (eg, issue4476). Instead, we will warn # the user that copy information was dropped, so if they didn't diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/manifest.py --- a/mercurial/manifest.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/manifest.py Thu Mar 05 15:52:07 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 @@ -40,6 +38,12 @@ ret._flags[fn] = flags return ret + def filesnotin(self, m2): + '''Set of files in this manifest that are not in the other''' + files = set(self.iterkeys()) + files.difference_update(m2.iterkeys()) + return files + def matches(self, match): '''generate a new manifest filtered by the match argument''' if match.always(): @@ -50,11 +54,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 +224,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 +253,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 +271,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 +286,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 +294,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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/mdiff.py --- a/mercurial/mdiff.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/mdiff.py Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/namespaces.py --- a/mercurial/namespaces.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/namespaces.py Thu Mar 05 15:52:07 2015 -0600 @@ -142,7 +142,7 @@ is used colorname: the name to use for colored log output; if not specified logname is used - logfmt: the format to use for (l10n-ed) log output; if not specified + logfmt: the format to use for (i18n-ed) log output; if not specified it is composed from logname listnames: function to list all names namemap: function that inputs a node, output name(s) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/obsolete.py --- a/mercurial/obsolete.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/obsolete.py Thu Mar 05 15:52:07 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 # -> (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. diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/parsers.c --- a/mercurial/parsers.c Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/parsers.c Thu Mar 05 15:52:07 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} }; diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/patch.py --- a/mercurial/patch.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/patch.py Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/repair.py --- a/mercurial/repair.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/repair.py Thu Mar 05 15:52:07 2015 -0600 @@ -181,6 +181,8 @@ repo.ui.pushbuffer() if isinstance(gen, bundle2.unbundle20): tr = repo.transaction('strip') + tr.hookargs = {'source': 'strip', + 'url': 'bundle:' + vfs.join(chgrpfile)} try: bundle2.processbundle(repo, gen, lambda: tr) tr.close() diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/revlog.py --- a/mercurial/revlog.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/revlog.py Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/revset.py --- a/mercurial/revset.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/revset.py Thu Mar 05 15:52:07 2015 -0600 @@ -323,8 +323,6 @@ def stringset(repo, subset, x): x = repo[x].rev() - if x == -1 and len(subset) == len(repo): - return baseset([-1]) if x in subset: return baseset([x]) return baseset() @@ -349,7 +347,7 @@ return r & subset def dagrange(repo, subset, x, y): - r = spanset(repo) + r = fullreposet(repo) xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y)) return xs & subset @@ -396,7 +394,7 @@ """ # i18n: "ancestor" is a keyword l = getlist(x) - rl = spanset(repo) + rl = fullreposet(repo) anc = None # (getset(repo, rl, i) for i in l) generates a list of lists @@ -412,7 +410,7 @@ return baseset() def _ancestors(repo, subset, x, followfirst=False): - heads = getset(repo, spanset(repo), x) + heads = getset(repo, fullreposet(repo), x) if not heads: return baseset() s = _revancestors(repo, heads, followfirst) @@ -544,7 +542,7 @@ else: return subset.filter(lambda r: matcher(getbi(ucl, r)[0])) - s = getset(repo, spanset(repo), x) + s = getset(repo, fullreposet(repo), x) b = set() for r in s: b.add(getbi(ucl, r)[0]) @@ -708,7 +706,7 @@ return subset.filter(matches) def _descendants(repo, subset, x, followfirst=False): - roots = getset(repo, spanset(repo), x) + roots = getset(repo, fullreposet(repo), x) if not roots: return baseset() s = _revdescendants(repo, roots, followfirst) @@ -744,9 +742,9 @@ is the same as passing all(). """ if x is not None: - sources = getset(repo, spanset(repo), x) + sources = getset(repo, fullreposet(repo), x) else: - sources = getall(repo, spanset(repo), x) + sources = fullreposet(repo) dests = set() @@ -994,7 +992,7 @@ """ # i18n: "all" is a keyword getargs(x, 0, 0, _("all takes no arguments")) - return subset + return subset & spanset(repo) # drop "null" if any def grep(repo, subset, x): """``grep(regex)`` @@ -1145,7 +1143,7 @@ # i18n: "limit" is a keyword raise error.ParseError(_("limit expects a number")) ss = subset - os = getset(repo, spanset(repo), l[0]) + os = getset(repo, fullreposet(repo), l[0]) result = [] it = iter(os) for x in xrange(lim): @@ -1172,7 +1170,7 @@ # i18n: "last" is a keyword raise error.ParseError(_("last expects a number")) ss = subset - os = getset(repo, spanset(repo), l[0]) + os = getset(repo, fullreposet(repo), l[0]) os.reverse() result = [] it = iter(os) @@ -1189,7 +1187,7 @@ """``max(set)`` Changeset with highest revision number in set. """ - os = getset(repo, spanset(repo), x) + os = getset(repo, fullreposet(repo), x) if os: m = os.max() if m in subset: @@ -1226,7 +1224,7 @@ """``min(set)`` Changeset with lowest revision number in set. """ - os = getset(repo, spanset(repo), x) + os = getset(repo, fullreposet(repo), x) if os: m = os.min() if m in subset: @@ -1322,7 +1320,7 @@ cl = repo.changelog # i18n: "only" is a keyword args = getargs(x, 1, 2, _('only takes one or two arguments')) - include = getset(repo, spanset(repo), args[0]) + include = getset(repo, fullreposet(repo), args[0]) if len(args) == 1: if not include: return baseset() @@ -1331,7 +1329,7 @@ exclude = [rev for rev in cl.headrevs() if not rev in descendants and not rev in include] else: - exclude = getset(repo, spanset(repo), args[1]) + exclude = getset(repo, fullreposet(repo), args[1]) results = set(cl.findmissingrevs(common=exclude, heads=include)) return subset & results @@ -1345,9 +1343,9 @@ for the first operation is selected. """ if x is not None: - dests = getset(repo, spanset(repo), x) + dests = getset(repo, fullreposet(repo), x) else: - dests = getall(repo, spanset(repo), x) + dests = fullreposet(repo) def _firstsrc(rev): src = _getrevsource(repo, rev) @@ -1400,7 +1398,7 @@ ps = set() cl = repo.changelog - for r in getset(repo, spanset(repo), x): + for r in getset(repo, fullreposet(repo), x): ps.add(cl.parentrevs(r)[0]) ps -= set([node.nullrev]) return subset & ps @@ -1421,7 +1419,7 @@ ps = set() cl = repo.changelog - for r in getset(repo, spanset(repo), x): + for r in getset(repo, fullreposet(repo), x): ps.add(cl.parentrevs(r)[1]) ps -= set([node.nullrev]) return subset & ps @@ -1435,7 +1433,7 @@ else: ps = set() cl = repo.changelog - for r in getset(repo, spanset(repo), x): + for r in getset(repo, fullreposet(repo), x): ps.update(cl.parentrevs(r)) ps -= set([node.nullrev]) return subset & ps @@ -1548,7 +1546,7 @@ except (TypeError, ValueError): # i18n: "rev" is a keyword raise error.ParseError(_("rev expects a number")) - if l not in fullreposet(repo) and l != node.nullrev: + if l not in repo.changelog and l != node.nullrev: return baseset() return subset & baseset([l]) @@ -1676,7 +1674,7 @@ """``roots(set)`` Changesets in set with no parent changeset in set. """ - s = getset(repo, spanset(repo), x) + s = getset(repo, fullreposet(repo), x) subset = baseset([r for r in s if r in subset]) cs = _children(repo, subset, s) return subset - cs @@ -2243,6 +2241,71 @@ except error.ParseError, inst: return (decl, None, None, parseerrordetail(inst)) +def _parsealiasdefn(defn, args): + """Parse alias definition ``defn`` + + This function also replaces alias argument references in the + specified definition by ``_aliasarg(ARGNAME)``. + + ``args`` is a list of alias argument names, or None if the alias + is declared as a symbol. + + This returns "tree" as parsing result. + + >>> args = ['$1', '$2', 'foo'] + >>> print prettyformat(_parsealiasdefn('$1 or foo', args)) + (or + (func + ('symbol', '_aliasarg') + ('string', '$1')) + (func + ('symbol', '_aliasarg') + ('string', 'foo'))) + >>> try: + ... _parsealiasdefn('$1 or $bar', args) + ... except error.ParseError, inst: + ... print parseerrordetail(inst) + at 6: '$' not for alias arguments + >>> args = ['$1', '$10', 'foo'] + >>> print prettyformat(_parsealiasdefn('$10 or foobar', args)) + (or + (func + ('symbol', '_aliasarg') + ('string', '$10')) + ('symbol', 'foobar')) + >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args)) + (or + ('string', '$1') + ('string', 'foo')) + """ + def tokenizedefn(program, lookup=None): + if args: + argset = set(args) + else: + argset = set() + + for t, value, pos in _tokenizealias(program, lookup=lookup): + if t == 'symbol': + if value in argset: + # emulate tokenization of "_aliasarg('ARGNAME')": + # "_aliasarg()" is an unknown symbol only used separate + # alias argument placeholders from regular strings. + yield ('symbol', '_aliasarg', pos) + yield ('(', None, pos) + yield ('string', value, pos) + yield (')', None, pos) + continue + elif value.startswith('$'): + raise error.ParseError(_("'$' not for alias arguments"), + pos) + yield (t, value, pos) + + p = parser.parser(tokenizedefn, elements) + tree, pos = p.parse(defn) + if pos != len(defn): + raise error.ParseError(_('invalid token'), pos) + return tree + class revsetalias(object): # whether own `error` information is already shown or not. # this avoids showing same warning multiple times at each `findaliases`. @@ -2260,16 +2323,8 @@ ' "%s": %s') % (self.name, self.error) return - if self.args: - for arg in self.args: - # _aliasarg() is an unknown symbol only used separate - # alias argument placeholders from regular strings. - value = value.replace(arg, '_aliasarg(%r)' % (arg,)) - try: - self.replacement, pos = parse(value) - if pos != len(value): - raise error.ParseError(_('invalid token'), pos) + self.replacement = _parsealiasdefn(value, self.args) # Check for placeholder injection _checkaliasarg(self.replacement, self.args) except error.ParseError, inst: @@ -2392,7 +2447,9 @@ tree = findaliases(ui, tree, showwarning=ui.warn) tree = foldconcat(tree) weight, tree = optimize(tree, True) - def mfunc(repo, subset): + def mfunc(repo, subset=None): + if subset is None: + subset = fullreposet(repo) if util.safehasattr(subset, 'isascending'): result = getset(repo, subset, tree) else: @@ -3146,18 +3203,7 @@ return it().next() return None -def spanset(repo, start=None, end=None): - """factory function to dispatch between fullreposet and actual spanset - - Feel free to update all spanset call sites and kill this function at some - point. - """ - if start is None and end is None: - return fullreposet(repo) - return _spanset(repo, start, end) - - -class _spanset(abstractsmartset): +class spanset(abstractsmartset): """Duck type for baseset class which represents a range of revisions and can work lazily and without having all the range in memory @@ -3261,15 +3307,21 @@ return x return None -class fullreposet(_spanset): +class fullreposet(spanset): """a set containing all revisions in the repo - This class exists to host special optimization. + This class exists to host special optimization and magic to handle virtual + revisions such as "null". """ def __init__(self, repo): super(fullreposet, self).__init__(repo) + def __contains__(self, rev): + # assumes the given rev is valid + hidden = self._hiddenrevs + return not (hidden and rev in hidden) + def __and__(self, other): """As self contains the whole repo, all of the other set should also be in self. Therefore `self & other = other`. diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/scmutil.py --- a/mercurial/scmutil.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/scmutil.py Thu Mar 05 15:52:07 2015 -0600 @@ -628,12 +628,22 @@ return repo[val].rev() seen, l = set(), revset.baseset([]) + + revsetaliases = [alias for (alias, _) in + repo.ui.configitems("revsetalias")] + for spec in revs: if l and not seen: seen = set(l) # attempt to parse old-style ranges first to deal with # things like old-tag which contain query metacharacters try: + # ... except for revset aliases without arguments. These + # should be parsed as soon as possible, because they might + # clash with a hash prefix. + if spec in revsetaliases: + raise error.RepoLookupError + if isinstance(spec, int): seen.add(spec) l = l + revset.baseset([spec]) @@ -641,6 +651,9 @@ if _revrangesep in spec: start, end = spec.split(_revrangesep, 1) + if start in revsetaliases or end in revsetaliases: + raise error.RepoLookupError + start = revfix(repo, start, 0) end = revfix(repo, end, len(repo) - 1) if end == nullrev and start < 0: @@ -672,11 +685,11 @@ # fall through to new-style queries if old-style fails m = revset.match(repo.ui, spec, repo) if seen or l: - dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen] + dl = [r for r in m(repo) if r not in seen] l = l + revset.baseset(dl) seen.update(dl) else: - l = m(repo, revset.spanset(repo)) + l = m(repo) return l diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/subrepo.py --- a/mercurial/subrepo.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/subrepo.py Thu Mar 05 15:52:07 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 @@ -835,7 +836,7 @@ def files(self): rev = self._state[1] ctx = self._repo[rev] - return ctx.manifest() + return ctx.manifest().keys() def filedata(self, name): rev = self._state[1] @@ -877,13 +878,11 @@ opts['date'] = None opts['rev'] = substate[1] - pats = [] - if not opts.get('all'): - pats = ['set:modified()'] self.filerevert(*pats, **opts) # Update the repo to the revision specified in the given substate - self.get(substate, overwrite=True) + if not opts.get('dry_run'): + self.get(substate, overwrite=True) def filerevert(self, *pats, **opts): ctx = self._repo[opts['rev']] @@ -1524,6 +1523,47 @@ return False @annotatesubrepoerror + def add(self, ui, match, prefix, explicitonly, **opts): + if self._gitmissing(): + return [] + + (modified, added, removed, + deleted, unknown, ignored, clean) = self.status(None, unknown=True, + clean=True) + + tracked = set() + # dirstates 'amn' warn, 'r' is added again + for l in (modified, added, deleted, clean): + tracked.update(l) + + # Unknown files not of interest will be rejected by the matcher + files = unknown + files.extend(match.files()) + + rejected = [] + + files = [f for f in sorted(set(files)) if match(f)] + for f in files: + exact = match.exact(f) + command = ["add"] + if exact: + command.append("-f") #should be added, even if ignored + if ui.verbose or not exact: + ui.status(_('adding %s\n') % match.rel(f)) + + if f in tracked: # hg prints 'adding' even if already tracked + if exact: + rejected.append(f) + continue + if not opts.get('dry_run'): + self._gitcommand(command + [f]) + + for f in rejected: + ui.warn(_("%s already tracked!\n") % match.abs(f)) + + return rejected + + @annotatesubrepoerror def remove(self): if self._gitmissing(): return @@ -1577,11 +1617,30 @@ @annotatesubrepoerror + def cat(self, match, prefix, **opts): + rev = self._state[1] + if match.anypats(): + return 1 #No support for include/exclude yet + + if not match.files(): + return 1 + + for f in match.files(): + output = self._gitcommand(["show", "%s:%s" % (rev, f)]) + fp = cmdutil.makefileobj(self._subparent, opts.get('output'), + self._ctx.node(), + pathname=os.path.join(prefix, f)) + fp.write(output) + fp.close() + return 0 + + + @annotatesubrepoerror def status(self, rev2, **opts): rev1 = self._state[1] if self._gitmissing() or not rev1: # if the repo is missing, return no results - return [], [], [], [], [], [], [] + return scmutil.status([], [], [], [], [], [], []) modified, added, removed = [], [], [] self._gitupdatestat() if rev2: @@ -1603,7 +1662,7 @@ deleted, unknown, ignored, clean = [], [], [], [] - if not rev2: + if opts.get('unknown'): command = ['ls-files', '--others', '--exclude-standard'] out = self._gitcommand(command) for line in out.split('\n'): @@ -1673,7 +1732,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): diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/tags.py --- a/mercurial/tags.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/tags.py Thu Mar 05 15:52:07 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)) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templater.py --- a/mercurial/templater.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templater.py Thu Mar 05 15:52:07 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:]]) diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/gitweb/map --- a/mercurial/templates/gitweb/map Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/gitweb/map Thu Mar 05 15:52:07 2015 -0600 @@ -140,7 +140,7 @@ parent {rev} - {changesetlink} {ifeq(node, basenode, '(current diff)', \'({difffrom})\')} + {changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')} ' difffrom = 'diff' diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/bookmarks.tmpl --- a/mercurial/templates/monoblue/bookmarks.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/bookmarks.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/branches.tmpl --- a/mercurial/templates/monoblue/branches.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/branches.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/changelog.tmpl --- a/mercurial/templates/monoblue/changelog.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/changelog.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -27,7 +27,7 @@
  • branches
  • files
  • {archives%archiveentry} -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/graph.tmpl --- a/mercurial/templates/monoblue/graph.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/graph.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -27,7 +27,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/help.tmpl --- a/mercurial/templates/monoblue/help.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/help.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/helptopics.tmpl --- a/mercurial/templates/monoblue/helptopics.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/helptopics.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/manifest.tmpl --- a/mercurial/templates/monoblue/manifest.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/manifest.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/map --- a/mercurial/templates/monoblue/map Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/map Thu Mar 05 15:52:07 2015 -0600 @@ -93,7 +93,7 @@ {author|user}@{rev} + title="{node|short}: {desc|escape|firstline}">{author|user}@{rev} {linenumber} @@ -129,7 +129,7 @@
    {changesetlink}
    ' changesetparentdiff = '
    parent {rev}
    -
    {changesetlink} {ifeq(node, basenode, '(current diff)', \'({difffrom})\')}
    ' +
    {changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')}
    ' difffrom = 'diff' filerevbranch = '
    branch
    {name|escape}
    ' filerevparent = ' diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/shortlog.tmpl --- a/mercurial/templates/monoblue/shortlog.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/shortlog.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,8 +26,8 @@
  • bookmarks
  • branches
  • files
  • - {archives%archiveentry} -
  • help
  • + {archives%archiveentry} +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/summary.tmpl --- a/mercurial/templates/monoblue/summary.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/summary.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/monoblue/tags.tmpl --- a/mercurial/templates/monoblue/tags.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/monoblue/tags.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/bookmarks.tmpl --- a/mercurial/templates/paper/bookmarks.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/bookmarks.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -23,7 +23,6 @@ -

    diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/fileannotate.tmpl --- a/mercurial/templates/paper/fileannotate.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/fileannotate.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -68,10 +68,12 @@
    + + {annotate%annotateline} diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/filelog.tmpl --- a/mercurial/templates/paper/filelog.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/filelog.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -35,7 +35,6 @@ -

    rev   line source
    + + {entries%filelogentry} diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/graph.tmpl --- a/mercurial/templates/paper/graph.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/graph.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -28,7 +28,6 @@ -

    age author description
    + @@ -20,6 +21,7 @@ + {entries%indexentry} diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/manifest.tmpl --- a/mercurial/templates/paper/manifest.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/manifest.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -39,11 +39,13 @@
    Name Description   
    + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/search.tmpl --- a/mercurial/templates/paper/search.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/search.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -43,11 +43,13 @@
    name size permissions
    [up]
    + + {entries} diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/shortlog.tmpl --- a/mercurial/templates/paper/shortlog.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/shortlog.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -30,7 +30,6 @@ -

    age author description
    + + {entries%shortlogentry} diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/paper/tags.tmpl --- a/mercurial/templates/paper/tags.tmpl Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/paper/tags.tmpl Thu Mar 05 15:52:07 2015 -0600 @@ -23,7 +23,6 @@ -

    age author description
    + + {entries%tagentry} diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/templates/static/style-paper.css --- a/mercurial/templates/static/style-paper.css Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/templates/static/style-paper.css Thu Mar 05 15:52:07 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%; diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/unionrepo.py --- a/mercurial/unionrepo.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/unionrepo.py Thu Mar 05 15:52:07 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): diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/util.h --- a/mercurial/util.h Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/util.h Thu Mar 05 15:52:07 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_ */ diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/util.py --- a/mercurial/util.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/util.py Thu Mar 05 15:52:07 2015 -0600 @@ -1352,11 +1352,11 @@ formats = defaultdateformats date = date.strip() - if date == _('now'): + if date == 'now' or date == _('now'): return makedate() - if date == _('today'): + if date == 'today' or date == _('today'): date = datetime.date.today().strftime('%b %d') - elif date == _('yesterday'): + elif date == 'yesterday' or date == _('yesterday'): date = (datetime.date.today() - datetime.timedelta(days=1)).strftime('%b %d') diff -r 3cc630be5f09 -r 4ef4e3c3c006 mercurial/windows.py --- a/mercurial/windows.py Fri Mar 06 00:14:22 2015 +0900 +++ b/mercurial/windows.py Thu Mar 05 15:52:07 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''' diff -r 3cc630be5f09 -r 4ef4e3c3c006 setup.py --- a/setup.py Fri Mar 06 00:14:22 2015 +0900 +++ b/setup.py Thu Mar 05 15:52:07 2015 -0600 @@ -63,6 +63,8 @@ raise SystemExit( "Couldn't import standard bz2 (incomplete Python install).") +ispypy = "PyPy" in sys.version + import os, stat, subprocess, time import re import shutil @@ -276,7 +278,7 @@ class hgdist(Distribution): - pure = 0 + pure = ispypy global_options = Distribution.global_options + \ [('pure', None, "use pure (slow) Python " @@ -555,7 +557,7 @@ if py2exeloaded: extra['console'] = [ {'script':'hg', - 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others', + 'copyright':'Copyright (C) 2005-2015 Matt Mackall and others', 'product_version':version}] # sub command of 'build' because 'py2exe' does not handle sub_commands build.sub_commands.insert(0, ('build_hgextindex', None)) diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/run-tests.py --- a/tests/run-tests.py Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/run-tests.py Thu Mar 05 15:52:07 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) diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-addremove.t --- a/tests/test-addremove.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-addremove.t Thu Mar 05 15:52:07 2015 -0600 @@ -30,12 +30,12 @@ adding foo $ hg forget foo #if windows - $ hg -v addremove nonexistant - nonexistant: The system cannot find the file specified + $ hg -v addremove nonexistent + nonexistent: The system cannot find the file specified [1] #else - $ hg -v addremove nonexistant - nonexistant: No such file or directory + $ hg -v addremove nonexistent + nonexistent: No such file or directory [1] #endif $ cd .. @@ -88,13 +88,13 @@ $ rm c #if windows - $ hg ci -A -m "c" nonexistant - nonexistant: The system cannot find the file specified + $ hg ci -A -m "c" nonexistent + nonexistent: The system cannot find the file specified abort: failed to mark all new/missing files as added/removed [255] #else - $ hg ci -A -m "c" nonexistant - nonexistant: No such file or directory + $ hg ci -A -m "c" nonexistent + nonexistent: No such file or directory abort: failed to mark all new/missing files as added/removed [255] #endif diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-bundle-type.t --- a/tests/test-bundle-type.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-bundle-type.t Thu Mar 05 15:52:07 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 .. diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-churn.t --- a/tests/test-churn.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-churn.t Thu Mar 05 15:52:07 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 .. diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-completion.t --- a/tests/test-completion.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-completion.t Thu Mar 05 15:52:07 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: diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-context.py --- a/tests/test-context.py Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-context.py Thu Mar 05 15:52:07 2015 -0600 @@ -51,7 +51,7 @@ for d in ctxb.diff(ctxa, git=True): print d -# test safeness and correctness of "cxt.status()" +# test safeness and correctness of "ctx.status()" print '= checking context.status():' # ancestor "wcctx ~ 2" diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-extension.t --- a/tests/test-extension.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-extension.t Thu Mar 05 15:52:07 2015 -0600 @@ -1140,3 +1140,27 @@ C sub3/3 $ cd .. + +Test synopsis and docstring extending + + $ hg init exthelp + $ cat > exthelp.py < 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! + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-gendoc.t --- a/tests/test-gendoc.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-gendoc.t Thu Mar 05 15:52:07 2015 -0600 @@ -1,4 +1,5 @@ #require docutils +#require gettext Test document extraction diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-glog.t --- a/tests/test-glog.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-glog.t Thu Mar 05 15:52:07 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 @@ -2192,13 +2202,6 @@ (func ('symbol', 'rev') ('symbol', '6')))) - --- log.nodes * (glob) - +++ glog.nodes * (glob) - @@ -1,3 +1,3 @@ - -nodetag 6 - nodetag 8 - nodetag 7 - +nodetag 6 Test --follow-first and forward --rev @@ -2240,6 +2243,14 @@ ('symbol', 'rev') ('symbol', '6')))) +Test --follow with --rev of graphlog extension + + $ hg --config extensions.graphlog= glog -qfr1 + o 1:216d4c92cf98 + | + o 0:f8035bb17114 + + Test subdir $ hg up -q 3 @@ -2354,4 +2365,14 @@ date: Thu Jan 01 00:00:00 1970 +0000 +should not draw line down to null due to the magic of fullreposet + + $ hg log -G -r 'all()' | tail -6 + | + o changeset: 0:f8035bb17114 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add a + + $ cd .. diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-grep.t --- a/tests/test-grep.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-grep.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-help.t --- a/tests/test-help.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-help.t Thu Mar 05 15:52:07 2015 -0600 @@ -411,7 +411,7 @@ Mercurial Distributed SCM (version *) (glob) (see http://mercurial.selenic.com for more information) - Copyright (C) 2005-2014 Matt Mackall and others + Copyright (C) 2005-2015 Matt Mackall and others This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1101,6 +1101,125 @@ abort: help section not found [255] +Test dynamic list of merge tools only shows up once + $ hg help merge-tools + Merge Tools + """"""""""" + + To merge files Mercurial uses merge tools. + + A merge tool combines two different versions of a file into a merged file. + Merge tools are given the two files and the greatest common ancestor of + the two file versions, so they can determine the changes made on both + branches. + + Merge tools are used both for "hg resolve", "hg merge", "hg update", "hg + backout" and in several extensions. + + Usually, the merge tool tries to automatically reconcile the files by + combining all non-overlapping changes that occurred separately in the two + different evolutions of the same initial base file. Furthermore, some + interactive merge programs make it easier to manually resolve conflicting + merges, either in a graphical way, or by inserting some conflict markers. + Mercurial does not include any interactive merge programs but relies on + external tools for that. + + Available merge tools + ===================== + + External merge tools and their properties are configured in the merge- + tools configuration section - see hgrc(5) - but they can often just be + named by their executable. + + A merge tool is generally usable if its executable can be found on the + system and if it can handle the merge. The executable is found if it is an + absolute or relative executable path or the name of an application in the + executable search path. The tool is assumed to be able to handle the merge + if it can handle symlinks if the file is a symlink, if it can handle + binary files if the file is binary, and if a GUI is available if the tool + requires a GUI. + + There are some internal merge tools which can be used. The internal merge + tools are: + + ":dump" + Creates three versions of the files to merge, containing the contents of + local, other and base. These files can then be used to perform a merge + manually. If the file to be merged is named "a.txt", these files will + accordingly be named "a.txt.local", "a.txt.other" and "a.txt.base" and + they will be placed in the same directory as "a.txt". + + ":fail" + Rather than attempting to merge files that were modified on both + branches, it marks them as unresolved. The resolve command must be used + to resolve these conflicts. + + ":local" + Uses the local version of files as the merged version. + + ":merge" + Uses the internal non-interactive simple merge algorithm for merging + files. It will fail if there are any conflicts and leave markers in the + partially merged file. Markers will have two sections, one for each side + of merge. + + ":merge3" + Uses the internal non-interactive simple merge algorithm for merging + files. It will fail if there are any conflicts and leave markers in the + partially merged file. Marker will have three sections, one from each + side of the merge and one for the base content. + + ":other" + Uses the other version of files as the merged version. + + ":prompt" + Asks the user which of the local or the other version to keep as the + merged version. + + ":tagmerge" + Uses the internal tag merge algorithm (experimental). + + Internal tools are always available and do not require a GUI but will by + default not handle symlinks or binary files. + + Choosing a merge tool + ===================== + + Mercurial uses these rules when deciding which merge tool to use: + + 1. If a tool has been specified with the --tool option to merge or + resolve, it is used. If it is the name of a tool in the merge-tools + configuration, its configuration is used. Otherwise the specified tool + must be executable by the shell. + 2. If the "HGMERGE" environment variable is present, its value is used and + must be executable by the shell. + 3. If the filename of the file to be merged matches any of the patterns in + the merge-patterns configuration section, the first usable merge tool + corresponding to a matching pattern is used. Here, binary capabilities + of the merge tool are not considered. + 4. If ui.merge is set it will be considered next. If the value is not the + name of a configured tool, the specified value is used and must be + executable by the shell. Otherwise the named tool is used if it is + usable. + 5. If any usable merge tools are present in the merge-tools configuration + section, the one with the highest priority is used. + 6. If a program named "hgmerge" can be found on the system, it is used - + but it will by default not be used for symlinks and binary files. + 7. If the file to be merged is not binary and is not a symlink, then + internal ":merge" is used. + 8. The merge of the file fails and must be resolved before commit. + + Note: + After selecting a merge program, Mercurial will by default attempt to + merge the files using a simple merge algorithm first. Only if it + doesn't succeed because of conflicting changes Mercurial will actually + execute the merge program. Whether to use the simple merge algorithm + first can be controlled by the premerge setting of the merge tool. + Premerge is enabled by default unless the file is binary or a symlink. + + See the merge-tools and ui sections of hgrc(5) for details on the + configuration of merge tools. + Test usage of section marks in help documents $ cd "$TESTDIR"/../doc diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgrc.t --- a/tests/test-hgrc.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgrc.t Thu Mar 05 15:52:07 2015 -0600 @@ -71,7 +71,7 @@ Mercurial Distributed SCM (version *) (glob) (see http://mercurial.selenic.com for more information) - Copyright (C) 2005-2014 Matt Mackall and others + Copyright (C) 2005-2015 Matt Mackall and others This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ unset FAKEPATH diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb-commands.t Thu Mar 05 15:52:07 2015 -0600 @@ -726,7 +726,6 @@ -

    tag node
    + + @@ -873,7 +874,8 @@ - + + @@ -894,8 +896,7 @@ [+]
    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    dateThu, 01 Jan 1970 00:00:00 +0000
    Thu, 01 Jan 1970 00:00:00 +0000
    parents
    +
    da/foo 1 @@ -1012,11 +1013,13 @@ + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb-descend-empties.t --- a/tests/test-hgweb-descend-empties.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb-descend-empties.t Thu Mar 05 15:52:07 2015 -0600 @@ -81,11 +81,13 @@
    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb-diffs.t --- a/tests/test-hgweb-diffs.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb-diffs.t Thu Mar 05 15:52:07 2015 -0600 @@ -97,7 +97,8 @@ - + + @@ -118,8 +119,7 @@ [+]
    name size permissions
    [up]
    dateThu, 01 Jan 1970 00:00:00 +0000
    Thu, 01 Jan 1970 00:00:00 +0000
    parents
    +
    - + + @@ -390,8 +391,7 @@ [+]
    a 1 @@ -369,7 +369,8 @@
    dateThu, 01 Jan 1970 00:00:00 +0000
    Thu, 01 Jan 1970 00:00:00 +0000
    parents
    +
    a 1 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb-empty.t --- a/tests/test-hgweb-empty.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb-empty.t Thu Mar 05 15:52:07 2015 -0600 @@ -48,7 +48,6 @@ -

    + + @@ -158,7 +159,6 @@ -

    age author description
    + + @@ -264,7 +266,6 @@ -

    age author description
    + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb-filelog.t --- a/tests/test-hgweb-filelog.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb-filelog.t Thu Mar 05 15:52:07 2015 -0600 @@ -156,7 +156,6 @@ -

    name size permissions
    [up]
    + + @@ -266,7 +267,6 @@ -

    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    + + @@ -376,7 +378,6 @@ -

    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    + + @@ -481,7 +484,6 @@ -

    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb-removed.t --- a/tests/test-hgweb-removed.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb-removed.t Thu Mar 05 15:52:07 2015 -0600 @@ -78,7 +78,8 @@ - + + @@ -99,8 +100,7 @@ [+]
    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    dateThu, 01 Jan 1970 00:00:00 +0000
    Thu, 01 Jan 1970 00:00:00 +0000
    parents cb9a9f314b8b
    +
    a 1 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgweb.t --- a/tests/test-hgweb.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgweb.t Thu Mar 05 15:52:07 2015 -0600 @@ -272,11 +272,13 @@ + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-hgwebdir.t --- a/tests/test-hgwebdir.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-hgwebdir.t Thu Mar 05 15:52:07 2015 -0600 @@ -201,6 +201,7 @@
    name size permissions
    [up]
    + @@ -209,6 +210,7 @@ + @@ -699,6 +701,7 @@
    Name Description   
    + @@ -707,6 +710,7 @@ + @@ -1128,6 +1132,7 @@
    Name Description   
    + @@ -1136,6 +1141,7 @@ + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-highlight.t --- a/tests/test-highlight.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-highlight.t Thu Mar 05 15:52:07 2015 -0600 @@ -268,10 +268,12 @@
    Name Description   
    + + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-histedit-arguments.t --- a/tests/test-histedit-arguments.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-histedit-arguments.t Thu Mar 05 15:52:07 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 -------------------------------------------------------------------- diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-histedit-drop.t --- a/tests/test-histedit-drop.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-histedit-drop.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-histedit-edit.t --- a/tests/test-histedit-edit.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-histedit-edit.t Thu Mar 05 15:52:07 2015 -0600 @@ -3,13 +3,14 @@ $ cat >> $HGRCPATH < [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: histedit in progress, can't strip 363532343133 + [255] + commit, then edit the revision $ hg ci -m 'wat' created new head diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-https.t --- a/tests/test-https.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-https.t Thu Mar 05 15:52:07 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] diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-largefiles-cache.t --- a/tests/test-largefiles-cache.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-largefiles-cache.t Thu Mar 05 15:52:07 2015 -0600 @@ -136,7 +136,7 @@ #endif Test issue 4053 (remove --after on a deleted, uncommitted file shouldn't say -it is missing, but a remove on a nonexistant unknown file still should. Same +it is missing, but a remove on a nonexistent unknown file still should. Same for a forget.) $ cd src diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-largefiles-misc.t --- a/tests/test-largefiles-misc.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-largefiles-misc.t Thu Mar 05 15:52:07 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 @@ -443,6 +451,10 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: anotherlarge + $ hg --debug log -T '{rev}: {desc}\n' ../sub/anotherlarge + updated patterns: ['../.hglf/sub/../sub/anotherlarge', '../sub/anotherlarge'] + 1: anotherlarge + $ hg log -G anotherlarge @ changeset: 1:9627a577c5e9 | tag: tip @@ -450,6 +462,30 @@ | date: Thu Jan 01 00:00:00 1970 +0000 | summary: anotherlarge | + + $ hg log glob:another* + changeset: 1:9627a577c5e9 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: anotherlarge + + $ hg --debug log -T '{rev}: {desc}\n' -G glob:another* + updated patterns: ['glob:../.hglf/sub/another*', 'glob:another*'] + @ 1: anotherlarge + | + +#if no-msys + $ hg --debug log -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys + updated patterns: ['glob:../.hglf/sub/another*'] + 1: anotherlarge + + $ hg --debug log -G -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys + updated patterns: ['glob:../.hglf/sub/another*'] + @ 1: anotherlarge + | +#endif + $ echo more >> anotherlarge $ hg st . M anotherlarge @@ -460,8 +496,33 @@ ? sub/anotherlarge.orig $ cd .. +Test glob logging from the root dir + $ hg log glob:**another* + changeset: 1:9627a577c5e9 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: anotherlarge + + $ hg log -G glob:**another* + @ changeset: 1:9627a577c5e9 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: anotherlarge + | + $ cd .. +Log from outer space + $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/sub/anotherlarge' + updated patterns: ['addrm2/.hglf/sub/anotherlarge', 'addrm2/sub/anotherlarge'] + 1: anotherlarge + $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/.hglf/sub/anotherlarge' + updated patterns: ['addrm2/.hglf/sub/anotherlarge'] + 1: anotherlarge + + Check error message while exchange ========================================================= diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-largefiles.t --- a/tests/test-largefiles.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-largefiles.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-log.t --- a/tests/test-log.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-log.t Thu Mar 05 15:52:07 2015 -0600 @@ -46,18 +46,31 @@ $ hg ci -me -d '5 0' Make sure largefiles doesn't interfere with logging a regular file - $ hg log a --config extensions.largefiles= - changeset: 0:9161b9aeaf16 - user: test - date: Thu Jan 01 00:00:01 1970 +0000 - summary: a - + $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles= + updated patterns: ['.hglf/a', 'a'] + 0: a $ hg log a changeset: 0:9161b9aeaf16 user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: a + $ hg log glob:a* + changeset: 3:2ca5ba701980 + user: test + date: Thu Jan 01 00:00:04 1970 +0000 + summary: d + + changeset: 0:9161b9aeaf16 + user: test + date: Thu Jan 01 00:00:01 1970 +0000 + summary: a + + $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles= + updated patterns: ['glob:.hglf/a*', 'glob:a*'] + 3: d + 0: a + log on directory $ hg log dir @@ -631,7 +644,7 @@ -log -f -r 1:tip +log -f -r '1 + 4' $ hg up -C 0 1 files updated, 0 files merged, 1 files removed, 0 files unresolved @@ -639,25 +652,24 @@ $ hg ci -Amb2 -d '1 0' adding b2 created new head - $ hg log -f -r 1:tip + $ hg log -f -r '1 + 4' + changeset: 4:ddb82e70d1a1 + tag: tip + parent: 0:67e992f2c4f3 + user: test + date: Thu Jan 01 00:00:01 1970 +0000 + summary: b2 + changeset: 1:3d5bf5654eda user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: r1 - changeset: 2:60c670bf5b30 + changeset: 0:67e992f2c4f3 user: test date: Thu Jan 01 00:00:01 1970 +0000 - summary: r2 + summary: base - changeset: 3:e62f78d544b4 - parent: 1:3d5bf5654eda - user: test - date: Thu Jan 01 00:00:01 1970 +0000 - summary: b1 - - - log -f -r null $ hg log -f -r null @@ -672,10 +684,17 @@ +log -f with null parent + + $ hg up -C null + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg log -f + + log -r . with two parents $ hg up -C 3 - 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -1339,6 +1358,11 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: add foo, related + changeset: 2:c4c64aedf0f7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add unrelated old foo + $ cd .. Issue2383: hg log showing _less_ differences than hg diff @@ -1652,7 +1676,7 @@ | o a -Ensure that largefiles doesn't intefere with following a normal file +Ensure that largefiles doesn't interfere with following a normal file $ hg --config extensions.largefiles= log -f d -T '{desc}' -G @ c | diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-merge-tools.t --- a/tests/test-merge-tools.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-merge-tools.t Thu Mar 05 15:52:07 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! diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-obsolete-tag-cache.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-obsolete-tag-cache.t Thu Mar 05 15:52:07 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 + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-obsolete.t --- a/tests/test-obsolete.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-obsolete.t Thu Mar 05 15:52:07 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 < 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-pull.t --- a/tests/test-pull.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-pull.t Thu Mar 05 15:52:07 2015 -0600 @@ -76,7 +76,7 @@ abort: file:// URLs can only refer to localhost [255] - $ hg pull -q file:../test + $ hg pull -q file:../test # no-msys It's tricky to make file:// URLs working on every platform with regular shell commands. diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-rebase-pull.t --- a/tests/test-rebase-pull.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-rebase-pull.t Thu Mar 05 15:52:07 2015 -0600 @@ -165,5 +165,47 @@ | o 0: 'C1' +pull --rebase works with bundle2 turned on - + $ cd ../a + $ echo R4 > R4 + $ hg ci -Am R4 + adding R4 + $ hg tglog + @ 5: 'R4' + | + o 4: 'R3' + | + o 3: 'R2' + | + o 2: 'R1' + | + o 1: 'C2' + | + o 0: 'C1' + + $ cd ../c + $ hg pull --rebase --config experimental.bundle2-exp=True --config experimental.strip-bundle2-version=02 + pulling from $TESTTMP/a (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + rebasing 5:518d153c0ba3 "L1" + saved backup bundle to $TESTTMP/c/.hg/strip-backup/518d153c0ba3-73407f14-backup.hg (glob) + $ hg tglog + @ 6: 'L1' + | + o 5: 'R4' + | + o 4: 'R3' + | + o 3: 'R2' + | + o 2: 'R1' + | + o 1: 'C2' + | + o 0: 'C1' + diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-rename.t --- a/tests/test-rename.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-rename.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-resolve.t --- a/tests/test-resolve.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-resolve.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-revset.t --- a/tests/test-revset.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-revset.t Thu Mar 05 15:52:07 2015 -0600 @@ -478,8 +478,36 @@ [255] Test null revision + $ log '(null)' + -1 + $ log '(null:0)' + -1 + 0 + $ log '(0:null)' + 0 + -1 + $ log 'null::0' + -1 + 0 + $ log 'null:tip - 0:' + -1 + $ log 'null: and null::' | head -1 + -1 + $ log 'null: or 0:' | head -2 + -1 + 0 $ log 'ancestors(null)' -1 + $ log 'reverse(null:)' | tail -2 + 0 + -1 + $ log 'first(null:)' + -1 + $ log 'min(null:)' + -1 + $ log 'tip:null and all()' | tail -2 + 1 + 0 $ log 'outgoing()' 8 @@ -1029,6 +1057,19 @@ ('symbol', 'tip') warning: failed to parse the declaration of revset alias "bad name": at 4: invalid token 9 + $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc + $ try 'strictreplacing("foo", tip)' + (func + ('symbol', 'strictreplacing') + (list + ('string', 'foo') + ('symbol', 'tip'))) + (or + ('symbol', 'tip') + (func + ('symbol', 'desc') + ('string', '$1'))) + 9 $ try 'd(2:5)' (func @@ -1114,6 +1155,62 @@ 3 2 +issue4553: check that revset aliases override existing hash prefix + + $ hg log -qr e + 6:e0cc66ef77e8 + + $ hg log -qr e --config revsetalias.e="all()" + 0:2785f51eece5 + 1:d75937da8da0 + 2:5ed5505e9f1c + 3:8528aa5637f2 + 4:2326846efdab + 5:904fa392b941 + 6:e0cc66ef77e8 + 7:013af1973af4 + 8:d5d0dcbdc4d9 + 9:24286f4ae135 + + $ hg log -qr e: --config revsetalias.e="0" + 0:2785f51eece5 + 1:d75937da8da0 + 2:5ed5505e9f1c + 3:8528aa5637f2 + 4:2326846efdab + 5:904fa392b941 + 6:e0cc66ef77e8 + 7:013af1973af4 + 8:d5d0dcbdc4d9 + 9:24286f4ae135 + + $ hg log -qr :e --config revsetalias.e="9" + 0:2785f51eece5 + 1:d75937da8da0 + 2:5ed5505e9f1c + 3:8528aa5637f2 + 4:2326846efdab + 5:904fa392b941 + 6:e0cc66ef77e8 + 7:013af1973af4 + 8:d5d0dcbdc4d9 + 9:24286f4ae135 + + $ hg log -qr e: + 6:e0cc66ef77e8 + 7:013af1973af4 + 8:d5d0dcbdc4d9 + 9:24286f4ae135 + + $ hg log -qr :e + 0:2785f51eece5 + 1:d75937da8da0 + 2:5ed5505e9f1c + 3:8528aa5637f2 + 4:2326846efdab + 5:904fa392b941 + 6:e0cc66ef77e8 + issue2549 - correct optimizations $ log 'limit(1 or 2 or 3, 2) and not 2' diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-ssh.t --- a/tests/test-ssh.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-ssh.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-status-color.t --- a/tests/test-status-color.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-status-color.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-strip.t --- a/tests/test-strip.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-strip.t Thu Mar 05 15:52:07 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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-subrepo-deep-nested-change.t --- a/tests/test-subrepo-deep-nested-change.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-subrepo-deep-nested-change.t Thu Mar 05 15:52:07 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 + 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 + + [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 diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-subrepo-git.t --- a/tests/test-subrepo-git.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-subrepo-git.t Thu Mar 05 15:52:07 2015 -0600 @@ -134,6 +134,7 @@ $ hg status --subrepos ? s/f $ hg add . + adding f $ git add f $ cd .. @@ -802,4 +803,197 @@ $ hg status --subrepos ? s/barfoo +show file at specific revision + $ cat > s/foobar << EOF + > woop woop + > fooo bar + > EOF + $ hg commit --subrepos -m "updated foobar" + committing subrepository s + $ cat > s/foobar << EOF + > current foobar + > (should not be visible using hg cat) + > EOF + + $ hg cat -r . s/foobar + woop woop + fooo bar (no-eol) + $ hg cat -r "parents(.)" s/foobar > catparents + + $ mkdir -p tmp/s + + $ hg cat -r "parents(.)" --output tmp/%% s/foobar + $ diff tmp/% catparents + + $ hg cat -r "parents(.)" --output tmp/%s s/foobar + $ diff tmp/foobar catparents + + $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar + $ diff tmp/s/otherfoobar catparents + + $ hg cat -r "parents(.)" --output tmp/%p s/foobar + $ diff tmp/s/foobar catparents + + $ hg cat -r "parents(.)" --output tmp/%H s/foobar + $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents + + $ hg cat -r "parents(.)" --output tmp/%R s/foobar + $ diff tmp/10 catparents + + $ hg cat -r "parents(.)" --output tmp/%h s/foobar + $ diff tmp/255ee8cf690e catparents + + $ rm tmp/10 + $ hg cat -r "parents(.)" --output tmp/%r s/foobar + $ diff tmp/10 catparents + + $ mkdir tmp/tc + $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar + $ diff tmp/tc/foobar catparents + +cleanup + $ rm -r tmp + $ rm catparents + +add git files, using either files or patterns + $ echo "hsss! hsssssssh!" > s/snake.python + $ echo "ccc" > s/c.c + $ echo "cpp" > s/cpp.cpp + + $ hg add s/snake.python s/c.c s/cpp.cpp + $ hg st --subrepos s + M s/foobar + A s/c.c + A s/cpp.cpp + A s/snake.python + ? s/barfoo + $ hg revert s + reverting subrepo ../gitroot + + $ hg add --subrepos "glob:**.python" + adding s/snake.python (glob) + $ hg st --subrepos s + A s/snake.python + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + $ hg revert s + reverting subrepo ../gitroot + + $ hg add --subrepos s + adding s/barfoo (glob) + adding s/c.c (glob) + adding s/cpp.cpp (glob) + adding s/foobar.orig (glob) + adding s/snake.python (glob) + $ hg st --subrepos s + A s/barfoo + A s/c.c + A s/cpp.cpp + A s/foobar.orig + A s/snake.python + $ hg revert s + reverting subrepo ../gitroot +make sure everything is reverted correctly + $ hg st --subrepos s + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + ? s/snake.python + + $ hg add --subrepos --exclude "path:s/c.c" + adding s/barfoo (glob) + adding s/cpp.cpp (glob) + adding s/foobar.orig (glob) + adding s/snake.python (glob) + $ hg st --subrepos s + A s/barfoo + A s/cpp.cpp + A s/foobar.orig + A s/snake.python + ? s/c.c + $ hg revert --all -q + +.hgignore should not have influence in subrepos + $ cat > .hgignore << EOF + > syntax: glob + > *.python + > EOF + $ hg add .hgignore + $ hg add --subrepos "glob:**.python" s/barfoo + adding s/snake.python (glob) + $ hg st --subrepos s + A s/barfoo + A s/snake.python + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + $ hg revert --all -q + +.gitignore should have influence, +except for explicitly added files (no patterns) + $ cat > s/.gitignore << EOF + > *.python + > EOF + $ hg add s/.gitignore + $ hg st --subrepos s + A s/.gitignore + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + $ hg add --subrepos "glob:**.python" + $ hg st --subrepos s + A s/.gitignore + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + $ hg add --subrepos s/snake.python + $ hg st --subrepos s + A s/.gitignore + A s/snake.python + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + +correctly do a dry run + $ hg add --subrepos s --dry-run + adding s/barfoo (glob) + adding s/c.c (glob) + adding s/cpp.cpp (glob) + adding s/foobar.orig (glob) + $ hg st --subrepos s + A s/.gitignore + A s/snake.python + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + +error given when adding an already tracked file + $ hg add s/.gitignore + s/.gitignore already tracked! + [1] + +removed files can be re-added + $ hg ci --subrepos -m 'snake' + committing subrepository s + $ cd s + $ git rm snake.python + rm 'snake.python' + $ touch snake.python $ cd .. + $ hg add s/snake.python + $ hg status -S + M s/snake.python + ? .hgignore + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + + $ cd .. diff -r 3cc630be5f09 -r 4ef4e3c3c006 tests/test-subrepo.t --- a/tests/test-subrepo.t Fri Mar 06 00:14:22 2015 +0900 +++ b/tests/test-subrepo.t Thu Mar 05 15:52:07 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
    rev   line source