# HG changeset patch # User Matt Mackall # Date 1379533936 18000 # Node ID d4ed26beae0e41c86c413c69c1e6f13c32eb3d33 # Parent da3808bcfbfa9467b2dee82ba51f398f1c371fac# Parent f2871c30e6a7ded2382bc4b48c02c10a04d712f8 merge with stable diff -r f2871c30e6a7 -r d4ed26beae0e contrib/check-code.py --- a/contrib/check-code.py Wed Sep 18 14:40:17 2013 -0400 +++ b/contrib/check-code.py Wed Sep 18 14:52:16 2013 -0500 @@ -61,11 +61,13 @@ (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"), (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"), (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"), + (r'(?'"), (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"), (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"), @@ -285,8 +287,9 @@ (r'(while|if|do|for)\(', "use space after while/if/do/for"), (r'return\(', "return is not a function"), (r' ;', "no space before ;"), + (r'[)][{]', "space between ) and {"), (r'\w+\* \w+', "use int *foo, not int* foo"), - (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"), + (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"), (r'\w+ (\+\+|--)', "use foo++, not foo ++"), (r'\w,\w', "missing whitespace after ,"), (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), @@ -324,7 +327,7 @@ checks = [ ('python', r'.*\.(py|cgi)$', pyfilters, pypats), ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats), - ('c', r'.*\.c$', cfilters, cpats), + ('c', r'.*\.[ch]$', cfilters, cpats), ('unified test', r'.*\.t$', utestfilters, utestpats), ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters, inrevlogpats), diff -r f2871c30e6a7 -r d4ed26beae0e contrib/perf.py --- a/contrib/perf.py Wed Sep 18 14:40:17 2013 -0400 +++ b/contrib/perf.py Wed Sep 18 14:52:16 2013 -0500 @@ -171,13 +171,14 @@ copies.pathcopies(ctx1, ctx2) timer(d) -@command('perfmanifest') -def perfmanifest(ui, repo): +@command('perfmanifest', [], 'REV') +def perfmanifest(ui, repo, rev): + ctx = scmutil.revsingle(repo, rev, rev) + t = ctx.manifestnode() def d(): - t = repo.manifest.tip() + repo.manifest._mancache.clear() + repo.manifest._cache = None repo.manifest.read(t) - repo.manifest.mapcache = None - repo.manifest._cache = None timer(d) @command('perfchangeset') diff -r f2871c30e6a7 -r d4ed26beae0e contrib/plan9/9mail --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/plan9/9mail Wed Sep 18 14:52:16 2013 -0500 @@ -0,0 +1,26 @@ +#!/bin/rc +# 9mail - Mercurial email wrapper for upas/marshal + +fn usage { + echo >[1=2] usage: mercurial/9mail -f from to [cc] + exit usage +} + +from=() +cc=() +to=() + +switch($1){ +case -f + from=$2 +case * + usage +} + +to=($3) +if(~ $#* 4) + cc=(-C $4) + +upasname=$from +upas/marshal $cc $to + diff -r f2871c30e6a7 -r d4ed26beae0e contrib/plan9/hgrc.d/9diff.rc --- a/contrib/plan9/hgrc.d/9diff.rc Wed Sep 18 14:40:17 2013 -0400 +++ b/contrib/plan9/hgrc.d/9diff.rc Wed Sep 18 14:52:16 2013 -0500 @@ -4,4 +4,4 @@ extdiff = [extdiff] -9diff = 9diff -cm $parent $child $root +9diff = /bin/mercurial/9diff -cm $parent $child $root diff -r f2871c30e6a7 -r d4ed26beae0e contrib/plan9/hgrc.d/9mail.rc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/plan9/hgrc.d/9mail.rc Wed Sep 18 14:52:16 2013 -0500 @@ -0,0 +1,4 @@ +# The 9mail to support patchbomb and other email wrappers +[email] +method = /bin/mercurial/9mail + diff -r f2871c30e6a7 -r d4ed26beae0e hgext/factotum.py --- a/hgext/factotum.py Wed Sep 18 14:40:17 2013 -0400 +++ b/hgext/factotum.py Wed Sep 18 14:52:16 2013 -0500 @@ -101,7 +101,7 @@ user, passwd = auth.get('username'), auth.get('password') if not user or not passwd: if not prefix: - prefix = '*' + prefix = realm.split(' ')[0].lower() params = 'service=%s prefix=%s' % (_service, prefix) if user: params = '%s user=%s' % (params, user) diff -r f2871c30e6a7 -r d4ed26beae0e hgext/histedit.py --- a/hgext/histedit.py Wed Sep 18 14:40:17 2013 -0400 +++ b/hgext/histedit.py Wed Sep 18 14:52:16 2013 -0500 @@ -419,10 +419,6 @@ if revs: revs = [repo.lookup(rev) for rev in revs] - # hexlify nodes from outgoing, because we're going to parse - # parent[0] using revsingle below, and if the binary hash - # contains special revset characters like ":" the revset - # parser can choke. outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force) if not outgoing.missing: raise util.Abort(_('no outgoing ancestors')) diff -r f2871c30e6a7 -r d4ed26beae0e hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py Wed Sep 18 14:40:17 2013 -0400 +++ b/hgext/largefiles/reposetup.py Wed Sep 18 14:52:16 2013 -0500 @@ -10,8 +10,7 @@ import copy import os -from mercurial import context, error, manifest, match as match_, util, \ - discovery +from mercurial import error, manifest, match as match_, util, discovery from mercurial import node as node_ from mercurial.i18n import _ from mercurial import localrepo @@ -92,14 +91,8 @@ else: # some calls in this function rely on the old version of status self.lfstatus = False - if isinstance(node1, context.changectx): - ctx1 = node1 - else: - ctx1 = self[node1] - if isinstance(node2, context.changectx): - ctx2 = node2 - else: - ctx2 = self[node2] + ctx1 = self[node1] + ctx2 = self[node2] working = ctx2.rev() is None parentworking = working and ctx1 == self['.'] diff -r f2871c30e6a7 -r d4ed26beae0e hgext/mq.py --- a/hgext/mq.py Wed Sep 18 14:40:17 2013 -0400 +++ b/hgext/mq.py Wed Sep 18 14:52:16 2013 -0500 @@ -66,6 +66,7 @@ from mercurial import repair, extensions, error, phases from mercurial import patch as patchmod from mercurial import localrepo +from mercurial import subrepo import os, re, errno, shutil commands.norepo += " qclone" @@ -800,6 +801,14 @@ p1, p2 = repo.dirstate.parents() repo.setparents(p1, merge) + if all_files and '.hgsubstate' in all_files: + wctx = repo['.'] + mctx = actx = repo[None] + overwrite = False + mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx, + overwrite) + files += mergedsubstate.keys() + match = scmutil.matchfiles(repo, files or []) oldtip = repo['tip'] n = newcommit(repo, None, message, ph.user, ph.date, match=match, @@ -975,11 +984,20 @@ else: raise util.Abort(_("local changes found")) + def localchangedsubreposfound(self, refresh=True): + if refresh: + raise util.Abort(_("local changed subrepos found, refresh first")) + else: + raise util.Abort(_("local changed subrepos found")) + def checklocalchanges(self, repo, force=False, refresh=True): cmdutil.checkunfinished(repo) m, a, r, d = repo.status()[:4] - if (m or a or r or d) and not force: - self.localchangesfound(refresh) + if not force: + if (m or a or r or d): + self.localchangesfound(refresh) + if self.checksubstate(repo): + self.localchangedsubreposfound(refresh) return m, a, r, d _reserved = ('series', 'status', 'guards', '.', '..') @@ -1450,6 +1468,8 @@ self.ui.status(_("popping %s\n") % patch.name) del self.applied[start:end] self.strip(repo, [rev], update=False, backup='strip') + for s, state in repo['.'].substate.items(): + repo['.'].sub(s).get(state) if self.applied: self.ui.write(_("now at: %s\n") % self.applied[-1].name) else: diff -r f2871c30e6a7 -r d4ed26beae0e hgext/progress.py --- a/hgext/progress.py Wed Sep 18 14:40:17 2013 -0400 +++ b/hgext/progress.py Wed Sep 18 14:52:16 2013 -0500 @@ -239,6 +239,13 @@ # this one are also closed if topic in self.topics: self.topics = self.topics[:self.topics.index(topic)] + # reset the last topic to the one we just unwound to, + # so that higher-level topics will be stickier than + # lower-level topics + if self.topics: + self.lasttopic = self.topics[-1] + else: + self.lasttopic = None else: if topic not in self.topics: self.starttimes[topic] = now diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/bundlerepo.py Wed Sep 18 14:52:16 2013 -0500 @@ -120,7 +120,7 @@ chain.append(iterrev) iterrev = self.index[iterrev][3] if text is None: - text = revlog.revlog.revision(self, iterrev) + text = self.baserevision(iterrev) while chain: delta = self._chunk(chain.pop()) @@ -130,6 +130,12 @@ self._cache = (node, rev, text) return text + def baserevision(self, nodeorrev): + # Revlog subclasses may override 'revision' method to modify format of + # content retrieved from revlog. To use bundlerevlog with such class one + # needs to override 'baserevision' and make more specific call here. + return revlog.revlog.revision(self, nodeorrev) + def addrevision(self, text, transaction, link, p1=None, p2=None, d=None): raise NotImplementedError def addgroup(self, revs, linkmapper, transaction): @@ -146,12 +152,21 @@ bundlerevlog.__init__(self, opener, self.indexfile, bundle, linkmapper) + def baserevision(self, nodeorrev): + # Although changelog doesn't override 'revision' method, some extensions + # may replace this class with another that does. Same story with + # manifest and filelog classes. + return changelog.changelog.revision(self, nodeorrev) + class bundlemanifest(bundlerevlog, manifest.manifest): def __init__(self, opener, bundle, linkmapper): manifest.manifest.__init__(self, opener) bundlerevlog.__init__(self, opener, self.indexfile, bundle, linkmapper) + def baserevision(self, nodeorrev): + return manifest.manifest.revision(self, nodeorrev) + class bundlefilelog(bundlerevlog, filelog.filelog): def __init__(self, opener, path, bundle, linkmapper, repo): filelog.filelog.__init__(self, opener, path) @@ -159,6 +174,9 @@ linkmapper) self._repo = repo + def baserevision(self, nodeorrev): + return filelog.filelog.revision(self, nodeorrev) + def _file(self, f): self._repo.file(f) diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/cmdutil.py --- a/mercurial/cmdutil.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/cmdutil.py Wed Sep 18 14:52:16 2013 -0500 @@ -1172,12 +1172,34 @@ 'filenames')) # The slow path checks files modified in every changeset. - for i in sorted(revs): - ctx = change(i) - matches = filter(match, ctx.files()) - if matches: - fncache[i] = matches - wanted.add(i) + # This is really slow on large repos, so compute the set lazily. + class lazywantedset(object): + def __init__(self): + self.set = set() + self.revs = set(revs) + + # No need to worry about locality here because it will be accessed + # in the same order as the increasing window below. + def __contains__(self, value): + if value in self.set: + return True + elif not value in self.revs: + return False + else: + self.revs.discard(value) + ctx = change(value) + matches = filter(match, ctx.files()) + if matches: + fncache[value] = matches + self.set.add(value) + return True + return False + + def discard(self, value): + self.revs.discard(value) + self.set.discard(value) + + wanted = lazywantedset() class followfilter(object): def __init__(self, onlyfirst=False): diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/commands.py --- a/mercurial/commands.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/commands.py Wed Sep 18 14:52:16 2013 -0500 @@ -1923,6 +1923,7 @@ util.writefile('.debugfsinfo', '') ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no')) ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no')) + ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no')) ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo') and 'yes' or 'no')) os.unlink('.debugfsinfo') diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/context.py --- a/mercurial/context.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/context.py Wed Sep 18 14:52:16 2013 -0500 @@ -16,11 +16,197 @@ propertycache = util.propertycache -class changectx(object): +class basectx(object): + """A basectx object represents the common logic for its children: + changectx: read-only context that is already present in the repo, + workingctx: a context that represents the working directory and can + be committed, + memctx: a context that represents changes in-memory and can also + be committed.""" + def __new__(cls, repo, changeid='', *args, **kwargs): + if isinstance(changeid, basectx): + return changeid + + o = super(basectx, cls).__new__(cls) + + o._repo = repo + o._rev = nullrev + o._node = nullid + + return o + + def __str__(self): + return short(self.node()) + + def __int__(self): + return self.rev() + + def __repr__(self): + return "<%s %s>" % (type(self).__name__, str(self)) + + def __eq__(self, other): + try: + return type(self) == type(other) and self._rev == other._rev + except AttributeError: + return False + + def __ne__(self, other): + return not (self == other) + + def __contains__(self, key): + return key in self._manifest + + def __getitem__(self, key): + return self.filectx(key) + + def __iter__(self): + for f in sorted(self._manifest): + yield f + + @propertycache + def substate(self): + return subrepo.state(self, self._repo.ui) + + def rev(self): + return self._rev + def node(self): + return self._node + def hex(self): + return hex(self.node()) + def manifest(self): + return self._manifest + def phasestr(self): + return phases.phasenames[self.phase()] + def mutable(self): + return self.phase() > phases.public + + def obsolete(self): + """True if the changeset is obsolete""" + return self.rev() in obsmod.getrevs(self._repo, 'obsolete') + + def extinct(self): + """True if the changeset is extinct""" + return self.rev() in obsmod.getrevs(self._repo, 'extinct') + + def unstable(self): + """True if the changeset is not obsolete but it's ancestor are""" + return self.rev() in obsmod.getrevs(self._repo, 'unstable') + + def bumped(self): + """True if the changeset try to be a successor of a public changeset + + Only non-public and non-obsolete changesets may be bumped. + """ + return self.rev() in obsmod.getrevs(self._repo, 'bumped') + + def divergent(self): + """Is a successors of a changeset with multiple possible successors set + + Only non-public and non-obsolete changesets may be divergent. + """ + return self.rev() in obsmod.getrevs(self._repo, 'divergent') + + def troubled(self): + """True if the changeset is either unstable, bumped or divergent""" + return self.unstable() or self.bumped() or self.divergent() + + def troubles(self): + """return the list of troubles affecting this changesets. + + Troubles are returned as strings. possible values are: + - unstable, + - bumped, + - divergent. + """ + troubles = [] + if self.unstable(): + troubles.append('unstable') + if self.bumped(): + troubles.append('bumped') + if self.divergent(): + troubles.append('divergent') + return troubles + + def parents(self): + """return contexts for each parent changeset""" + return self._parents + + def p1(self): + return self._parents[0] + + def p2(self): + if len(self._parents) == 2: + return self._parents[1] + return changectx(self._repo, -1) + + def _fileinfo(self, path): + if '_manifest' in self.__dict__: + try: + return self._manifest[path], self._manifest.flags(path) + except KeyError: + raise error.ManifestLookupError(self._node, path, + _('not found in manifest')) + if '_manifestdelta' in self.__dict__ or path in self.files(): + if path in self._manifestdelta: + return (self._manifestdelta[path], + self._manifestdelta.flags(path)) + node, flag = self._repo.manifest.find(self._changeset[0], path) + if not node: + raise error.ManifestLookupError(self._node, path, + _('not found in manifest')) + + return node, flag + + def filenode(self, path): + return self._fileinfo(path)[0] + + def flags(self, path): + try: + return self._fileinfo(path)[1] + except error.LookupError: + return '' + + def sub(self, path): + return subrepo.subrepo(self, path) + + def match(self, pats=[], include=None, exclude=None, default='glob'): + r = self._repo + return matchmod.match(r.root, r.getcwd(), pats, + include, exclude, default, + auditor=r.auditor, ctx=self) + + def diff(self, ctx2=None, match=None, **opts): + """Returns a diff generator for the given contexts and matcher""" + if ctx2 is None: + ctx2 = self.p1() + if ctx2 is not None: + ctx2 = self._repo[ctx2] + diffopts = patch.diffopts(self._repo.ui, opts) + return patch.diff(self._repo, ctx2.node(), self.node(), + match=match, opts=diffopts) + + @propertycache + def _dirs(self): + return scmutil.dirs(self._manifest) + + def dirs(self): + return self._dirs + + def dirty(self): + return False + +class changectx(basectx): """A changecontext object makes access to data related to a particular - changeset convenient.""" + changeset convenient. It represents a read-only context already presnt in + the repo.""" def __init__(self, repo, changeid=''): """changeid is a revision number, node, or tag""" + + # since basectx.__new__ already took care of copying the object, we + # don't need to do anything in __init__, so we just exit here + if isinstance(changeid, basectx): + return + if changeid == '': changeid = '.' self._repo = repo @@ -114,30 +300,12 @@ raise error.RepoLookupError( _("unknown revision '%s'") % changeid) - def __str__(self): - return short(self.node()) - - def __int__(self): - return self.rev() - - def __repr__(self): - return "" % str(self) - def __hash__(self): try: return hash(self._rev) except AttributeError: return id(self) - def __eq__(self, other): - try: - return self._rev == other._rev - except AttributeError: - return False - - def __ne__(self, other): - return not (self == other) - def __nonzero__(self): return self._rev != nullrev @@ -160,33 +328,11 @@ p = p[:-1] return [changectx(self._repo, x) for x in p] - @propertycache - def substate(self): - return subrepo.state(self, self._repo.ui) - - def __contains__(self, key): - return key in self._manifest - - def __getitem__(self, key): - return self.filectx(key) - - def __iter__(self): - for f in sorted(self._manifest): - yield f - def changeset(self): return self._changeset - def manifest(self): - return self._manifest def manifestnode(self): return self._changeset[0] - def rev(self): - return self._rev - def node(self): - return self._node - def hex(self): - return hex(self._node) def user(self): return self._changeset[1] def date(self): @@ -207,25 +353,9 @@ return self._repo.nodebookmarks(self._node) def phase(self): return self._repo._phasecache.phase(self._repo, self._rev) - def phasestr(self): - return phases.phasenames[self.phase()] - def mutable(self): - return self.phase() > phases.public def hidden(self): return self._rev in repoview.filterrevs(self._repo, 'visible') - def parents(self): - """return contexts for each parent changeset""" - return self._parents - - def p1(self): - return self._parents[0] - - def p2(self): - if len(self._parents) == 2: - return self._parents[1] - return changectx(self._repo, -1) - def children(self): """return contexts for each child changeset""" c = self._repo.changelog.children(self._node) @@ -239,80 +369,6 @@ for d in self._repo.changelog.descendants([self._rev]): yield changectx(self._repo, d) - def obsolete(self): - """True if the changeset is obsolete""" - return self.rev() in obsmod.getrevs(self._repo, 'obsolete') - - def extinct(self): - """True if the changeset is extinct""" - return self.rev() in obsmod.getrevs(self._repo, 'extinct') - - def unstable(self): - """True if the changeset is not obsolete but it's ancestor are""" - return self.rev() in obsmod.getrevs(self._repo, 'unstable') - - def bumped(self): - """True if the changeset try to be a successor of a public changeset - - Only non-public and non-obsolete changesets may be bumped. - """ - return self.rev() in obsmod.getrevs(self._repo, 'bumped') - - def divergent(self): - """Is a successors of a changeset with multiple possible successors set - - Only non-public and non-obsolete changesets may be divergent. - """ - return self.rev() in obsmod.getrevs(self._repo, 'divergent') - - def troubled(self): - """True if the changeset is either unstable, bumped or divergent""" - return self.unstable() or self.bumped() or self.divergent() - - def troubles(self): - """return the list of troubles affecting this changesets. - - Troubles are returned as strings. possible values are: - - unstable, - - bumped, - - divergent. - """ - troubles = [] - if self.unstable(): - troubles.append('unstable') - if self.bumped(): - troubles.append('bumped') - if self.divergent(): - troubles.append('divergent') - return troubles - - def _fileinfo(self, path): - if '_manifest' in self.__dict__: - try: - return self._manifest[path], self._manifest.flags(path) - except KeyError: - raise error.ManifestLookupError(self._node, path, - _('not found in manifest')) - if '_manifestdelta' in self.__dict__ or path in self.files(): - if path in self._manifestdelta: - return (self._manifestdelta[path], - self._manifestdelta.flags(path)) - node, flag = self._repo.manifest.find(self._changeset[0], path) - if not node: - raise error.ManifestLookupError(self._node, path, - _('not found in manifest')) - - return node, flag - - def filenode(self, path): - return self._fileinfo(path)[0] - - def flags(self, path): - try: - return self._fileinfo(path)[1] - except error.LookupError: - return '' - def filectx(self, path, fileid=None, filelog=None): """get a file context from this changeset""" if fileid is None: @@ -353,83 +409,15 @@ if match.bad(fn, _('no such file in rev %s') % self) and match(fn): yield fn - def sub(self, path): - return subrepo.subrepo(self, path) - - def match(self, pats=[], include=None, exclude=None, default='glob'): - r = self._repo - return matchmod.match(r.root, r.getcwd(), pats, - include, exclude, default, - auditor=r.auditor, ctx=self) - - def diff(self, ctx2=None, match=None, **opts): - """Returns a diff generator for the given contexts and matcher""" - if ctx2 is None: - ctx2 = self.p1() - if ctx2 is not None and not isinstance(ctx2, changectx): - ctx2 = self._repo[ctx2] - diffopts = patch.diffopts(self._repo.ui, opts) - return patch.diff(self._repo, ctx2.node(), self.node(), - match=match, opts=diffopts) - - @propertycache - def _dirs(self): - return scmutil.dirs(self._manifest) - - def dirs(self): - return self._dirs - - def dirty(self): - return False - -class filectx(object): - """A filecontext object makes access to data related to a particular - filerevision convenient.""" - def __init__(self, repo, path, changeid=None, fileid=None, - filelog=None, changectx=None): - """changeid can be a changeset revision, node, or tag. - fileid can be a file revision or node.""" - self._repo = repo - self._path = path - - assert (changeid is not None - or fileid is not None - or changectx is not None), \ - ("bad args: changeid=%r, fileid=%r, changectx=%r" - % (changeid, fileid, changectx)) - - if filelog is not None: - self._filelog = filelog - - if changeid is not None: - self._changeid = changeid - if changectx is not None: - self._changectx = changectx - if fileid is not None: - self._fileid = fileid - - @propertycache - def _changectx(self): - try: - return changectx(self._repo, self._changeid) - except error.RepoLookupError: - # Linkrev may point to any revision in the repository. When the - # repository is filtered this may lead to `filectx` trying to build - # `changectx` for filtered revision. In such case we fallback to - # creating `changectx` on the unfiltered version of the reposition. - # This fallback should not be an issue because `changectx` from - # `filectx` are not used in complex operations that care about - # filtering. - # - # This fallback is a cheap and dirty fix that prevent several - # crashes. It does not ensure the behavior is correct. However the - # behavior was not correct before filtering either and "incorrect - # behavior" is seen as better as "crash" - # - # Linkrevs have several serious troubles with filtering that are - # complicated to solve. Proper handling of the issue here should be - # considered when solving linkrev issue are on the table. - return changectx(self._repo.unfiltered(), self._changeid) +class basefilectx(object): + """A filecontext object represents the common logic for its children: + filectx: read-only access to a filerevision that is already present + in the repo, + workingfilectx: a filecontext that represents files from the working + directory, + memfilectx: a filecontext that represents files in-memory.""" + def __new__(cls, repo, path, *args, **kwargs): + return super(basefilectx, cls).__new__(cls) @propertycache def _filelog(self): @@ -468,10 +456,10 @@ return False def __str__(self): - return "%s@%s" % (self.path(), short(self.node())) + return "%s@%s" % (self.path(), self._changectx) def __repr__(self): - return "" % str(self) + return "<%s %s>" % (type(self).__name__, str(self)) def __hash__(self): try: @@ -481,7 +469,7 @@ def __eq__(self, other): try: - return (self._path == other._path + return (type(self) == type(other) and self._path == other._path and self._filenode == other._filenode) except AttributeError: return False @@ -489,12 +477,6 @@ def __ne__(self, other): return not (self == other) - def filectx(self, fileid): - '''opens an arbitrary revision of the file without - opening a new filelog''' - return filectx(self._repo, self._path, fileid=fileid, - filelog=self._filelog) - def filerev(self): return self._filerev def filenode(self): @@ -510,7 +492,7 @@ def node(self): return self._changectx.node() def hex(self): - return hex(self.node()) + return self._changectx.hex() def user(self): return self._changectx.user() def date(self): @@ -532,12 +514,8 @@ def changectx(self): return self._changectx - def data(self): - return self._filelog.read(self._filenode) def path(self): return self._path - def size(self): - return self._filelog.size(self._filerev) def isbinary(self): try: @@ -560,31 +538,6 @@ return True - def renamed(self): - """check if file was actually renamed in this changeset revision - - If rename logged in file revision, we report copy for changeset only - if file revisions linkrev points back to the changeset in question - or both changeset parents contain different file revisions. - """ - - renamed = self._filelog.renamed(self._filenode) - if not renamed: - return renamed - - if self.rev() == self.linkrev(): - return renamed - - name = self.path() - fnode = self._filenode - for p in self._changectx.parents(): - try: - if fnode == p.filenode(name): - return None - except error.LookupError: - pass - return renamed - def parents(self): p = self._path fl = self._filelog @@ -606,12 +559,6 @@ return p[1] return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog) - def children(self): - # hard for renames - c = self._filelog.children(self._filenode) - return [filectx(self._repo, self._path, fileid=x, - filelog=self._filelog) for x in c] - def annotate(self, follow=False, linenumber=None, diffopts=None): '''returns a list of tuples of (ctx, line) for each line in the file, where ctx is the filectx of the node where @@ -783,15 +730,100 @@ self._copycache[sc2] = copies.pathcopies(c2) return self._copycache[sc2] -class workingctx(changectx): - """A workingctx object makes access to data related to - the current working directory convenient. - date - any valid date string or (unixtime, offset), or None. - user - username string, or None. - extra - a dictionary of extra values, or None. - changes - a list of file lists as returned by localrepo.status() - or None to use the repository status. - """ +class filectx(basefilectx): + """A filecontext object makes access to data related to a particular + filerevision convenient.""" + def __init__(self, repo, path, changeid=None, fileid=None, + filelog=None, changectx=None): + """changeid can be a changeset revision, node, or tag. + fileid can be a file revision or node.""" + self._repo = repo + self._path = path + + assert (changeid is not None + or fileid is not None + or changectx is not None), \ + ("bad args: changeid=%r, fileid=%r, changectx=%r" + % (changeid, fileid, changectx)) + + if filelog is not None: + self._filelog = filelog + + if changeid is not None: + self._changeid = changeid + if changectx is not None: + self._changectx = changectx + if fileid is not None: + self._fileid = fileid + + @propertycache + def _changectx(self): + try: + return changectx(self._repo, self._changeid) + except error.RepoLookupError: + # Linkrev may point to any revision in the repository. When the + # repository is filtered this may lead to `filectx` trying to build + # `changectx` for filtered revision. In such case we fallback to + # creating `changectx` on the unfiltered version of the reposition. + # This fallback should not be an issue because `changectx` from + # `filectx` are not used in complex operations that care about + # filtering. + # + # This fallback is a cheap and dirty fix that prevent several + # crashes. It does not ensure the behavior is correct. However the + # behavior was not correct before filtering either and "incorrect + # behavior" is seen as better as "crash" + # + # Linkrevs have several serious troubles with filtering that are + # complicated to solve. Proper handling of the issue here should be + # considered when solving linkrev issue are on the table. + return changectx(self._repo.unfiltered(), self._changeid) + + def filectx(self, fileid): + '''opens an arbitrary revision of the file without + opening a new filelog''' + return filectx(self._repo, self._path, fileid=fileid, + filelog=self._filelog) + + def data(self): + return self._filelog.read(self._filenode) + def size(self): + return self._filelog.size(self._filerev) + + def renamed(self): + """check if file was actually renamed in this changeset revision + + If rename logged in file revision, we report copy for changeset only + if file revisions linkrev points back to the changeset in question + or both changeset parents contain different file revisions. + """ + + renamed = self._filelog.renamed(self._filenode) + if not renamed: + return renamed + + if self.rev() == self.linkrev(): + return renamed + + name = self.path() + fnode = self._filenode + for p in self._changectx.parents(): + try: + if fnode == p.filenode(name): + return None + except error.LookupError: + pass + return renamed + + def children(self): + # hard for renames + c = self._filelog.children(self._filenode) + return [filectx(self._repo, self._path, fileid=x, + filelog=self._filelog) for x in c] + +class committablectx(basectx): + """A committablectx object provides common functionality for a context that + wants the ability to commit, e.g. workingctx or memctx.""" def __init__(self, repo, text="", user=None, date=None, extra=None, changes=None): self._repo = repo @@ -827,9 +859,6 @@ def __str__(self): return str(self._parents[0]) + "+" - def __repr__(self): - return "" % str(self) - def __nonzero__(self): return True @@ -904,12 +933,6 @@ return man - def __iter__(self): - d = self._repo.dirstate - for f in d: - if d[f] != 'r': - yield f - @propertycache def _status(self): return self._repo.status()[:4] @@ -922,13 +945,6 @@ def _date(self): return util.makedate() - @propertycache - def _parents(self): - p = self._repo.dirstate.parents() - if p[1] == nullid: - p = p[:-1] - return [changectx(self._repo, x) for x in p] - def status(self, ignored=False, clean=False, unknown=False): """Explicit status query Unless this method is used to query the working copy status, the @@ -945,8 +961,6 @@ self._status = stat[:4] return stat - def manifest(self): - return self._manifest def user(self): return self._user or self._repo.ui.username() def date(self): @@ -1016,11 +1030,6 @@ except OSError: return '' - def filectx(self, path, filelog=None): - """get a file context from the working directory""" - return workingfilectx(self._repo, path, workingctx=self, - filelog=filelog) - def ancestor(self, c2): """return the ancestor context of self and c2""" return self._parents[0].ancestor(c2) # punt on two parents for now @@ -1029,6 +1038,61 @@ return sorted(self._repo.dirstate.walk(match, sorted(self.substate), True, False)) + def ancestors(self): + for a in self._repo.changelog.ancestors( + [p.rev() for p in self._parents]): + yield changectx(self._repo, a) + + def markcommitted(self, node): + """Perform post-commit cleanup necessary after committing this ctx + + Specifically, this updates backing stores this working context + wraps to reflect the fact that the changes reflected by this + workingctx have been committed. For example, it marks + modified and added files as normal in the dirstate. + + """ + + for f in self.modified() + self.added(): + self._repo.dirstate.normal(f) + for f in self.removed(): + self._repo.dirstate.drop(f) + self._repo.dirstate.setparents(node) + + def dirs(self): + return self._repo.dirstate.dirs() + +class workingctx(committablectx): + """A workingctx object makes access to data related to + the current working directory convenient. + date - any valid date string or (unixtime, offset), or None. + user - username string, or None. + extra - a dictionary of extra values, or None. + changes - a list of file lists as returned by localrepo.status() + or None to use the repository status. + """ + def __init__(self, repo, text="", user=None, date=None, extra=None, + changes=None): + super(workingctx, self).__init__(repo, text, user, date, extra, changes) + + def __iter__(self): + d = self._repo.dirstate + for f in d: + if d[f] != 'r': + yield f + + @propertycache + def _parents(self): + p = self._repo.dirstate.parents() + if p[1] == nullid: + p = p[:-1] + return [changectx(self._repo, x) for x in p] + + def filectx(self, path, filelog=None): + """get a file context from the working directory""" + return workingfilectx(self._repo, path, workingctx=self, + filelog=filelog) + def dirty(self, missing=False, merge=True, branch=True): "check whether a working directory is modified" # check subrepos first @@ -1093,11 +1157,6 @@ finally: wlock.release() - def ancestors(self): - for a in self._repo.changelog.ancestors( - [p.rev() for p in self._parents]): - yield changectx(self._repo, a) - def undelete(self, list): pctxs = self.parents() wlock = self._repo.wlock() @@ -1129,31 +1188,10 @@ finally: wlock.release() - def markcommitted(self, node): - """Perform post-commit cleanup necessary after committing this ctx - - Specifically, this updates backing stores this working context - wraps to reflect the fact that the changes reflected by this - workingctx have been committed. For example, it marks - modified and added files as normal in the dirstate. - - """ - - for f in self.modified() + self.added(): - self._repo.dirstate.normal(f) - for f in self.removed(): - self._repo.dirstate.drop(f) - self._repo.dirstate.setparents(node) - - def dirs(self): - return self._repo.dirstate.dirs() - -class workingfilectx(filectx): - """A workingfilectx object makes access to data related to a particular - file in the working directory convenient.""" - def __init__(self, repo, path, filelog=None, workingctx=None): - """changeid can be a changeset revision, node, or tag. - fileid can be a file revision or node.""" +class committablefilectx(basefilectx): + """A committablefilectx provides common functionality for a file context + that wants the ability to commit, e.g. workingfilectx or memfilectx.""" + def __init__(self, repo, path, filelog=None, ctx=None): self._repo = repo self._path = path self._changeid = None @@ -1161,30 +1199,12 @@ if filelog is not None: self._filelog = filelog - if workingctx: - self._changectx = workingctx - - @propertycache - def _changectx(self): - return workingctx(self._repo) + if ctx: + self._changectx = ctx def __nonzero__(self): return True - def __str__(self): - return "%s@%s" % (self.path(), self._changectx) - - def __repr__(self): - return "" % str(self) - - def data(self): - return self._repo.wread(self._path) - def renamed(self): - rp = self._repo.dirstate.copied(self._path) - if not rp: - return None - return rp, self._changectx._parents[0]._manifest.get(rp, nullid) - def parents(self): '''return parent filectxs, following copies if necessary''' def filenode(ctx, path): @@ -1209,6 +1229,24 @@ def children(self): return [] +class workingfilectx(committablefilectx): + """A workingfilectx object makes access to data related to a particular + file in the working directory convenient.""" + def __init__(self, repo, path, filelog=None, workingctx=None): + super(workingfilectx, self).__init__(repo, path, filelog, workingctx) + + @propertycache + def _changectx(self): + return workingctx(self._repo) + + def data(self): + return self._repo.wread(self._path) + def renamed(self): + rp = self._repo.dirstate.copied(self._path) + if not rp: + return None + return rp, self._changectx._parents[0]._manifest.get(rp, nullid) + def size(self): return os.lstat(self._repo.wjoin(self._path)).st_size def date(self): diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/dirstate.py --- a/mercurial/dirstate.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/dirstate.py Wed Sep 18 14:52:16 2013 -0500 @@ -801,12 +801,9 @@ mexact = match.exact dirignore = self._dirignore checkexec = self._checkexec - checklink = self._checklink copymap = self._copymap lastnormaltime = self._lastnormaltime - lnkkind = stat.S_IFLNK - # We need to do full walks when either # - we're listing all clean files, or # - match.traversedir does something, because match.traversedir should @@ -827,20 +824,14 @@ if not st and state in "nma": dadd(fn) elif state == 'n': - # The "mode & lnkkind != lnkkind or self._checklink" - # lines are an expansion of "islink => checklink" - # where islink means "is this a link?" and checklink - # means "can we check links?". mtime = int(st.st_mtime) if (size >= 0 and ((size != st.st_size and size != st.st_size & _rangemask) or ((mode ^ st.st_mode) & 0100 and checkexec)) - and (mode & lnkkind != lnkkind or checklink) or size == -2 # other parent or fn in copymap): madd(fn) - elif ((time != mtime and time != mtime & _rangemask) - and (mode & lnkkind != lnkkind or checklink)): + elif time != mtime and time != mtime & _rangemask: ladd(fn) elif mtime == lastnormaltime: # fn may have been changed in the same timeslot without diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/dispatch.py --- a/mercurial/dispatch.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/dispatch.py Wed Sep 18 14:52:16 2013 -0500 @@ -88,11 +88,47 @@ try: try: + debugger = 'pdb' + debugtrace = { + 'pdb' : pdb.set_trace + } + debugmortem = { + 'pdb' : pdb.post_mortem + } + + # read --config before doing anything else + # (e.g. to change trust settings for reading .hg/hgrc) + cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args)) + + if req.repo: + # copy configs that were passed on the cmdline (--config) to + # the repo ui + for cfg in cfgs: + req.repo.ui.setconfig(*cfg) + + debugger = ui.config("ui", "debugger") + if not debugger: + debugger = 'pdb' + + try: + debugmod = __import__(debugger) + except ImportError: + debugmod = pdb + + debugtrace[debugger] = debugmod.set_trace + debugmortem[debugger] = debugmod.post_mortem + # enter the debugger before command execution if '--debugger' in req.args: ui.warn(_("entering debugger - " "type c to continue starting hg or h for help\n")) - pdb.set_trace() + + if (debugger != 'pdb' and + debugtrace[debugger] == debugtrace['pdb']): + ui.warn(_("%s debugger specified " + "but its module was not found\n") % debugger) + + debugtrace[debugger]() try: return _dispatch(req) finally: @@ -101,7 +137,7 @@ # enter the debugger when we hit an exception if '--debugger' in req.args: traceback.print_exc() - pdb.post_mortem(sys.exc_info()[2]) + debugmortem[debugger](sys.exc_info()[2]) ui.traceback() raise @@ -619,10 +655,6 @@ args = req.args ui = req.ui - # read --config before doing anything else - # (e.g. to change trust settings for reading .hg/hgrc) - cfgs = _parseconfig(ui, _earlygetopt(['--config'], args)) - # check for cwd cwd = _earlygetopt(['--cwd'], args) if cwd: @@ -699,10 +731,6 @@ if req.repo: uis.add(req.repo.ui) - # copy configs that were passed on the cmdline (--config) to the repo ui - for cfg in cfgs: - req.repo.ui.setconfig(*cfg) - if options['verbose'] or options['debug'] or options['quiet']: for opt in ('verbose', 'debug', 'quiet'): val = str(bool(options[opt])) diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/hgweb/webcommands.py Wed Sep 18 14:52:16 2013 -0500 @@ -9,13 +9,15 @@ import webutil from mercurial import error, encoding, archival, templater, templatefilters from mercurial.node import short, hex, nullid -from mercurial.util import binary +from mercurial import util 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. @@ -57,7 +59,7 @@ if guessmime: mt = mimetypes.guess_type(path)[0] if mt is None: - mt = binary(text) and 'application/binary' or 'text/plain' + mt = util.binary(text) and 'application/binary' or 'text/plain' if mt.startswith('text/'): mt += '; charset="%s"' % encoding.encoding @@ -69,7 +71,7 @@ text = fctx.data() parity = paritygen(web.stripecount) - if binary(text): + if util.binary(text): mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' text = '(binary:%s)' % mt @@ -109,9 +111,14 @@ raise inst def _search(web, req, tmpl): + MODE_REVISION = 'rev' + MODE_KEYWORD = 'keyword' + MODE_REVSET = 'revset' - def changelist(**map): - count = 0 + def revsearch(ctx): + yield ctx + + def keywordsearch(query): lower = encoding.lower qw = lower(query).split() @@ -137,6 +144,62 @@ if miss: continue + yield ctx + + def revsetsearch(revs): + for r in revs: + yield web.repo[r] + + searchfuncs = { + MODE_REVISION: revsearch, + MODE_KEYWORD: keywordsearch, + MODE_REVSET: revsetsearch, + } + + def getsearchmode(query): + try: + ctx = web.repo[query] + except (error.RepoError, error.LookupError): + # query is not an exact revision pointer, need to + # decide if it's a revset expession or keywords + pass + else: + return MODE_REVISION, ctx + + revdef = 'reverse(%s)' % query + try: + tree, pos = revset.parse(revdef) + except ParseError: + # can't parse to a revset tree + return MODE_KEYWORD, query + + if revset.depth(tree) <= 2: + # no revset syntax used + return MODE_KEYWORD, query + + if util.any((token, (value or '')[:3]) == ('string', 're:') + for token, value, pos in revset.tokenize(revdef)): + return MODE_KEYWORD, query + + funcsused = revset.funcsused(tree) + if not funcsused.issubset(revset.safesymbols): + return MODE_KEYWORD, query + + mfunc = revset.match(web.repo.ui, revdef) + try: + revs = mfunc(web.repo, list(web.repo)) + return MODE_REVSET, revs + # ParseError: wrongly placed tokens, wrongs arguments, etc + # RepoLookupError: no such revision, e.g. in 'revision:' + # Abort: bookmark/tag not exists + # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo + except (ParseError, RepoLookupError, Abort, LookupError): + return MODE_KEYWORD, query + + def changelist(**map): + count = 0 + + for ctx in searchfunc(funcarg): count += 1 n = ctx.node() showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n) @@ -176,6 +239,9 @@ morevars['revcount'] = revcount * 2 morevars['rev'] = query + mode, funcarg = getsearchmode(query) + searchfunc = searchfuncs[mode] + tip = web.repo['tip'] parity = paritygen(web.stripecount) @@ -188,23 +254,15 @@ query = '' if 'node' in req.form: ctx = webutil.changectx(web.repo, req) + elif 'rev' in req.form: + return _search(web, req, tmpl) else: - if 'rev' in req.form: - query = req.form['rev'][0] - hi = query - else: - hi = 'tip' - try: - ctx = web.repo[hi] - except (error.RepoError, error.LookupError): - return _search(web, req, tmpl) # XXX redirect to 404 page? + ctx = web.repo['tip'] - def changelist(latestonly, **map): + def changelist(): revs = [] if pos != -1: revs = web.repo.changelog.revs(pos, 0) - if latestonly: - revs = (revs.next(),) curcount = 0 for i in revs: ctx = web.repo[i] @@ -213,7 +271,7 @@ files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles) curcount += 1 - if curcount > revcount: + if curcount > revcount + 1: break yield {"parity": parity.next(), "author": ctx.user(), @@ -249,10 +307,18 @@ changenav = webutil.revnav(web.repo).gen(pos, revcount, count) + entries = list(changelist()) + latestentry = entries[:1] + if len(entries) > revcount: + nextentry = entries[-1:] + entries = entries[:-1] + else: + nextentry = [] + return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav, node=ctx.hex(), rev=pos, changesets=count, - entries=lambda **x: changelist(latestonly=False, **x), - latestentry=lambda **x: changelist(latestonly=True, **x), + entries=entries, + latestentry=latestentry, nextentry=nextentry, archives=web.archivelist("tip"), revcount=revcount, morevars=morevars, lessvars=lessvars, query=query) @@ -617,7 +683,7 @@ context = parsecontext(web.config('web', 'comparisoncontext', '5')) def filelines(f): - if binary(f.data()): + if util.binary(f.data()): mt = mimetypes.guess_type(f.path())[0] if not mt: mt = 'application/octet-stream' @@ -675,7 +741,7 @@ def annotate(**map): last = None - if binary(fctx.data()): + if util.binary(fctx.data()): mt = (mimetypes.guess_type(fctx.path())[0] or 'application/octet-stream') lines = enumerate([((fctx.filectx(fctx.filerev()), 1), diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/httpclient/__init__.py --- a/mercurial/httpclient/__init__.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/httpclient/__init__.py Wed Sep 18 14:52:16 2013 -0500 @@ -495,6 +495,10 @@ else: raise BadRequestData('body has no __len__() nor read()') + # If we're reusing the underlying socket, there are some + # conditions where we'll want to retry, so make a note of the + # state of self.sock + fresh_socket = self.sock is None self._connect() outgoing_headers = self._buildheaders( method, path, hdrs, self.http_version) @@ -640,6 +644,26 @@ # the whole request if response is None: response = self.response_class(self.sock, self.timeout, method) + if not fresh_socket: + if not response._select(): + # This means the response failed to get any response + # data at all, and in all probability the socket was + # closed before the server even saw our request. Try + # the request again on a fresh socket. + logging.debug('response._select() failed during request().' + ' Assuming request needs to be retried.') + self.sock = None + # Call this method explicitly to re-try the + # request. We don't use self.request() because + # some tools (notably Mercurial) expect to be able + # to subclass and redefine request(), and they + # don't have the same argspec as we do. + # + # TODO restructure sending of requests to avoid + # this recursion + return HTTPConnection.request( + self, method, path, body=body, headers=headers, + expect_continue=expect_continue) data_left = bool(outgoing_headers or body) if data_left: logger.info('stopped sending request early, ' diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/localrepo.py --- a/mercurial/localrepo.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/localrepo.py Wed Sep 18 14:52:16 2013 -0500 @@ -39,6 +39,8 @@ """propertycache that apply to unfiltered repo only""" def __get__(self, repo, type=None): + if hasunfilteredcache(repo, self.name): + return getattr(repo.unfiltered(), self.name) return super(unfilteredpropertycache, self).__get__(repo.unfiltered()) class filteredpropertycache(propertycache): @@ -1457,14 +1459,8 @@ del mf[fn] return mf - if isinstance(node1, context.changectx): - ctx1 = node1 - else: - ctx1 = self[node1] - if isinstance(node2, context.changectx): - ctx2 = node2 - else: - ctx2 = self[node2] + ctx1 = self[node1] + ctx2 = self[node2] working = ctx2.rev() is None parentworking = working and ctx1 == self['.'] @@ -1561,7 +1557,7 @@ for f in modified: if ctx2.flags(f) == 'l': d = ctx2[f].data() - if len(d) >= 1024 or '\n' in d or util.binary(d): + if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d): self.ui.debug('ignoring suspect symlink placeholder' ' "%s"\n' % f) continue diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/obsolete.py --- a/mercurial/obsolete.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/obsolete.py Wed Sep 18 14:52:16 2013 -0500 @@ -371,7 +371,7 @@ lock.release() def syncpush(repo, remote): - """utility function to push bookmark to a remote + """utility function to push obsolete markers to a remote Exist mostly to allow overridding for experimentation purpose""" if (_enabled and repo.obsstore and @@ -387,7 +387,7 @@ repo.ui.warn(msg) def syncpull(repo, remote, gettransaction): - """utility function to pull bookmark to a remote + """utility function to pull obsolete markers from a remote The `gettransaction` is function that return the pull transaction, creating one if necessary. We return the transaction to inform the calling code that diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/parsers.c --- a/mercurial/parsers.c Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/parsers.c Wed Sep 18 14:52:16 2013 -0500 @@ -14,16 +14,32 @@ #include "util.h" +static int8_t hextable[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 0-9 */ + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A-F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a-f */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + static inline int hexdigit(const char *p, Py_ssize_t off) { - char c = p[off]; + int8_t val = hextable[(unsigned char)p[off]]; - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; + if (val >= 0) { + return val; + } PyErr_SetString(PyExc_ValueError, "input contains non-hex character"); return 0; @@ -61,7 +77,7 @@ static PyObject *parse_manifest(PyObject *self, PyObject *args) { PyObject *mfdict, *fdict; - char *str, *cur, *start, *zero; + char *str, *start, *end; int len; if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest", @@ -70,30 +86,34 @@ &str, &len)) goto quit; - for (start = cur = str, zero = NULL; cur < str + len; cur++) { + start = str; + end = str + len; + while (start < end) { PyObject *file = NULL, *node = NULL; PyObject *flags = NULL; + char *zero = NULL, *newline = NULL; ptrdiff_t nlen; - if (!*cur) { - zero = cur; - continue; - } - else if (*cur != '\n') - continue; - + zero = memchr(start, '\0', end - start); if (!zero) { PyErr_SetString(PyExc_ValueError, "manifest entry has no separator"); goto quit; } + newline = memchr(zero + 1, '\n', end - (zero + 1)); + if (!newline) { + PyErr_SetString(PyExc_ValueError, + "manifest contains trailing garbage"); + goto quit; + } + file = PyBytes_FromStringAndSize(start, zero - start); if (!file) goto bail; - nlen = cur - zero - 1; + nlen = newline - zero - 1; node = unhexlify(zero + 1, nlen > 40 ? 40 : (int)nlen); if (!node) @@ -112,8 +132,7 @@ if (PyDict_SetItem(mfdict, file, node) == -1) goto bail; - start = cur + 1; - zero = NULL; + start = newline + 1; Py_XDECREF(flags); Py_XDECREF(node); @@ -126,12 +145,6 @@ goto quit; } - if (len > 0 && *(cur - 1) != '\n') { - PyErr_SetString(PyExc_ValueError, - "manifest contains trailing garbage"); - goto quit; - } - Py_INCREF(Py_None); return Py_None; quit: @@ -142,8 +155,8 @@ { PyObject *dmap, *cmap, *parents = NULL, *ret = NULL; PyObject *fname = NULL, *cname = NULL, *entry = NULL; - char *str, *cur, *end, *cpos; - int state, mode, size, mtime; + char state, *str, *cur, *end, *cpos; + int mode, size, mtime; unsigned int flen; int len; @@ -330,7 +343,7 @@ * this. */ if (PyDict_SetItem(map, k, dirstate_unset) == -1) goto bail; - mode = 0, size = -1, mtime = -1; + mtime = -1; } putbe32(mode, p); putbe32(size, p + 4); @@ -524,11 +537,12 @@ uncomp_len, base_rev, link_rev, parent_1, parent_2, c_node_id, 20); - if (entry) + if (entry) { PyObject_GC_UnTrack(entry); + Py_INCREF(entry); + } self->cache[pos] = entry; - Py_INCREF(entry); return entry; } @@ -1195,14 +1209,19 @@ long sp; bitmask *seen; + if (gca == NULL) + return PyErr_NoMemory(); + for (i = 0; i < revcount; i++) { if (revs[i] > maxrev) maxrev = revs[i]; } seen = calloc(sizeof(*seen), maxrev + 1); - if (seen == NULL) + if (seen == NULL) { + Py_DECREF(gca); return PyErr_NoMemory(); + } for (i = 0; i < revcount; i++) seen[revs[i]] = 1ull << i; diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/pure/parsers.py Wed Sep 18 14:52:16 2013 -0500 @@ -100,11 +100,11 @@ # systems with a granularity of 1 sec). This commonly happens # for at least a couple of files on 'update'. # The user could change the file without changing its size - # within the same second. Invalidate the file's stat data in + # within the same second. Invalidate the file's mtime in # dirstate, forcing future 'status' calls to compare the - # contents of the file. This prevents mistakenly treating such - # files as clean. - e = (e[0], 0, -1, -1) # mark entry as 'unset' + # contents of the file if the size is the same. This prevents + # mistakenly treating such files as clean. + e = (e[0], e[1], e[2], -1) dmap[f] = e if f in copymap: diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/revlog.py --- a/mercurial/revlog.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/revlog.py Wed Sep 18 14:52:16 2013 -0500 @@ -14,7 +14,7 @@ # import stuff from node for others to import from revlog from node import bin, hex, nullid, nullrev from i18n import _ -import ancestor, mdiff, parsers, error, util +import ancestor, mdiff, parsers, error, util, templatefilters import struct, zlib, errno _pack = struct.pack @@ -845,16 +845,43 @@ def _chunkraw(self, startrev, endrev): start = self.start(startrev) - length = self.end(endrev) - start + end = self.end(endrev) if self._inline: start += (startrev + 1) * self._io.size + end += (endrev + 1) * self._io.size + length = end - start return self._getchunk(start, length) def _chunk(self, rev): return decompress(self._chunkraw(rev, rev)) - def _chunkbase(self, rev): - return self._chunk(rev) + def _chunks(self, revs): + '''faster version of [self._chunk(rev) for rev in revs] + + Assumes that revs is in ascending order.''' + if not revs: + return [] + start = self.start + length = self.length + inline = self._inline + iosize = self._io.size + buffer = util.buffer + + l = [] + ladd = l.append + + # preload the cache + self._chunkraw(revs[0], revs[-1]) + offset, data = self._chunkcache + + for rev in revs: + chunkstart = start(rev) + if inline: + chunkstart += (rev + 1) * iosize + chunklength = length(rev) + ladd(decompress(buffer(data, chunkstart - offset, chunklength))) + + return l def _chunkclear(self): self._chunkcache = (0, '') @@ -919,21 +946,22 @@ else: iterrev -= 1 e = index[iterrev] - chain.reverse() - base = iterrev if iterrev == cachedrev: # cache hit text = self._cache[2] + else: + chain.append(iterrev) + chain.reverse() # drop cache to save memory self._cache = None - self._chunkraw(base, rev) + bins = self._chunks(chain) if text is None: - text = str(self._chunkbase(base)) + text = str(bins[0]) + bins = bins[1:] - bins = [self._chunk(r) for r in chain] text = mdiff.patches(text, bins) text = self._checkhash(text, node, rev) @@ -943,10 +971,16 @@ def _checkhash(self, text, node, rev): p1, p2 = self.parents(node) + self.checkhash(text, p1, p2, node, rev) + return text + + def checkhash(self, text, p1, p2, node, rev=None): if node != hash(text, p1, p2): - raise RevlogError(_("integrity check failed on %s:%d") - % (self.indexfile, rev)) - return text + revornode = rev + if revornode is None: + revornode = templatefilters.short(hex(node)) + raise RevlogError(_("integrity check failed on %s:%s") + % (self.indexfile, revornode)) def checkinlinesize(self, tr, fp=None): if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline: @@ -987,7 +1021,8 @@ tr.replace(self.indexfile, trindex * self._io.size) self._chunkclear() - def addrevision(self, text, transaction, link, p1, p2, cachedelta=None): + def addrevision(self, text, transaction, link, p1, p2, cachedelta=None, + node=None): """add a revision to the log text - the revision data to add @@ -995,11 +1030,14 @@ link - the linkrev data to add p1, p2 - the parent nodeids of the revision cachedelta - an optional precomputed delta + node - nodeid of revision; typically node is not specified, and it is + computed by default as hash(text, p1, p2), however subclasses might + use different hashing method (and override checkhash() in such case) """ if link == nullrev: raise RevlogError(_("attempted to add linkrev -1 to %s") % self.indexfile) - node = hash(text, p1, p2) + node = node or hash(text, p1, p2) if node in self.nodemap: return node @@ -1063,9 +1101,7 @@ ifh.flush() basetext = self.revision(self.node(cachedelta[0])) btext[0] = mdiff.patch(basetext, cachedelta[1]) - chk = hash(btext[0], p1, p2) - if chk != node: - raise RevlogError(_("consistency error in delta")) + self.checkhash(btext[0], p1, p2, node) return btext[0] def builddelta(rev): diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/revset.py --- a/mercurial/revset.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/revset.py Wed Sep 18 14:52:16 2013 -0500 @@ -1599,6 +1599,75 @@ "_list": _list, } +# symbols which can't be used for a DoS attack for any given input +# (e.g. those which accept regexes as plain strings shouldn't be included) +# functions that just return a lot of changesets (like all) don't count here +safesymbols = set([ + "adds", + "all", + "ancestor", + "ancestors", + "_firstancestors", + "author", + "bisect", + "bisected", + "bookmark", + "branch", + "branchpoint", + "bumped", + "bundle", + "children", + "closed", + "converted", + "date", + "desc", + "descendants", + "_firstdescendants", + "destination", + "divergent", + "draft", + "extinct", + "extra", + "file", + "filelog", + "first", + "follow", + "_followfirst", + "head", + "heads", + "hidden", + "id", + "keyword", + "last", + "limit", + "_matchfiles", + "max", + "merge", + "min", + "modifies", + "obsolete", + "origin", + "outgoing", + "p1", + "p2", + "parents", + "present", + "public", + "remote", + "removes", + "rev", + "reverse", + "roots", + "sort", + "secret", + "matching", + "tag", + "tagged", + "user", + "unstable", + "_list", +]) + methods = { "range": rangeset, "dagrange": dagrange, @@ -1935,5 +2004,22 @@ output = '\n'.join((' '*l + s) for l, s in lines) return output +def depth(tree): + if isinstance(tree, tuple): + return max(map(depth, tree)) + 1 + else: + return 0 + +def funcsused(tree): + if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'): + return set() + else: + funcs = set() + for s in tree[1:]: + funcs |= funcsused(s) + if tree[0] == 'func': + funcs.add(tree[1][1]) + return funcs + # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values() diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/scmutil.py --- a/mercurial/scmutil.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/scmutil.py Wed Sep 18 14:52:16 2013 -0500 @@ -755,7 +755,8 @@ ctx = repo[None] dirstate = repo.dirstate - walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False) + walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False, + full=False) for abs, st in walkresults.iteritems(): dstate = dirstate[abs] if dstate == '?' and audit_path.check(abs): diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/subrepo.py --- a/mercurial/subrepo.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/subrepo.py Wed Sep 18 14:52:16 2013 -0500 @@ -237,6 +237,7 @@ # record merged .hgsubstate writestate(repo, sm) + return sm def _updateprompt(ui, sub, dirty, local, remote): if dirty: diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templatefilters.py --- a/mercurial/templatefilters.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templatefilters.py Wed Sep 18 14:52:16 2013 -0500 @@ -15,15 +15,15 @@ """ return text.replace('\n', '
\n') -agescales = [("year", 3600 * 24 * 365), - ("month", 3600 * 24 * 30), - ("week", 3600 * 24 * 7), - ("day", 3600 * 24), - ("hour", 3600), - ("minute", 60), - ("second", 1)] +agescales = [("year", 3600 * 24 * 365, 'Y'), + ("month", 3600 * 24 * 30, 'M'), + ("week", 3600 * 24 * 7, 'W'), + ("day", 3600 * 24, 'd'), + ("hour", 3600, 'h'), + ("minute", 60, 'm'), + ("second", 1, 's')] -def age(date): +def age(date, abbrev=False): """:age: Date. Returns a human-readable date/time difference between the given date/time and the current date/time. """ @@ -32,7 +32,9 @@ if c == 1: return t return t + "s" - def fmt(t, c): + def fmt(t, c, a): + if abbrev: + return "%d%s" % (c, a) return "%d %s" % (c, plural(t, c)) now = time.time() @@ -48,12 +50,12 @@ if delta > agescales[0][1] * 2: return util.shortdate(date) - for t, s in agescales: + for t, s, a in agescales: n = delta // s if n >= 2 or s == 1: if future: - return '%s from now' % fmt(t, n) - return '%s ago' % fmt(t, n) + return '%s from now' % fmt(t, n, a) + return '%s ago' % fmt(t, n, a) def basename(path): """:basename: Any text. Treats the text as a path, and returns the last diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/paper/fileannotate.tmpl --- a/mercurial/templates/paper/fileannotate.tmpl Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/paper/fileannotate.tmpl Wed Sep 18 14:52:16 2013 -0500 @@ -65,7 +65,6 @@ children {child%filerevchild} -{changesettag}
diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/paper/filecomparison.tmpl --- a/mercurial/templates/paper/filecomparison.tmpl Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/paper/filecomparison.tmpl Wed Sep 18 14:52:16 2013 -0500 @@ -64,7 +64,6 @@ children {child%filerevchild} -{changesettag}
diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/paper/filediff.tmpl --- a/mercurial/templates/paper/filediff.tmpl Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/paper/filediff.tmpl Wed Sep 18 14:52:16 2013 -0500 @@ -64,7 +64,6 @@ children {child%filerevchild} -{changesettag}
diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/paper/filerevision.tmpl --- a/mercurial/templates/paper/filerevision.tmpl Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/paper/filerevision.tmpl Wed Sep 18 14:52:16 2013 -0500 @@ -63,7 +63,6 @@ children {child%filerevchild} -{changesettag}
diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/paper/shortlog.tmpl --- a/mercurial/templates/paper/shortlog.tmpl Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/paper/shortlog.tmpl Wed Sep 18 14:52:16 2013 -0500 @@ -72,6 +72,18 @@ | rev {rev}: {changenav%navshort}
+ +
diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/paper/shortlogentry.tmpl --- a/mercurial/templates/paper/shortlogentry.tmpl Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/paper/shortlogentry.tmpl Wed Sep 18 14:52:16 2013 -0500 @@ -1,5 +1,5 @@ {date|rfc822date} {author|person} - {desc|strip|firstline|escape|nonempty}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags % '{name|escape} '}{bookmarks % '{name|escape} '} + {desc|strip|firstline|escape|nonempty}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag} diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/templates/static/mercurial.js --- a/mercurial/templates/static/mercurial.js Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/templates/static/mercurial.js Wed Sep 18 14:52:16 2013 -0500 @@ -23,7 +23,7 @@ ]; function Graph() { - + this.canvas = document.getElementById('graph'); if (window.G_vmlCanvasManager) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas); this.ctx = this.canvas.getContext('2d'); @@ -35,13 +35,13 @@ this.cell = [2, 0]; this.columns = 0; this.revlink = ''; - + this.scale = function(height) { this.bg_height = height; this.box_size = Math.floor(this.bg_height / 1.2); this.cell_height = this.box_size; } - + function colorPart(num) { num *= 255 num = num < 0 ? 0 : num; @@ -55,7 +55,7 @@ } this.setColor = function(color, bg, fg) { - + // Set the colour. // // If color is a string, expect an hexadecimal RGB @@ -81,11 +81,11 @@ this.ctx.strokeStyle = s; this.ctx.fillStyle = s; return s; - + } this.edge = function(x0, y0, x1, y1, color, width) { - + this.setColor(color, 0.0, 0.65); if(width >= 0) this.ctx.lineWidth = width; @@ -93,28 +93,28 @@ this.ctx.moveTo(x0, y0); this.ctx.lineTo(x1, y1); this.ctx.stroke(); - + } this.render = function(data) { - + var backgrounds = ''; var nodedata = ''; - + for (var i in data) { - + var parity = i % 2; this.cell[1] += this.bg_height; this.bg[1] += this.bg_height; - + var cur = data[i]; var node = cur[1]; var edges = cur[2]; var fold = false; - + var prevWidth = this.ctx.lineWidth; for (var j in edges) { - + line = edges[j]; start = line[0]; end = line[1]; @@ -125,44 +125,44 @@ var branchcolor = line[4]; if(branchcolor) color = branchcolor; - + if (end > this.columns || start > this.columns) { this.columns += 1; } - + if (start == this.columns && start > end) { var fold = true; } - + x0 = this.cell[0] + this.box_size * start + this.box_size / 2; y0 = this.bg[1] - this.bg_height / 2; x1 = this.cell[0] + this.box_size * end + this.box_size / 2; y1 = this.bg[1] + this.bg_height / 2; - + this.edge(x0, y0, x1, y1, color, width); - + } this.ctx.lineWidth = prevWidth; - + // Draw the revision node in the right column - + column = node[0] color = node[1] - + radius = this.box_size / 8; x = this.cell[0] + this.box_size * column + this.box_size / 2; y = this.bg[1] - this.bg_height / 2; var add = this.vertex(x, y, color, parity, cur); backgrounds += add[0]; nodedata += add[1]; - + if (fold) this.columns -= 1; - + } - + document.getElementById('nodebgs').innerHTML += backgrounds; document.getElementById('graphnodes').innerHTML += nodedata; - + } } @@ -258,6 +258,7 @@ // We want both: date + (age) node.textContent += ' ('+agevalue+')'; } else { + node.title = node.textContent; node.textContent = agevalue; } } @@ -297,3 +298,108 @@ setLinewrap(!getLinewrap()); } + +function format(str, replacements) { + return str.replace(/%(\w+)%/g, function(match, p1) { + return String(replacements[p1]); + }); +} + +function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) { + xfr = new XMLHttpRequest(); + xfr.onreadystatechange = function() { + if (xfr.readyState === 4) { + try { + if (xfr.status === 200) { + onsuccess(xfr.responseText); + } else { + throw 'server error'; + } + } catch (e) { + onerror(e); + } finally { + oncomplete(); + } + } + }; + + xfr.open(method, url); + xfr.send(); + onstart(); + return xfr; +} + +function removeByClassName(className) { + var nodes = document.getElementsByClassName(className); + while (nodes.length) { + nodes[0].parentNode.removeChild(nodes[0]); + } +} + +function docFromHTML(html) { + var doc = document.implementation.createHTMLDocument(''); + doc.documentElement.innerHTML = html; + return doc; +} + +function appendFormatHTML(element, formatStr, replacements) { + element.insertAdjacentHTML('beforeend', format(formatStr, replacements)); +} + +function ajaxScrollInit(urlFormat, + nextHash, + nextHashRegex, + containerSelector, + messageFormat) { + updateInitiated = false; + container = document.querySelector(containerSelector); + + function scrollHandler() { + if (updateInitiated) { + return; + } + + var scrollHeight = document.documentElement.scrollHeight; + var clientHeight = document.documentElement.clientHeight; + var scrollTop = document.body.scrollTop + || document.documentElement.scrollTop; + + if (scrollHeight - (scrollTop + clientHeight) < 50) { + updateInitiated = true; + + if (!nextHash) { + return; + } + + makeRequest( + format(urlFormat, {hash: nextHash}), + 'GET', + function onstart() { + }, + function onsuccess(htmlText) { + var m = htmlText.match(nextHashRegex); + nextHash = m ? m[1] : null; + + var doc = docFromHTML(htmlText); + var nodes = doc.querySelector(containerSelector).children; + while (nodes.length) { + var node = nodes[0]; + node = document.adoptNode(node); + container.appendChild(node); + } + process_dates(); + }, + function onerror(errorText) { + }, + function oncomplete() { + updateInitiated = false; + scrollHandler(); + } + ); + } + } + + window.addEventListener('scroll', scrollHandler); + window.addEventListener('resize', scrollHandler); + scrollHandler(); +} diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/ui.py --- a/mercurial/ui.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/ui.py Wed Sep 18 14:52:16 2013 -0500 @@ -176,7 +176,7 @@ alternates = [name] for n in alternates: - value = self._data(untrusted).get(section, name, None) + value = self._data(untrusted).get(section, n, None) if value is not None: name = n break @@ -184,10 +184,11 @@ value = default if self.debugflag and not untrusted and self._reportuntrusted: - uvalue = self._ucfg.get(section, name) - if uvalue is not None and uvalue != value: - self.debug("ignoring untrusted configuration option " - "%s.%s = %s\n" % (section, name, uvalue)) + for n in alternates: + uvalue = self._ucfg.get(section, n) + if uvalue is not None and uvalue != value: + self.debug("ignoring untrusted configuration option " + "%s.%s = %s\n" % (section, n, uvalue)) return value def configpath(self, section, name, default=None, untrusted=False): diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/unionrepo.py --- a/mercurial/unionrepo.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/unionrepo.py Wed Sep 18 14:52:16 2013 -0500 @@ -70,7 +70,7 @@ self.revlog2.rev(self.node(rev1)), self.revlog2.rev(self.node(rev2))) elif rev1 <= self.repotiprev and rev2 <= self.repotiprev: - return revlog.revlog.revdiff(self, rev1, rev2) + return self.baserevdiff(rev1, rev2) return mdiff.textdiff(self.revision(self.node(rev1)), self.revision(self.node(rev2))) @@ -93,10 +93,20 @@ text = self.revlog2.revision(node) self._cache = (node, rev, text) else: - text = revlog.revlog.revision(self, rev) + text = self.baserevision(rev) # already cached return text + def baserevision(self, nodeorrev): + # Revlog subclasses may override 'revision' method to modify format of + # content retrieved from revlog. To use unionrevlog with such class one + # needs to override 'baserevision' and make more specific call here. + return revlog.revlog.revision(self, nodeorrev) + + def baserevdiff(self, rev1, rev2): + # Exists for the same purpose as baserevision. + return revlog.revlog.revdiff(self, rev1, rev2) + def addrevision(self, text, transaction, link, p1=None, p2=None, d=None): raise NotImplementedError def addgroup(self, revs, linkmapper, transaction): @@ -114,6 +124,15 @@ unionrevlog.__init__(self, opener, self.indexfile, changelog2, linkmapper) + def baserevision(self, nodeorrev): + # Although changelog doesn't override 'revision' method, some extensions + # may replace this class with another that does. Same story with + # manifest and filelog classes. + return changelog.changelog.revision(self, nodeorrev) + + def baserevdiff(self, rev1, rev2): + return changelog.changelog.revdiff(self, rev1, rev2) + class unionmanifest(unionrevlog, manifest.manifest): def __init__(self, opener, opener2, linkmapper): manifest.manifest.__init__(self, opener) @@ -121,6 +140,12 @@ unionrevlog.__init__(self, opener, self.indexfile, manifest2, linkmapper) + def baserevision(self, nodeorrev): + return manifest.manifest.revision(self, nodeorrev) + + def baserevdiff(self, rev1, rev2): + return manifest.manifest.revdiff(self, rev1, rev2) + class unionfilelog(unionrevlog, filelog.filelog): def __init__(self, opener, path, opener2, linkmapper, repo): filelog.filelog.__init__(self, opener, path) @@ -129,6 +154,12 @@ linkmapper) self._repo = repo + def baserevision(self, nodeorrev): + return filelog.filelog.revision(self, nodeorrev) + + def baserevdiff(self, rev1, rev2): + return filelog.filelog.revdiff(self, rev1, rev2) + def _file(self, f): self._repo.file(f) diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/url.py --- a/mercurial/url.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/url.py Wed Sep 18 14:52:16 2013 -0500 @@ -108,8 +108,13 @@ def proxy_open(self, req, proxy, type_): host = req.get_host().split(':')[0] - if host in self.no_list: - return None + for e in self.no_list: + if host == e: + return None + if e.startswith('*.') and host.endswith(e[2:]): + return None + if e.startswith('.') and host.endswith(e[1:]): + return None # work around a bug in Python < 2.4.2 # (it leaves a "\n" at the end of Proxy-authorization headers) diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/util.h --- a/mercurial/util.h Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/util.h Wed Sep 18 14:52:16 2013 -0500 @@ -121,7 +121,12 @@ #ifdef _MSC_VER /* msvc 6.0 has problems */ #define inline __inline +typedef signed char int8_t; +typedef short int16_t; +typedef long int32_t; +typedef __int64 int64_t; typedef unsigned char uint8_t; +typedef unsigned short uint16_t; typedef unsigned long uint32_t; typedef unsigned __int64 uint64_t; #else @@ -146,6 +151,17 @@ #define inline __inline #endif +#if defined(_MSC_VER) && (_MSC_VER >= 1300) +static inline uint32_t getbe32(const char *c) +{ + return _byteswap_ulong(*(uint32_t *)c); +} +#elif GCC_VERSION >= 403 +static inline uint32_t getbe32(const char *c) +{ + return __builtin_bswap32(*(uint32_t *)c); +} +#else static inline uint32_t getbe32(const char *c) { const unsigned char *d = (const unsigned char *)c; @@ -155,7 +171,21 @@ (d[2] << 8) | (d[3])); } +#endif +#if defined(_MSC_VER) && (_MSC_VER >= 1300) +static inline void putbe32(uint32_t x, char *c) +{ + x = _byteswap_ulong(x); + *(uint32_t *)c = x; +} +#elif GCC_VERSION >= 403 +static inline void putbe32(uint32_t x, char *c) +{ + x = __builtin_bswap32(x); + *(uint32_t *)c = x; +} +#else static inline void putbe32(uint32_t x, char *c) { c[0] = (x >> 24) & 0xff; @@ -163,5 +193,6 @@ c[2] = (x >> 8) & 0xff; c[3] = (x) & 0xff; } +#endif #endif /* _HG_UTIL_H_ */ diff -r f2871c30e6a7 -r d4ed26beae0e mercurial/util.py --- a/mercurial/util.py Wed Sep 18 14:40:17 2013 -0400 +++ b/mercurial/util.py Wed Sep 18 14:52:16 2013 -0500 @@ -242,6 +242,10 @@ def __contains__(self, key): return key in self._cache + def clear(self): + self._cache.clear() + self._order = deque() + def lrucachefunc(func): '''cache most recent results of function calls''' cache = {} @@ -465,7 +469,8 @@ return str(val) origcmd = cmd cmd = quotecommand(cmd) - if sys.platform == 'plan9': + if sys.platform == 'plan9' and (sys.version_info[0] == 2 + and sys.version_info[1] < 7): # subprocess kludge to work around issues in half-baked Python # ports, notably bichued/python: if not cwd is None: diff -r f2871c30e6a7 -r d4ed26beae0e setup.py --- a/setup.py Wed Sep 18 14:40:17 2013 -0400 +++ b/setup.py Wed Sep 18 14:52:16 2013 -0500 @@ -423,14 +423,21 @@ pymodules = [] +common_depends = ['mercurial/util.h'] + extmodules = [ - Extension('mercurial.base85', ['mercurial/base85.c']), - Extension('mercurial.bdiff', ['mercurial/bdiff.c']), - Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']), - Extension('mercurial.mpatch', ['mercurial/mpatch.c']), + Extension('mercurial.base85', ['mercurial/base85.c'], + depends=common_depends), + Extension('mercurial.bdiff', ['mercurial/bdiff.c'], + depends=common_depends), + Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'], + depends=common_depends), + Extension('mercurial.mpatch', ['mercurial/mpatch.c'], + depends=common_depends), Extension('mercurial.parsers', ['mercurial/dirs.c', 'mercurial/parsers.c', - 'mercurial/pathencode.c']), + 'mercurial/pathencode.c'], + depends=common_depends), ] osutil_ldflags = [] @@ -443,7 +450,8 @@ pymodules.append('mercurial.pure.osutil') else: extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'], - extra_link_args=osutil_ldflags)) + extra_link_args=osutil_ldflags, + depends=common_depends)) # the -mno-cygwin option has been deprecated for years Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler @@ -467,7 +475,8 @@ if hasfunction(cc, 'inotify_add_watch'): inotify = Extension('hgext.inotify.linux._inotify', ['hgext/inotify/linux/_inotify.c'], - ['mercurial']) + ['mercurial'], + depends=common_depends) inotify.optional = True extmodules.append(inotify) packages.extend(['hgext.inotify', 'hgext.inotify.linux']) diff -r f2871c30e6a7 -r d4ed26beae0e tests/run-tests.py --- a/tests/run-tests.py Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/run-tests.py Wed Sep 18 14:52:16 2013 -0500 @@ -921,8 +921,10 @@ else: return ignore("doesn't match keyword") + if not lctest.startswith("test-"): + return skip("not a test file") for ext, func, out in testtypes: - if lctest.startswith("test-") and lctest.endswith(ext): + if lctest.endswith(ext): runner = func ref = os.path.join(TESTDIR, test + out) break diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-hgweb-commands.t Wed Sep 18 14:52:16 2013 -0500 @@ -305,6 +305,18 @@ | rev 3: (0) tip
+ + @@ -537,6 +549,153 @@ + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=stable&style=raw' | grep 'revision:' + revision: 2 + +Search with revset syntax + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=tip^&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "tip^" + + changeset: 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe + revision: 2 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: branch + branch: stable + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(all(),2)^&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "last(all(),2)^" + + changeset: 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe + revision: 2 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: branch + branch: stable + + changeset: a4f92ed23982be056b9852de5dfe873eaac7f0de + revision: 1 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: Added tag 1.0 for changeset 2ef0ac749a14 + branch: default + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(all(,2)^&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "last(all(,2)^" + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(al(),2)^&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "last(al(),2)^" + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=bookmark(anotherthing)&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "bookmark(anotherthing)" + + changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f + revision: 0 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: base + tag: 1.0 + bookmark: anotherthing + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=bookmark(abc)&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "bookmark(abc)" + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=deadbeef:&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "deadbeef:" + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=user("test")&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "user("test")" + + changeset: cad8025a2e87f88c06259790adfa15acb4080123 + revision: 3 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: branch commit with null character: \x00 (esc) + branch: unstable + tag: tip + bookmark: something + + changeset: 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe + revision: 2 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: branch + branch: stable + + changeset: a4f92ed23982be056b9852de5dfe873eaac7f0de + revision: 1 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: Added tag 1.0 for changeset 2ef0ac749a14 + branch: default + + changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f + revision: 0 + user: test + date: Thu, 01 Jan 1970 00:00:00 +0000 + summary: base + tag: 1.0 + bookmark: anotherthing + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=user("re:test")&style=raw' + 200 Script output follows + + + # HG changesets search + # Node ID cad8025a2e87f88c06259790adfa15acb4080123 + # Query "user("re:test")" + + File-related @@ -637,7 +796,6 @@ children 1d22e65f027e -
@@ -1256,8 +1414,10 @@ Graph json escape of multibyte character - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/' \ - > | grep -a '^var data =' + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/' > out + >>> for line in open("out"): + ... if line.startswith("var data ="): + ... print line, var data = [["061dd13ba3c3", [0, 1], [[0, 0, 1, -1, ""]], "\\u80fd", "test", "1970-01-01", ["unstable", true], ["tip"], ["something"]], ["cad8025a2e87", [0, 1], [[0, 0, 1, 3, "FF0000"]], "branch commit with null character: \x00", "test", "1970-01-01", ["unstable", false], [], []], ["1d22e65f027e", [0, 1], [[0, 0, 1, 3, ""]], "branch", "test", "1970-01-01", ["stable", true], [], []], ["a4f92ed23982", [0, 1], [[0, 0, 1, 3, ""]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], ["anotherthing"]]]; (esc) capabilities diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-hgweb-diffs.t --- a/tests/test-hgweb-diffs.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-hgweb-diffs.t Wed Sep 18 14:52:16 2013 -0500 @@ -269,7 +269,6 @@ children -
@@ -538,7 +537,6 @@ children -
@@ -638,7 +636,6 @@ children -
@@ -760,7 +757,6 @@ children -
@@ -884,7 +880,6 @@ children -
diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-hgweb-empty.t --- a/tests/test-hgweb-empty.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-hgweb-empty.t Wed Sep 18 14:52:16 2013 -0500 @@ -90,6 +90,18 @@ | rev -1:
+ +
@@ -185,6 +197,18 @@ | rev -1:
+ +
diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-hgweb-removed.t --- a/tests/test-hgweb-removed.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-hgweb-removed.t Wed Sep 18 14:52:16 2013 -0500 @@ -213,7 +213,6 @@ children -
diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-highlight.t --- a/tests/test-highlight.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-highlight.t Wed Sep 18 14:52:16 2013 -0500 @@ -132,7 +132,6 @@ children -
@@ -264,7 +263,6 @@ children -
diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-import-merge.t --- a/tests/test-import-merge.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-import-merge.t Wed Sep 18 14:52:16 2013 -0500 @@ -129,7 +129,9 @@ $ echo a>>a $ hg ci -m3 $ hg export 2 | head -7 > ../a.patch - $ hg export tip | tail -n +8 >> ../a.patch + $ hg export tip > out + >>> apatch = open("../a.patch", "a") + >>> apatch.write("".join(open("out").readlines()[7:])) $ cd .. $ hg clone -qr0 repo3 repo3-clone diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-lrucachedict.py --- a/tests/test-lrucachedict.py Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-lrucachedict.py Wed Sep 18 14:52:16 2013 -0500 @@ -31,5 +31,8 @@ d['f'] = 'vf' printifpresent(d, ['b', 'c', 'd', 'e', 'f']) + d.clear() + printifpresent(d, ['b', 'c', 'd', 'e', 'f']) + if __name__ == '__main__': test_lrucachedict() diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-lrucachedict.py.out --- a/tests/test-lrucachedict.py.out Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-lrucachedict.py.out Wed Sep 18 14:52:16 2013 -0500 @@ -24,3 +24,8 @@ 'e' in d: False 'f' in d: True d['f']: vf +'b' in d: False +'c' in d: False +'d' in d: False +'e' in d: False +'f' in d: False diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-mq-subrepo.t --- a/tests/test-mq-subrepo.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-mq-subrepo.t Wed Sep 18 14:52:16 2013 -0500 @@ -213,6 +213,7 @@ handle subrepos safely on qpush/qpop +(and we cannot qpop / qpush with a modified subrepo) $ mkrepo repo-2499-qpush $ mksubrepo sub @@ -220,31 +221,56 @@ $ hg -R sub ci -m0sub $ echo sub = sub > .hgsub $ hg add .hgsub - $ hg qnew -m0 0.diff + $ hg commit -m0 $ hg debugsub path sub source sub revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31 + $ echo foo > ./sub/a + $ hg -R sub commit -m foo + $ hg commit -m1 + $ hg qimport -r "0:tip" + $ hg -R sub id --id + aa037b301eba qpop + $ hg -R sub update 0000 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg qpop - popping 0.diff - patch queue now empty - $ hg status -AS - $ hg debugsub - -qpush - $ hg qpush - applying 0.diff + abort: local changed subrepos found, refresh first + [255] + $ hg revert sub + reverting subrepo sub + adding sub/a + $ hg qpop + popping 1.diff now at: 0.diff $ hg status -AS C .hgsub C .hgsubstate C sub/a - $ hg debugsub - path sub - source sub - revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31 + $ hg -R sub id --id + b2fdb12cd82b + +qpush + $ hg -R sub update 0000 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg qpush + abort: local changed subrepos found, refresh first + [255] + $ hg revert sub + reverting subrepo sub + adding sub/a + $ hg qpush + applying 1.diff + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + now at: 1.diff + $ hg status -AS + C .hgsub + C .hgsubstate + C sub/a + $ hg -R sub id --id + aa037b301eba $ cd .. diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-parse-date.t --- a/tests/test-parse-date.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-parse-date.t Wed Sep 18 14:52:16 2013 -0500 @@ -242,7 +242,7 @@ >>> yesterday = (datetime.date.today() - datetime.timedelta(days=1)).strftime("%b %d") >>> dates = open('dates', 'w') >>> dates.write(today + '\n') - >>> dates.write(yesterday) + >>> dates.write(yesterday + '\n') >>> dates.close() $ hg ci -d "`sed -n '1p' dates`" -m "today is a good day to code" $ hg log -d today --template '{desc}\n' diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-progress.t --- a/tests/test-progress.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-progress.t Wed Sep 18 14:52:16 2013 -0500 @@ -1,6 +1,14 @@ $ cat > loop.py < from mercurial import commands + > import time + > class incrementingtime(object): + > def __init__(self): + > self._time = 0.0 + > def __call__(self): + > self._time += 0.25 + > return self._time + > time.time = incrementingtime() > > def loop(ui, loops, **opts): > loops = int(loops) @@ -19,9 +27,14 @@ > if opts.get('parallel'): > ui.progress('other', i, 'other.%d' % i, 'othernum', total) > if nested: - > for j in range(2): - > ui.progress('nested', j, 'nested.%d' % j, 'nestnum', 2) - > ui.progress('nested', None, 'nested.done', 'nestnum', 2) + > nested_steps = 2 + > if i and i % 4 == 0: + > nested_steps = 5 + > for j in range(nested_steps): + > ui.progress( + > 'nested', j, 'nested.%d' % j, 'nestnum', nested_steps) + > ui.progress( + > 'nested', None, 'nested.done', 'nestnum', nested_steps) > ui.progress('loop', None, 'loop.done', 'loopnum', total) > > commands.norepo += " loop" @@ -69,6 +82,24 @@ loop [===============================> ] 2/3\r (no-eol) (esc) \r (no-eol) (esc) +Test nested long-lived topic which has the same name as a short-lived +peer. We shouldn't get stuck showing the short-lived inner steps, and +should go back to skipping the inner steps when the slow nested step +finishes. + + $ hg -y loop 7 --nested + \r (no-eol) (esc) + loop [ ] 0/7\r (no-eol) (esc) + loop [=====> ] 1/7\r (no-eol) (esc) + loop [============> ] 2/7\r (no-eol) (esc) + loop [===================> ] 3/7\r (no-eol) (esc) + loop [==========================> ] 4/7\r (no-eol) (esc) + nested [==========================> ] 3/5\r (no-eol) (esc) + nested [===================================> ] 4/5\r (no-eol) (esc) + loop [=================================> ] 5/7\r (no-eol) (esc) + loop [========================================> ] 6/7\r (no-eol) (esc) + \r (no-eol) (esc) + $ hg --config progress.changedelay=0 -y loop 3 --nested \r (no-eol) (esc) diff -r f2871c30e6a7 -r d4ed26beae0e tests/test-symlink-placeholder.t --- a/tests/test-symlink-placeholder.t Wed Sep 18 14:40:17 2013 -0400 +++ b/tests/test-symlink-placeholder.t Wed Sep 18 14:52:16 2013 -0500 @@ -41,6 +41,13 @@ a (no-eol) $ hg --config extensions.n=$TESTTMP/nolink.py st --debug +Empty placeholder: + + $ rm b + $ touch b + $ hg --config extensions.n=$TESTTMP/nolink.py st --debug + ignoring suspect symlink placeholder "b" + Write binary data to the placeholder: >>> open('b', 'w').write('this is a binary\0')