# HG changeset patch # User Matt Mackall # Date 1425315319 21600 # Node ID 4e865115566e75f938cbff9dcf081da39008a161 # Parent 90f08728e0f7cc92d4bae67965d18d8ae5cc312c# Parent aae338f9da70ffcbf9e19ac247339e9caf50f210 merge with stable diff -r aae338f9da70 -r 4e865115566e contrib/check-commit --- a/contrib/check-commit Mon Mar 02 10:29:45 2015 -0600 +++ b/contrib/check-commit Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e hgext/churn.py --- a/hgext/churn.py Mon Mar 02 10:29:45 2015 -0600 +++ b/hgext/churn.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e hgext/color.py --- a/hgext/color.py Mon Mar 02 10:29:45 2015 -0600 +++ b/hgext/color.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e hgext/histedit.py --- a/hgext/histedit.py Mon Mar 02 10:29:45 2015 -0600 +++ b/hgext/histedit.py Mon Mar 02 10:55:19 2015 -0600 @@ -158,6 +158,7 @@ from mercurial import error from mercurial import copies from mercurial import context +from mercurial import extensions from mercurial import hg from mercurial import node from mercurial import repair @@ -189,13 +190,13 @@ """) class histeditstate(object): - def __init__(self, repo, parentctx=None, rules=None, keep=None, + def __init__(self, repo, parentctxnode=None, rules=None, keep=None, topmost=None, replacements=None, lock=None, wlock=None): self.repo = repo self.rules = rules self.keep = keep self.topmost = topmost - self.parentctx = parentctx + self.parentctxnode = parentctxnode self.lock = lock self.wlock = wlock if replacements is None: @@ -214,7 +215,7 @@ parentctxnode, rules, keep, topmost, replacements = pickle.load(fp) - self.parentctx = self.repo[parentctxnode] + self.parentctxnode = parentctxnode self.rules = rules self.keep = keep self.topmost = topmost @@ -222,7 +223,7 @@ def write(self): fp = self.repo.vfs('histedit-state', 'w') - pickle.dump((self.parentctx.node(), self.rules, self.keep, + pickle.dump((self.parentctxnode, self.rules, self.keep, self.topmost, self.replacements), fp) fp.close() @@ -346,10 +347,11 @@ return repo.commitctx(new) def pick(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] if oldctx.parents()[0] == ctx: - ui.debug('node %s unchanged\n' % ha) + ui.debug('node %s unchanged\n' % ha[:12]) return oldctx, [] hg.update(repo, ctx.node()) stats = applychanges(ui, repo, oldctx, opts) @@ -361,14 +363,15 @@ n = commit(text=oldctx.description(), user=oldctx.user(), date=oldctx.date(), extra=oldctx.extra()) if n is None: - ui.warn(_('%s: empty changeset\n') % node.hex(ha)) + ui.warn(_('%s: empty changeset\n') % ha[:12]) return ctx, [] new = repo[n] return new, [(oldctx.node(), (n,))] def edit(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] hg.update(repo, ctx.node()) applychanges(ui, repo, oldctx, opts) @@ -382,17 +385,18 @@ return fold(ui, state, ha, rollupopts) def fold(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] hg.update(repo, ctx.node()) stats = applychanges(ui, repo, oldctx, opts) if stats and stats[3] > 0: raise error.InterventionRequired( _('Fix up the change and run hg histedit --continue')) - n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), + n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(), date=oldctx.date(), extra=oldctx.extra()) if n is None: - ui.warn(_('%s: empty changeset') % node.hex(ha)) + ui.warn(_('%s: empty changeset') % ha[:12]) return ctx, [] return finishfold(ui, repo, ctx, oldctx, n, opts, []) @@ -438,12 +442,14 @@ return repo[n], replacements def drop(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] return ctx, [(repo[ha].node(), ())] def message(ui, state, ha, opts): - repo, ctx = state.repo, state.parentctx + repo, ctxnode = state.repo, state.parentctxnode + ctx = repo[ctxnode] oldctx = repo[ha] hg.update(repo, ctx.node()) stats = applychanges(ui, repo, oldctx, opts) @@ -503,6 +509,7 @@ [('', 'commands', '', _('Read history edits from the specified file.')), ('c', 'continue', False, _('continue an edit already in progress')), + ('', 'edit-plan', False, _('edit remaining actions list')), ('k', 'keep', False, _("don't strip old nodes after edit is complete")), ('', 'abort', False, _('abort an edit in progress')), @@ -552,6 +559,7 @@ # basic argument incompatibility processing outg = opts.get('outgoing') cont = opts.get('continue') + editplan = opts.get('edit_plan') abort = opts.get('abort') force = opts.get('force') rules = opts.get('commands', '') @@ -560,13 +568,18 @@ if force and not outg: raise util.Abort(_('--force only allowed with --outgoing')) if cont: - if util.any((outg, abort, revs, freeargs, rules)): + if util.any((outg, abort, revs, freeargs, rules, editplan)): raise util.Abort(_('no arguments allowed with --continue')) goal = 'continue' elif abort: - if util.any((outg, revs, freeargs, rules)): + if util.any((outg, revs, freeargs, rules, editplan)): raise util.Abort(_('no arguments allowed with --abort')) goal = 'abort' + elif editplan: + if util.any((outg, revs, freeargs)): + raise util.Abort(_('only --commands argument allowed with' + '--edit-plan')) + goal = 'edit-plan' else: if os.path.exists(os.path.join(repo.path, 'histedit-state')): raise util.Abort(_('history edit already in progress, try ' @@ -579,6 +592,10 @@ _('only one repo argument allowed with --outgoing')) else: revs.extend(freeargs) + if len(revs) == 0: + histeditdefault = ui.config('histedit', 'defaultrev') + if histeditdefault: + revs.append(histeditdefault) if len(revs) != 1: raise util.Abort( _('histedit requires exactly one ancestor revision')) @@ -589,17 +606,34 @@ # rebuild state if goal == 'continue': - state = histeditstate(repo) state.read() state = bootstrapcontinue(ui, state, opts) + elif goal == 'edit-plan': + state = histeditstate(repo) + state.read() + if not rules: + comment = editcomment % (state.parentctx, node.short(state.topmost)) + rules = ruleeditor(repo, ui, state.rules, comment) + else: + if rules == '-': + f = sys.stdin + else: + f = open(rules) + rules = f.read() + f.close() + rules = [l for l in (r.strip() for r in rules.splitlines()) + if l and not l.startswith('#')] + rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules]) + state.rules = rules + state.write() + return elif goal == 'abort': - state = histeditstate(repo) state.read() mapping, tmpnodes, leafs, _ntm = processreplacement(state) ui.debug('restore wc to old parent %s\n' % node.short(state.topmost)) # check whether we should update away parentnodes = [c.node() for c in repo[None].parents()] - for n in leafs | set([state.parentctx.node()]): + for n in leafs | set([state.parentctxnode]): if n in parentnodes: hg.clean(repo, state.topmost) break @@ -634,16 +668,8 @@ ctxs = [repo[r] for r in revs] if not rules: - rules = '\n'.join([makedesc(c) for c in ctxs]) - rules += '\n\n' - rules += editcomment % (node.short(root), node.short(topmost)) - rules = ui.edit(rules, ui.username()) - # Save edit rules in .hg/histedit-last-edit.txt in case - # the user needs to ask for help after something - # surprising happens. - f = open(repo.join('histedit-last-edit.txt'), 'w') - f.write(rules) - f.close() + comment = editcomment % (node.short(root), node.short(topmost)) + rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment) else: if rules == '-': f = sys.stdin @@ -655,9 +681,9 @@ if l and not l.startswith('#')] rules = verifyrules(rules, repo, ctxs) - parentctx = repo[root].parents()[0] + parentctxnode = repo[root].parents()[0].node() - state.parentctx = parentctx + state.parentctxnode = parentctxnode state.rules = rules state.keep = keep state.topmost = topmost @@ -666,12 +692,14 @@ while state.rules: state.write() action, ha = state.rules.pop(0) - ui.debug('histedit: processing %s %s\n' % (action, ha)) + ui.debug('histedit: processing %s %s\n' % (action, ha[:12])) actfunc = actiontable[action] - state.parentctx, replacement_ = actfunc(ui, state, ha, opts) + parentctx, replacement_ = actfunc(ui, state, ha, opts) + state.parentctxnode = parentctx.node() state.replacements.extend(replacement_) + state.write() - hg.update(repo, state.parentctx.node()) + hg.update(repo, state.parentctxnode) mapping, tmpnodes, created, ntm = processreplacement(state) if mapping: @@ -724,7 +752,8 @@ return newchildren def bootstrapcontinue(ui, state, opts): - repo, parentctx = state.repo, state.parentctx + repo, parentctxnode = state.repo, state.parentctxnode + parentctx = repo[parentctxnode] action, currentnode = state.rules.pop(0) ctx = repo[currentnode] @@ -736,7 +765,7 @@ if s.modified or s.added or s.removed or s.deleted: # prepare the message for the commit to comes if action in ('f', 'fold', 'r', 'roll'): - message = 'fold-temp-revision %s' % currentnode + message = 'fold-temp-revision %s' % currentnode[:12] else: message = ctx.description() editopt = action in ('e', 'edit', 'm', 'mess') @@ -780,7 +809,7 @@ # otherwise update "parentctx" before proceeding to further operation parentctx = repo[newchildren[-1]] - state.parentctx = parentctx + state.parentctxnode = parentctx.node() state.replacements.extend(replacements) return state @@ -801,20 +830,40 @@ raise util.Abort(_('cannot edit immutable changeset: %s') % root) return [c.node() for c in ctxs] -def makedesc(c): - """build a initial action line for a ctx `c` +def makedesc(repo, action, rev): + """build a initial action line for a ctx line are in the form: - pick + """ + ctx = repo[rev] summary = '' - if c.description(): - summary = c.description().splitlines()[0] - line = 'pick %s %d %s' % (c, c.rev(), summary) + if ctx.description(): + summary = ctx.description().splitlines()[0] + line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary) # trim to 80 columns so it's not stupidly wide in my editor return util.ellipsis(line, 80) +def ruleeditor(repo, ui, rules, editcomment=""): + """open an editor to edit rules + + rules are in the format [ [act, ctx], ...] like in state.rules + """ + rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules]) + rules += '\n\n' + rules += editcomment + rules = ui.edit(rules, ui.username()) + + # Save edit rules in .hg/histedit-last-edit.txt in case + # the user needs to ask for help after something + # surprising happens. + f = open(repo.join('histedit-last-edit.txt'), 'w') + f.write(rules) + f.close() + + return rules + def verifyrules(rules, repo, ctxs): """Verify that there exists exactly one edit rule per given changeset. @@ -822,7 +871,7 @@ or a rule on a changeset outside of the user-given range. """ parsed = [] - expected = set(str(c) for c in ctxs) + expected = set(c.hex() for c in ctxs) seen = set() for r in rules: if ' ' not in r: @@ -830,22 +879,24 @@ action, rest = r.split(' ', 1) ha = rest.strip().split(' ', 1)[0] try: - ha = str(repo[ha]) # ensure its a short hash + ha = repo[ha].hex() except error.RepoError: - raise util.Abort(_('unknown changeset %s listed') % ha) + raise util.Abort(_('unknown changeset %s listed') % ha[:12]) if ha not in expected: raise util.Abort( _('may not use changesets other than the ones listed')) if ha in seen: - raise util.Abort(_('duplicated command for changeset %s') % ha) + raise util.Abort(_('duplicated command for changeset %s') % + ha[:12]) seen.add(ha) if action not in actiontable: raise util.Abort(_('unknown action "%s"') % action) parsed.append([action, ha]) missing = sorted(expected - seen) # sort to stabilize output if missing: - raise util.Abort(_('missing rules for changeset %s') % missing[0], - hint=_('do you want to use the drop action?')) + raise util.Abort(_('missing rules for changeset %s') % + missing[0][:12], + hint=_('do you want to use the drop action?')) return parsed def processreplacement(state): @@ -965,6 +1016,23 @@ finally: release(lock) +def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs): + if isinstance(nodelist, str): + nodelist = [nodelist] + if os.path.exists(os.path.join(repo.path, 'histedit-state')): + state = histeditstate(repo) + state.read() + histedit_nodes = set([ctx for (action, ctx) in state.rules]) + strip_nodes = set([repo[n].hex() for n in nodelist]) + common_nodes = histedit_nodes & strip_nodes + if common_nodes: + raise util.Abort(_('unable to strip %s. Nodes are ' + 'used by history edit in progress.') + % ', '.join(common_nodes)) + return orig(ui, repo, nodelist, *args, **kwargs) + +extensions.wrapfunction(repair, 'strip', stripwrapper) + def summaryhook(ui, repo): if not os.path.exists(repo.join('histedit-state')): return diff -r aae338f9da70 -r 4e865115566e hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Mon Mar 02 10:29:45 2015 -0600 +++ b/hgext/largefiles/overrides.py Mon Mar 02 10:55:19 2015 -0600 @@ -559,16 +559,6 @@ # this isn't legal, let the original function deal with it return orig(ui, repo, pats, opts, rename) - def makestandin(relpath): - path = pathutil.canonpath(repo.root, repo.getcwd(), relpath) - return os.path.join(repo.wjoin(lfutil.standin(path))) - - fullpats = scmutil.expandpats(pats) - dest = fullpats[-1] - - if os.path.isdir(dest): - if not os.path.isdir(makestandin(dest)): - os.makedirs(makestandin(dest)) # This could copy both lfiles and normal files in one command, # but we don't want to do that. First replace their matcher to # only match normal files and run it, then replace it to just @@ -595,6 +585,17 @@ except OSError: return result + def makestandin(relpath): + path = pathutil.canonpath(repo.root, repo.getcwd(), relpath) + return os.path.join(repo.wjoin(lfutil.standin(path))) + + fullpats = scmutil.expandpats(pats) + dest = fullpats[-1] + + if os.path.isdir(dest): + if not os.path.isdir(makestandin(dest)): + os.makedirs(makestandin(dest)) + try: try: # When we call orig below it creates the standins but we don't add @@ -715,10 +716,17 @@ default='relpath'): match = oldmatch(ctx, pats, opts, globbed, default) m = copy.copy(match) + + # revert supports recursing into subrepos, and though largefiles + # currently doesn't work correctly in that case, this match is + # called, so the lfdirstate above may not be the correct one for + # this invocation of match. + lfdirstate = lfutil.openlfdirstate(ctx._repo.ui, ctx._repo, 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 +828,14 @@ sourcerepo, destrepo = result repo = destrepo.local() + # If largefiles is required for this repo, permanently enable it locally + if 'largefiles' in repo.requirements: + fp = repo.vfs('hgrc', 'a', text=True) + try: + fp.write('\n[extensions]\nlargefiles=\n') + finally: + fp.close() + # Caching is implicitly limited to 'rev' option, since the dest repo was # truncated at that point. The user may expect a download count with # this option, so attempt whether or not this is a largefile repo. diff -r aae338f9da70 -r 4e865115566e hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py Mon Mar 02 10:29:45 2015 -0600 +++ b/hgext/largefiles/reposetup.py Mon Mar 02 10:55:19 2015 -0600 @@ -329,10 +329,10 @@ actualfiles.append(lf) if not matcheddir: # There may still be normal files in the dir, so - # make sure a directory is in the list, which - # forces status to walk and call the match - # function on the matcher. Windows does NOT - # require this. + # make sure _a_ directory is in the list, which + # forces status/dirstate to walk all files and + # call the match function on the matcher, even + # on case sensitive filesytems. actualfiles.append('.') matcheddir = True # Nothing in dir, so readd it diff -r aae338f9da70 -r 4e865115566e hgext/pager.py --- a/hgext/pager.py Mon Mar 02 10:29:45 2015 -0600 +++ b/hgext/pager.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/branchmap.py --- a/mercurial/branchmap.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/branchmap.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/bundle2.py --- a/mercurial/bundle2.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/bundle2.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/bundlerepo.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/changegroup.py --- a/mercurial/changegroup.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/changegroup.py Mon Mar 02 10:55:19 2015 -0600 @@ -659,8 +659,11 @@ pr() fl = repo.file(f) o = len(fl) - if not fl.addgroup(source, revmap, trp): - raise util.Abort(_("received file revlog group is empty")) + try: + if not fl.addgroup(source, revmap, trp): + raise util.Abort(_("received file revlog group is empty")) + except error.CensoredBaseError, e: + raise util.Abort(_("received delta base is censored: %s") % e) revisions += len(fl) - o files += 1 if f in needfiles: diff -r aae338f9da70 -r 4e865115566e mercurial/changelog.py --- a/mercurial/changelog.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/changelog.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/cmdutil.py --- a/mercurial/cmdutil.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/cmdutil.py Mon Mar 02 10:55:19 2015 -0600 @@ -1473,14 +1473,7 @@ function on each context in the window in forward order.''' follow = opts.get('follow') or opts.get('follow_first') - - if opts.get('rev'): - revs = scmutil.revrange(repo, opts.get('rev')) - elif follow: - revs = repo.revs('reverse(:.)') - else: - revs = revset.spanset(repo) - revs.reverse() + revs = _logrevs(repo, opts) if not revs: return [] wanted = set() @@ -1822,6 +1815,21 @@ expr = None return expr, filematcher +def _logrevs(repo, opts): + # Default --rev value depends on --follow but --follow behaviour + # depends on revisions resolved from --rev... + follow = opts.get('follow') or opts.get('follow_first') + if opts.get('rev'): + revs = scmutil.revrange(repo, opts['rev']) + elif follow and repo.dirstate.p1() == nullid: + revs = revset.baseset() + elif follow: + revs = repo.revs('reverse(:.)') + else: + revs = revset.spanset(repo) + revs.reverse() + return revs + def getgraphlogrevs(repo, pats, opts): """Return (revs, expr, filematcher) where revs is an iterable of revision numbers, expr is a revset string built from log options @@ -1830,28 +1838,14 @@ callable taking a revision number and returning a match objects filtering the files to be detailed when displaying the revision. """ - if not len(repo): - return [], None, None limit = loglimit(opts) - # Default --rev value depends on --follow but --follow behaviour - # depends on revisions resolved from --rev... - follow = opts.get('follow') or opts.get('follow_first') - possiblyunsorted = False # whether revs might need sorting - if opts.get('rev'): - revs = scmutil.revrange(repo, opts['rev']) - # Don't sort here because _makelogrevset might depend on the - # order of revs - possiblyunsorted = True - else: - if follow and len(repo) > 0: - revs = repo.revs('reverse(:.)') - else: - revs = revset.spanset(repo) - revs.reverse() + revs = _logrevs(repo, opts) if not revs: return revset.baseset(), None, None expr, filematcher = _makelogrevset(repo, pats, opts, revs) - if possiblyunsorted: + if opts.get('rev'): + # User-specified revs might be unsorted, but don't sort before + # _makelogrevset because it might depend on the order of revs revs.sort(reverse=True) if expr: # Revset matchers often operate faster on revisions in changelog @@ -1882,16 +1876,7 @@ filtering the files to be detailed when displaying the revision. """ limit = loglimit(opts) - # Default --rev value depends on --follow but --follow behaviour - # depends on revisions resolved from --rev... - follow = opts.get('follow') or opts.get('follow_first') - if opts.get('rev'): - revs = scmutil.revrange(repo, opts['rev']) - elif follow: - revs = repo.revs('reverse(:.)') - else: - revs = revset.spanset(repo) - revs.reverse() + revs = _logrevs(repo, opts) if not revs: return revset.baseset([]), None, None expr, filematcher = _makelogrevset(repo, pats, opts, revs) @@ -2798,14 +2783,14 @@ _performrevert(repo, parents, ctx, actions) - # get the list of subrepos that must be reverted - subrepomatch = scmutil.match(ctx, pats, opts) - targetsubs = sorted(s for s in ctx.substate if subrepomatch(s)) - - if targetsubs: - # Revert the subrepos on the revert list - for sub in targetsubs: - ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts) + # get the list of subrepos that must be reverted + subrepomatch = scmutil.match(ctx, pats, opts) + targetsubs = sorted(s for s in ctx.substate if subrepomatch(s)) + + if targetsubs: + # Revert the subrepos on the revert list + for sub in targetsubs: + ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts) finally: wlock.release() diff -r aae338f9da70 -r 4e865115566e mercurial/commands.py --- a/mercurial/commands.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/commands.py Mon Mar 02 10:55:19 2015 -0600 @@ -2885,7 +2885,7 @@ weight, optimizedtree = revset.optimize(newtree, True) ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n") func = revset.match(ui, expr) - for c in func(repo, revset.spanset(repo)): + for c in func(repo): ui.write("%s\n" % c) @command('debugsetparents', [], _('REV1 [REV2]')) @@ -4984,9 +4984,9 @@ Returns 0 on success, 1 if an update had unresolved files. """ source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch')) + ui.status(_('pulling from %s\n') % util.hidepassword(source)) other = hg.peer(repo, opts, source) try: - ui.status(_('pulling from %s\n') % util.hidepassword(source)) revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev')) @@ -5225,7 +5225,7 @@ ('m', 'mark', None, _('mark files as resolved')), ('u', 'unmark', None, _('mark files as unresolved')), ('n', 'no-status', None, _('hide status prefix'))] - + mergetoolopts + walkopts, + + mergetoolopts + walkopts + formatteropts, _('[OPTION]... [FILE]...'), inferrepo=True) def resolve(ui, repo, *pats, **opts): @@ -5277,11 +5277,25 @@ raise util.Abort(_('no files or directories specified'), hint=('use --all to remerge all files')) + if show: + fm = ui.formatter('resolve', opts) + ms = mergemod.mergestate(repo) + m = scmutil.match(repo[None], pats, opts) + for f in ms: + if not m(f): + continue + l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved'}[ms[f]] + fm.startitem() + fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l) + fm.write('path', '%s\n', f, label=l) + fm.end() + return 0 + wlock = repo.wlock() try: ms = mergemod.mergestate(repo) - if not (ms.active() or repo.dirstate.p2() != nullid) and not show: + if not (ms.active() or repo.dirstate.p2() != nullid): raise util.Abort( _('resolve command not applicable when not merging')) @@ -5295,14 +5309,7 @@ didwork = True - if show: - if nostatus: - ui.write("%s\n" % f) - else: - ui.write("%s %s\n" % (ms[f].upper(), f), - label='resolve.' + - {'u': 'unresolved', 'r': 'resolved'}[ms[f]]) - elif mark: + if mark: ms.mark(f, "r") elif unmark: ms.mark(f, "u") @@ -5334,10 +5341,8 @@ finally: wlock.release() - # Nudge users into finishing an unfinished operation. We don't print - # this with the list/show operation because we want list/show to remain - # machine readable. - if not list(ms.unresolved()) and not show: + # Nudge users into finishing an unfinished operation + if not list(ms.unresolved()): ui.status(_('(no more unresolved files)\n')) return ret diff -r aae338f9da70 -r 4e865115566e mercurial/context.py --- a/mercurial/context.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/context.py Mon Mar 02 10:55:19 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 diff -r aae338f9da70 -r 4e865115566e mercurial/copies.py --- a/mercurial/copies.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/copies.py Mon Mar 02 10:55:19 2015 -0600 @@ -144,6 +144,15 @@ del c[k] return c +def _computeforwardmissing(a, b): + """Computes which files are in b but not a. + This is its own function so extensions can easily wrap this call to see what + files _forwardcopies is about to process. + """ + missing = set(b.manifest().iterkeys()) + missing.difference_update(a.manifest().iterkeys()) + return missing + def _forwardcopies(a, b): '''find {dst@b: src@a} copy mapping where a is an ancestor of b''' @@ -167,9 +176,7 @@ # we currently don't try to find where old files went, too expensive # this means we can miss a case like 'hg rm b; hg cp a b' cm = {} - missing = set(b.manifest().iterkeys()) - missing.difference_update(a.manifest().iterkeys()) - + missing = _computeforwardmissing(a, b) ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True) for f in missing: fctx = b[f] @@ -208,6 +215,22 @@ return _backwardrenames(x, y) return _chain(x, y, _backwardrenames(x, a), _forwardcopies(a, y)) +def _computenonoverlap(repo, m1, m2, ma): + """Computes the files exclusive to m1 and m2. + This is its own function so extensions can easily wrap this call to see what + files mergecopies is about to process. + """ + u1 = _nonoverlap(m1, m2, ma) + u2 = _nonoverlap(m2, m1, ma) + + if u1: + repo.ui.debug(" unmatched files in local:\n %s\n" + % "\n ".join(u1)) + if u2: + repo.ui.debug(" unmatched files in other:\n %s\n" + % "\n ".join(u2)) + return u1, u2 + def mergecopies(repo, c1, c2, ca): """ Find moves and copies between context c1 and c2 that are relevant @@ -261,15 +284,7 @@ repo.ui.debug(" searching for copies back to rev %d\n" % limit) - u1 = _nonoverlap(m1, m2, ma) - u2 = _nonoverlap(m2, m1, ma) - - if u1: - repo.ui.debug(" unmatched files in local:\n %s\n" - % "\n ".join(u1)) - if u2: - repo.ui.debug(" unmatched files in other:\n %s\n" - % "\n ".join(u2)) + u1, u2 = _computenonoverlap(repo, m1, m2, ma) for f in u1: checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy) diff -r aae338f9da70 -r 4e865115566e mercurial/dispatch.py --- a/mercurial/dispatch.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/dispatch.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/error.py --- a/mercurial/error.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/error.py Mon Mar 02 10:55:19 2015 -0600 @@ -22,6 +22,10 @@ class LookupError(RevlogError, KeyError): def __init__(self, name, index, message): self.name = name + self.index = index + # this can't be called 'message' because at least some installs of + # Python 2.6+ complain about the 'message' property being deprecated + self.lookupmessage = message if isinstance(name, str) and len(name) == 20: from node import short name = short(name) @@ -61,7 +65,7 @@ """Exception raised when a remote repo reports failure""" class ParseError(Exception): - """Exception raised when parsing config files (msg[, pos])""" + """Raised when parsing config files and {rev,file}sets (msg[, pos])""" class RepoError(Exception): def __init__(self, *args, **kw): @@ -139,3 +143,11 @@ def __init__(self, filename, node): from node import short RevlogError.__init__(self, '%s:%s' % (filename, short(node))) + +class CensoredBaseError(RevlogError): + """error raised when a delta is rejected because its base is censored + + A delta based on a censored revision must be formed as single patch + operation which replaces the entire base with new content. This ensures + the delta may be applied by clones which have not censored the base. + """ diff -r aae338f9da70 -r 4e865115566e mercurial/extensions.py --- a/mercurial/extensions.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/extensions.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/filelog.py --- a/mercurial/filelog.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/filelog.py Mon Mar 02 10:55:19 2015 -0600 @@ -29,7 +29,7 @@ def _censoredtext(text): m, offs = parsemeta(text) - return m and "censored" in m and not text[offs:] + return m and "censored" in m class filelog(revlog.revlog): def __init__(self, opener, path): @@ -64,7 +64,7 @@ node = self.node(rev) if self.renamed(node): return len(self.read(node)) - if self._iscensored(rev): + if self.iscensored(rev): return 0 # XXX if self.read(node).startswith("\1\n"), this returns (size+4) @@ -85,7 +85,7 @@ return False # censored files compare against the empty file - if self._iscensored(self.rev(node)): + if self.iscensored(self.rev(node)): return text != '' # renaming a file produces a different hash, even if the data @@ -104,9 +104,6 @@ raise error.CensoredNodeError(self.indexfile, node) raise - def _file(self, f): - return filelog(self.opener, f) - - def _iscensored(self, rev): + def iscensored(self, rev): """Check if a file revision is censored.""" return self.flags(rev) & revlog.REVIDX_ISCENSORED diff -r aae338f9da70 -r 4e865115566e mercurial/filemerge.py --- a/mercurial/filemerge.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/filemerge.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/help.py --- a/mercurial/help.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/help.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/help/hgweb.txt --- a/mercurial/help/hgweb.txt Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/help/hgweb.txt Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/help/subrepos.txt --- a/mercurial/help/subrepos.txt Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/help/subrepos.txt Mon Mar 02 10:55:19 2015 -0600 @@ -91,7 +91,7 @@ -S/--subrepos is specified. :cat: cat currently only handles exact file matches in subrepos. - Git and Subversion subrepositories are currently ignored. + Subversion subrepositories are currently ignored. :commit: commit creates a consistent snapshot of the state of the entire project and its subrepositories. If any subrepositories diff -r aae338f9da70 -r 4e865115566e mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/hgweb/webcommands.py Mon Mar 02 10:55:19 2015 -0600 @@ -13,27 +13,58 @@ from common import paritygen, staticfile, get_contact, ErrorResponse from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND from mercurial import graphmod, patch -from mercurial import help as helpmod from mercurial import scmutil from mercurial.i18n import _ from mercurial.error import ParseError, RepoLookupError, Abort from mercurial import revset -# __all__ is populated with the allowed commands. Be sure to add to it if -# you're adding a new command, or the new command won't work. +__all__ = [] +commands = {} + +class webcommand(object): + """Decorator used to register a web command handler. + + The decorator takes as its positional arguments the name/path the + command should be accessible under. + + Usage: + + @webcommand('mycommand') + def mycommand(web, req, tmpl): + pass + """ + + def __init__(self, name): + self.name = name -__all__ = [ - 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev', - 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff', - 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help', -] + def __call__(self, func): + __all__.append(self.name) + commands[self.name] = func + return func + +@webcommand('log') +def log(web, req, tmpl): + """ + /log[/{revision}[/{path}]] + -------------------------- -def log(web, req, tmpl): + Show repository or file history. + + For URLs of the form ``/log/{revision}``, a list of changesets starting at + the specified changeset identifier is shown. If ``{revision}`` is not + defined, the default is ``tip``. This form is equivalent to the + ``changelog`` handler. + + For URLs of the form ``/log/{revision}/{file}``, the history for a specific + file will be shown. This form is equivalent to the ``filelog`` handler. + """ + if 'file' in req.form and req.form['file'][0]: return filelog(web, req, tmpl) else: return changelog(web, req, tmpl) +@webcommand('rawfile') def rawfile(web, req, tmpl): guessmime = web.configbool('web', 'guessmime', False) @@ -98,7 +129,26 @@ rename=webutil.renamelink(fctx), permissions=fctx.manifest().flags(f)) +@webcommand('file') def file(web, req, tmpl): + """ + /file/{revision}[/{path}] + ------------------------- + + Show information about a directory or file in the repository. + + Info about the ``path`` given as a URL parameter will be rendered. + + If ``path`` is a directory, information about the entries in that + directory will be rendered. This form is equivalent to the ``manifest`` + handler. + + If ``path`` is a file, information about that file will be shown via + the ``filerevision`` template. + + If ``path`` is not defined, information about the root directory will + be rendered. + """ path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) if not path: return manifest(web, req, tmpl) @@ -187,7 +237,7 @@ mfunc = revset.match(web.repo.ui, revdef) try: - revs = mfunc(web.repo, revset.baseset(web.repo)) + revs = mfunc(web.repo) return MODE_REVSET, revs # ParseError: wrongly placed tokens, wrongs arguments, etc # RepoLookupError: no such revision, e.g. in 'revision:' @@ -267,7 +317,31 @@ modedesc=searchfunc[1], showforcekw=showforcekw, showunforcekw=showunforcekw) +@webcommand('changelog') def changelog(web, req, tmpl, shortlog=False): + """ + /changelog[/{revision}] + ----------------------- + + Show information about multiple changesets. + + If the optional ``revision`` URL argument is absent, information about + all changesets starting at ``tip`` will be rendered. If the ``revision`` + argument is present, changesets will be shown starting from the specified + revision. + + If ``revision`` is absent, the ``rev`` query string argument may be + defined. This will perform a search for changesets. + + The argument for ``rev`` can be a single revision, a revision set, + or a literal keyword to search for in changeset data (equivalent to + :hg:`log -k`. + + The ``revcount`` query string argument defines the maximum numbers of + changesets to render. + + For non-searches, the ``changelog`` template will be rendered. + """ query = '' if 'node' in req.form: @@ -326,10 +400,36 @@ archives=web.archivelist("tip"), revcount=revcount, morevars=morevars, lessvars=lessvars, query=query) +@webcommand('shortlog') def shortlog(web, req, tmpl): + """ + /shortlog + --------- + + Show basic information about a set of changesets. + + This accepts the same parameters as the ``changelog`` handler. The only + difference is the ``shortlog`` template will be rendered instead of the + ``changelog`` template. + """ return changelog(web, req, tmpl, shortlog=True) +@webcommand('changeset') def changeset(web, req, tmpl): + """ + /changeset[/{revision}] + ----------------------- + + Show information about a single changeset. + + A URL path argument is the changeset identifier to show. See ``hg help + revisions`` for possible values. If not defined, the ``tip`` changeset + will be shown. + + The ``changeset`` template is rendered. Contents of the ``changesettag``, + ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many + templates related to diffs may all be used to produce the output. + """ ctx = webutil.changectx(web.repo, req) basectx = webutil.basechangectx(web.repo, req) if basectx is None: @@ -382,7 +482,7 @@ inbranch=webutil.nodeinbranch(web.repo, ctx), branches=webutil.nodebranchdict(web.repo, ctx)) -rev = changeset +rev = webcommand('rev')(changeset) def decodepath(path): """Hook for mapping a path in the repository to a path in the @@ -392,7 +492,23 @@ the virtual file system presented by the manifest command below.""" return path +@webcommand('manifest') def manifest(web, req, tmpl): + """ + /manifest[/{revision}[/{path}]] + ------------------------------- + + Show information about a directory. + + If the URL path arguments are defined, information about the root + directory for the ``tip`` changeset will be shown. + + Because this handler can only show information for directories, it + is recommended to use the ``file`` handler instead, as it can handle both + directories and files. + + The ``manifest`` template will be rendered for this handler. + """ ctx = webutil.changectx(web.repo, req) path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) mf = ctx.manifest() @@ -474,7 +590,18 @@ inbranch=webutil.nodeinbranch(web.repo, ctx), branches=webutil.nodebranchdict(web.repo, ctx)) +@webcommand('tags') def tags(web, req, tmpl): + """ + /tags + ----- + + Show information about tags. + + No arguments are accepted. + + The ``tags`` template is rendered. + """ i = list(reversed(web.repo.tagslist())) parity = paritygen(web.stripecount) @@ -496,7 +623,18 @@ entriesnotip=lambda **x: entries(True, False, **x), latestentry=lambda **x: entries(True, True, **x)) +@webcommand('bookmarks') def bookmarks(web, req, tmpl): + """ + /bookmarks + ---------- + + Show information about bookmarks. + + No arguments are accepted. + + The ``bookmarks`` template is rendered. + """ i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] parity = paritygen(web.stripecount) @@ -516,7 +654,20 @@ entries=lambda **x: entries(latestonly=False, **x), latestentry=lambda **x: entries(latestonly=True, **x)) +@webcommand('branches') def branches(web, req, tmpl): + """ + /branches + --------- + + Show information about branches. + + All known branches are contained in the output, even closed branches. + + No arguments are accepted. + + The ``branches`` template is rendered. + """ tips = [] heads = web.repo.heads() parity = paritygen(web.stripecount) @@ -547,7 +698,19 @@ entries=lambda **x: entries(0, **x), latestentry=lambda **x: entries(1, **x)) +@webcommand('summary') def summary(web, req, tmpl): + """ + /summary + -------- + + Show a summary of repository state. + + Information about the latest changesets, bookmarks, tags, and branches + is captured by this handler. + + The ``summary`` template is rendered. + """ i = reversed(web.repo.tagslist()) def tagentries(**map): @@ -632,7 +795,19 @@ node=tip.hex(), archives=web.archivelist("tip")) +@webcommand('filediff') def filediff(web, req, tmpl): + """ + /diff/{revision}/{path} + ----------------------- + + Show how a file changed in a particular commit. + + The ``filediff`` template is rendered. + + This hander is registered under both the ``/diff`` and ``/filediff`` + paths. ``/diff`` is used in modern code. + """ fctx, ctx = None, None try: fctx = webutil.filectx(web.repo, req) @@ -672,9 +847,25 @@ child=webutil.children(ctx), diff=diffs) -diff = filediff +diff = webcommand('diff')(filediff) + +@webcommand('comparison') +def comparison(web, req, tmpl): + """ + /comparison/{revision}/{path} + ----------------------------- -def comparison(web, req, tmpl): + Show a comparison between the old and new versions of a file from changes + made on a particular revision. + + This is similar to the ``diff`` handler. However, this form features + a split or side-by-side diff rather than a unified diff. + + The ``context`` query string argument can be used to control the lines of + context in the diff. + + The ``filecomparison`` template is rendered. + """ ctx = webutil.changectx(web.repo, req) if 'file' not in req.form: raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') @@ -732,7 +923,16 @@ rightnode=hex(rightnode), comparison=comparison) +@webcommand('annotate') def annotate(web, req, tmpl): + """ + /annotate/{revision}/{path} + --------------------------- + + Show changeset information for each line in a file. + + The ``fileannotate`` template is rendered. + """ fctx = webutil.filectx(web.repo, req) f = fctx.path() parity = paritygen(web.stripecount) @@ -784,7 +984,19 @@ child=webutil.children(fctx), permissions=fctx.manifest().flags(f)) +@webcommand('filelog') def filelog(web, req, tmpl): + """ + /filelog/{revision}/{path} + -------------------------- + + Show information about the history of a file in the repository. + + The ``revcount`` query string argument can be defined to control the + maximum number of entries to show. + + The ``filelog`` template will be rendered. + """ try: fctx = webutil.filectx(web.repo, req) @@ -862,7 +1074,27 @@ latestentry=latestentry, revcount=revcount, morevars=morevars, lessvars=lessvars) +@webcommand('archive') def archive(web, req, tmpl): + """ + /archive/{revision}.{format}[/{path}] + ------------------------------------- + + Obtain an archive of repository content. + + The content and type of the archive is defined by a URL path parameter. + ``format`` is the file extension of the archive type to be generated. e.g. + ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your + server configuration. + + The optional ``path`` URL parameter controls content to include in the + archive. If omitted, every file in the specified revision is present in the + archive. If included, only the specified file or contents of the specified + directory will be included in the archive. + + No template is used for this handler. Raw, binary content is generated. + """ + type_ = req.form.get('type', [None])[0] allowed = web.configlist("web", "allow_archive") key = req.form['node'][0] @@ -911,6 +1143,7 @@ return [] +@webcommand('static') def static(web, req, tmpl): fname = req.form['file'][0] # a repo owner may set web.static in .hg/hgrc to get any file @@ -924,7 +1157,24 @@ staticfile(static, fname, req) return [] +@webcommand('graph') def graph(web, req, tmpl): + """ + /graph[/{revision}] + ------------------- + + Show information about the graphical topology of the repository. + + Information rendered by this handler can be used to create visual + representations of repository topology. + + The ``revision`` URL parameter controls the starting changeset. + + The ``revcount`` query string argument can define the number of changesets + to show information for. + + This handler will render the ``graph`` template. + """ ctx = webutil.changectx(web.repo, req) rev = ctx.rev() @@ -1047,8 +1297,23 @@ doc = _('(no help text available)') return doc +@webcommand('help') def help(web, req, tmpl): + """ + /help[/{topic}] + --------------- + + Render help documentation. + + This web command is roughly equivalent to :hg:`help`. If a ``topic`` + is defined, that help topic will be rendered. If not, an index of + available help topics will be rendered. + + The ``help`` template will be rendered when requesting help for a topic. + ``helptopics`` will be rendered for the index of help topics. + """ from mercurial import commands # avoid cycle + from mercurial import help as helpmod # avoid cycle topicname = req.form.get('node', [None])[0] if not topicname: diff -r aae338f9da70 -r 4e865115566e mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/hgweb/webutil.py Mon Mar 02 10:55:19 2015 -0600 @@ -138,9 +138,10 @@ yield d def parents(ctx, hide=None): - if (isinstance(ctx, context.basefilectx) and - ctx.changectx().rev() != ctx.linkrev()): - return _siblings([ctx._repo[ctx.linkrev()]], hide) + if isinstance(ctx, context.basefilectx): + introrev = ctx.introrev() + if ctx.changectx().rev() != introrev: + return _siblings([ctx._repo[introrev]], hide) return _siblings(ctx.parents(), hide) def children(ctx, hide=None): diff -r aae338f9da70 -r 4e865115566e mercurial/localrepo.py --- a/mercurial/localrepo.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/localrepo.py Mon Mar 02 10:55:19 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): diff -r aae338f9da70 -r 4e865115566e mercurial/manifest.py --- a/mercurial/manifest.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/manifest.py Mon Mar 02 10:55:19 2015 -0600 @@ -10,13 +10,8 @@ import array, struct class manifestdict(dict): - def __init__(self, mapping=None, flags=None): - if mapping is None: - mapping = {} - if flags is None: - flags = {} - dict.__init__(self, mapping) - self._flags = flags + def __init__(self): + self._flags = {} def __setitem__(self, k, v): assert v is not None dict.__setitem__(self, k, v) @@ -26,7 +21,10 @@ """Set the flags (symlink, executable) for path f.""" self._flags[f] = flags def copy(self): - return manifestdict(self, dict.copy(self._flags)) + copy = manifestdict() + dict.__init__(copy, self) + copy._flags = dict.copy(self._flags) + return copy def intersectfiles(self, files): '''make a new manifestdict with the intersection of self with files @@ -50,11 +48,11 @@ (not match.anypats() and util.all(fn in self for fn in files))): return self.intersectfiles(files) - mf = self.copy() - for fn in mf.keys(): + m = self.copy() + for fn in m.keys(): if not match(fn): - del mf[fn] - return mf + del m[fn] + return m def diff(self, m2, clean=False): '''Finds changes between the current manifest and m2. @@ -220,9 +218,14 @@ class manifest(revlog.revlog): def __init__(self, opener): - # we expect to deal with not more than four revs at a time, - # during a commit --amend - self._mancache = util.lrucachedict(4) + # During normal operations, we expect to deal with not more than four + # revs at a time (such as during commit --amend). When rebasing large + # stacks of commits, the number can go up, hence the config knob below. + cachesize = 4 + opts = getattr(opener, 'options', None) + if opts is not None: + cachesize = opts.get('manifestcachesize', cachesize) + self._mancache = util.lrucachedict(cachesize) revlog.revlog.__init__(self, opener, "00manifest.i") def readdelta(self, node): @@ -244,16 +247,16 @@ return self._mancache[node][0] text = self.revision(node) arraytext = array.array('c', text) - mapping = _parse(text) - self._mancache[node] = (mapping, arraytext) - return mapping + m = _parse(text) + self._mancache[node] = (m, arraytext) + return m def find(self, node, f): '''look up entry for a single file efficiently. return (node, flags) pair if found, (None, None) if not.''' if node in self._mancache: - mapping = self._mancache[node][0] - return mapping.get(f), mapping.flags(f) + m = self._mancache[node][0] + return m.get(f), m.flags(f) text = self.revision(node) start, end = _msearch(text, f) if start == end: @@ -262,7 +265,7 @@ f, n = l.split('\0') return revlog.bin(n[:40]), n[40:-1] - def add(self, map, transaction, link, p1, p2, added, removed): + def add(self, m, transaction, link, p1, p2, added, removed): if p1 in self._mancache: # If our first parent is in the manifest cache, we can # compute a delta here using properties we know about the @@ -277,7 +280,7 @@ # since the lists are already sorted work.sort() - arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work) + arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work) cachedelta = self.rev(p1), deltatext text = util.buffer(arraytext) else: @@ -285,11 +288,11 @@ # just encode a fulltext of the manifest and pass that # through to the revlog layer, and let it handle the delta # process. - text = map.text() + text = m.text() arraytext = array.array('c', text) cachedelta = None n = self.addrevision(text, transaction, link, p1, p2, cachedelta) - self._mancache[n] = (map, arraytext) + self._mancache[n] = (m, arraytext) return n diff -r aae338f9da70 -r 4e865115566e mercurial/mdiff.py --- a/mercurial/mdiff.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/mdiff.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/obsolete.py --- a/mercurial/obsolete.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/obsolete.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/parsers.c --- a/mercurial/parsers.c Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/parsers.c Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/patch.py --- a/mercurial/patch.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/patch.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/revlog.py --- a/mercurial/revlog.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/revlog.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/revset.py --- a/mercurial/revset.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/revset.py Mon Mar 02 10:55:19 2015 -0600 @@ -349,7 +349,7 @@ return r & subset def dagrange(repo, subset, x, y): - r = spanset(repo) + r = fullreposet(repo) xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y)) return xs & subset @@ -396,7 +396,7 @@ """ # i18n: "ancestor" is a keyword l = getlist(x) - rl = spanset(repo) + rl = fullreposet(repo) anc = None # (getset(repo, rl, i) for i in l) generates a list of lists @@ -412,7 +412,7 @@ return baseset() def _ancestors(repo, subset, x, followfirst=False): - heads = getset(repo, spanset(repo), x) + heads = getset(repo, fullreposet(repo), x) if not heads: return baseset() s = _revancestors(repo, heads, followfirst) @@ -544,7 +544,7 @@ else: return subset.filter(lambda r: matcher(getbi(ucl, r)[0])) - s = getset(repo, spanset(repo), x) + s = getset(repo, fullreposet(repo), x) b = set() for r in s: b.add(getbi(ucl, r)[0]) @@ -708,7 +708,7 @@ return subset.filter(matches) def _descendants(repo, subset, x, followfirst=False): - roots = getset(repo, spanset(repo), x) + roots = getset(repo, fullreposet(repo), x) if not roots: return baseset() s = _revdescendants(repo, roots, followfirst) @@ -744,9 +744,9 @@ is the same as passing all(). """ if x is not None: - sources = getset(repo, spanset(repo), x) + sources = getset(repo, fullreposet(repo), x) else: - sources = getall(repo, spanset(repo), x) + sources = getall(repo, fullreposet(repo), x) dests = set() @@ -1145,7 +1145,7 @@ # i18n: "limit" is a keyword raise error.ParseError(_("limit expects a number")) ss = subset - os = getset(repo, spanset(repo), l[0]) + os = getset(repo, fullreposet(repo), l[0]) result = [] it = iter(os) for x in xrange(lim): @@ -1172,7 +1172,7 @@ # i18n: "last" is a keyword raise error.ParseError(_("last expects a number")) ss = subset - os = getset(repo, spanset(repo), l[0]) + os = getset(repo, fullreposet(repo), l[0]) os.reverse() result = [] it = iter(os) @@ -1189,7 +1189,7 @@ """``max(set)`` Changeset with highest revision number in set. """ - os = getset(repo, spanset(repo), x) + os = getset(repo, fullreposet(repo), x) if os: m = os.max() if m in subset: @@ -1226,7 +1226,7 @@ """``min(set)`` Changeset with lowest revision number in set. """ - os = getset(repo, spanset(repo), x) + os = getset(repo, fullreposet(repo), x) if os: m = os.min() if m in subset: @@ -1322,7 +1322,7 @@ cl = repo.changelog # i18n: "only" is a keyword args = getargs(x, 1, 2, _('only takes one or two arguments')) - include = getset(repo, spanset(repo), args[0]) + include = getset(repo, fullreposet(repo), args[0]) if len(args) == 1: if not include: return baseset() @@ -1331,7 +1331,7 @@ exclude = [rev for rev in cl.headrevs() if not rev in descendants and not rev in include] else: - exclude = getset(repo, spanset(repo), args[1]) + exclude = getset(repo, fullreposet(repo), args[1]) results = set(cl.findmissingrevs(common=exclude, heads=include)) return subset & results @@ -1345,9 +1345,9 @@ for the first operation is selected. """ if x is not None: - dests = getset(repo, spanset(repo), x) + dests = getset(repo, fullreposet(repo), x) else: - dests = getall(repo, spanset(repo), x) + dests = getall(repo, fullreposet(repo), x) def _firstsrc(rev): src = _getrevsource(repo, rev) @@ -1400,7 +1400,7 @@ ps = set() cl = repo.changelog - for r in getset(repo, spanset(repo), x): + for r in getset(repo, fullreposet(repo), x): ps.add(cl.parentrevs(r)[0]) ps -= set([node.nullrev]) return subset & ps @@ -1421,7 +1421,7 @@ ps = set() cl = repo.changelog - for r in getset(repo, spanset(repo), x): + for r in getset(repo, fullreposet(repo), x): ps.add(cl.parentrevs(r)[1]) ps -= set([node.nullrev]) return subset & ps @@ -1435,7 +1435,7 @@ else: ps = set() cl = repo.changelog - for r in getset(repo, spanset(repo), x): + for r in getset(repo, fullreposet(repo), x): ps.update(cl.parentrevs(r)) ps -= set([node.nullrev]) return subset & ps @@ -1548,7 +1548,7 @@ except (TypeError, ValueError): # i18n: "rev" is a keyword raise error.ParseError(_("rev expects a number")) - if l not in fullreposet(repo) and l != node.nullrev: + if l not in repo.changelog and l != node.nullrev: return baseset() return subset & baseset([l]) @@ -1676,7 +1676,7 @@ """``roots(set)`` Changesets in set with no parent changeset in set. """ - s = getset(repo, spanset(repo), x) + s = getset(repo, fullreposet(repo), x) subset = baseset([r for r in s if r in subset]) cs = _children(repo, subset, s) return subset - cs @@ -2243,6 +2243,71 @@ except error.ParseError, inst: return (decl, None, None, parseerrordetail(inst)) +def _parsealiasdefn(defn, args): + """Parse alias definition ``defn`` + + This function also replaces alias argument references in the + specified definition by ``_aliasarg(ARGNAME)``. + + ``args`` is a list of alias argument names, or None if the alias + is declared as a symbol. + + This returns "tree" as parsing result. + + >>> args = ['$1', '$2', 'foo'] + >>> print prettyformat(_parsealiasdefn('$1 or foo', args)) + (or + (func + ('symbol', '_aliasarg') + ('string', '$1')) + (func + ('symbol', '_aliasarg') + ('string', 'foo'))) + >>> try: + ... _parsealiasdefn('$1 or $bar', args) + ... except error.ParseError, inst: + ... print parseerrordetail(inst) + at 6: '$' not for alias arguments + >>> args = ['$1', '$10', 'foo'] + >>> print prettyformat(_parsealiasdefn('$10 or foobar', args)) + (or + (func + ('symbol', '_aliasarg') + ('string', '$10')) + ('symbol', 'foobar')) + >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args)) + (or + ('string', '$1') + ('string', 'foo')) + """ + def tokenizedefn(program, lookup=None): + if args: + argset = set(args) + else: + argset = set() + + for t, value, pos in _tokenizealias(program, lookup=lookup): + if t == 'symbol': + if value in argset: + # emulate tokenization of "_aliasarg('ARGNAME')": + # "_aliasarg()" is an unknown symbol only used separate + # alias argument placeholders from regular strings. + yield ('symbol', '_aliasarg', pos) + yield ('(', None, pos) + yield ('string', value, pos) + yield (')', None, pos) + continue + elif value.startswith('$'): + raise error.ParseError(_("'$' not for alias arguments"), + pos) + yield (t, value, pos) + + p = parser.parser(tokenizedefn, elements) + tree, pos = p.parse(defn) + if pos != len(defn): + raise error.ParseError(_('invalid token'), pos) + return tree + class revsetalias(object): # whether own `error` information is already shown or not. # this avoids showing same warning multiple times at each `findaliases`. @@ -2260,16 +2325,8 @@ ' "%s": %s') % (self.name, self.error) return - if self.args: - for arg in self.args: - # _aliasarg() is an unknown symbol only used separate - # alias argument placeholders from regular strings. - value = value.replace(arg, '_aliasarg(%r)' % (arg,)) - try: - self.replacement, pos = parse(value) - if pos != len(value): - raise error.ParseError(_('invalid token'), pos) + self.replacement = _parsealiasdefn(value, self.args) # Check for placeholder injection _checkaliasarg(self.replacement, self.args) except error.ParseError, inst: @@ -2392,7 +2449,9 @@ tree = findaliases(ui, tree, showwarning=ui.warn) tree = foldconcat(tree) weight, tree = optimize(tree, True) - def mfunc(repo, subset): + def mfunc(repo, subset=None): + if subset is None: + subset = fullreposet(repo) if util.safehasattr(subset, 'isascending'): result = getset(repo, subset, tree) else: @@ -3146,18 +3205,7 @@ return it().next() return None -def spanset(repo, start=None, end=None): - """factory function to dispatch between fullreposet and actual spanset - - Feel free to update all spanset call sites and kill this function at some - point. - """ - if start is None and end is None: - return fullreposet(repo) - return _spanset(repo, start, end) - - -class _spanset(abstractsmartset): +class spanset(abstractsmartset): """Duck type for baseset class which represents a range of revisions and can work lazily and without having all the range in memory @@ -3261,7 +3309,7 @@ return x return None -class fullreposet(_spanset): +class fullreposet(spanset): """a set containing all revisions in the repo This class exists to host special optimization. diff -r aae338f9da70 -r 4e865115566e mercurial/scmutil.py --- a/mercurial/scmutil.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/scmutil.py Mon Mar 02 10:55:19 2015 -0600 @@ -672,11 +672,11 @@ # fall through to new-style queries if old-style fails m = revset.match(repo.ui, spec, repo) if seen or l: - dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen] + dl = [r for r in m(repo) if r not in seen] l = l + revset.baseset(dl) seen.update(dl) else: - l = m(repo, revset.spanset(repo)) + l = m(repo) return l diff -r aae338f9da70 -r 4e865115566e mercurial/subrepo.py --- a/mercurial/subrepo.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/subrepo.py Mon Mar 02 10:55:19 2015 -0600 @@ -626,6 +626,7 @@ os.path.join(prefix, self._path), explicitonly, **opts) + @annotatesubrepoerror def addremove(self, m, prefix, opts, dry_run, similarity): # In the same way as sub directories are processed, once in a subrepo, # always entry any of its subrepos. Don't corrupt the options that will @@ -877,13 +878,11 @@ opts['date'] = None opts['rev'] = substate[1] - pats = [] - if not opts.get('all'): - pats = ['set:modified()'] self.filerevert(*pats, **opts) # Update the repo to the revision specified in the given substate - self.get(substate, overwrite=True) + if not opts.get('dry_run'): + self.get(substate, overwrite=True) def filerevert(self, *pats, **opts): ctx = self._repo[opts['rev']] @@ -1577,6 +1576,25 @@ @annotatesubrepoerror + def cat(self, match, prefix, **opts): + rev = self._state[1] + if match.anypats(): + return 1 #No support for include/exclude yet + + if not match.files(): + return 1 + + for f in match.files(): + output = self._gitcommand(["show", "%s:%s" % (rev, f)]) + fp = cmdutil.makefileobj(self._subparent, opts.get('output'), + self._ctx.node(), + pathname=os.path.join(prefix, f)) + fp.write(output) + fp.close() + return 0 + + + @annotatesubrepoerror def status(self, rev2, **opts): rev1 = self._state[1] if self._gitmissing() or not rev1: @@ -1673,7 +1691,8 @@ util.rename(os.path.join(self._abspath, name), os.path.join(self._abspath, bakname)) - self.get(substate, overwrite=True) + if not opts.get('dry_run'): + self.get(substate, overwrite=True) return [] def shortid(self, revid): diff -r aae338f9da70 -r 4e865115566e mercurial/tags.py --- a/mercurial/tags.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/tags.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/templater.py --- a/mercurial/templater.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templater.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/templates/gitweb/map --- a/mercurial/templates/gitweb/map Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/gitweb/map Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/bookmarks.tmpl --- a/mercurial/templates/monoblue/bookmarks.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/bookmarks.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/branches.tmpl --- a/mercurial/templates/monoblue/branches.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/branches.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/changelog.tmpl --- a/mercurial/templates/monoblue/changelog.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/changelog.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -27,7 +27,7 @@
  • branches
  • files
  • {archives%archiveentry} -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/graph.tmpl --- a/mercurial/templates/monoblue/graph.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/graph.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -27,7 +27,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/help.tmpl --- a/mercurial/templates/monoblue/help.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/help.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/helptopics.tmpl --- a/mercurial/templates/monoblue/helptopics.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/helptopics.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/manifest.tmpl --- a/mercurial/templates/monoblue/manifest.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/manifest.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/map --- a/mercurial/templates/monoblue/map Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/map Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/shortlog.tmpl --- a/mercurial/templates/monoblue/shortlog.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/shortlog.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,8 +26,8 @@
  • bookmarks
  • branches
  • files
  • - {archives%archiveentry} -
  • help
  • + {archives%archiveentry} +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/summary.tmpl --- a/mercurial/templates/monoblue/summary.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/summary.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/monoblue/tags.tmpl --- a/mercurial/templates/monoblue/tags.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/monoblue/tags.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -26,7 +26,7 @@
  • bookmarks
  • branches
  • files
  • -
  • help
  • +
  • help
  • diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/bookmarks.tmpl --- a/mercurial/templates/paper/bookmarks.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/bookmarks.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -23,7 +23,6 @@ -

    diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/fileannotate.tmpl --- a/mercurial/templates/paper/fileannotate.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/fileannotate.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -68,10 +68,12 @@
    + + {annotate%annotateline} diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/filelog.tmpl --- a/mercurial/templates/paper/filelog.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/filelog.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -35,7 +35,6 @@ -

    rev   line source
    + + {entries%filelogentry} diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/graph.tmpl --- a/mercurial/templates/paper/graph.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/graph.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -28,7 +28,6 @@ -

    age author description
    + @@ -20,6 +21,7 @@ + {entries%indexentry} diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/manifest.tmpl --- a/mercurial/templates/paper/manifest.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/manifest.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -39,11 +39,13 @@
    Name Description   
    + + diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/search.tmpl --- a/mercurial/templates/paper/search.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/search.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -43,11 +43,13 @@
    name size permissions
    [up]
    + + {entries} diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/shortlog.tmpl --- a/mercurial/templates/paper/shortlog.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/shortlog.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -30,7 +30,6 @@ -

    age author description
    + + {entries%shortlogentry} diff -r aae338f9da70 -r 4e865115566e mercurial/templates/paper/tags.tmpl --- a/mercurial/templates/paper/tags.tmpl Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/paper/tags.tmpl Mon Mar 02 10:55:19 2015 -0600 @@ -23,7 +23,6 @@ -

    age author description
    + + {entries%tagentry} diff -r aae338f9da70 -r 4e865115566e mercurial/templates/static/style-paper.css --- a/mercurial/templates/static/style-paper.css Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/templates/static/style-paper.css Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/unionrepo.py --- a/mercurial/unionrepo.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/unionrepo.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/util.h --- a/mercurial/util.h Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/util.h Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e mercurial/windows.py --- a/mercurial/windows.py Mon Mar 02 10:29:45 2015 -0600 +++ b/mercurial/windows.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/run-tests.py --- a/tests/run-tests.py Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/run-tests.py Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-bundle-type.t --- a/tests/test-bundle-type.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-bundle-type.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-churn.t --- a/tests/test-churn.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-churn.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-completion.t --- a/tests/test-completion.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-completion.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-extension.t --- a/tests/test-extension.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-extension.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-gendoc.t --- a/tests/test-gendoc.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-gendoc.t Mon Mar 02 10:55:19 2015 -0600 @@ -1,4 +1,5 @@ #require docutils +#require gettext Test document extraction diff -r aae338f9da70 -r 4e865115566e tests/test-glog.t --- a/tests/test-glog.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-glog.t Mon Mar 02 10:55:19 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 diff -r aae338f9da70 -r 4e865115566e tests/test-grep.t --- a/tests/test-grep.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-grep.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-help.t --- a/tests/test-help.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-help.t Mon Mar 02 10:55:19 2015 -0600 @@ -1101,6 +1101,125 @@ abort: help section not found [255] +Test dynamic list of merge tools only shows up once + $ hg help merge-tools + Merge Tools + """"""""""" + + To merge files Mercurial uses merge tools. + + A merge tool combines two different versions of a file into a merged file. + Merge tools are given the two files and the greatest common ancestor of + the two file versions, so they can determine the changes made on both + branches. + + Merge tools are used both for "hg resolve", "hg merge", "hg update", "hg + backout" and in several extensions. + + Usually, the merge tool tries to automatically reconcile the files by + combining all non-overlapping changes that occurred separately in the two + different evolutions of the same initial base file. Furthermore, some + interactive merge programs make it easier to manually resolve conflicting + merges, either in a graphical way, or by inserting some conflict markers. + Mercurial does not include any interactive merge programs but relies on + external tools for that. + + Available merge tools + ===================== + + External merge tools and their properties are configured in the merge- + tools configuration section - see hgrc(5) - but they can often just be + named by their executable. + + A merge tool is generally usable if its executable can be found on the + system and if it can handle the merge. The executable is found if it is an + absolute or relative executable path or the name of an application in the + executable search path. The tool is assumed to be able to handle the merge + if it can handle symlinks if the file is a symlink, if it can handle + binary files if the file is binary, and if a GUI is available if the tool + requires a GUI. + + There are some internal merge tools which can be used. The internal merge + tools are: + + ":dump" + Creates three versions of the files to merge, containing the contents of + local, other and base. These files can then be used to perform a merge + manually. If the file to be merged is named "a.txt", these files will + accordingly be named "a.txt.local", "a.txt.other" and "a.txt.base" and + they will be placed in the same directory as "a.txt". + + ":fail" + Rather than attempting to merge files that were modified on both + branches, it marks them as unresolved. The resolve command must be used + to resolve these conflicts. + + ":local" + Uses the local version of files as the merged version. + + ":merge" + Uses the internal non-interactive simple merge algorithm for merging + files. It will fail if there are any conflicts and leave markers in the + partially merged file. Markers will have two sections, one for each side + of merge. + + ":merge3" + Uses the internal non-interactive simple merge algorithm for merging + files. It will fail if there are any conflicts and leave markers in the + partially merged file. Marker will have three sections, one from each + side of the merge and one for the base content. + + ":other" + Uses the other version of files as the merged version. + + ":prompt" + Asks the user which of the local or the other version to keep as the + merged version. + + ":tagmerge" + Uses the internal tag merge algorithm (experimental). + + Internal tools are always available and do not require a GUI but will by + default not handle symlinks or binary files. + + Choosing a merge tool + ===================== + + Mercurial uses these rules when deciding which merge tool to use: + + 1. If a tool has been specified with the --tool option to merge or + resolve, it is used. If it is the name of a tool in the merge-tools + configuration, its configuration is used. Otherwise the specified tool + must be executable by the shell. + 2. If the "HGMERGE" environment variable is present, its value is used and + must be executable by the shell. + 3. If the filename of the file to be merged matches any of the patterns in + the merge-patterns configuration section, the first usable merge tool + corresponding to a matching pattern is used. Here, binary capabilities + of the merge tool are not considered. + 4. If ui.merge is set it will be considered next. If the value is not the + name of a configured tool, the specified value is used and must be + executable by the shell. Otherwise the named tool is used if it is + usable. + 5. If any usable merge tools are present in the merge-tools configuration + section, the one with the highest priority is used. + 6. If a program named "hgmerge" can be found on the system, it is used - + but it will by default not be used for symlinks and binary files. + 7. If the file to be merged is not binary and is not a symlink, then + internal ":merge" is used. + 8. The merge of the file fails and must be resolved before commit. + + Note: + After selecting a merge program, Mercurial will by default attempt to + merge the files using a simple merge algorithm first. Only if it + doesn't succeed because of conflicting changes Mercurial will actually + execute the merge program. Whether to use the simple merge algorithm + first can be controlled by the premerge setting of the merge tool. + Premerge is enabled by default unless the file is binary or a symlink. + + See the merge-tools and ui sections of hgrc(5) for details on the + configuration of merge tools. + Test usage of section marks in help documents $ cd "$TESTDIR"/../doc diff -r aae338f9da70 -r 4e865115566e tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb-commands.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-hgweb-descend-empties.t --- a/tests/test-hgweb-descend-empties.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb-descend-empties.t Mon Mar 02 10:55:19 2015 -0600 @@ -81,11 +81,13 @@
    age author description
    Thu, 01 Jan 1970 00:00:00 +0000
    + + diff -r aae338f9da70 -r 4e865115566e tests/test-hgweb-diffs.t --- a/tests/test-hgweb-diffs.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb-diffs.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-hgweb-empty.t --- a/tests/test-hgweb-empty.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb-empty.t Mon Mar 02 10:55:19 2015 -0600 @@ -48,7 +48,6 @@ -

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

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

    age author description
    + + diff -r aae338f9da70 -r 4e865115566e tests/test-hgweb-filelog.t --- a/tests/test-hgweb-filelog.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb-filelog.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-hgweb-removed.t --- a/tests/test-hgweb-removed.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb-removed.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-hgweb.t --- a/tests/test-hgweb.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgweb.t Mon Mar 02 10:55:19 2015 -0600 @@ -272,11 +272,13 @@ + + diff -r aae338f9da70 -r 4e865115566e tests/test-hgwebdir.t --- a/tests/test-hgwebdir.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-hgwebdir.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-highlight.t --- a/tests/test-highlight.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-highlight.t Mon Mar 02 10:55:19 2015 -0600 @@ -268,10 +268,12 @@
    Name Description   
    + + diff -r aae338f9da70 -r 4e865115566e tests/test-histedit-arguments.t --- a/tests/test-histedit-arguments.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-histedit-arguments.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-histedit-drop.t --- a/tests/test-histedit-drop.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-histedit-drop.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-histedit-edit.t --- a/tests/test-histedit-edit.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-histedit-edit.t Mon Mar 02 10:55:19 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: unable to strip 652413bf663ef2a641cab26574e46d5f5a64a55a. Nodes are used by history edit in progress. + [255] + commit, then edit the revision $ hg ci -m 'wat' created new head diff -r aae338f9da70 -r 4e865115566e tests/test-https.t --- a/tests/test-https.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-https.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-largefiles-misc.t --- a/tests/test-largefiles-misc.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-largefiles-misc.t Mon Mar 02 10:55:19 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 diff -r aae338f9da70 -r 4e865115566e tests/test-largefiles.t --- a/tests/test-largefiles.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-largefiles.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-log.t --- a/tests/test-log.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-log.t Mon Mar 02 10:55:19 2015 -0600 @@ -672,10 +672,17 @@ +log -f with null parent + + $ hg up -C null + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg log -f + + log -r . with two parents $ hg up -C 3 - 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r aae338f9da70 -r 4e865115566e tests/test-merge-tools.t --- a/tests/test-merge-tools.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-merge-tools.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-obsolete-tag-cache.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-obsolete-tag-cache.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-obsolete.t --- a/tests/test-obsolete.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-obsolete.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-rename.t --- a/tests/test-rename.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-rename.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-resolve.t --- a/tests/test-resolve.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-resolve.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-revset.t --- a/tests/test-revset.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-revset.t Mon Mar 02 10:55:19 2015 -0600 @@ -1029,6 +1029,19 @@ ('symbol', 'tip') warning: failed to parse the declaration of revset alias "bad name": at 4: invalid token 9 + $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc + $ try 'strictreplacing("foo", tip)' + (func + ('symbol', 'strictreplacing') + (list + ('string', 'foo') + ('symbol', 'tip'))) + (or + ('symbol', 'tip') + (func + ('symbol', 'desc') + ('string', '$1'))) + 9 $ try 'd(2:5)' (func diff -r aae338f9da70 -r 4e865115566e tests/test-ssh.t --- a/tests/test-ssh.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-ssh.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-status-color.t --- a/tests/test-status-color.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-status-color.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-strip.t --- a/tests/test-strip.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-strip.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-subrepo-deep-nested-change.t --- a/tests/test-subrepo-deep-nested-change.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-subrepo-deep-nested-change.t Mon Mar 02 10:55:19 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 aae338f9da70 -r 4e865115566e tests/test-subrepo-git.t --- a/tests/test-subrepo-git.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-subrepo-git.t Mon Mar 02 10:55:19 2015 -0600 @@ -802,4 +802,52 @@ $ hg status --subrepos ? s/barfoo +show file at specific revision + $ cat > s/foobar << EOF + > woop woop + > fooo bar + > EOF + $ hg commit --subrepos -m "updated foobar" + committing subrepository s + $ cat > s/foobar << EOF + > current foobar + > (should not be visible using hg cat) + > EOF + + $ hg cat -r . s/foobar + woop woop + fooo bar (no-eol) + $ hg cat -r "parents(.)" s/foobar > catparents + + $ mkdir -p tmp/s + + $ hg cat -r "parents(.)" --output tmp/%% s/foobar + $ diff tmp/% catparents + + $ hg cat -r "parents(.)" --output tmp/%s s/foobar + $ diff tmp/foobar catparents + + $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar + $ diff tmp/s/otherfoobar catparents + + $ hg cat -r "parents(.)" --output tmp/%p s/foobar + $ diff tmp/s/foobar catparents + + $ hg cat -r "parents(.)" --output tmp/%H s/foobar + $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents + + $ hg cat -r "parents(.)" --output tmp/%R s/foobar + $ diff tmp/10 catparents + + $ hg cat -r "parents(.)" --output tmp/%h s/foobar + $ diff tmp/255ee8cf690e catparents + + $ rm tmp/10 + $ hg cat -r "parents(.)" --output tmp/%r s/foobar + $ diff tmp/10 catparents + + $ mkdir tmp/tc + $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar + $ diff tmp/tc/foobar catparents + $ cd .. diff -r aae338f9da70 -r 4e865115566e tests/test-subrepo.t --- a/tests/test-subrepo.t Mon Mar 02 10:29:45 2015 -0600 +++ b/tests/test-subrepo.t Mon Mar 02 10:55:19 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