--- a/contrib/check-code.py Tue May 14 18:43:53 2013 -0500
+++ b/contrib/check-code.py Tue May 14 18:52:52 2013 -0500
@@ -109,6 +109,16 @@
(r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
winglobmsg),
(r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+ (r'^ reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+ (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+ (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+ (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
+ '\$TESTTMP/unix-repo$'),
+ (r'^ moving \S+/.*[^)]$', winglobmsg),
+ (r'^ no changes made to subrepo since.*/.*[^)]$',
+ winglobmsg, '\$TESTTMP/unix-repo$'),
+ (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$',
+ winglobmsg, '\$TESTTMP/unix-repo$'),
],
# warnings
[
--- a/hgext/convert/common.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/common.py Tue May 14 18:52:52 2013 -0500
@@ -5,7 +5,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import base64, errno, subprocess, os, datetime
+import base64, errno, subprocess, os, datetime, re
import cPickle as pickle
from mercurial import util
from mercurial.i18n import _
@@ -63,6 +63,14 @@
self.encoding = 'utf-8'
+ def checkhexformat(self, revstr):
+ """ fails if revstr is not a 40 byte hex. mercurial and git both uses
+ such format for their revision numbering
+ """
+ if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
+ raise util.Abort(_('splicemap entry %s is not a valid revision'
+ ' identifier') % revstr)
+
def before(self):
pass
@@ -164,6 +172,13 @@
"""
return {}
+ def checkrevformat(self, revstr):
+ """revstr is a string that describes a revision in the given
+ source control system. Return true if revstr has correct
+ format.
+ """
+ return True
+
class converter_sink(object):
"""Conversion sink (target) interface"""
@@ -424,34 +439,6 @@
self.fp.close()
self.fp = None
-def parsesplicemap(path):
- """Parse a splicemap, return a child/parents dictionary."""
- if not path:
- return {}
- m = {}
- try:
- fp = open(path, 'r')
- for i, line in enumerate(fp):
- line = line.splitlines()[0].rstrip()
- if not line:
- # Ignore blank lines
- continue
- try:
- child, parents = line.split(' ', 1)
- parents = parents.replace(',', ' ').split()
- except ValueError:
- raise util.Abort(_('syntax error in %s(%d): child parent1'
- '[,parent2] expected') % (path, i + 1))
- pp = []
- for p in parents:
- if p not in pp:
- pp.append(p)
- m[child] = pp
- except IOError, e:
- if e.errno != errno.ENOENT:
- raise
- return m
-
def makedatetimestamp(t):
"""Like util.makedate() but for time t instead of current time"""
delta = (datetime.datetime.utcfromtimestamp(t) -
--- a/hgext/convert/convcmd.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/convcmd.py Tue May 14 18:52:52 2013 -0500
@@ -15,9 +15,9 @@
from gnuarch import gnuarch_source
from bzr import bzr_source
from p4 import p4_source
-import filemap, common
+import filemap
-import os, shutil
+import os, shutil, shlex
from mercurial import hg, util, encoding
from mercurial.i18n import _
@@ -118,9 +118,53 @@
self.readauthormap(opts.get('authormap'))
self.authorfile = self.dest.authorfile()
- self.splicemap = common.parsesplicemap(opts.get('splicemap'))
+ self.splicemap = self.parsesplicemap(opts.get('splicemap'))
self.branchmap = mapfile(ui, opts.get('branchmap'))
+ def parsesplicemap(self, path):
+ """ check and validate the splicemap format and
+ return a child/parents dictionary.
+ Format checking has two parts.
+ 1. generic format which is same across all source types
+ 2. specific format checking which may be different for
+ different source type. This logic is implemented in
+ checkrevformat function in source files like
+ hg.py, subversion.py etc.
+ """
+
+ if not path:
+ return {}
+ m = {}
+ try:
+ fp = open(path, 'r')
+ for i, line in enumerate(fp):
+ line = line.splitlines()[0].rstrip()
+ if not line:
+ # Ignore blank lines
+ continue
+ # split line
+ lex = shlex.shlex(line, posix=True)
+ lex.whitespace_split = True
+ lex.whitespace += ','
+ line = list(lex)
+ # check number of parents
+ if not (2 <= len(line) <= 3):
+ raise util.Abort(_('syntax error in %s(%d): child parent1'
+ '[,parent2] expected') % (path, i + 1))
+ for part in line:
+ self.source.checkrevformat(part)
+ child, p1, p2 = line[0], line[1:2], line[2:]
+ if p1 == p2:
+ m[child] = p1
+ else:
+ m[child] = p1 + p2
+ # if file does not exist or error reading, exit
+ except IOError:
+ raise util.Abort(_('splicemap file not found or error reading %s:')
+ % path)
+ return m
+
+
def walktree(self, heads):
'''Return a mapping that identifies the uncommitted parents of every
uncommitted changeset.'''
--- a/hgext/convert/git.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/git.py Tue May 14 18:52:52 2013 -0500
@@ -296,3 +296,8 @@
pass
return bookmarks
+
+ def checkrevformat(self, revstr):
+ """ git revision string is a 40 byte hex """
+ self.checkhexformat(revstr)
+
--- a/hgext/convert/hg.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/hg.py Tue May 14 18:52:52 2013 -0500
@@ -397,3 +397,7 @@
def getbookmarks(self):
return bookmarks.listbookmarks(self.repo)
+
+ def checkrevformat(self, revstr):
+ """ Mercurial, revision string is a 40 byte hex """
+ self.checkhexformat(revstr)
--- a/hgext/convert/subversion.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/subversion.py Tue May 14 18:52:52 2013 -0500
@@ -452,6 +452,14 @@
del self.commits[rev]
return commit
+ def checkrevformat(self, revstr):
+ """ fails if revision format does not match the correct format"""
+ if not re.match(r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
+ '[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]'
+ '{12,12}(.*)\@[0-9]+$',revstr):
+ raise util.Abort(_('splicemap entry %s is not a valid revision'
+ ' identifier') % revstr)
+
def gettags(self):
tags = {}
if self.tags is None:
--- a/hgext/inotify/client.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/inotify/client.py Tue May 14 18:52:52 2013 -0500
@@ -159,7 +159,8 @@
vdirs = cs.read(nbytes)
if vdirs:
for vdir in vdirs.split('\0'):
- match.dir(vdir)
+ if match.explicitdir:
+ match.explicitdir(vdir)
return results
--- a/hgext/purge.py Tue May 14 18:43:53 2013 -0500
+++ b/hgext/purge.py Tue May 14 18:52:52 2013 -0500
@@ -97,7 +97,7 @@
directories = []
match = scmutil.match(repo[None], dirs, opts)
- match.dir = directories.append
+ match.explicitdir = match.traversedir = directories.append
status = repo.status(match=match, ignored=opts['all'], unknown=True)
for f in sorted(status[4] + status[5]):
--- a/mercurial/commands.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/commands.py Tue May 14 18:52:52 2013 -0500
@@ -767,9 +767,8 @@
('d', 'delete', False, _('delete a given bookmark')),
('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
('i', 'inactive', False, _('mark a bookmark inactive'))],
- _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
-def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
- rename=None, inactive=False):
+ _('hg bookmarks [OPTIONS]... [NAME]...'))
+def bookmark(ui, repo, *names, **opts):
'''track a line of development with movable markers
Bookmarks are pointers to certain commits that move when committing.
@@ -796,6 +795,12 @@
active even if -i/--inactive is not given. If no NAME is given, the
current active bookmark will be marked inactive.
'''
+ force = opts.get('force')
+ rev = opts.get('rev')
+ delete = opts.get('delete')
+ rename = opts.get('rename')
+ inactive = opts.get('inactive')
+
hexfn = ui.debugflag and hex or short
marks = repo._bookmarks
cur = repo.changectx('.').node()
@@ -846,21 +851,24 @@
raise util.Abort(_("--rev is incompatible with --delete"))
if rename and rev:
raise util.Abort(_("--rev is incompatible with --rename"))
- if mark is None and (delete or rev):
+ if not names and (delete or rev):
raise util.Abort(_("bookmark name required"))
if delete:
- if mark not in marks:
- raise util.Abort(_("bookmark '%s' does not exist") % mark)
- if mark == repo._bookmarkcurrent:
- bookmarks.setcurrent(repo, None)
- del marks[mark]
+ for mark in names:
+ if mark not in marks:
+ raise util.Abort(_("bookmark '%s' does not exist") % mark)
+ if mark == repo._bookmarkcurrent:
+ bookmarks.setcurrent(repo, None)
+ del marks[mark]
marks.write()
elif rename:
- if mark is None:
+ if not names:
raise util.Abort(_("new bookmark name required"))
- mark = checkformat(mark)
+ elif len(names) > 1:
+ raise util.Abort(_("only one new bookmark name allowed"))
+ mark = checkformat(names[0])
if rename not in marks:
raise util.Abort(_("bookmark '%s' does not exist") % rename)
checkconflict(repo, mark, force)
@@ -870,19 +878,23 @@
del marks[rename]
marks.write()
- elif mark is not None:
- mark = checkformat(mark)
- if inactive and mark == repo._bookmarkcurrent:
- bookmarks.setcurrent(repo, None)
- return
- tgt = cur
- if rev:
- tgt = scmutil.revsingle(repo, rev).node()
- checkconflict(repo, mark, force, tgt)
- marks[mark] = tgt
- if not inactive and cur == marks[mark] and not rev:
- bookmarks.setcurrent(repo, mark)
- elif cur != tgt and mark == repo._bookmarkcurrent:
+ elif names:
+ newact = None
+ for mark in names:
+ mark = checkformat(mark)
+ if newact is None:
+ newact = mark
+ if inactive and mark == repo._bookmarkcurrent:
+ bookmarks.setcurrent(repo, None)
+ return
+ tgt = cur
+ if rev:
+ tgt = scmutil.revsingle(repo, rev).node()
+ checkconflict(repo, mark, force, tgt)
+ marks[mark] = tgt
+ if not inactive and cur == marks[newact] and not rev:
+ bookmarks.setcurrent(repo, newact)
+ elif cur != tgt and newact == repo._bookmarkcurrent:
bookmarks.setcurrent(repo, None)
marks.write()
--- a/mercurial/context.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/context.py Tue May 14 18:52:52 2013 -0500
@@ -398,7 +398,7 @@
("bad args: changeid=%r, fileid=%r, changectx=%r"
% (changeid, fileid, changectx))
- if filelog:
+ if filelog is not None:
self._filelog = filelog
if changeid is not None:
@@ -437,7 +437,9 @@
@propertycache
def _changeid(self):
- if '_changectx' in self.__dict__:
+ if '_changeid' in self.__dict__:
+ return self._changeid
+ elif '_changectx' in self.__dict__:
return self._changectx.rev()
else:
return self._filelog.linkrev(self._filerev)
@@ -1167,7 +1169,7 @@
self._changeid = None
self._filerev = self._filenode = None
- if filelog:
+ if filelog is not None:
self._filelog = filelog
if workingctx:
self._changectx = workingctx
--- a/mercurial/copies.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/copies.py Tue May 14 18:52:52 2013 -0500
@@ -222,65 +222,8 @@
fullcopy = {}
diverge = {}
- def related(f1, f2, limit):
- # Walk back to common ancestor to see if the two files originate
- # from the same file. Since workingfilectx's rev() is None it messes
- # up the integer comparison logic, hence the pre-step check for
- # None (f1 and f2 can only be workingfilectx's initially).
-
- if f1 == f2:
- return f1 # a match
-
- g1, g2 = f1.ancestors(), f2.ancestors()
- try:
- f1r, f2r = f1.rev(), f2.rev()
-
- if f1r is None:
- f1 = g1.next()
- if f2r is None:
- f2 = g2.next()
-
- while True:
- f1r, f2r = f1.rev(), f2.rev()
- if f1r > f2r:
- f1 = g1.next()
- elif f2r > f1r:
- f2 = g2.next()
- elif f1 == f2:
- return f1 # a match
- elif f1r == f2r or f1r < limit or f2r < limit:
- return False # copy no longer relevant
- except StopIteration:
- return False
-
- def checkcopies(f, m1, m2):
- '''check possible copies of f from m1 to m2'''
- of = None
- seen = set([f])
- for oc in ctx(f, m1[f]).ancestors():
- ocr = oc.rev()
- of = oc.path()
- if of in seen:
- # check limit late - grab last rename before
- if ocr < limit:
- break
- continue
- seen.add(of)
-
- fullcopy[f] = of # remember for dir rename detection
- if of not in m2:
- continue # no match, keep looking
- if m2[of] == ma.get(of):
- break # no merge needed, quit early
- c2 = ctx(of, m2[of])
- cr = related(oc, c2, ca.rev())
- if cr and (of == f or of == c2.path()): # non-divergent
- copy[f] = of
- of = None
- break
-
- if of in ma:
- diverge.setdefault(of, []).append(f)
+ def _checkcopies(f, m1, m2):
+ checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
repo.ui.debug(" searching for copies back to rev %d\n" % limit)
@@ -295,9 +238,9 @@
% "\n ".join(u2))
for f in u1:
- checkcopies(f, m1, m2)
+ _checkcopies(f, m1, m2)
for f in u2:
- checkcopies(f, m2, m1)
+ _checkcopies(f, m2, m1)
renamedelete = {}
renamedelete2 = set()
@@ -386,3 +329,78 @@
break
return copy, movewithdir, diverge, renamedelete
+
+def checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy):
+ """
+ check possible copies of f from m1 to m2
+
+ ctx = function accepting (filename, node) that returns a filectx.
+ f = the filename to check
+ m1 = the source manifest
+ m2 = the destination manifest
+ ca = the changectx of the common ancestor
+ limit = the rev number to not search beyond
+ diverge = record all diverges in this dict
+ copy = record all non-divergent copies in this dict
+ fullcopy = record all copies in this dict
+ """
+
+ ma = ca.manifest()
+
+ def _related(f1, f2, limit):
+ # Walk back to common ancestor to see if the two files originate
+ # from the same file. Since workingfilectx's rev() is None it messes
+ # up the integer comparison logic, hence the pre-step check for
+ # None (f1 and f2 can only be workingfilectx's initially).
+
+ if f1 == f2:
+ return f1 # a match
+
+ g1, g2 = f1.ancestors(), f2.ancestors()
+ try:
+ f1r, f2r = f1.rev(), f2.rev()
+
+ if f1r is None:
+ f1 = g1.next()
+ if f2r is None:
+ f2 = g2.next()
+
+ while True:
+ f1r, f2r = f1.rev(), f2.rev()
+ if f1r > f2r:
+ f1 = g1.next()
+ elif f2r > f1r:
+ f2 = g2.next()
+ elif f1 == f2:
+ return f1 # a match
+ elif f1r == f2r or f1r < limit or f2r < limit:
+ return False # copy no longer relevant
+ except StopIteration:
+ return False
+
+ of = None
+ seen = set([f])
+ for oc in ctx(f, m1[f]).ancestors():
+ ocr = oc.rev()
+ of = oc.path()
+ if of in seen:
+ # check limit late - grab last rename before
+ if ocr < limit:
+ break
+ continue
+ seen.add(of)
+
+ fullcopy[f] = of # remember for dir rename detection
+ if of not in m2:
+ continue # no match, keep looking
+ if m2[of] == ma.get(of):
+ break # no merge needed, quit early
+ c2 = ctx(of, m2[of])
+ cr = _related(oc, c2, ca.rev())
+ if cr and (of == f or of == c2.path()): # non-divergent
+ copy[f] = of
+ of = None
+ break
+
+ if of in ma:
+ diverge.setdefault(of, []).append(f)
--- a/mercurial/dirstate.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/dirstate.py Tue May 14 18:52:52 2013 -0500
@@ -522,18 +522,15 @@
return True
return False
- def walk(self, match, subrepos, unknown, ignored):
- '''
- Walk recursively through the directory tree, finding all files
- matched by match.
+ def _walkexplicit(self, match, subrepos):
+ '''Get stat data about the files explicitly specified by match.
- Return a dict mapping filename to stat-like object (either
- mercurial.osutil.stat instance or return value of os.stat()).
- '''
-
- def fwarn(f, msg):
- self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
- return False
+ Return a triple (results, dirsfound, dirsnotfound).
+ - results is a mapping from filename to stat result. It also contains
+ listings mapping subrepos and .hg to None.
+ - dirsfound is a list of files found to be directories.
+ - dirsnotfound is a list of files that the dirstate thinks are
+ directories and that were not found.'''
def badtype(mode):
kind = _('unknown')
@@ -549,41 +546,23 @@
kind = _('directory')
return _('unsupported file type (type is %s)') % kind
- ignore = self._ignore
- dirignore = self._dirignore
- if ignored:
- ignore = util.never
- dirignore = util.never
- elif not unknown:
- # if unknown and ignored are False, skip step 2
- ignore = util.always
- dirignore = util.always
-
- matchfn = match.matchfn
- matchalways = match.always()
+ matchedir = match.explicitdir
badfn = match.bad
dmap = self._map
normpath = util.normpath
- listdir = osutil.listdir
lstat = os.lstat
getkind = stat.S_IFMT
dirkind = stat.S_IFDIR
regkind = stat.S_IFREG
lnkkind = stat.S_IFLNK
join = self._join
- work = []
- wadd = work.append
+ dirsfound = []
+ foundadd = dirsfound.append
+ dirsnotfound = []
+ notfoundadd = dirsnotfound.append
- exact = skipstep3 = False
- if matchfn == match.exact: # match.exact
- exact = True
- dirignore = util.always # skip step 2
- elif match.files() and not match.anypats(): # match.match, no patterns
- skipstep3 = True
-
- if not exact and self._checkcase:
+ if match.matchfn != match.exact and self._checkcase:
normalize = self._normalize
- skipstep3 = False
else:
normalize = None
@@ -604,7 +583,6 @@
results = dict.fromkeys(subrepos)
results['.hg'] = None
- # step 1: find all explicit files
for ff in files:
if normalize:
nf = normalize(normpath(ff), False, True)
@@ -617,13 +595,12 @@
st = lstat(join(nf))
kind = getkind(st.st_mode)
if kind == dirkind:
- skipstep3 = False
if nf in dmap:
#file deleted on disk but still in dirstate
results[nf] = None
- match.dir(nf)
- if not dirignore(nf):
- wadd(nf)
+ if matchedir:
+ matchedir(nf)
+ foundadd(nf)
elif kind == regkind or kind == lnkkind:
results[nf] = st
else:
@@ -637,12 +614,75 @@
prefix = nf + "/"
for fn in dmap:
if fn.startswith(prefix):
- match.dir(nf)
- skipstep3 = False
+ if matchedir:
+ matchedir(nf)
+ notfoundadd(nf)
break
else:
badfn(ff, inst.strerror)
+ return results, dirsfound, dirsnotfound
+
+ def walk(self, match, subrepos, unknown, ignored, full=True):
+ '''
+ Walk recursively through the directory tree, finding all files
+ matched by match.
+
+ If full is False, maybe skip some known-clean files.
+
+ Return a dict mapping filename to stat-like object (either
+ mercurial.osutil.stat instance or return value of os.stat()).
+
+ '''
+ # full is a flag that extensions that hook into walk can use -- this
+ # implementation doesn't use it at all. This satisfies the contract
+ # because we only guarantee a "maybe".
+
+ def fwarn(f, msg):
+ self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
+ return False
+
+ ignore = self._ignore
+ dirignore = self._dirignore
+ if ignored:
+ ignore = util.never
+ dirignore = util.never
+ elif not unknown:
+ # if unknown and ignored are False, skip step 2
+ ignore = util.always
+ dirignore = util.always
+
+ matchfn = match.matchfn
+ matchalways = match.always()
+ matchtdir = match.traversedir
+ dmap = self._map
+ listdir = osutil.listdir
+ lstat = os.lstat
+ dirkind = stat.S_IFDIR
+ regkind = stat.S_IFREG
+ lnkkind = stat.S_IFLNK
+ join = self._join
+
+ exact = skipstep3 = False
+ if matchfn == match.exact: # match.exact
+ exact = True
+ dirignore = util.always # skip step 2
+ elif match.files() and not match.anypats(): # match.match, no patterns
+ skipstep3 = True
+
+ if not exact and self._checkcase:
+ normalize = self._normalize
+ skipstep3 = False
+ else:
+ normalize = None
+
+ # step 1: find all explicit files
+ results, work, dirsnotfound = self._walkexplicit(match, subrepos)
+
+ skipstep3 = skipstep3 and not (work or dirsnotfound)
+ work = [d for d in work if not dirignore(d)]
+ wadd = work.append
+
# step 2: visit subdirectories
while work:
nd = work.pop()
@@ -666,7 +706,8 @@
if nf not in results:
if kind == dirkind:
if not ignore(nf):
- match.dir(nf)
+ if matchtdir:
+ matchtdir(nf)
wadd(nf)
if nf in dmap and (matchalways or matchfn(nf)):
results[nf] = None
@@ -766,8 +807,13 @@
lnkkind = stat.S_IFLNK
- for fn, st in self.walk(match, subrepos, listunknown,
- listignored).iteritems():
+ # We need to do full walks when either
+ # - we're listing all clean files, or
+ # - match.traversedir does something, because match.traversedir should
+ # be called for every dir in the working dir
+ full = listclean or match.traversedir is not None
+ for fn, st in self.walk(match, subrepos, listunknown, listignored,
+ full=full).iteritems():
if fn not in dmap:
if (listignored or mexact(fn)) and dirignore(fn):
if listignored:
--- a/mercurial/filelog.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/filelog.py Tue May 14 18:52:52 2013 -0500
@@ -31,7 +31,7 @@
class filelog(revlog.revlog):
def __init__(self, opener, path):
- revlog.revlog.__init__(self, opener,
+ super(filelog, self).__init__(opener,
"/".join(("data", path + ".i")))
def read(self, node):
@@ -64,7 +64,7 @@
return len(self.read(node))
# XXX if self.read(node).startswith("\1\n"), this returns (size+4)
- return revlog.revlog.size(self, rev)
+ return super(filelog, self).size(rev)
def cmp(self, node, text):
"""compare text with a given file revision
@@ -76,7 +76,7 @@
if text.startswith('\1\n'):
t = '\1\n\1\n' + text
- samehashes = not revlog.revlog.cmp(self, node, t)
+ samehashes = not super(filelog, self).cmp(node, t)
if samehashes:
return False
--- a/mercurial/fileset.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/fileset.py Tue May 14 18:52:52 2013 -0500
@@ -263,23 +263,10 @@
raise error.ParseError(_('invalid match pattern: %s') % e)
return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
-_units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
- M=2**20, MB=2**20, G=2**30, GB=2**30)
-
-def _sizetoint(s):
- try:
- s = s.strip()
- for k, v in _units.items():
- if s.endswith(k):
- return int(float(s[:-len(k)]) * v)
- return int(s)
- except ValueError:
- raise error.ParseError(_("couldn't parse size: %s") % s)
-
def _sizetomax(s):
try:
s = s.strip()
- for k, v in _units.items():
+ for k, v in util._sizeunits:
if s.endswith(k):
# max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
n = s[:-len(k)]
@@ -306,23 +293,23 @@
expr = getstring(x, _("size requires an expression")).strip()
if '-' in expr: # do we have a range?
a, b = expr.split('-', 1)
- a = _sizetoint(a)
- b = _sizetoint(b)
+ a = util.sizetoint(a)
+ b = util.sizetoint(b)
m = lambda x: x >= a and x <= b
elif expr.startswith("<="):
- a = _sizetoint(expr[2:])
+ a = util.sizetoint(expr[2:])
m = lambda x: x <= a
elif expr.startswith("<"):
- a = _sizetoint(expr[1:])
+ a = util.sizetoint(expr[1:])
m = lambda x: x < a
elif expr.startswith(">="):
- a = _sizetoint(expr[2:])
+ a = util.sizetoint(expr[2:])
m = lambda x: x >= a
elif expr.startswith(">"):
- a = _sizetoint(expr[1:])
+ a = util.sizetoint(expr[1:])
m = lambda x: x > a
elif expr[0].isdigit or expr[0] == '.':
- a = _sizetoint(expr)
+ a = util.sizetoint(expr)
b = _sizetomax(expr)
m = lambda x: x >= a and x <= b
else:
--- a/mercurial/help/templates.txt Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/help/templates.txt Tue May 14 18:52:52 2013 -0500
@@ -6,8 +6,8 @@
You can customize output for any "log-like" command: log,
outgoing, incoming, tip, parents, heads and glog.
-Four styles are packaged with Mercurial: default (the style used
-when no explicit preference is passed), compact, changelog,
+Five styles are packaged with Mercurial: default (the style used
+when no explicit preference is passed), compact, changelog, phases
and xml.
Usage::
--- a/mercurial/httpclient/__init__.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/httpclient/__init__.py Tue May 14 18:52:52 2013 -0500
@@ -37,6 +37,9 @@
* implements ssl inline instead of in a different class
"""
+# Many functions in this file have too many arguments.
+# pylint: disable=R0913
+
import cStringIO
import errno
import httplib
@@ -117,6 +120,8 @@
def _close(self):
if self._reader is not None:
+ # We're a friend of the reader class here.
+ # pylint: disable=W0212
self._reader._close()
def readline(self):
@@ -137,6 +142,7 @@
return ''.join(blocks)
def read(self, length=None):
+ """Read data from the response body."""
# if length is None, unbounded read
while (not self.complete() # never select on a finished read
and (not length # unbounded, so we wait for complete()
@@ -150,7 +156,8 @@
return r
def _select(self):
- r, _, _ = select.select([self.sock], [], [], self._timeout)
+ r, unused_write, unused_err = select.select(
+ [self.sock], [], [], self._timeout)
if not r:
# socket was not readable. If the response is not
# complete, raise a timeout.
@@ -170,13 +177,16 @@
# raise an exception if this is an invalid situation.
if not data:
if self._reader:
+ # We're a friend of the reader class here.
+ # pylint: disable=W0212
self._reader._close()
return False
else:
self._load_response(data)
return True
- def _load_response(self, data):
+ # This method gets replaced by _load later, which confuses pylint.
+ def _load_response(self, data): # pylint: disable=E0202
# Being here implies we're not at the end of the headers yet,
# since at the end of this method if headers were completely
# loaded we replace this method with the load() method of the
@@ -201,7 +211,7 @@
# handle 100-continue response
hdrs, body = self.raw_response.split(self._end_headers, 1)
- http_ver, status = hdrs.split(' ', 1)
+ unused_http_ver, status = hdrs.split(' ', 1)
if status.startswith('100'):
self.raw_response = body
self.continued = True
@@ -260,9 +270,13 @@
self.will_close = True
if body:
+ # We're a friend of the reader class here.
+ # pylint: disable=W0212
self._reader._load(body)
logger.debug('headers complete')
self.headers = headers
+ # We're a friend of the reader class here.
+ # pylint: disable=W0212
self._load_response = self._reader._load
@@ -335,9 +349,9 @@
self._proxy_port))
if self.ssl:
# TODO proxy header support
- data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
- self.port),
- {}, HTTP_VER_1_0)
+ data = self._buildheaders('CONNECT', '%s:%d' % (self.host,
+ self.port),
+ {}, HTTP_VER_1_0)
sock.send(data)
sock.setblocking(0)
r = self.response_class(sock, self.timeout, 'CONNECT')
@@ -345,6 +359,9 @@
'Timed out waiting for CONNECT response from proxy')
while not r.complete():
try:
+ # We're a friend of the response class, so let
+ # us use the private attribute.
+ # pylint: disable=W0212
if not r._select():
if not r.complete():
raise timeout_exc
@@ -376,7 +393,7 @@
sock.setblocking(0)
self.sock = sock
- def buildheaders(self, method, path, headers, http_ver):
+ def _buildheaders(self, method, path, headers, http_ver):
if self.ssl and self.port == 443 or self.port == 80:
# default port for protocol, so leave it out
hdrhost = self.host
@@ -437,6 +454,11 @@
return True
return False
+ def _reconnect(self, where):
+ logger.info('reconnecting during %s', where)
+ self.close()
+ self._connect()
+
def request(self, method, path, body=None, headers={},
expect_continue=False):
"""Send a request to the server.
@@ -474,16 +496,11 @@
raise BadRequestData('body has no __len__() nor read()')
self._connect()
- outgoing_headers = self.buildheaders(
+ outgoing_headers = self._buildheaders(
method, path, hdrs, self.http_version)
response = None
first = True
- def reconnect(where):
- logger.info('reconnecting during %s', where)
- self.close()
- self._connect()
-
while ((outgoing_headers or body)
and not (response and response.complete())):
select_timeout = self.timeout
@@ -523,14 +540,17 @@
except socket.sslerror, e:
if e.args[0] != socket.SSL_ERROR_WANT_READ:
raise
- logger.debug(
- 'SSL_ERROR_WANT_READ while sending data, retrying...')
+ logger.debug('SSL_ERROR_WANT_READ while sending '
+ 'data, retrying...')
continue
if not data:
logger.info('socket appears closed in read')
self.sock = None
self._current_response = None
if response is not None:
+ # We're a friend of the response class, so let
+ # us use the private attribute.
+ # pylint: disable=W0212
response._close()
# This if/elif ladder is a bit subtle,
# comments in each branch should help.
@@ -550,7 +570,7 @@
logger.info(
'Connection appeared closed in read on first'
' request loop iteration, will retry.')
- reconnect('read')
+ self._reconnect('read')
continue
else:
# We didn't just send the first data hunk,
@@ -563,7 +583,11 @@
'response was missing or incomplete!')
logger.debug('read %d bytes in request()', len(data))
if response is None:
- response = self.response_class(r[0], self.timeout, method)
+ response = self.response_class(
+ r[0], self.timeout, method)
+ # We're a friend of the response class, so let us
+ # use the private attribute.
+ # pylint: disable=W0212
response._load_response(data)
# Jump to the next select() call so we load more
# data if the server is still sending us content.
@@ -576,6 +600,8 @@
if w and out:
try:
if getattr(out, 'read', False):
+ # pylint guesses the type of out incorrectly here
+ # pylint: disable=E1103
data = out.read(OUTGOING_BUFFER_SIZE)
if not data:
continue
@@ -599,14 +625,10 @@
elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
and not first):
raise
- reconnect('write')
+ self._reconnect('write')
amt = self.sock.send(out)
logger.debug('sent %d', amt)
first = False
- # stash data we think we sent in case the socket breaks
- # when we read from it
- if was_first:
- sent_data = out[:amt]
if out is body:
body = out[amt:]
else:
@@ -616,7 +638,6 @@
# the whole request
if response is None:
response = self.response_class(self.sock, self.timeout, method)
- complete = response.complete()
data_left = bool(outgoing_headers or body)
if data_left:
logger.info('stopped sending request early, '
@@ -629,10 +650,14 @@
self._current_response = response
def getresponse(self):
+ """Returns the response to the most recent request."""
if self._current_response is None:
raise httplib.ResponseNotReady()
r = self._current_response
while r.headers is None:
+ # We're a friend of the response class, so let us use the
+ # private attribute.
+ # pylint: disable=W0212
if not r._select() and not r.complete():
raise _readers.HTTPRemoteClosedError()
if r.will_close:
--- a/mercurial/httpclient/_readers.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/httpclient/_readers.py Tue May 14 18:52:52 2013 -0500
@@ -33,7 +33,6 @@
"""
import httplib
-import itertools
import logging
logger = logging.getLogger(__name__)
@@ -59,33 +58,35 @@
self._done_chunks = []
self.available_data = 0
- def addchunk(self, data):
+ def _addchunk(self, data):
self._done_chunks.append(data)
self.available_data += len(data)
- def pushchunk(self, data):
+ def _pushchunk(self, data):
self._done_chunks.insert(0, data)
self.available_data += len(data)
- def popchunk(self):
+ def _popchunk(self):
b = self._done_chunks.pop(0)
self.available_data -= len(b)
return b
def done(self):
+ """Returns true if the response body is entirely read."""
return self._finished
def read(self, amt):
+ """Read amt bytes from the response body."""
if self.available_data < amt and not self._finished:
raise ReadNotReady()
blocks = []
need = amt
while self._done_chunks:
- b = self.popchunk()
+ b = self._popchunk()
if len(b) > need:
nb = b[:need]
- self.pushchunk(b[need:])
+ self._pushchunk(b[need:])
b = nb
blocks.append(b)
need -= len(b)
@@ -107,11 +108,11 @@
blocks = []
while self._done_chunks:
- b = self.popchunk()
+ b = self._popchunk()
i = b.find(delimstr) + len(delimstr)
if i:
if i < len(b):
- self.pushchunk(b[i:])
+ self._pushchunk(b[i:])
blocks.append(b[:i])
break
else:
@@ -154,8 +155,9 @@
if data:
assert not self._finished, (
'tried to add data (%r) to a closed reader!' % data)
- logger.debug('%s read an additional %d data', self.name, len(data))
- self.addchunk(data)
+ logger.debug('%s read an additional %d data',
+ self.name, len(data)) # pylint: disable=E1101
+ self._addchunk(data)
class CloseIsEndReader(AbstractSimpleReader):
@@ -172,7 +174,7 @@
name = 'content-length'
def __init__(self, amount):
- AbstractReader.__init__(self)
+ AbstractSimpleReader.__init__(self)
self._amount = amount
if amount == 0:
self._finished = True
@@ -199,7 +201,8 @@
logger.debug('chunked read an additional %d data', len(data))
position = 0
if self._leftover_data:
- logger.debug('chunked reader trying to finish block from leftover data')
+ logger.debug(
+ 'chunked reader trying to finish block from leftover data')
# TODO: avoid this string concatenation if possible
data = self._leftover_data + data
position = self._leftover_skip_amt
@@ -224,6 +227,6 @@
self._finished = True
logger.debug('closing chunked reader due to chunk of length 0')
return
- self.addchunk(data[block_start:block_start + amt])
+ self._addchunk(data[block_start:block_start + amt])
position = block_start + amt + len(self._eol)
# no-check-code
--- a/mercurial/httpclient/socketutil.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/httpclient/socketutil.py Tue May 14 18:52:52 2013 -0500
@@ -39,7 +39,8 @@
try:
import ssl
- ssl.wrap_socket # make demandimporters load the module
+ # make demandimporters load the module
+ ssl.wrap_socket # pylint: disable=W0104
have_ssl = True
except ImportError:
import httplib
@@ -52,12 +53,13 @@
create_connection = socket.create_connection
except AttributeError:
def create_connection(address):
+ """Backport of socket.create_connection from Python 2.6."""
host, port = address
msg = "getaddrinfo returns an empty list"
sock = None
for res in socket.getaddrinfo(host, port, 0,
socket.SOCK_STREAM):
- af, socktype, proto, _canonname, sa = res
+ af, socktype, proto, unused_canonname, sa = res
try:
sock = socket.socket(af, socktype, proto)
logger.info("connect: (%s, %s)", host, port)
@@ -80,8 +82,11 @@
CERT_REQUIRED = ssl.CERT_REQUIRED
else:
class FakeSocket(httplib.FakeSocket):
- """Socket wrapper that supports SSL.
- """
+ """Socket wrapper that supports SSL."""
+
+ # Silence lint about this goofy backport class
+ # pylint: disable=W0232,E1101,R0903,R0913,C0111
+
# backport the behavior from Python 2.6, which is to busy wait
# on the socket instead of anything nice. Sigh.
# See http://bugs.python.org/issue3890 for more info.
@@ -107,11 +112,16 @@
CERT_OPTIONAL = 1
CERT_REQUIRED = 2
+ # Disable unused-argument because we're making a dumb wrapper
+ # that's like an upstream method.
+ #
+ # pylint: disable=W0613,R0913
def wrap_socket(sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=_PROTOCOL_SSLv23, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True):
+ """Backport of ssl.wrap_socket from Python 2.6."""
if cert_reqs != CERT_NONE and ca_certs:
raise CertificateValidationUnsupported(
'SSL certificate validation requires the ssl module'
@@ -120,6 +130,7 @@
# borrow httplib's workaround for no ssl.wrap_socket
sock = FakeSocket(sock, sslob)
return sock
+ # pylint: enable=W0613,R0913
class CertificateValidationUnsupported(Exception):
--- a/mercurial/localrepo.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/localrepo.py Tue May 14 18:52:52 2013 -0500
@@ -1145,7 +1145,7 @@
if not force:
vdirs = []
- match.dir = vdirs.append
+ match.explicitdir = vdirs.append
match.bad = fail
wlock = self.wlock()
--- a/mercurial/match.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/match.py Tue May 14 18:52:52 2013 -0500
@@ -119,8 +119,12 @@
found/accessed, with an error message
'''
pass
- def dir(self, f):
- pass
+ # If this is set, it will be called when an explicitly listed directory is
+ # visited.
+ explicitdir = None
+ # If this is set, it will be called when a directory discovered by recursive
+ # traversal is visited.
+ traversedir = None
def missing(self, f):
pass
def exact(self, f):
--- a/mercurial/merge.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/merge.py Tue May 14 18:52:52 2013 -0500
@@ -95,6 +95,7 @@
def _checkunknownfile(repo, wctx, mctx, f):
return (not repo.dirstate._ignore(f)
and os.path.isfile(repo.wjoin(f))
+ and repo.wopener.audit.check(f)
and repo.dirstate.normalize(f) not in repo.dirstate
and mctx[f].cmp(wctx[f]))
--- a/mercurial/patch.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/patch.py Tue May 14 18:52:52 2013 -0500
@@ -481,7 +481,7 @@
def close(self):
wctx = self.repo[None]
- addremoved = set(self.changed)
+ changed = set(self.changed)
for src, dst in self.copied:
scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
if self.removed:
@@ -491,14 +491,10 @@
# File was deleted and no longer belongs to the
# dirstate, it was probably marked added then
# deleted, and should not be considered by
- # addremove().
- addremoved.discard(f)
- if addremoved:
- cwd = self.repo.getcwd()
- if cwd:
- addremoved = [util.pathto(self.repo.root, cwd, f)
- for f in addremoved]
- scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
+ # marktouched().
+ changed.discard(f)
+ if changed:
+ scmutil.marktouched(self.repo, changed, self.similarity)
return sorted(self.changed)
class filestore(object):
@@ -1397,12 +1393,7 @@
ui.warn(line + '\n')
finally:
if files:
- cfiles = list(files)
- cwd = repo.getcwd()
- if cwd:
- cfiles = [util.pathto(repo.root, cwd, f)
- for f in cfiles]
- scmutil.addremove(repo, cfiles, similarity=similarity)
+ scmutil.marktouched(repo, files, similarity)
code = fp.close()
if code:
raise PatchError(_("patch command failed: %s") %
--- a/mercurial/scmutil.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/scmutil.py Tue May 14 18:52:52 2013 -0500
@@ -685,26 +685,11 @@
if similarity is None:
similarity = float(opts.get('similarity') or 0)
# we'd use status here, except handling of symlinks and ignore is tricky
- added, unknown, deleted, removed = [], [], [], []
- audit_path = pathauditor(repo.root)
m = match(repo[None], pats, opts)
rejected = []
m.bad = lambda x, y: rejected.append(x)
- ctx = repo[None]
- dirstate = repo.dirstate
- walkresults = dirstate.walk(m, sorted(ctx.substate), True, False)
- for abs, st in walkresults.iteritems():
- dstate = dirstate[abs]
- if dstate == '?' and audit_path.check(abs):
- unknown.append(abs)
- elif dstate != 'r' and not st:
- deleted.append(abs)
- # for finding renames
- elif dstate == 'r':
- removed.append(abs)
- elif dstate == 'a':
- added.append(abs)
+ added, unknown, deleted, removed = _interestingfiles(repo, m)
unknownset = set(unknown)
toprint = unknownset.copy()
@@ -718,32 +703,101 @@
status = _('removing %s\n') % ((pats and rel) or abs)
repo.ui.status(status)
- copies = {}
- if similarity > 0:
- for old, new, score in similar.findrenames(repo,
- added + unknown, removed + deleted, similarity):
- if repo.ui.verbose or not m.exact(old) or not m.exact(new):
- repo.ui.status(_('recording removal of %s as rename to %s '
- '(%d%% similar)\n') %
- (m.rel(old), m.rel(new), score * 100))
- copies[new] = old
+ renames = _findrenames(repo, m, added + unknown, removed + deleted,
+ similarity)
if not dry_run:
- wctx = repo[None]
- wlock = repo.wlock()
- try:
- wctx.forget(deleted)
- wctx.add(unknown)
- for new, old in copies.iteritems():
- wctx.copy(old, new)
- finally:
- wlock.release()
+ _markchanges(repo, unknown, deleted, renames)
+
+ for f in rejected:
+ if f in m.files():
+ return 1
+ return 0
+
+def marktouched(repo, files, similarity=0.0):
+ '''Assert that files have somehow been operated upon. files are relative to
+ the repo root.'''
+ m = matchfiles(repo, files)
+ rejected = []
+ m.bad = lambda x, y: rejected.append(x)
+
+ added, unknown, deleted, removed = _interestingfiles(repo, m)
+
+ if repo.ui.verbose:
+ unknownset = set(unknown)
+ toprint = unknownset.copy()
+ toprint.update(deleted)
+ for abs in sorted(toprint):
+ if abs in unknownset:
+ status = _('adding %s\n') % abs
+ else:
+ status = _('removing %s\n') % abs
+ repo.ui.status(status)
+
+ renames = _findrenames(repo, m, added + unknown, removed + deleted,
+ similarity)
+
+ _markchanges(repo, unknown, deleted, renames)
for f in rejected:
if f in m.files():
return 1
return 0
+def _interestingfiles(repo, matcher):
+ '''Walk dirstate with matcher, looking for files that addremove would care
+ about.
+
+ This is different from dirstate.status because it doesn't care about
+ whether files are modified or clean.'''
+ added, unknown, deleted, removed = [], [], [], []
+ audit_path = pathauditor(repo.root)
+
+ ctx = repo[None]
+ dirstate = repo.dirstate
+ walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False)
+ for abs, st in walkresults.iteritems():
+ dstate = dirstate[abs]
+ if dstate == '?' and audit_path.check(abs):
+ unknown.append(abs)
+ elif dstate != 'r' and not st:
+ deleted.append(abs)
+ # for finding renames
+ elif dstate == 'r':
+ removed.append(abs)
+ elif dstate == 'a':
+ added.append(abs)
+
+ return added, unknown, deleted, removed
+
+def _findrenames(repo, matcher, added, removed, similarity):
+ '''Find renames from removed files to added ones.'''
+ renames = {}
+ if similarity > 0:
+ for old, new, score in similar.findrenames(repo, added, removed,
+ similarity):
+ if (repo.ui.verbose or not matcher.exact(old)
+ or not matcher.exact(new)):
+ repo.ui.status(_('recording removal of %s as rename to %s '
+ '(%d%% similar)\n') %
+ (matcher.rel(old), matcher.rel(new),
+ score * 100))
+ renames[new] = old
+ return renames
+
+def _markchanges(repo, unknown, deleted, renames):
+ '''Marks the files in unknown as added, the files in deleted as removed,
+ and the files in renames as copied.'''
+ wctx = repo[None]
+ wlock = repo.wlock()
+ try:
+ wctx.forget(deleted)
+ wctx.add(unknown)
+ for new, old in renames.iteritems():
+ wctx.copy(old, new)
+ finally:
+ wlock.release()
+
def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
"""Update the dirstate to reflect the intent of copying src to dst. For
different reasons it might not end with dst being marked as copied from src.
--- a/mercurial/store.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/store.py Tue May 14 18:52:52 2013 -0500
@@ -322,13 +322,16 @@
def datafiles(self):
return self._walk('data', True)
+ def topfiles(self):
+ # yield manifest before changelog
+ return reversed(self._walk('', False))
+
def walk(self):
'''yields (unencoded, encoded, size)'''
# yield data files first
for x in self.datafiles():
yield x
- # yield manifest before changelog
- for x in reversed(self._walk('', False)):
+ for x in self.topfiles():
yield x
def copylist(self):
--- a/mercurial/templater.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/templater.py Tue May 14 18:52:52 2013 -0500
@@ -394,6 +394,16 @@
engines = {'default': engine}
+def stylelist():
+ path = templatepath()[0]
+ dirlist = os.listdir(path)
+ stylelist = []
+ for file in dirlist:
+ split = file.split(".")
+ if split[0] == "map-cmdline":
+ stylelist.append(split[1])
+ return ", ".join(sorted(stylelist))
+
class templater(object):
def __init__(self, mapfile, filters={}, defaults={}, cache={},
@@ -415,7 +425,8 @@
if not mapfile:
return
if not os.path.exists(mapfile):
- raise util.Abort(_('style not found: %s') % mapfile)
+ raise util.Abort(_("style '%s' not found") % mapfile,
+ hint=_("available styles: %s") % stylelist())
conf = config.config()
conf.read(mapfile)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/templates/map-cmdline.phases Tue May 14 18:52:52 2013 -0500
@@ -0,0 +1,25 @@
+changeset = 'changeset: {rev}:{node|short}\n{branches}{bookmarks}{tags}phase: {phase}\n{parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n'
+changeset_quiet = '{rev}:{node|short}\n'
+changeset_verbose = 'changeset: {rev}:{node|short}\n{branches}{bookmarks}{tags}{parents}user: {author}\ndate: {date|date}\n{files}{file_copies_switch}description:\n{desc|strip}\n\n\n'
+changeset_debug = 'changeset: {rev}:{node}\n{branches}{bookmarks}{tags}phase: {phase}\n{parents}{manifest}user: {author}\ndate: {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies_switch}{extras}description:\n{desc|strip}\n\n\n'
+start_files = 'files: '
+file = ' {file}'
+end_files = '\n'
+start_file_mods = 'files: '
+file_mod = ' {file_mod}'
+end_file_mods = '\n'
+start_file_adds = 'files+: '
+file_add = ' {file_add}'
+end_file_adds = '\n'
+start_file_dels = 'files-: '
+file_del = ' {file_del}'
+end_file_dels = '\n'
+start_file_copies = 'copies: '
+file_copy = ' {name} ({source})'
+end_file_copies = '\n'
+parent = 'parent: {rev}:{node|formatnode}\n'
+manifest = 'manifest: {rev}:{node}\n'
+branch = 'branch: {branch}\n'
+tag = 'tag: {tag}\n'
+bookmark = 'bookmark: {bookmark}\n'
+extra = 'extra: {key}={value|stringescape}\n'
--- a/mercurial/ui.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/ui.py Tue May 14 18:52:52 2013 -0500
@@ -6,7 +6,7 @@
# GNU General Public License version 2 or any later version.
from i18n import _
-import errno, getpass, os, re, socket, sys, tempfile, traceback
+import errno, getpass, os, socket, sys, tempfile, traceback
import config, scmutil, util, error, formatter
class ui(object):
@@ -284,22 +284,16 @@
ConfigError: foo.invalid is not a byte quantity ('somevalue')
"""
- orig = string = self.config(section, name)
- if orig is None:
+ value = self.config(section, name)
+ if value is None:
if not isinstance(default, str):
return default
- orig = string = default
- multiple = 1
- m = re.match(r'([^kmbg]+?)\s*([kmg]?)b?$', string, re.I)
- if m:
- string, key = m.groups()
- key = key.lower()
- multiple = dict(k=1024, m=1048576, g=1073741824).get(key, 1)
+ value = default
try:
- return int(float(string) * multiple)
- except ValueError:
+ return util.sizetoint(value)
+ except error.ParseError:
raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
- % (section, name, orig))
+ % (section, name, value))
def configlist(self, section, name, default=None, untrusted=False):
"""parse a configuration element as a list of comma/space separated
--- a/mercurial/util.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/util.py Tue May 14 18:52:52 2013 -0500
@@ -1924,3 +1924,25 @@
(' ' * _timenesting[0], func.__name__,
timecount(elapsed)))
return wrapper
+
+_sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
+ ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
+
+def sizetoint(s):
+ '''Convert a space specifier to a byte count.
+
+ >>> sizetoint('30')
+ 30
+ >>> sizetoint('2.2kb')
+ 2252
+ >>> sizetoint('6M')
+ 6291456
+ '''
+ t = s.strip().lower()
+ try:
+ for k, u in _sizeunits:
+ if t.endswith(k):
+ return int(float(t[:-len(k)]) * u)
+ return int(t)
+ except ValueError:
+ raise error.ParseError(_("couldn't parse size: %s") % s)
--- a/mercurial/wireproto.py Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/wireproto.py Tue May 14 18:52:52 2013 -0500
@@ -523,6 +523,10 @@
def _allowstream(ui):
return ui.configbool('server', 'uncompressed', True, untrusted=True)
+def _walkstreamfiles(repo):
+ # this is it's own function so extensions can override it
+ return repo.store.walk()
+
def stream(repo, proto):
'''If the server supports streaming clone, it advertises the "stream"
capability with a value representing the version and flags of the repo
@@ -544,7 +548,7 @@
lock = repo.lock()
try:
repo.ui.debug('scanning\n')
- for name, ename, size in repo.store.walk():
+ for name, ename, size in _walkstreamfiles(repo):
if size:
entries.append((name, size))
total_bytes += size
--- a/tests/run-tests.py Tue May 14 18:43:53 2013 -0500
+++ b/tests/run-tests.py Tue May 14 18:52:52 2013 -0500
@@ -1120,6 +1120,8 @@
childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
childtmp = os.path.join(HGTMP, 'child%d' % j)
childopts += ['--tmpdir', childtmp]
+ if options.keep_tmpdir:
+ childopts.append('--keep-tmpdir')
cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
vlog(' '.join(cmdline))
proc = subprocess.Popen(cmdline, executable=cmdline[0])
@@ -1288,7 +1290,8 @@
global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
TESTDIR = os.environ["TESTDIR"] = os.getcwd()
if options.tmpdir:
- options.keep_tmpdir = True
+ if not options.child:
+ options.keep_tmpdir = True
tmpdir = options.tmpdir
if os.path.exists(tmpdir):
# Meaning of tmpdir has changed since 1.3: we used to create
--- a/tests/test-bookmarks-current.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-bookmarks-current.t Tue May 14 18:52:52 2013 -0500
@@ -43,16 +43,19 @@
$ hg bookmarks
* Z -1:000000000000
-new bookmark Y
+new bookmarks X and Y, first one made active
- $ hg bookmark Y
+ $ hg bookmark Y X
list bookmarks
$ hg bookmark
+ X -1:000000000000
* Y -1:000000000000
Z -1:000000000000
+ $ hg bookmark -d X
+
commit
$ echo 'b' > b
--- a/tests/test-bookmarks.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-bookmarks.t Tue May 14 18:52:52 2013 -0500
@@ -168,11 +168,14 @@
$ hg bookmark -d REVSET
$ hg bookmark -d TIP
-rename without new name
+rename without new name or multiple names
$ hg bookmark -m Y
abort: new bookmark name required
[255]
+ $ hg bookmark -m Y Y2 Y3
+ abort: only one new bookmark name allowed
+ [255]
delete without name
@@ -417,8 +420,9 @@
a@ 2:db815d6d32e6
x y 2:db815d6d32e6
- $ hg bookmark -d @
- $ hg bookmark -d a@
+delete multiple bookmarks at once
+
+ $ hg bookmark -d @ a@
test clone with a bookmark named "default" (issue3677)
--- a/tests/test-command-template.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-command-template.t Tue May 14 18:52:52 2013 -0500
@@ -458,7 +458,8 @@
Error if no style:
$ hg log --style notexist
- abort: style not found: notexist
+ abort: style 'notexist' not found
+ (available styles: bisect, changelog, compact, default, phases, xml)
[255]
Error if style missing key:
--- a/tests/test-commandserver.py Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-commandserver.py Tue May 14 18:52:52 2013 -0500
@@ -25,7 +25,11 @@
else:
return channel, server.stdout.read(length)
-def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None):
+def sep(text):
+ return text.replace('\\', '/')
+
+def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None,
+ outfilter=lambda x: x):
print ' runcommand', ' '.join(args)
sys.stdout.flush()
server.stdin.write('runcommand\n')
@@ -37,7 +41,7 @@
while True:
ch, data = readchannel(server)
if ch == 'o':
- output.write(data)
+ output.write(outfilter(data))
output.flush()
elif ch == 'e':
error.write(data)
@@ -249,7 +253,8 @@
# make it public; draft marker moves to 4:7966c8e3734d
runcommand(server, ['phase', '-p', '.'])
- runcommand(server, ['phase', '.']) # load _phasecache.phaseroots
+ # load _phasecache.phaseroots
+ runcommand(server, ['phase', '.'], outfilter=sep)
# strip 1::4 outside server
os.system('hg -q --config extensions.mq= strip 1')
--- a/tests/test-convert-git.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-convert-git.t Tue May 14 18:52:52 2013 -0500
@@ -13,6 +13,10 @@
$ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
$ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
$ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
+ $ INVALIDID1=afd12345af
+ $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
+ $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
+ $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
$ count=10
$ commit()
> {
@@ -298,6 +302,36 @@
$ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
$ cd ..
+test invalid splicemap1
+
+ $ cat > splicemap <<EOF
+ > $VALIDID1
+ > EOF
+ $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
+ initializing destination git-repo2-splicemap1-hg repository
+ abort: syntax error in splicemap(1): child parent1[,parent2] expected
+ [255]
+
+test invalid splicemap2
+
+ $ cat > splicemap <<EOF
+ > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
+ > EOF
+ $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
+ initializing destination git-repo2-splicemap2-hg repository
+ abort: syntax error in splicemap(1): child parent1[,parent2] expected
+ [255]
+
+test invalid splicemap3
+
+ $ cat > splicemap <<EOF
+ > $INVALIDID1 $INVALIDID2
+ > EOF
+ $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
+ initializing destination git-repo2-splicemap3-hg repository
+ abort: splicemap entry afd12345af is not a valid revision identifier
+ [255]
+
convert sub modules
$ hg convert git-repo6 git-repo6-hg
initializing destination git-repo6-hg repository
--- a/tests/test-convert-splicemap.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-convert-splicemap.t Tue May 14 18:52:52 2013 -0500
@@ -37,6 +37,8 @@
$ hg ci -Am addaandd
adding a
adding d
+ $ INVALIDID1=afd12345af
+ $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
$ CHILDID1=`hg id --debug -i`
$ echo d >> d
$ hg ci -Am changed
@@ -53,7 +55,7 @@
o 0:527cdedf31fb "addaandd" files: a d
-test invalid splicemap
+test invalid splicemap1
$ cat > splicemap <<EOF
> $CHILDID2
@@ -62,6 +64,24 @@
abort: syntax error in splicemap(1): child parent1[,parent2] expected
[255]
+test invalid splicemap2
+
+ $ cat > splicemap <<EOF
+ > $CHILDID2 $PARENTID1, $PARENTID2, $PARENTID2
+ > EOF
+ $ hg convert --splicemap splicemap repo2 repo1
+ abort: syntax error in splicemap(1): child parent1[,parent2] expected
+ [255]
+
+test invalid splicemap3
+
+ $ cat > splicemap <<EOF
+ > $INVALIDID1 $INVALIDID2
+ > EOF
+ $ hg convert --splicemap splicemap repo2 repo1
+ abort: splicemap entry afd12345af is not a valid revision identifier
+ [255]
+
splice repo2 on repo1
$ cat > splicemap <<EOF
--- a/tests/test-convert-svn-source.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-convert-svn-source.t Tue May 14 18:52:52 2013 -0500
@@ -16,6 +16,8 @@
#else
$ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
#endif
+ $ INVALIDREVISIONID=svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1
+ $ VALIDREVISIONID=svn:a2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk/mytrunk@1
Now test that it works with trunk/tags layout, but no branches yet.
@@ -168,6 +170,15 @@
|
o 0 second letter files: letter2.txt
+test invalid splicemap1
+
+ $ cat > splicemap <<EOF
+ > $INVALIDREVISIONID $VALIDREVISIONID
+ > EOF
+ $ hg convert --splicemap splicemap "$SVNREPOURL/proj%20B/mytrunk" smap
+ initializing destination smap repository
+ abort: splicemap entry svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1 is not a valid revision identifier
+ [255]
Test stop revision
$ hg convert --rev 1 "$SVNREPOURL/proj%20B/mytrunk" stoprev
--- a/tests/test-log.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-log.t Tue May 14 18:52:52 2013 -0500
@@ -84,6 +84,25 @@
abort: cannot follow file not in parent revision: "dir"
[255]
+-f, a wrong style
+
+ $ hg log -f -l1 --style something
+ abort: style 'something' not found
+ (available styles: bisect, changelog, compact, default, phases, xml)
+ [255]
+
+-f, phases style
+
+
+ $ hg log -f -l1 --style phases
+ changeset: 4:7e4639b4691b
+ tag: tip
+ phase: draft
+ user: test
+ date: Thu Jan 01 00:00:05 1970 +0000
+ summary: e
+
+
-f, but no args
$ hg log -f
--- a/tests/test-nested-repo.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-nested-repo.t Tue May 14 18:52:52 2013 -0500
@@ -8,6 +8,9 @@
$ hg add b
$ hg st
+ $ echo y > b/y
+ $ hg st
+
Should fail:
$ hg st b/x
--- a/tests/test-rebase-rename.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-rebase-rename.t Tue May 14 18:52:52 2013 -0500
@@ -22,7 +22,7 @@
adding d/b
$ hg mv d d-renamed
- moving d/b to d-renamed/b
+ moving d/b to d-renamed/b (glob)
$ hg ci -m 'rename B'
$ hg up -q -C 1
--- a/tests/test-rename-dir-merge.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-rename-dir-merge.t Tue May 14 18:52:52 2013 -0500
@@ -51,7 +51,7 @@
getting b/b
updating: b/b 4/5 files (80.00%)
updating: a/c 5/5 files (100.00%)
- moving a/c to b/c
+ moving a/c to b/c (glob)
3 files updated, 0 files merged, 2 files removed, 0 files unresolved
(branch merge, don't forget to commit)
--- a/tests/test-subrepo.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-subrepo.t Tue May 14 18:52:52 2013 -0500
@@ -391,7 +391,7 @@
$ hg -R s update '.^'
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg push
- pushing to $TESTTMP/t
+ pushing to $TESTTMP/t (glob)
no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
no changes made to subrepo s since last push to $TESTTMP/t/s
no changes made to subrepo t since last push to $TESTTMP/t/t
@@ -400,7 +400,7 @@
[1]
$ echo foo >> s/a
$ hg push
- pushing to $TESTTMP/t
+ pushing to $TESTTMP/t (glob)
no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
no changes made to subrepo s since last push to $TESTTMP/t/s
no changes made to subrepo t since last push to $TESTTMP/t/t
@@ -415,7 +415,7 @@
$ echo foo >> s/ss/a
$ hg -R s/ss commit -m 'test dirty store detection'
$ hg push
- pushing to $TESTTMP/t
+ pushing to $TESTTMP/t (glob)
pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
searching for changes
adding changesets
@@ -431,7 +431,7 @@
a subrepo store may be clean versus one repo but not versus another
$ hg push
- pushing to $TESTTMP/t
+ pushing to $TESTTMP/t (glob)
no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
no changes made to subrepo s since last push to $TESTTMP/t/s
no changes made to subrepo t since last push to $TESTTMP/t/t
@@ -798,7 +798,7 @@
Try to push from the other side
$ hg -R issue1852a push `pwd`/issue1852c
- pushing to $TESTTMP/issue1852c
+ pushing to $TESTTMP/issue1852c (glob)
pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
searching for changes
no changes found
--- a/tests/test-symlinks.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-symlinks.t Tue May 14 18:52:52 2013 -0500
@@ -160,6 +160,15 @@
adding bar/a
adding foo
removing foo/a
+
+commit and update back
+
+ $ hg ci -mb
+ $ hg up '.^'
+ 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg up tip
+ 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
$ cd ..
== root of repository is symlinked ==
--- a/tests/test-tag.t Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-tag.t Tue May 14 18:52:52 2013 -0500
@@ -316,7 +316,7 @@
adding test
$ hg init repo-tag-target
$ hg -R repo-tag --config hooks.commit="\"hg\" push \"`pwd`/repo-tag-target\"" tag tag
- pushing to $TESTTMP/repo-tag-target
+ pushing to $TESTTMP/repo-tag-target (glob)
searching for changes
adding changesets
adding manifests