Mercurial > hg
changeset 25102:bb2f543b48b5
merge with stable
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Fri, 15 May 2015 11:52:09 -0500 |
parents | d6e7ac651973 (diff) 91c2278c68a3 (current diff) |
children | ce00b2e96d09 |
files | hgext/rebase.py mercurial/revset.py tests/test-revset.t |
diffstat | 98 files changed, 3032 insertions(+), 1419 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Thu May 14 21:35:06 2015 -0700 +++ b/Makefile Fri May 15 11:52:09 2015 -0500 @@ -157,6 +157,16 @@ N=`cd dist && echo mercurial-*.mpkg | sed 's,\.mpkg$$,,'` && hdiutil create -srcfolder dist/$$N.mpkg/ -scrub -volname "$$N" -ov packages/osx/$$N.dmg rm -rf dist/mercurial-*.mpkg +debian-jessie: + mkdir -p packages/debian-jessie + contrib/builddeb + mv debbuild/*.deb packages/debian-jessie + rm -rf debbuild + +docker-debian-jessie: + mkdir -p packages/debian/jessie + contrib/dockerdeb jessie + fedora20: mkdir -p packages/fedora20 contrib/buildrpm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/builddeb Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,62 @@ +#!/bin/sh -e +# +# Build a Mercurial debian package from the current repo +# +# Tested on Jessie (stable as of original script authoring.) + +. $(dirname $0)/packagelib.sh + +BUILD=1 +DEBBUILDDIR="$PWD/debbuild" +while [ "$1" ]; do + case "$1" in + --prepare ) + shift + BUILD= + ;; + --debbuilddir ) + shift + DEBBUILDDIR="$1" + shift + ;; + * ) + echo "Invalid parameter $1!" 1>&2 + exit 1 + ;; + esac +done + +set -u + +rm -rf $DEBBUILDDIR +mkdir -p $DEBBUILDDIR + +if [ ! -d .hg ]; then + echo 'You are not inside a Mercurial repository!' 1>&2 + exit 1 +fi + +gethgversion + +cp -r $PWD/contrib/debian $DEBBUILDDIR/DEBIAN +chmod -R 0755 $DEBBUILDDIR/DEBIAN + +control=$DEBBUILDDIR/DEBIAN/control + +# This looks like sed -i, but sed -i behaves just differently enough +# between BSD and GNU sed that I gave up and did the dumb thing. +sed "s/__VERSION__/$version/" < $control > $control.tmp +mv $control.tmp $control + +if [ "$BUILD" ]; then + dpkg-deb --build $DEBBUILDDIR + mv $DEBBUILDDIR.deb $DEBBUILDDIR/mercurial-$version-$release.deb + if [ $? = 0 ]; then + echo + echo "Built packages for $version-$release:" + find $DEBBUILDDIR/ -type f -newer $control + fi +else + echo "Prepared sources for $version-$release $control are in $DEBBUILDDIR - use like:" + echo "dpkg-deb --build $DEBBUILDDIR" +fi
--- a/contrib/buildrpm Thu May 14 21:35:06 2015 -0700 +++ b/contrib/buildrpm Fri May 15 11:52:09 2015 -0500 @@ -7,6 +7,8 @@ # - CentOS 5 # - centOS 6 +. $(dirname $0)/packagelib.sh + BUILD=1 RPMBUILDDIR="$PWD/rpmbuild" while [ "$1" ]; do @@ -45,25 +47,8 @@ exit 1 fi -# build local hg and use it -python setup.py build_py -c -d . -HG="$PWD/hg" -PYTHONPATH="$PWD/mercurial/pure" -export PYTHONPATH - -mkdir -p $RPMBUILDDIR/SOURCES $RPMBUILDDIR/SPECS $RPMBUILDDIR/RPMS $RPMBUILDDIR/SRPMS $RPMBUILDDIR/BUILD - -hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'` +gethgversion -if echo $hgversion | grep -- '-' > /dev/null 2>&1; then - # nightly build case, version is like 1.3.1+250-20b91f91f9ca - version=`echo $hgversion | cut -d- -f1` - release=`echo $hgversion | cut -d- -f2 | sed -e 's/+.*//'` -else - # official tag, version is like 1.3.1 - version=`echo $hgversion | sed -e 's/+.*//'` - release='0' -fi if [ "$PYTHONVER" ]; then release=$release+$PYTHONVER RPMPYTHONVER=$PYTHONVER
--- a/contrib/check-code.py Thu May 14 21:35:06 2015 -0700 +++ b/contrib/check-code.py Fri May 15 11:52:09 2015 -0500 @@ -217,9 +217,6 @@ (r'(\w|\)),\w', "missing whitespace after ,"), (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"), - (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n' - r'((?:\n|\1\s.*\n)+?))+\1finally:', - 'no try/except/finally in Python 2.4'), (r'(?<!def)(\s+|^|\()next\(.+\)', 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'), (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?' @@ -238,7 +235,8 @@ (r'class\s[^( \n]+:', "old-style class, use class foo(object)"), (r'class\s[^( \n]+\(\):', "class foo() not available in Python 2.4, use class foo(object)"), - (r'\b(%s)\(' % '|'.join(keyword.kwlist), + (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist + if k not in ('print', 'exec')), "Python keyword is not a function"), (r',]', "unneeded trailing ',' in list"), # (r'class\s[A-Z][^\(]*\((?!Exception)', @@ -247,9 +245,7 @@ # (r'^\s*print\s+', "avoid using print in core and extensions"), (r'[\x80-\xff]', "non-ASCII character literal"), (r'("\')\.format\(', "str.format() not available in Python 2.4"), - (r'^\s*with\s+', "with not available in Python 2.4"), (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"), - (r'^\s*except.* as .*:', "except as not available in Python 2.4"), (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"), (r'(?<!def)\s+(any|all|format)\(', "any/all/format not available in Python 2.4", 'no-py24'), @@ -281,7 +277,6 @@ (r'opener\([^)]*\).read\(', "use opener.read() instead"), (r'BaseException', 'not in Python 2.4, use Exception'), - (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'), (r'opener\([^)]*\).write\(', "use opener.write() instead"), (r'[\s\(](open|file)\([^)]*\)\.read\(',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/debian/control Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,9 @@ +Package: mercurial +Version: __VERSION__ +Section: vcs +Priority: optional +Architecture: all +Depends: python +Conflicts: mercurial-common +Maintainer: Mercurial Developers <mercurial-devel@selenic.com> +Description: Mercurial (probably nightly) package built by upstream.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/docker/debian-jessie Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,11 @@ +FROM debian:jessie +RUN apt-get update && apt-get install -y \ + build-essential \ + debhelper \ + dh-python \ + devscripts \ + python \ + python-all-dev \ + python-docutils \ + zip \ + unzip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerdeb Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,39 @@ +#!/bin/bash -eu + +. $(dirname $0)/dockerlib.sh +. $(dirname $0)/packagelib.sh + +BUILDDIR=$(dirname $0) +export ROOTDIR=$(cd $BUILDDIR/..; pwd) + +checkdocker + +PLATFORM="debian-$1" +shift # extra params are passed to build process + +initcontainer $PLATFORM + +DEBBUILDDIR=$ROOTDIR/packages/$PLATFORM +contrib/builddeb --debbuilddir $DEBBUILDDIR/staged --prepare + +DSHARED=/mnt/shared/ +if [ $(uname) = "Darwin" ] ; then + $DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED -v $PWD:/mnt/hg $CONTAINER \ + sh -c "cd /mnt/hg && make clean && make local" +fi +$DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED -v $PWD:/mnt/hg $CONTAINER \ + sh -c "cd /mnt/hg && make PREFIX=$DSHARED/staged/usr install" +$DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED $CONTAINER \ + dpkg-deb --build $DSHARED/staged +if [ $(uname) = "Darwin" ] ; then + $DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED -v $PWD:/mnt/hg $CONTAINER \ + sh -c "cd /mnt/hg && make clean" +fi + +gethgversion + +rm -r $DEBBUILDDIR/staged +mv $DEBBUILDDIR/staged.deb $DEBBUILDDIR/mercurial-$version-$release.deb + +echo +echo "Build complete - results can be found in $DEBBUILDDIR"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerlib.sh Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,42 @@ +#!/bin/sh -eu + +# This function exists to set up the DOCKER variable and verify that +# it's the binary we expect. It also verifies that the docker service +# is running on the system and we can talk to it. +function checkdocker() { + if which docker.io >> /dev/null 2>&1 ; then + DOCKER=docker.io + elif which docker >> /dev/null 2>&1 ; then + DOCKER=docker + else + echo "Error: docker must be installed" + exit 1 + fi + + $DOCKER -h 2> /dev/null | grep -q Jansens && { echo "Error: $DOCKER is the Docking System Tray - install docker.io instead"; exit 1; } + $DOCKER version | grep -q "^Client version:" || { echo "Error: unexpected output from \"$DOCKER version\""; exit 1; } + $DOCKER version | grep -q "^Server version:" || { echo "Error: could not get docker server version - check it is running and your permissions"; exit 1; } +} + +# Construct a container and leave its name in $CONTAINER for future use. +function initcontainer() { + [ "$1" ] || { echo "Error: platform name must be specified"; exit 1; } + + DFILE="$ROOTDIR/contrib/docker/$1" + [ -f "$DFILE" ] || { echo "Error: docker file $DFILE not found"; exit 1; } + + CONTAINER="hg-dockerrpm-$1" + DBUILDUSER=build + ( + cat $DFILE + if [ $(uname) = "Darwin" ] ; then + # The builder is using boot2docker on OS X, so we're going to + # *guess* the uid of the user inside the VM that is actually + # running docker. This is *very likely* to fail at some point. + echo RUN useradd $DBUILDUSER -u 1000 + else + echo RUN groupadd $DBUILDUSER -g `id -g` + echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER + fi + ) | $DOCKER build --tag $CONTAINER - +}
--- a/contrib/dockerrpm Thu May 14 21:35:06 2015 -0700 +++ b/contrib/dockerrpm Fri May 15 11:52:09 2015 -0500 @@ -1,36 +1,16 @@ #!/bin/bash -e +. $(dirname $0)/dockerlib.sh + BUILDDIR=$(dirname $0) -ROOTDIR=$(cd $BUILDDIR/..; pwd) +export ROOTDIR=$(cd $BUILDDIR/..; pwd) -if which docker.io >> /dev/null 2>&1 ; then - DOCKER=docker.io -elif which docker >> /dev/null 2>&1 ; then - DOCKER=docker -else - echo "Error: docker must be installed" - exit 1 -fi - -$DOCKER -h 2> /dev/null | grep -q Jansens && { echo "Error: $DOCKER is the Docking System Tray - install docker.io instead"; exit 1; } -$DOCKER version | grep -q "^Client version:" || { echo "Error: unexpected output from \"$DOCKER version\""; exit 1; } -$DOCKER version | grep -q "^Server version:" || { echo "Error: could not get docker server version - check it is running and your permissions"; exit 1; } +checkdocker PLATFORM="$1" -[ "$PLATFORM" ] || { echo "Error: platform name must be specified"; exit 1; } shift # extra params are passed to buildrpm -DFILE="$ROOTDIR/contrib/docker/$PLATFORM" -[ -f "$DFILE" ] || { echo "Error: docker file $DFILE not found"; exit 1; } - -CONTAINER="hg-dockerrpm-$PLATFORM" - -DBUILDUSER=build -( -cat $DFILE -echo RUN groupadd $DBUILDUSER -g `id -g` -echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER -) | $DOCKER build --tag $CONTAINER - +initcontainer $PLATFORM RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $*
--- a/contrib/import-checker.py Thu May 14 21:35:06 2015 -0700 +++ b/contrib/import-checker.py Fri May 15 11:52:09 2015 -0500 @@ -215,14 +215,20 @@ return len(c), c def main(argv): - if len(argv) < 2: - print 'Usage: %s file [file] [file] ...' + if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2): + print 'Usage: %s {-|file [file] [file] ...}' return 1 + if argv[1] == '-': + argv = argv[:1] + argv.extend(l.rstrip() for l in sys.stdin.readlines()) + localmods = {} used_imports = {} any_errors = False for source_path in argv[1:]: + modname = dotted_name_of_path(source_path, trimpure=True) + localmods[modname] = source_path + for modname, source_path in sorted(localmods.iteritems()): f = open(source_path) - modname = dotted_name_of_path(source_path, trimpure=True) src = f.read() used_imports[modname] = sorted( imported_modules(src, ignore_nested=True))
--- a/contrib/mercurial.spec Thu May 14 21:35:06 2015 -0700 +++ b/contrib/mercurial.spec Fri May 15 11:52:09 2015 -0500 @@ -37,8 +37,8 @@ %if "%{?withpython}" BuildRequires: readline-devel, openssl-devel, ncurses-devel, zlib-devel, bzip2-devel %else -BuildRequires: python >= 2.4, python-devel, python-docutils >= 0.5 -Requires: python >= 2.4 +BuildRequires: python >= 2.6, python-devel, python-docutils >= 0.5 +Requires: python >= 2.6 %endif # The hgk extension uses the wish tcl interpreter, but we don't enforce it #Requires: tk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/packagelib.sh Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,19 @@ +gethgversion() { + make clean + make local || make local PURE=--pure + HG="$PWD/hg" + + $HG version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; } + + hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'` + + if echo $hgversion | grep -- '-' > /dev/null 2>&1; then + # nightly build case, version is like 1.3.1+250-20b91f91f9ca + version=`echo $hgversion | cut -d- -f1` + release=`echo $hgversion | cut -d- -f2 | sed -e 's/+.*//'` + else + # official tag, version is like 1.3.1 + version=`echo $hgversion | sed -e 's/+.*//'` + release='0' + fi +}
--- a/contrib/revsetbenchmarks.txt Thu May 14 21:35:06 2015 -0700 +++ b/contrib/revsetbenchmarks.txt Fri May 15 11:52:09 2015 -0500 @@ -17,6 +17,7 @@ # those two `roots(...)` inputs are close to what phase movement use. roots((tip~100::) - (tip~100::tip)) roots((0::) - (0::tip)) +42:68 and roots(42:tip) ::p1(p1(tip)):: public() :10000 and public()
--- a/contrib/wix/guids.wxi Thu May 14 21:35:06 2015 -0700 +++ b/contrib/wix/guids.wxi Fri May 15 11:52:09 2015 -0500 @@ -28,6 +28,7 @@ <?define templates.atom.guid = {D30E14A5-8AF0-4268-8B00-00BEE9E09E39} ?> <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?> <?define templates.gitweb.guid = {827334AF-1EFD-421B-962C-5660A068F612} ?> + <?define templates.json.guid = {F535BE7A-EC34-46E0-B9BE-013F3DBAFB19} ?> <?define templates.monoblue.guid = {8060A1E4-BD4C-453E-92CB-9536DC44A9E3} ?> <?define templates.paper.guid = {61AB1DE9-645F-46ED-8AF8-0CF02267FFBB} ?> <?define templates.raw.guid = {834DF8D7-9784-43A6-851D-A96CE1B3575B} ?>
--- a/contrib/wix/templates.wxs Thu May 14 21:35:06 2015 -0700 +++ b/contrib/wix/templates.wxs Fri May 15 11:52:09 2015 -0500 @@ -12,6 +12,7 @@ <ComponentRef Id="templates.atom" /> <ComponentRef Id="templates.coal" /> <ComponentRef Id="templates.gitweb" /> + <ComponentRef Id="templates.json" /> <ComponentRef Id="templates.monoblue" /> <ComponentRef Id="templates.paper" /> <ComponentRef Id="templates.raw" /> @@ -36,6 +37,13 @@ <File Name="map-cmdline.phases" /> </Component> + <Directory Id="templates.jsondir" Name="json"> + <Component Id="templates.json" Guid="$(var.templates.json.guid)" Win64='$(var.IsX64)'> + <File Id="json.changelist.tmpl" Name="changelist.tmpl" KeyPath="yes" /> + <File Id="json.map" Name="map" /> + </Component> + </Directory> + <Directory Id="templates.atomdir" Name="atom"> <Component Id="templates.atom" Guid="$(var.templates.atom.guid)" Win64='$(var.IsX64)'> <File Id="atom.changelog.tmpl" Name="changelog.tmpl" KeyPath="yes" />
--- a/hgext/bugzilla.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/bugzilla.py Fri May 15 11:52:09 2015 -0500 @@ -279,7 +279,7 @@ from mercurial.i18n import _ from mercurial.node import short -from mercurial import cmdutil, mail, templater, util +from mercurial import cmdutil, mail, util import re, time, urlparse, xmlrpclib testedwith = 'internal' @@ -876,8 +876,6 @@ if not mapfile and not tmpl: tmpl = _('changeset {node|short} in repo {root} refers ' 'to bug {bug}.\ndetails:\n\t{desc|tabindent}') - if tmpl: - tmpl = templater.parsestring(tmpl, quoted=False) t = cmdutil.changeset_templater(self.ui, self.repo, False, None, tmpl, mapfile, False) self.ui.pushbuffer()
--- a/hgext/churn.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/churn.py Fri May 15 11:52:09 2015 -0500 @@ -9,7 +9,7 @@ '''command to display statistics about repository history''' from mercurial.i18n import _ -from mercurial import patch, cmdutil, scmutil, util, templater, commands +from mercurial import patch, cmdutil, scmutil, util, commands from mercurial import encoding import os import time, datetime @@ -19,7 +19,6 @@ testedwith = 'internal' def maketemplater(ui, repo, tmpl): - tmpl = templater.parsestring(tmpl, quoted=False) try: t = cmdutil.changeset_templater(ui, repo, False, None, tmpl, None, False)
--- a/hgext/factotum.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/factotum.py Fri May 15 11:52:09 2015 -0500 @@ -67,21 +67,20 @@ while True: fd = os.open('%s/rpc' % _mountpoint, os.O_RDWR) try: - try: - os.write(fd, 'start %s' % params) - l = os.read(fd, ERRMAX).split() - if l[0] == 'ok': - os.write(fd, 'read') - status, user, passwd = os.read(fd, ERRMAX).split(None, 2) - if status == 'ok': - if passwd.startswith("'"): - if passwd.endswith("'"): - passwd = passwd[1:-1].replace("''", "'") - else: - raise util.Abort(_('malformed password string')) - return (user, passwd) - except (OSError, IOError): - raise util.Abort(_('factotum not responding')) + os.write(fd, 'start %s' % params) + l = os.read(fd, ERRMAX).split() + if l[0] == 'ok': + os.write(fd, 'read') + status, user, passwd = os.read(fd, ERRMAX).split(None, 2) + if status == 'ok': + if passwd.startswith("'"): + if passwd.endswith("'"): + passwd = passwd[1:-1].replace("''", "'") + else: + raise util.Abort(_('malformed password string')) + return (user, passwd) + except (OSError, IOError): + raise util.Abort(_('factotum not responding')) finally: os.close(fd) getkey(self, params)
--- a/hgext/hgcia.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/hgcia.py Fri May 15 11:52:09 2015 -0500 @@ -43,7 +43,7 @@ from mercurial.i18n import _ from mercurial.node import bin, short -from mercurial import cmdutil, patch, templater, util, mail +from mercurial import cmdutil, patch, util, mail import email.Parser import socket, xmlrpclib @@ -206,7 +206,6 @@ template = self.dstemplate else: template = self.deftemplate - template = templater.parsestring(template, quoted=False) t = cmdutil.changeset_templater(self.ui, self.repo, False, None, template, style, False) self.templater = t
--- a/hgext/keyword.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/keyword.py Fri May 15 11:52:09 2015 -0500 @@ -83,7 +83,7 @@ ''' from mercurial import commands, context, cmdutil, dispatch, filelog, extensions -from mercurial import localrepo, match, patch, templatefilters, templater, util +from mercurial import localrepo, match, patch, templatefilters, util from mercurial import scmutil, pathutil from mercurial.hgweb import webcommands from mercurial.i18n import _ @@ -191,8 +191,7 @@ kwmaps = self.ui.configitems('keywordmaps') if kwmaps: # override default templates - self.templates = dict((k, templater.parsestring(v, False)) - for k, v in kwmaps) + self.templates = dict(kwmaps) else: self.templates = _defaultkwmaps(self.ui) @@ -457,9 +456,7 @@ repo.commit(text=msg) ui.status(_('\n\tkeywords expanded\n')) ui.write(repo.wread(fn)) - for root, dirs, files in os.walk(tmpdir): - for f in files: - util.unlinkpath(repo.vfs.reljoin(root, f)) + repo.wvfs.rmtree(repo.root) @command('kwexpand', commands.walkopts,
--- a/hgext/largefiles/overrides.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/largefiles/overrides.py Fri May 15 11:52:09 2015 -0500 @@ -578,14 +578,13 @@ nolfiles = False installnormalfilesmatchfn(repo[None].manifest()) try: - try: - result = orig(ui, repo, pats, opts, rename) - except util.Abort, e: - if str(e) != _('no files to copy'): - raise e - else: - nonormalfiles = True - result = 0 + result = orig(ui, repo, pats, opts, rename) + except util.Abort, e: + if str(e) != _('no files to copy'): + raise e + else: + nonormalfiles = True + result = 0 finally: restorematchfn() @@ -608,86 +607,85 @@ os.makedirs(makestandin(dest)) try: - try: - # When we call orig below it creates the standins but we don't add - # them to the dir state until later so lock during that time. - wlock = repo.wlock() + # When we call orig below it creates the standins but we don't add + # them to the dir state until later so lock during that time. + wlock = repo.wlock() - manifest = repo[None].manifest() - def overridematch(ctx, pats=[], opts={}, globbed=False, - default='relpath'): - newpats = [] - # The patterns were previously mangled to add the standin - # directory; we need to remove that now - for pat in pats: - if match_.patkind(pat) is None and lfutil.shortname in pat: - newpats.append(pat.replace(lfutil.shortname, '')) - else: - newpats.append(pat) - match = oldmatch(ctx, newpats, opts, globbed, default) - m = copy.copy(match) - lfile = lambda f: lfutil.standin(f) in manifest - m._files = [lfutil.standin(f) for f in m._files if lfile(f)] - m._fmap = set(m._files) - origmatchfn = m.matchfn - m.matchfn = lambda f: (lfutil.isstandin(f) and - (f in manifest) and - origmatchfn(lfutil.splitstandin(f)) or - None) - return m - oldmatch = installmatchfn(overridematch) - listpats = [] + manifest = repo[None].manifest() + def overridematch(ctx, pats=[], opts={}, globbed=False, + default='relpath'): + newpats = [] + # The patterns were previously mangled to add the standin + # directory; we need to remove that now for pat in pats: - if match_.patkind(pat) is not None: - listpats.append(pat) + if match_.patkind(pat) is None and lfutil.shortname in pat: + newpats.append(pat.replace(lfutil.shortname, '')) else: - listpats.append(makestandin(pat)) + newpats.append(pat) + match = oldmatch(ctx, newpats, opts, globbed, default) + m = copy.copy(match) + lfile = lambda f: lfutil.standin(f) in manifest + m._files = [lfutil.standin(f) for f in m._files if lfile(f)] + m._fmap = set(m._files) + origmatchfn = m.matchfn + m.matchfn = lambda f: (lfutil.isstandin(f) and + (f in manifest) and + origmatchfn(lfutil.splitstandin(f)) or + None) + return m + oldmatch = installmatchfn(overridematch) + listpats = [] + for pat in pats: + if match_.patkind(pat) is not None: + listpats.append(pat) + else: + listpats.append(makestandin(pat)) - try: - origcopyfile = util.copyfile - copiedfiles = [] - def overridecopyfile(src, dest): - if (lfutil.shortname in src and - dest.startswith(repo.wjoin(lfutil.shortname))): - destlfile = dest.replace(lfutil.shortname, '') - if not opts['force'] and os.path.exists(destlfile): - raise IOError('', - _('destination largefile already exists')) - copiedfiles.append((src, dest)) - origcopyfile(src, dest) - - util.copyfile = overridecopyfile - result += orig(ui, repo, listpats, opts, rename) - finally: - util.copyfile = origcopyfile - - lfdirstate = lfutil.openlfdirstate(ui, repo) - for (src, dest) in copiedfiles: + try: + origcopyfile = util.copyfile + copiedfiles = [] + def overridecopyfile(src, dest): if (lfutil.shortname in src and dest.startswith(repo.wjoin(lfutil.shortname))): - srclfile = src.replace(repo.wjoin(lfutil.standin('')), '') - destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '') - destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.' - if not os.path.isdir(destlfiledir): - os.makedirs(destlfiledir) - if rename: - os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile)) + destlfile = dest.replace(lfutil.shortname, '') + if not opts['force'] and os.path.exists(destlfile): + raise IOError('', + _('destination largefile already exists')) + copiedfiles.append((src, dest)) + origcopyfile(src, dest) + + util.copyfile = overridecopyfile + result += orig(ui, repo, listpats, opts, rename) + finally: + util.copyfile = origcopyfile - # The file is gone, but this deletes any empty parent - # directories as a side-effect. - util.unlinkpath(repo.wjoin(srclfile), True) - lfdirstate.remove(srclfile) - else: - util.copyfile(repo.wjoin(srclfile), - repo.wjoin(destlfile)) + lfdirstate = lfutil.openlfdirstate(ui, repo) + for (src, dest) in copiedfiles: + if (lfutil.shortname in src and + dest.startswith(repo.wjoin(lfutil.shortname))): + srclfile = src.replace(repo.wjoin(lfutil.standin('')), '') + destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '') + destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.' + if not os.path.isdir(destlfiledir): + os.makedirs(destlfiledir) + if rename: + os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile)) - lfdirstate.add(destlfile) - lfdirstate.write() - except util.Abort, e: - if str(e) != _('no files to copy'): - raise e - else: - nolfiles = True + # The file is gone, but this deletes any empty parent + # directories as a side-effect. + util.unlinkpath(repo.wjoin(srclfile), True) + lfdirstate.remove(srclfile) + else: + util.copyfile(repo.wjoin(srclfile), + repo.wjoin(destlfile)) + + lfdirstate.add(destlfile) + lfdirstate.write() + except util.Abort, e: + if str(e) != _('no files to copy'): + raise e + else: + nolfiles = True finally: restorematchfn() wlock.release() @@ -985,7 +983,7 @@ for subpath in sorted(ctx.substate): sub = ctx.sub(subpath) submatch = match_.narrowmatcher(subpath, match) - sub.archive(archiver, os.path.join(prefix, repo._path) + '/', submatch) + sub.archive(archiver, prefix + repo._path + '/', submatch) # If a largefile is modified, the change is not reflected in its # standin until a commit. cmdutil.bailifchanged() raises an exception
--- a/hgext/largefiles/proto.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/largefiles/proto.py Fri May 15 11:52:09 2015 -0500 @@ -31,17 +31,16 @@ tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) try: - try: - proto.getfile(tmpfp) - tmpfp._fp.seek(0) - if sha != lfutil.hexsha1(tmpfp._fp): - raise IOError(0, _('largefile contents do not match hash')) - tmpfp.close() - lfutil.linktousercache(repo, sha) - except IOError, e: - repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % - (sha, e.strerror)) - return wireproto.pushres(1) + proto.getfile(tmpfp) + tmpfp._fp.seek(0) + if sha != lfutil.hexsha1(tmpfp._fp): + raise IOError(0, _('largefile contents do not match hash')) + tmpfp.close() + lfutil.linktousercache(repo, sha) + except IOError, e: + repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % + (sha, e.strerror)) + return wireproto.pushres(1) finally: tmpfp.discard()
--- a/hgext/largefiles/remotestore.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/largefiles/remotestore.py Fri May 15 11:52:09 2015 -0500 @@ -36,13 +36,12 @@ self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash)) fd = None try: - try: - fd = lfutil.httpsendfile(self.ui, filename) - except IOError, e: - raise util.Abort( - _('remotestore: could not open file %s: %s') - % (filename, str(e))) + fd = lfutil.httpsendfile(self.ui, filename) return self._put(hash, fd) + except IOError, e: + raise util.Abort( + _('remotestore: could not open file %s: %s') + % (filename, str(e))) finally: if fd: fd.close()
--- a/hgext/mq.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/mq.py Fri May 15 11:52:09 2015 -0500 @@ -376,14 +376,17 @@ if repo.ui.configbool('mq', 'secret', False): phase = phases.secret if phase is not None: - backup = repo.ui.backupconfig('phases', 'new-commit') + phasebackup = repo.ui.backupconfig('phases', 'new-commit') + allowemptybackup = repo.ui.backupconfig('ui', 'allowemptycommit') try: if phase is not None: repo.ui.setconfig('phases', 'new-commit', phase, 'mq') + repo.ui.setconfig('ui', 'allowemptycommit', True) return repo.commit(*args, **kwargs) finally: + repo.ui.restoreconfig(allowemptybackup) if phase is not None: - repo.ui.restoreconfig(backup) + repo.ui.restoreconfig(phasebackup) class AbortNoCleanup(error.Abort): pass @@ -807,9 +810,10 @@ def apply(self, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, all_files=None, tobackup=None, keepchanges=False): - wlock = lock = tr = None + wlock = dsguard = lock = tr = None try: wlock = repo.wlock() + dsguard = cmdutil.dirstateguard(repo, 'mq.apply') lock = repo.lock() tr = repo.transaction("qpush") try: @@ -818,21 +822,22 @@ tobackup=tobackup, keepchanges=keepchanges) tr.close() self.savedirty() + dsguard.close() return ret except AbortNoCleanup: tr.close() self.savedirty() + dsguard.close() raise except: # re-raises try: tr.abort() finally: repo.invalidate() - repo.dirstate.invalidate() self.invalidate() raise finally: - release(tr, lock, wlock) + release(tr, lock, dsguard, wlock) self.removeundo(repo) def _apply(self, repo, series, list=False, update_status=True, @@ -1681,8 +1686,9 @@ bmlist = repo[top].bookmarks() + dsguard = None try: - repo.dirstate.beginparentchange() + dsguard = cmdutil.dirstateguard(repo, 'mq.refresh') if diffopts.git or diffopts.upgrade: copies = {} for dst in a: @@ -1735,13 +1741,12 @@ # assumes strip can roll itself back if interrupted repo.setparents(*cparents) - repo.dirstate.endparentchange() self.applied.pop() self.applieddirty = True strip(self.ui, repo, [top], update=False, backup=False) - except: # re-raises - repo.dirstate.invalidate() - raise + dsguard.close() + finally: + release(dsguard) try: # might be nice to attempt to roll back strip after this
--- a/hgext/notify.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/notify.py Fri May 15 11:52:09 2015 -0500 @@ -138,7 +138,7 @@ # load. This was not a problem on Python 2.7. import email.Parser from mercurial.i18n import _ -from mercurial import patch, cmdutil, templater, util, mail +from mercurial import patch, cmdutil, util, mail import fnmatch testedwith = 'internal' @@ -190,8 +190,6 @@ self.ui.config('notify', 'template')) if not mapfile and not template: template = deftemplates.get(hooktype) or single_template - if template: - template = templater.parsestring(template, quoted=False) self.t = cmdutil.changeset_templater(self.ui, self.repo, False, None, template, mapfile, False)
--- a/hgext/rebase.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/rebase.py Fri May 15 11:52:09 2015 -0500 @@ -67,7 +67,7 @@ ('e', 'edit', False, _('invoke editor on commit messages')), ('l', 'logfile', '', _('read collapse commit message from file'), _('FILE')), - ('', 'keep', False, _('keep original changesets')), + ('k', 'keep', False, _('keep original changesets')), ('', 'keepbranches', False, _('keep original branch names')), ('D', 'detach', False, _('(DEPRECATED)')), ('i', 'interactive', False, _('(DEPRECATED)')), @@ -358,9 +358,9 @@ # Keep track of the current bookmarks in order to reset them later currentbookmarks = repo._bookmarks.copy() - activebookmark = activebookmark or repo._bookmarkcurrent + activebookmark = activebookmark or repo._activebookmark if activebookmark: - bookmarks.unsetcurrent(repo) + bookmarks.deactivate(repo) extrafn = _makeextrafn(extrafns) @@ -498,7 +498,7 @@ if (activebookmark and repo['.'].node() == repo._bookmarks[activebookmark]): - bookmarks.setcurrent(repo, activebookmark) + bookmarks.activate(repo, activebookmark) finally: release(lock, wlock) @@ -530,10 +530,9 @@ '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev but also store useful information in extra. Return node of committed revision.''' + dsguard = cmdutil.dirstateguard(repo, 'rebase') try: - repo.dirstate.beginparentchange() repo.setparents(repo[p1].node(), repo[p2].node()) - repo.dirstate.endparentchange() ctx = repo[rev] if commitmsg is None: commitmsg = ctx.description() @@ -552,11 +551,10 @@ repo.ui.restoreconfig(backup) repo.dirstate.setbranch(repo[newnode].branch()) + dsguard.close() return newnode - except util.Abort: - # Invalidate the previous setparents - repo.dirstate.invalidate() - raise + finally: + release(dsguard) def rebasenode(repo, rev, p1, base, state, collapse, target): 'Rebase a single revision rev on top of p1 using base as merge ancestor' @@ -893,7 +891,7 @@ repair.strip(repo.ui, repo, strippoints) if activebookmark and activebookmark in repo._bookmarks: - bookmarks.setcurrent(repo, activebookmark) + bookmarks.activate(repo, activebookmark) clearstatus(repo) repo.ui.warn(_('rebase aborted\n')) @@ -1057,7 +1055,7 @@ hg.update(repo, dest) if bookmarks.update(repo, [movemarkfrom], repo['.'].node()): ui.status(_("updating bookmark %s\n") - % repo._bookmarkcurrent) + % repo._activebookmark) else: if opts.get('tool'): raise util.Abort(_('--tool can only be used with --rebase')) @@ -1118,4 +1116,3 @@ _("use 'hg rebase --continue' or 'hg rebase --abort'")]) # ensure rebased rev are not hidden extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible) -
--- a/hgext/shelve.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/shelve.py Fri May 15 11:52:09 2015 -0500 @@ -163,7 +163,7 @@ # we never need the user, so we use a generic user for all shelve operations user = 'shelve@localhost' - label = repo._bookmarkcurrent or parent.branch() or 'default' + label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') @@ -284,17 +284,15 @@ """subcommand that deletes a specific shelve""" if not pats: raise util.Abort(_('no shelved changes specified!')) - wlock = None + wlock = repo.wlock() try: - wlock = repo.wlock() - try: - for name in pats: - for suffix in 'hg patch'.split(): - shelvedfile(repo, name, suffix).unlink() - except OSError, err: - if err.errno != errno.ENOENT: - raise - raise util.Abort(_("shelved change '%s' not found") % name) + for name in pats: + for suffix in 'hg patch'.split(): + shelvedfile(repo, name, suffix).unlink() + except OSError, err: + if err.errno != errno.ENOENT: + raise + raise util.Abort(_("shelved change '%s' not found") % name) finally: lockmod.release(wlock)
--- a/hgext/strip.py Thu May 14 21:35:06 2015 -0700 +++ b/hgext/strip.py Fri May 15 11:52:09 2015 -0500 @@ -60,8 +60,8 @@ marks = repo._bookmarks if bookmark: - if bookmark == repo._bookmarkcurrent: - bookmarks.unsetcurrent(repo) + if bookmark == repo._activebookmark: + bookmarks.deactivate(repo) del marks[bookmark] marks.write() ui.write(_("bookmark '%s' deleted\n") % bookmark)
--- a/mercurial/archival.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/archival.py Fri May 15 11:52:09 2015 -0500 @@ -37,6 +37,10 @@ prefix = util.pconvert(lpfx) if not prefix.endswith('/'): prefix += '/' + # Drop the leading '.' path component if present, so Windows can read the + # zip files (issue4634) + if prefix.startswith('./'): + prefix = prefix[2:] if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix: raise util.Abort(_('archive prefix contains illegal components')) return prefix
--- a/mercurial/bookmarks.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/bookmarks.py Fri May 15 11:52:09 2015 -0500 @@ -8,7 +8,7 @@ import os from mercurial.i18n import _ from mercurial.node import hex, bin -from mercurial import encoding, error, util, obsolete, lock as lockmod +from mercurial import encoding, util, obsolete, lock as lockmod import errno class bmstore(dict): @@ -22,7 +22,7 @@ {hash}\s{name}\n (the same format as localtags) in .hg/bookmarks. The mapping is stored as {name: nodeid}. - This class does NOT handle the "current" bookmark state at this + This class does NOT handle the "active" bookmark state at this time. """ @@ -83,8 +83,8 @@ def _writerepo(self, repo): """Factored out for extensibility""" - if repo._bookmarkcurrent not in self: - unsetcurrent(repo) + if repo._activebookmark not in self: + deactivate(repo) wlock = repo.wlock() try: @@ -106,13 +106,12 @@ for name, node in self.iteritems(): fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name))) -def readcurrent(repo): - '''Get the current bookmark - - If we use gittish branches we have a current bookmark that - we are on. This function returns the name of the bookmark. It - is stored in .hg/bookmarks.current - ''' +def readactive(repo): + """ + Get the active bookmark. We can have an active bookmark that updates + itself as we commit. This function returns the name of that bookmark. + It is stored in .hg/bookmarks.current + """ mark = None try: file = repo.vfs('bookmarks.current') @@ -129,17 +128,17 @@ file.close() return mark -def setcurrent(repo, mark): - '''Set the name of the bookmark that we are currently on - - Set the name of the bookmark that we are on (hg update <bookmark>). +def activate(repo, mark): + """ + Set the given bookmark to be 'active', meaning that this bookmark will + follow new commits that are made. The name is recorded in .hg/bookmarks.current - ''' + """ if mark not in repo._bookmarks: raise AssertionError('bookmark %s does not exist!' % mark) - current = repo._bookmarkcurrent - if current == mark: + active = repo._activebookmark + if active == mark: return wlock = repo.wlock() @@ -149,42 +148,36 @@ file.close() finally: wlock.release() - repo._bookmarkcurrent = mark + repo._activebookmark = mark -def unsetcurrent(repo): +def deactivate(repo): + """ + Unset the active bookmark in this reposiotry. + """ wlock = repo.wlock() try: - try: - repo.vfs.unlink('bookmarks.current') - repo._bookmarkcurrent = None - except OSError, inst: - if inst.errno != errno.ENOENT: - raise + repo.vfs.unlink('bookmarks.current') + repo._activebookmark = None + except OSError, inst: + if inst.errno != errno.ENOENT: + raise finally: wlock.release() -def iscurrent(repo, mark=None, parents=None): - '''Tell whether the current bookmark is also active +def isactivewdirparent(repo): + """ + Tell whether the 'active' bookmark (the one that follows new commits) + points to one of the parents of the current working directory (wdir). - I.e., the bookmark listed in .hg/bookmarks.current also points to a - parent of the working directory. - ''' - if not mark: - mark = repo._bookmarkcurrent - if not parents: - parents = [p.node() for p in repo[None].parents()] + While this is normally the case, it can on occasion be false; for example, + immediately after a pull, the active bookmark can be moved to point + to a place different than the wdir. This is solved by running `hg update`. + """ + mark = repo._activebookmark marks = repo._bookmarks + parents = [p.node() for p in repo[None].parents()] return (mark in marks and marks[mark] in parents) -def updatecurrentbookmark(repo, oldnode, curbranch): - try: - return update(repo, oldnode, repo.branchtip(curbranch)) - except error.RepoLookupError: - if curbranch == "default": # no default branch! - return update(repo, oldnode, repo.lookup("tip")) - else: - raise util.Abort(_("branch %s not found") % curbranch) - def deletedivergent(repo, deletefrom, bm): '''Delete divergent versions of bm on nodes in deletefrom. @@ -207,33 +200,33 @@ check out and where to move the active bookmark from, if needed.''' movemarkfrom = None if checkout is None: - curmark = repo._bookmarkcurrent - if iscurrent(repo): + activemark = repo._activebookmark + if isactivewdirparent(repo): movemarkfrom = repo['.'].node() - elif curmark: - ui.status(_("updating to active bookmark %s\n") % curmark) - checkout = curmark + elif activemark: + ui.status(_("updating to active bookmark %s\n") % activemark) + checkout = activemark return (checkout, movemarkfrom) def update(repo, parents, node): deletefrom = parents marks = repo._bookmarks update = False - cur = repo._bookmarkcurrent - if not cur: + active = repo._activebookmark + if not active: return False - if marks[cur] in parents: + if marks[active] in parents: new = repo[node] divs = [repo[b] for b in marks - if b.split('@', 1)[0] == cur.split('@', 1)[0]] + if b.split('@', 1)[0] == active.split('@', 1)[0]] anc = repo.changelog.ancestors([new.rev()]) deletefrom = [b.node() for b in divs if b.rev() in anc or b == new] - if validdest(repo, repo[marks[cur]], new): - marks[cur] = new.node() + if validdest(repo, repo[marks[active]], new): + marks[active] = new.node() update = True - if deletedivergent(repo, deletefrom, cur): + if deletedivergent(repo, deletefrom, active): update = True if update:
--- a/mercurial/bundlerepo.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/bundlerepo.py Fri May 15 11:52:09 2015 -0500 @@ -177,11 +177,10 @@ return manifest.manifest.revision(self, nodeorrev) class bundlefilelog(bundlerevlog, filelog.filelog): - def __init__(self, opener, path, bundle, linkmapper, repo): + def __init__(self, opener, path, bundle, linkmapper): filelog.filelog.__init__(self, opener, path) bundlerevlog.__init__(self, opener, self.indexfile, bundle, linkmapper) - self._repo = repo def baserevision(self, nodeorrev): return filelog.filelog.revision(self, nodeorrev) @@ -322,8 +321,7 @@ if f in self.bundlefilespos: self.bundle.seek(self.bundlefilespos[f]) - return bundlefilelog(self.svfs, f, self.bundle, - self.changelog.rev, self) + return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev) else: return filelog.filelog(self.svfs, f)
--- a/mercurial/changegroup.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/changegroup.py Fri May 15 11:52:09 2015 -0500 @@ -283,8 +283,6 @@ if bundlecaps is None: bundlecaps = set() self._bundlecaps = bundlecaps - self._changelog = repo.changelog - self._manifest = repo.manifest reorder = repo.ui.config('bundle', 'reorder', 'auto') if reorder == 'auto': reorder = None @@ -304,7 +302,7 @@ def fileheader(self, fname): return chunkheader(len(fname)) + fname - def group(self, nodelist, revlog, lookup, units=None, reorder=None): + def group(self, nodelist, revlog, lookup, units=None): """Calculate a delta group, yielding a sequence of changegroup chunks (strings). @@ -325,7 +323,7 @@ # for generaldelta revlogs, we linearize the revs; this will both be # much quicker and generate a much smaller bundle - if (revlog._generaldelta and reorder is not False) or reorder: + if (revlog._generaldelta and self._reorder is None) or self._reorder: dag = dagutil.revlogdag(revlog) revs = set(revlog.rev(n) for n in nodelist) revs = dag.linearize(revs) @@ -347,23 +345,20 @@ for c in self.revchunk(revlog, curr, prev, linknode): yield c + if units is not None: + self._progress(msgbundling, None) yield self.close() # filter any nodes that claim to be part of the known set - def prune(self, revlog, missing, commonrevs, source): + def prune(self, revlog, missing, commonrevs): rr, rl = revlog.rev, revlog.linkrev return [n for n in missing if rl(rr(n)) not in commonrevs] def generate(self, commonrevs, clnodes, fastpathlinkrev, source): '''yield a sequence of changegroup chunks (strings)''' repo = self._repo - cl = self._changelog - mf = self._manifest - reorder = self._reorder - progress = self._progress - - # for progress output - msgbundling = _('bundling') + cl = repo.changelog + ml = repo.manifest clrevorder = {} mfs = {} # needed manifests @@ -383,20 +378,34 @@ self._verbosenote(_('uncompressed size of bundle content:\n')) size = 0 - for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets'), - reorder=reorder): + for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')): size += len(chunk) yield chunk self._verbosenote(_('%8.i (changelog)\n') % size) - progress(msgbundling, None) + # We need to make sure that the linkrev in the changegroup refers to + # the first changeset that introduced the manifest or file revision. + # The fastpath is usually safer than the slowpath, because the filelogs + # are walked in revlog order. + # + # When taking the slowpath with reorder=None and the manifest revlog + # uses generaldelta, the manifest may be walked in the "wrong" order. + # Without 'clrevorder', we would get an incorrect linkrev (see fix in + # cc0ff93d0c0c). + # + # When taking the fastpath, we are only vulnerable to reordering + # of the changelog itself. The changelog never uses generaldelta, so + # it is only reordered when reorder=True. To handle this case, we + # simply take the slowpath, which already has the 'clrevorder' logic. + # This was also fixed in cc0ff93d0c0c. + fastpathlinkrev = fastpathlinkrev and not self._reorder # Callback for the manifest, used to collect linkrevs for filelog # revisions. # Returns the linkrev node (collected in lookupcl). def lookupmf(x): clnode = mfs[x] - if not fastpathlinkrev or reorder: - mdata = mf.readfast(x) + if not fastpathlinkrev: + mdata = ml.readfast(x) for f, n in mdata.iteritems(): if f in changedfiles: # record the first changeset introducing this filelog @@ -407,25 +416,23 @@ fclnodes[n] = clnode return clnode - mfnodes = self.prune(mf, mfs, commonrevs, source) + mfnodes = self.prune(ml, mfs, commonrevs) size = 0 - for chunk in self.group(mfnodes, mf, lookupmf, units=_('manifests'), - reorder=reorder): + for chunk in self.group(mfnodes, ml, lookupmf, units=_('manifests')): size += len(chunk) yield chunk self._verbosenote(_('%8.i (manifests)\n') % size) - progress(msgbundling, None) mfs.clear() - needed = set(cl.rev(x) for x in clnodes) + clrevs = set(cl.rev(x) for x in clnodes) def linknodes(filerevlog, fname): - if fastpathlinkrev and not reorder: + if fastpathlinkrev: llr = filerevlog.linkrev def genfilenodes(): for r in filerevlog: linkrev = llr(r) - if linkrev in needed: + if linkrev in clrevs: yield filerevlog.node(r), cl.node(linkrev) return dict(genfilenodes()) return fnodes.get(fname, {}) @@ -435,15 +442,14 @@ yield chunk yield self.close() - progress(msgbundling, None) if clnodes: repo.hook('outgoing', node=hex(clnodes[0]), source=source) + # The 'source' parameter is useful for extensions def generatefiles(self, changedfiles, linknodes, commonrevs, source): repo = self._repo progress = self._progress - reorder = self._reorder msgbundling = _('bundling') total = len(changedfiles) @@ -460,18 +466,18 @@ def lookupfilelog(x): return linkrevnodes[x] - filenodes = self.prune(filerevlog, linkrevnodes, commonrevs, source) + filenodes = self.prune(filerevlog, linkrevnodes, commonrevs) if filenodes: progress(msgbundling, i + 1, item=fname, unit=msgfiles, total=total) h = self.fileheader(fname) size = len(h) yield h - for chunk in self.group(filenodes, filerevlog, lookupfilelog, - reorder=reorder): + for chunk in self.group(filenodes, filerevlog, lookupfilelog): size += len(chunk) yield chunk self._verbosenote(_('%8.i %s\n') % (size, fname)) + progress(msgbundling, None) def deltaparent(self, revlog, rev, p1, p2, prev): return prev @@ -513,11 +519,13 @@ version = '02' deltaheader = _CHANGEGROUPV2_DELTA_HEADER - def group(self, nodelist, revlog, lookup, units=None, reorder=None): - if (revlog._generaldelta and reorder is not True): - reorder = False - return super(cg2packer, self).group(nodelist, revlog, lookup, - units=units, reorder=reorder) + def __init__(self, repo, bundlecaps=None): + super(cg2packer, self).__init__(repo, bundlecaps) + if self._reorder is None: + # Since generaldelta is directly supported by cg2, reordering + # generally doesn't help, so we disable it by default (treating + # bundle.reorder=auto just like bundle.reorder=False). + self._reorder = False def deltaparent(self, revlog, rev, p1, p2, prev): dp = revlog.deltaparent(rev) @@ -788,8 +796,8 @@ if repo.ui.configbool('server', 'validate', default=False): # validate incoming csets have their manifests for cset in xrange(clstart, clend): - mfest = repo.changelog.read(repo.changelog.node(cset))[0] - mfest = repo.manifest.readdelta(mfest) + mfnode = repo.changelog.read(repo.changelog.node(cset))[0] + mfest = repo.manifest.readdelta(mfnode) # store file nodes we must see for f, n in mfest.iteritems(): needfiles.setdefault(f, set()).add(n)
--- a/mercurial/changelog.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/changelog.py Fri May 15 11:52:09 2015 -0500 @@ -172,14 +172,6 @@ self.rev(self.node(0)) return self._nodecache - def hasnode(self, node): - """filtered version of revlog.hasnode""" - try: - i = self.rev(node) - return i not in self.filteredrevs - except KeyError: - return False - def headrevs(self): if self.filteredrevs: try:
--- a/mercurial/cmdutil.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/cmdutil.py Fri May 15 11:52:09 2015 -0500 @@ -818,6 +818,7 @@ msg = _('applied to working directory') rejects = False + dsguard = None try: cmdline_message = logmessage(ui, opts) @@ -859,7 +860,7 @@ n = None if update: - repo.dirstate.beginparentchange() + dsguard = dirstateguard(repo, 'tryimportone') if p1 != parents[0]: updatefunc(repo, p1.node()) if p2 != parents[1]: @@ -896,10 +897,16 @@ editor = None else: editor = getcommiteditor(editform=editform, **opts) - n = repo.commit(message, opts.get('user') or user, - opts.get('date') or date, match=m, - editor=editor, force=partial) - repo.dirstate.endparentchange() + allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit') + try: + if partial: + repo.ui.setconfig('ui', 'allowemptycommit', True) + n = repo.commit(message, opts.get('user') or user, + opts.get('date') or date, match=m, + editor=editor) + finally: + repo.ui.restoreconfig(allowemptyback) + dsguard.close() else: if opts.get('exact') or opts.get('import_branch'): branch = branch or 'default' @@ -937,6 +944,7 @@ msg = _('created %s') % short(n) return (msg, n, rejects) finally: + lockmod.release(dsguard) os.unlink(tmpname) def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False, @@ -1443,9 +1451,9 @@ tmpl = ui.config('ui', 'logtemplate') if tmpl: try: - tmpl = templater.parsestring(tmpl) + tmpl = templater.unquotestring(tmpl) except SyntaxError: - tmpl = templater.parsestring(tmpl, quoted=False) + pass return tmpl, None else: style = util.expandpath(ui.config('ui', 'style', '')) @@ -1477,9 +1485,9 @@ t = ui.config('templates', tmpl) if t: try: - tmpl = templater.parsestring(t) + tmpl = templater.unquotestring(t) except SyntaxError: - tmpl = templater.parsestring(t, quoted=False) + tmpl = t return tmpl, None if tmpl == 'list': @@ -2336,7 +2344,7 @@ return True return False - isdir = f in deleteddirs or f in wctx.dirs() + isdir = f in deleteddirs or wctx.hasdir(f) if f in repo.dirstate or isdir or f == '.' or insubrepo(): continue @@ -2464,9 +2472,10 @@ ui.note(_('amending changeset %s\n') % old) base = old.p1() - wlock = lock = newid = None + wlock = dsguard = lock = newid = None try: wlock = repo.wlock() + dsguard = dirstateguard(repo, 'amend') lock = repo.lock() tr = repo.transaction('amend') try: @@ -2480,13 +2489,13 @@ # First, do a regular commit to record all changes in the working # directory (if there are any) ui.callhooks = False - currentbookmark = repo._bookmarkcurrent + activebookmark = repo._activebookmark try: - repo._bookmarkcurrent = None + repo._activebookmark = None opts['message'] = 'temporary amend commit for %s' % old node = commit(ui, repo, commitfunc, pats, opts) finally: - repo._bookmarkcurrent = currentbookmark + repo._activebookmark = activebookmark ui.callhooks = True ctx = repo[node] @@ -2637,6 +2646,7 @@ tr.close() finally: tr.release() + dsguard.close() if not createmarkers and newid != old.node(): # Strip the intermediate commit (if there was one) and the amended # commit @@ -2645,9 +2655,7 @@ ui.note(_('stripping amended changeset %s\n') % old) repair.strip(ui, repo, old.node(), topic='amend-backup') finally: - if newid is None: - repo.dirstate.invalidate() - lockmod.release(lock, wlock) + lockmod.release(lock, dsguard, wlock) return newid def commiteditor(repo, ctx, subs, editform=''): @@ -2721,8 +2729,8 @@ edittext.append(_("HG: branch merge")) if ctx.branch(): edittext.append(_("HG: branch '%s'") % ctx.branch()) - if bookmarks.iscurrent(repo): - edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent) + if bookmarks.isactivewdirparent(repo): + edittext.append(_("HG: bookmark '%s'") % repo._activebookmark) edittext.extend([_("HG: subrepo %s") % s for s in subs]) edittext.extend([_("HG: added %s") % f for f in added]) edittext.extend([_("HG: changed %s") % f for f in modified]) @@ -3259,3 +3267,59 @@ for f, clearable, allowcommit, msg, hint in unfinishedstates: if clearable and repo.vfs.exists(f): util.unlink(repo.join(f)) + +class dirstateguard(object): + '''Restore dirstate at unexpected failure. + + At the construction, this class does: + + - write current ``repo.dirstate`` out, and + - save ``.hg/dirstate`` into the backup file + + This restores ``.hg/dirstate`` from backup file, if ``release()`` + is invoked before ``close()``. + + This just removes the backup file at ``close()`` before ``release()``. + ''' + + def __init__(self, repo, name): + repo.dirstate.write() + self._repo = repo + self._filename = 'dirstate.backup.%s.%d' % (name, id(self)) + repo.vfs.write(self._filename, repo.vfs.tryread('dirstate')) + self._active = True + self._closed = False + + def __del__(self): + if self._active: # still active + # this may occur, even if this class is used correctly: + # for example, releasing other resources like transaction + # may raise exception before ``dirstateguard.release`` in + # ``release(tr, ....)``. + self._abort() + + def close(self): + if not self._active: # already inactivated + msg = (_("can't close already inactivated backup: %s") + % self._filename) + raise util.Abort(msg) + + self._repo.vfs.unlink(self._filename) + self._active = False + self._closed = True + + def _abort(self): + # this "invalidate()" prevents "wlock.release()" from writing + # changes of dirstate out after restoring to original status + self._repo.dirstate.invalidate() + + self._repo.vfs.rename(self._filename, 'dirstate') + self._active = False + + def release(self): + if not self._closed: + if not self._active: # already inactivated + msg = (_("can't release already inactivated backup: %s") + % self._filename) + raise util.Abort(msg) + self._abort()
--- a/mercurial/commands.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/commands.py Fri May 15 11:52:09 2015 -0500 @@ -979,8 +979,8 @@ if mark not in marks: raise util.Abort(_("bookmark '%s' does not exist") % mark) - if mark == repo._bookmarkcurrent: - bookmarks.unsetcurrent(repo) + if mark == repo._activebookmark: + bookmarks.deactivate(repo) del marks[mark] marks.write() @@ -994,8 +994,8 @@ raise util.Abort(_("bookmark '%s' does not exist") % rename) checkconflict(repo, mark, cur, force) marks[mark] = marks[rename] - if repo._bookmarkcurrent == rename and not inactive: - bookmarks.setcurrent(repo, mark) + if repo._activebookmark == rename and not inactive: + bookmarks.activate(repo, mark) del marks[rename] marks.write() @@ -1005,8 +1005,8 @@ mark = checkformat(mark) if newact is None: newact = mark - if inactive and mark == repo._bookmarkcurrent: - bookmarks.unsetcurrent(repo) + if inactive and mark == repo._activebookmark: + bookmarks.deactivate(repo) return tgt = cur if rev: @@ -1014,18 +1014,18 @@ checkconflict(repo, mark, cur, 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.unsetcurrent(repo) + bookmarks.activate(repo, newact) + elif cur != tgt and newact == repo._activebookmark: + bookmarks.deactivate(repo) marks.write() elif inactive: if len(marks) == 0: ui.status(_("no bookmarks set\n")) - elif not repo._bookmarkcurrent: + elif not repo._activebookmark: ui.status(_("no active bookmark\n")) else: - bookmarks.unsetcurrent(repo) + bookmarks.deactivate(repo) finally: wlock.release() else: # show bookmarks @@ -1035,7 +1035,7 @@ if len(marks) == 0 and not fm: ui.status(_("no bookmarks set\n")) for bmark, n in sorted(marks.iteritems()): - current = repo._bookmarkcurrent + current = repo._activebookmark if bmark == current: prefix, label = '*', 'bookmarks.current' else: @@ -1506,7 +1506,7 @@ match, extra=extra) - current = repo._bookmarkcurrent + current = repo._activebookmark marks = old.bookmarks() node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts) if node == old.node(): @@ -1519,7 +1519,7 @@ for bm in marks: newmarks[bm] = node if bm == current: - bookmarks.setcurrent(repo, bm) + bookmarks.activate(repo, bm) newmarks.write() else: def commitfunc(ui, repo, message, match, opts): @@ -2545,26 +2545,25 @@ try: tr = repo.transaction('debugobsolete') try: - try: - date = opts.get('date') - if date: - date = util.parsedate(date) - else: - date = None - prec = parsenodeid(precursor) - parents = None - if opts['record_parents']: - if prec not in repo.unfiltered(): - raise util.Abort('cannot used --record-parents on ' - 'unknown changesets') - parents = repo.unfiltered()[prec].parents() - parents = tuple(p.node() for p in parents) - repo.obsstore.create(tr, prec, succs, opts['flags'], - parents=parents, date=date, - metadata=metadata) - tr.close() - except ValueError, exc: - raise util.Abort(_('bad obsmarker input: %s') % exc) + date = opts.get('date') + if date: + date = util.parsedate(date) + else: + date = None + prec = parsenodeid(precursor) + parents = None + if opts['record_parents']: + if prec not in repo.unfiltered(): + raise util.Abort('cannot used --record-parents on ' + 'unknown changesets') + parents = repo.unfiltered()[prec].parents() + parents = tuple(p.node() for p in parents) + repo.obsstore.create(tr, prec, succs, opts['flags'], + parents=parents, date=date, + metadata=metadata) + tr.close() + except ValueError, exc: + raise util.Abort(_('bad obsmarker input: %s') % exc) finally: tr.release() finally: @@ -4213,7 +4212,7 @@ cmdutil.bailifchanged(repo) base = opts["base"] - wlock = lock = tr = None + wlock = dsguard = lock = tr = None msgs = [] ret = 0 @@ -4221,7 +4220,7 @@ try: try: wlock = repo.wlock() - repo.dirstate.beginparentchange() + dsguard = cmdutil.dirstateguard(repo, 'import') if not opts.get('no_commit'): lock = repo.lock() tr = repo.transaction('import') @@ -4262,18 +4261,16 @@ tr.close() if msgs: repo.savecommitmessage('\n* * *\n'.join(msgs)) - repo.dirstate.endparentchange() + dsguard.close() return ret - except: # re-raises - # wlock.release() indirectly calls dirstate.write(): since - # we're crashing, we do not want to change the working dir - # parent after all, so make sure it writes nothing - repo.dirstate.invalidate() - raise + finally: + # TODO: get rid of this meaningless try/finally enclosing. + # this is kept only to reduce changes in a patch. + pass finally: if tr: tr.release() - release(lock, wlock) + release(lock, dsguard, wlock) @command('incoming|in', [('f', 'force', None, @@ -4702,9 +4699,9 @@ if node: node = scmutil.revsingle(repo, node).node() - if not node and repo._bookmarkcurrent: - bmheads = repo.bookmarkheads(repo._bookmarkcurrent) - curhead = repo[repo._bookmarkcurrent].node() + if not node and repo._activebookmark: + bmheads = repo.bookmarkheads(repo._activebookmark) + curhead = repo[repo._activebookmark].node() if len(bmheads) == 2: if curhead == bmheads[0]: node = bmheads[1] @@ -4719,7 +4716,7 @@ "please merge with an explicit rev or bookmark"), hint=_("run 'hg heads' to see all heads")) - if not node and not repo._bookmarkcurrent: + if not node and not repo._activebookmark: branch = repo[None].branch() bheads = repo.branchheads(branch) nbhs = [bh for bh in bheads if not repo[bh].bookmarks()] @@ -5049,7 +5046,7 @@ return 0 if not ret and not checkout: if bookmarks.update(repo, [movemarkfrom], repo['.'].node()): - ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent) + ui.status(_("updating bookmark %s\n") % repo._activebookmark) return ret if modheads > 1: currentbranchheads = len(repo.branchheads()) @@ -5914,7 +5911,7 @@ ui.status(m, label='log.branch') if marks: - current = repo._bookmarkcurrent + current = repo._activebookmark # i18n: column positioning for "hg summary" ui.write(_('bookmarks:'), label='log.bookmark') if current is not None: @@ -6405,15 +6402,15 @@ if not ret and movemarkfrom: if bookmarks.update(repo, [movemarkfrom], repo['.'].node()): - ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent) + ui.status(_("updating bookmark %s\n") % repo._activebookmark) elif brev in repo._bookmarks: - bookmarks.setcurrent(repo, brev) + bookmarks.activate(repo, brev) ui.status(_("(activating bookmark %s)\n") % brev) elif brev: - if repo._bookmarkcurrent: + if repo._activebookmark: ui.status(_("(leaving bookmark %s)\n") % - repo._bookmarkcurrent) - bookmarks.unsetcurrent(repo) + repo._activebookmark) + bookmarks.deactivate(repo) return ret
--- a/mercurial/config.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/config.py Fri May 15 11:52:09 2015 -0500 @@ -10,10 +10,11 @@ import os, errno class config(object): - def __init__(self, data=None): + def __init__(self, data=None, includepaths=[]): self._data = {} self._source = {} self._unset = [] + self._includepaths = includepaths if data: for k in data._data: self._data[k] = data[k].copy() @@ -110,13 +111,17 @@ item = None cont = False m = includere.match(l) - if m: - inc = util.expandpath(m.group(1)) - base = os.path.dirname(src) - inc = os.path.normpath(os.path.join(base, inc)) - if include: + + if m and include: + expanded = util.expandpath(m.group(1)) + includepaths = [os.path.dirname(src)] + self._includepaths + + for base in includepaths: + inc = os.path.normpath(os.path.join(base, expanded)) + try: include(inc, remap=remap, sections=sections) + break except IOError, inst: if inst.errno != errno.ENOENT: raise error.ParseError(_("cannot include %s (%s)")
--- a/mercurial/context.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/context.py Fri May 15 11:52:09 2015 -0500 @@ -459,7 +459,7 @@ pass except (error.FilteredIndexError, error.FilteredLookupError, error.FilteredRepoLookupError): - if repo.filtername == 'visible': + if repo.filtername.startswith('visible'): msg = _("hidden revision '%s'") % changeid hint = _('use --hidden to access hidden revisions') raise error.FilteredRepoLookupError(msg, hint=hint)
--- a/mercurial/crecord.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/crecord.py Fri May 15 11:52:09 2015 -0500 @@ -20,7 +20,8 @@ # os.name is one of: 'posix', 'nt', 'dos', 'os2', 'mac', or 'ce' if os.name == 'posix': - import curses, fcntl, termios + import curses + import fcntl, termios else: # I have no idea if wcurses works with crecord... try:
--- a/mercurial/dirs.c Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/dirs.c Fri May 15 11:52:09 2015 -0500 @@ -9,7 +9,6 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> -#include <string.h> #include "util.h" /* @@ -29,23 +28,25 @@ PyObject *dict; } dirsObject; -static inline Py_ssize_t _finddir(PyObject *path, Py_ssize_t pos) +static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos) { - const char *s = PyString_AS_STRING(path); + while (pos != -1) { + if (path[pos] == '/') + break; + pos -= 1; + } - const char *ret = strchr(s + pos, '/'); - return (ret != NULL) ? (ret - s) : -1; + return pos; } static int _addpath(PyObject *dirs, PyObject *path) { - char *cpath = PyString_AS_STRING(path); - Py_ssize_t len = PyString_GET_SIZE(path); - Py_ssize_t pos = -1; + const char *cpath = PyString_AS_STRING(path); + Py_ssize_t pos = PyString_GET_SIZE(path); PyObject *key = NULL; int ret = -1; - while ((pos = _finddir(path, pos + 1)) != -1) { + while ((pos = _finddir(cpath, pos - 1)) != -1) { PyObject *val; /* It's likely that every prefix already has an entry @@ -53,18 +54,10 @@ deallocating a string for each prefix we check. */ if (key != NULL) ((PyStringObject *)key)->ob_shash = -1; - else if (pos != 0) { - /* pos >= 1, which means that len >= 2. This is - guaranteed to produce a non-interned string. */ - key = PyString_FromStringAndSize(cpath, len); - if (key == NULL) - goto bail; - } else { - /* pos == 0, which means we need to increment the dir - count for the empty string. We need to make sure we - don't muck around with interned strings, so throw it - away later. */ - key = PyString_FromString(""); + else { + /* Force Python to not reuse a small shared string. */ + key = PyString_FromStringAndSize(cpath, + pos < 2 ? 2 : pos); if (key == NULL) goto bail; } @@ -74,11 +67,7 @@ val = PyDict_GetItem(dirs, key); if (val != NULL) { PyInt_AS_LONG(val) += 1; - if (pos != 0) - PyString_AS_STRING(key)[pos] = '/'; - else - key = NULL; - continue; + break; } /* Force Python to not reuse a small shared int. */ @@ -92,9 +81,6 @@ Py_DECREF(val); if (ret == -1) goto bail; - - /* Clear the key out since we've already exposed it to Python - and can't mutate it further. */ Py_CLEAR(key); } ret = 0; @@ -107,14 +93,15 @@ static int _delpath(PyObject *dirs, PyObject *path) { - Py_ssize_t pos = -1; + char *cpath = PyString_AS_STRING(path); + Py_ssize_t pos = PyString_GET_SIZE(path); PyObject *key = NULL; int ret = -1; - while ((pos = _finddir(path, pos + 1)) != -1) { + while ((pos = _finddir(cpath, pos - 1)) != -1) { PyObject *val; - key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos); + key = PyString_FromStringAndSize(cpath, pos); if (key == NULL) goto bail; @@ -126,9 +113,11 @@ goto bail; } - if (--PyInt_AS_LONG(val) <= 0 && - PyDict_DelItem(dirs, key) == -1) - goto bail; + if (--PyInt_AS_LONG(val) <= 0) { + if (PyDict_DelItem(dirs, key) == -1) + goto bail; + } else + break; Py_CLEAR(key); } ret = 0;
--- a/mercurial/filemerge.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/filemerge.py Fri May 15 11:52:09 2015 -0500 @@ -354,7 +354,6 @@ ui = repo.ui template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker) - template = templater.parsestring(template, quoted=False) tmpl = templater.templater(None, cache={'conflictmarker': template}) pad = max(len(l) for l in labels)
--- a/mercurial/help/config.txt Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/help/config.txt Fri May 15 11:52:09 2015 -0500 @@ -376,8 +376,8 @@ HG: -- HG: user: {author}\n{ifeq(p2rev, "-1", "", "HG: branch merge\n") - }HG: branch '{branch}'\n{if(currentbookmark, - "HG: bookmark '{currentbookmark}'\n") }{subrepos % + }HG: branch '{branch}'\n{if(activebookmark, + "HG: bookmark '{activebookmark}'\n") }{subrepos % "HG: subrepo {subrepo}\n" }{file_adds % "HG: added {file}\n" }{file_mods % "HG: changed {file}\n" }{file_dels %
--- a/mercurial/help/templates.txt Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/help/templates.txt Fri May 15 11:52:09 2015 -0500 @@ -67,7 +67,7 @@ - Output the description set to a fill-width of 30:: - $ hg log -r 0 --template "{fill(desc, '30')}" + $ hg log -r 0 --template "{fill(desc, 30)}" - Use a conditional to test for the default branch:: @@ -104,4 +104,4 @@ - Print the first word of each line of a commit message:: - $ hg log --template "{word(\"0\", desc)}\n" + $ hg log --template "{word(0, desc)}\n"
--- a/mercurial/hg.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/hg.py Fri May 15 11:52:09 2015 -0500 @@ -497,7 +497,7 @@ destrepo.ui.status(status) _update(destrepo, uprev) if update in destrepo._bookmarks: - bookmarks.setcurrent(destrepo, update) + bookmarks.activate(destrepo, update) finally: release(srclock, destlock) if cleandir is not None:
--- a/mercurial/hgweb/hgwebdir_mod.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/hgweb/hgwebdir_mod.py Fri May 15 11:52:09 2015 -0500 @@ -176,71 +176,70 @@ def run_wsgi(self, req): try: - try: - self.refresh() + self.refresh() - virtual = req.env.get("PATH_INFO", "").strip('/') - tmpl = self.templater(req) - ctype = tmpl('mimetype', encoding=encoding.encoding) - ctype = templater.stringify(ctype) + virtual = req.env.get("PATH_INFO", "").strip('/') + tmpl = self.templater(req) + ctype = tmpl('mimetype', encoding=encoding.encoding) + ctype = templater.stringify(ctype) - # a static file - if virtual.startswith('static/') or 'static' in req.form: - if virtual.startswith('static/'): - fname = virtual[7:] - else: - fname = req.form['static'][0] - static = self.ui.config("web", "static", None, - untrusted=False) - if not static: - tp = self.templatepath or templater.templatepaths() - if isinstance(tp, str): - tp = [tp] - static = [os.path.join(p, 'static') for p in tp] - staticfile(static, fname, req) - return [] + # a static file + if virtual.startswith('static/') or 'static' in req.form: + if virtual.startswith('static/'): + fname = virtual[7:] + else: + fname = req.form['static'][0] + static = self.ui.config("web", "static", None, + untrusted=False) + if not static: + tp = self.templatepath or templater.templatepaths() + if isinstance(tp, str): + tp = [tp] + static = [os.path.join(p, 'static') for p in tp] + staticfile(static, fname, req) + return [] - # top-level index - elif not virtual: - req.respond(HTTP_OK, ctype) - return self.makeindex(req, tmpl) + # top-level index + elif not virtual: + req.respond(HTTP_OK, ctype) + return self.makeindex(req, tmpl) - # nested indexes and hgwebs + # nested indexes and hgwebs - repos = dict(self.repos) - virtualrepo = virtual - while virtualrepo: - real = repos.get(virtualrepo) - if real: - req.env['REPO_NAME'] = virtualrepo - try: - # ensure caller gets private copy of ui - repo = hg.repository(self.ui.copy(), real) - return hgweb(repo).run_wsgi(req) - except IOError, inst: - msg = inst.strerror - raise ErrorResponse(HTTP_SERVER_ERROR, msg) - except error.RepoError, inst: - raise ErrorResponse(HTTP_SERVER_ERROR, str(inst)) + repos = dict(self.repos) + virtualrepo = virtual + while virtualrepo: + real = repos.get(virtualrepo) + if real: + req.env['REPO_NAME'] = virtualrepo + try: + # ensure caller gets private copy of ui + repo = hg.repository(self.ui.copy(), real) + return hgweb(repo).run_wsgi(req) + except IOError, inst: + msg = inst.strerror + raise ErrorResponse(HTTP_SERVER_ERROR, msg) + except error.RepoError, inst: + raise ErrorResponse(HTTP_SERVER_ERROR, str(inst)) - up = virtualrepo.rfind('/') - if up < 0: - break - virtualrepo = virtualrepo[:up] + up = virtualrepo.rfind('/') + if up < 0: + break + virtualrepo = virtualrepo[:up] - # browse subdirectories - subdir = virtual + '/' - if [r for r in repos if r.startswith(subdir)]: - req.respond(HTTP_OK, ctype) - return self.makeindex(req, tmpl, subdir) + # browse subdirectories + subdir = virtual + '/' + if [r for r in repos if r.startswith(subdir)]: + req.respond(HTTP_OK, ctype) + return self.makeindex(req, tmpl, subdir) - # prefixes not found - req.respond(HTTP_NOT_FOUND, ctype) - return tmpl("notfound", repo=virtual) + # prefixes not found + req.respond(HTTP_NOT_FOUND, ctype) + return tmpl("notfound", repo=virtual) - except ErrorResponse, err: - req.respond(err, ctype) - return tmpl('error', error=err.message or '') + except ErrorResponse, err: + req.respond(err, ctype) + return tmpl('error', error=err.message or '') finally: tmpl = None
--- a/mercurial/hook.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/hook.py Fri May 15 11:52:09 2015 -0500 @@ -39,26 +39,25 @@ if demandimportenabled: demandimport.disable() try: + obj = __import__(modname) + except ImportError: + e1 = sys.exc_type, sys.exc_value, sys.exc_traceback try: - obj = __import__(modname) + # extensions are loaded with hgext_ prefix + obj = __import__("hgext_%s" % modname) except ImportError: - e1 = sys.exc_type, sys.exc_value, sys.exc_traceback - try: - # extensions are loaded with hgext_ prefix - obj = __import__("hgext_%s" % modname) - except ImportError: - e2 = sys.exc_type, sys.exc_value, sys.exc_traceback - if ui.tracebackflag: - ui.warn(_('exception from first failed import ' - 'attempt:\n')) - ui.traceback(e1) - if ui.tracebackflag: - ui.warn(_('exception from second failed import ' - 'attempt:\n')) - ui.traceback(e2) - raise util.Abort(_('%s hook is invalid ' - '(import of "%s" failed)') % - (hname, modname)) + e2 = sys.exc_type, sys.exc_value, sys.exc_traceback + if ui.tracebackflag: + ui.warn(_('exception from first failed import ' + 'attempt:\n')) + ui.traceback(e1) + if ui.tracebackflag: + ui.warn(_('exception from second failed import ' + 'attempt:\n')) + ui.traceback(e2) + raise util.Abort(_('%s hook is invalid ' + '(import of "%s" failed)') % + (hname, modname)) finally: if demandimportenabled: demandimport.enable() @@ -79,27 +78,26 @@ starttime = time.time() try: - try: - # redirect IO descriptors to the ui descriptors so hooks - # that write directly to these don't mess up the command - # protocol when running through the command server - old = sys.stdout, sys.stderr, sys.stdin - sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin + # redirect IO descriptors to the ui descriptors so hooks + # that write directly to these don't mess up the command + # protocol when running through the command server + old = sys.stdout, sys.stderr, sys.stdin + sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin - r = obj(ui=ui, repo=repo, hooktype=name, **args) - except KeyboardInterrupt: + r = obj(ui=ui, repo=repo, hooktype=name, **args) + except KeyboardInterrupt: + raise + except Exception, exc: + if isinstance(exc, util.Abort): + ui.warn(_('error: %s hook failed: %s\n') % + (hname, exc.args[0])) + else: + ui.warn(_('error: %s hook raised an exception: ' + '%s\n') % (hname, exc)) + if throw: raise - except Exception, exc: - if isinstance(exc, util.Abort): - ui.warn(_('error: %s hook failed: %s\n') % - (hname, exc.args[0])) - else: - ui.warn(_('error: %s hook raised an exception: ' - '%s\n') % (hname, exc)) - if throw: - raise - ui.traceback() - return True + ui.traceback() + return True finally: sys.stdout, sys.stderr, sys.stdin = old duration = time.time() - starttime
--- a/mercurial/httppeer.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/httppeer.py Fri May 15 11:52:09 2015 -0500 @@ -198,16 +198,15 @@ headers = {'Content-Type': 'application/mercurial-0.1'} try: - try: - r = self._call(cmd, data=fp, headers=headers, **args) - vals = r.split('\n', 1) - if len(vals) < 2: - raise error.ResponseError(_("unexpected response:"), r) - return vals - except socket.error, err: - if err.args[0] in (errno.ECONNRESET, errno.EPIPE): - raise util.Abort(_('push failed: %s') % err.args[1]) - raise util.Abort(err.args[1]) + r = self._call(cmd, data=fp, headers=headers, **args) + vals = r.split('\n', 1) + if len(vals) < 2: + raise error.ResponseError(_("unexpected response:"), r) + return vals + except socket.error, err: + if err.args[0] in (errno.ECONNRESET, errno.EPIPE): + raise util.Abort(_('push failed: %s') % err.args[1]) + raise util.Abort(err.args[1]) finally: fp.close() os.unlink(tempname)
--- a/mercurial/ignore.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/ignore.py Fri May 15 11:52:09 2015 -0500 @@ -40,18 +40,35 @@ except KeyError: warnings.append(_("ignoring invalid syntax '%s'") % s) continue - pat = syntax + line + + linesyntax = syntax for s, rels in syntaxes.iteritems(): if line.startswith(rels): - pat = line + linesyntax = rels + line = line[len(rels):] break elif line.startswith(s+':'): - pat = rels + line[len(s) + 1:] + linesyntax = rels + line = line[len(s) + 1:] break - patterns.append(pat) + patterns.append(linesyntax + line) return patterns, warnings +def readignorefile(filepath, warn, skipwarning=False): + try: + pats = [] + fp = open(filepath) + pats, warnings = ignorepats(fp) + fp.close() + for warning in warnings: + warn("%s: %s\n" % (filepath, warning)) + except IOError, inst: + if not skipwarning: + warn(_("skipping unreadable ignore file '%s': %s\n") % + (filepath, inst.strerror)) + return pats + def readpats(root, files, warn): '''return a dict mapping ignore-file-name to list-of-patterns''' @@ -59,17 +76,9 @@ for f in files: if f in pats: continue - try: - pats[f] = [] - fp = open(f) - pats[f], warnings = ignorepats(fp) - fp.close() - for warning in warnings: - warn("%s: %s\n" % (f, warning)) - except IOError, inst: - if f != files[0]: - warn(_("skipping unreadable ignore file '%s': %s\n") % - (f, inst.strerror)) + skipwarning = f == files[0] + pats[f] = readignorefile(f, warn, skipwarning=skipwarning) + return [(f, pats[f]) for f in files if f in pats] def ignore(root, files, warn):
--- a/mercurial/localrepo.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/localrepo.py Fri May 15 11:52:09 2015 -0500 @@ -192,11 +192,11 @@ class localrepository(object): - supportedformats = set(('revlogv1', 'generaldelta', 'manifestv2')) + supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest', + 'manifestv2')) _basesupported = supportedformats | set(('store', 'fncache', 'shared', 'dotencode')) - openerreqs = set(('revlogv1', 'generaldelta', 'manifestv2')) - requirements = ['revlogv1'] + openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2')) filtername = None # a list of (ui, featureset) functions. @@ -204,9 +204,10 @@ featuresetupfuncs = set() def _baserequirements(self, create): - return self.requirements[:] + return ['revlogv1'] def __init__(self, baseui, path=None, create=False): + self.requirements = set() self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True) self.wopener = self.wvfs self.root = self.wvfs.base @@ -243,14 +244,14 @@ if not self.wvfs.exists(): self.wvfs.makedirs() self.vfs.makedir(notindexed=True) - requirements = self._baserequirements(create) + self.requirements.update(self._baserequirements(create)) if self.ui.configbool('format', 'usestore', True): self.vfs.mkdir("store") - requirements.append("store") + self.requirements.add("store") if self.ui.configbool('format', 'usefncache', True): - requirements.append("fncache") + self.requirements.add("fncache") if self.ui.configbool('format', 'dotencode', True): - requirements.append('dotencode') + self.requirements.add('dotencode') # create an invalid changelog self.vfs.append( "00changelog.i", @@ -258,21 +259,22 @@ ' dummy changelog to prevent using the old repo layout' ) if self.ui.configbool('format', 'generaldelta', False): - requirements.append("generaldelta") + self.requirements.add("generaldelta") + if self.ui.configbool('experimental', 'treemanifest', False): + self.requirements.add("treemanifest") if self.ui.configbool('experimental', 'manifestv2', False): - requirements.append("manifestv2") - requirements = set(requirements) + self.requirements.add("manifestv2") else: raise error.RepoError(_("repository %s not found") % path) elif create: raise error.RepoError(_("repository %s already exists") % path) else: try: - requirements = scmutil.readrequires(self.vfs, self.supported) + self.requirements = scmutil.readrequires( + self.vfs, self.supported) except IOError, inst: if inst.errno != errno.ENOENT: raise - requirements = set() self.sharedpath = self.path try: @@ -287,13 +289,14 @@ if inst.errno != errno.ENOENT: raise - self.store = store.store(requirements, self.sharedpath, scmutil.vfs) + self.store = store.store( + self.requirements, self.sharedpath, scmutil.vfs) self.spath = self.store.path self.svfs = self.store.vfs self.sopener = self.svfs self.sjoin = self.store.join self.vfs.createmode = self.store.createmode - self._applyrequirements(requirements) + self._applyopenerreqs() if create: self._writerequirements() @@ -336,9 +339,8 @@ caps.add('bundle2=' + urllib.quote(capsblob)) return caps - def _applyrequirements(self, requirements): - self.requirements = requirements - self.svfs.options = dict((r, 1) for r in requirements + def _applyopenerreqs(self): + self.svfs.options = dict((r, 1) for r in self.requirements if r in self.openerreqs) chunkcachesize = self.ui.configint('format', 'chunkcachesize') if chunkcachesize is not None: @@ -349,15 +351,9 @@ manifestcachesize = self.ui.configint('format', 'manifestcachesize') if manifestcachesize is not None: self.svfs.options['manifestcachesize'] = manifestcachesize - usetreemanifest = self.ui.configbool('experimental', 'treemanifest') - if usetreemanifest is not None: - self.svfs.options['usetreemanifest'] = usetreemanifest def _writerequirements(self): - reqfile = self.vfs("requires", "w") - for r in sorted(self.requirements): - reqfile.write("%s\n" % r) - reqfile.close() + scmutil.writerequires(self.vfs, self.requirements) def _checknested(self, path): """Determine if path is a legal nested repository.""" @@ -419,8 +415,8 @@ return bookmarks.bmstore(self) @repofilecache('bookmarks.current') - def _bookmarkcurrent(self): - return bookmarks.readcurrent(self) + def _activebookmark(self): + return bookmarks.readactive(self) def bookmarkheads(self, bookmark): name = bookmark.split('@', 1)[0] @@ -1466,9 +1462,10 @@ cctx = context.workingcommitctx(self, status, text, user, date, extra) - if (not force and not extra.get("close") and not merge - and not cctx.files() - and wctx.branch() == wctx.p1().branch()): + allowemptycommit = (wctx.branch() != wctx.p1().branch() + or extra.get('close') or merge or cctx.files() + or self.ui.configbool('ui', 'allowemptycommit')) + if not allowemptycommit: return None if merge and cctx.deleted(): @@ -1521,7 +1518,7 @@ def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2): # hack for command that use a temporary commit (eg: histedit) # temporary commit got stripped before hook release - if node in self: + if self.changelog.hasnode(ret): self.hook("commit", node=node, parent1=parent1, parent2=parent2) self._afterlock(commithook) @@ -1754,7 +1751,7 @@ """ return util.hooks() - def stream_in(self, remote, requirements): + def stream_in(self, remote, remotereqs): lock = self.lock() try: # Save remote branchmap. We will use it later @@ -1827,10 +1824,11 @@ util.bytecount(total_bytes / elapsed))) # new requirements = old non-format requirements + - # new format-related + # new format-related remote requirements # requirements from the streamed-in repository - requirements.update(set(self.requirements) - self.supportedformats) - self._applyrequirements(requirements) + self.requirements = remotereqs | ( + self.requirements - self.supportedformats) + self._applyopenerreqs() self._writerequirements() if rbranchmap:
--- a/mercurial/manifest.c Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/manifest.c Fri May 15 11:52:09 2015 -0500 @@ -235,7 +235,7 @@ PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL; l = lmiter_nextline((lmIter *)o); if (!l) { - goto bail; + goto done; } pl = pathlen(l); path = PyString_FromStringAndSize(l->start, pl); @@ -244,10 +244,10 @@ flags = PyString_FromStringAndSize(l->start + consumed, l->len - consumed - 1); if (!path || !hash || !flags) { - goto bail; + goto done; } ret = PyTuple_Pack(3, path, hash, flags); - bail: +done: Py_XDECREF(path); Py_XDECREF(hash); Py_XDECREF(flags); @@ -672,7 +672,7 @@ copy->pydata = self->pydata; Py_INCREF(copy->pydata); return copy; - nomem: +nomem: PyErr_NoMemory(); Py_XDECREF(copy); return NULL; @@ -724,7 +724,7 @@ } copy->livelines = copy->numlines; return copy; - nomem: +nomem: PyErr_NoMemory(); Py_XDECREF(copy); return NULL; @@ -845,7 +845,7 @@ } Py_DECREF(emptyTup); return ret; - nomem: +nomem: PyErr_NoMemory(); Py_XDECREF(ret); Py_XDECREF(emptyTup);
--- a/mercurial/manifest.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/manifest.py Fri May 15 11:52:09 2015 -0500 @@ -444,11 +444,15 @@ class treemanifest(object): def __init__(self, dir='', text=''): self._dir = dir + self._node = revlog.nullid self._dirs = {} # Using _lazymanifest here is a little slower than plain old dicts self._files = {} self._flags = {} - self.parse(text) + def readsubtree(subdir, subm): + raise AssertionError('treemanifest constructor only accepts ' + 'flat manifests') + self.parse(text, readsubtree) def _subpath(self, path): return self._dir + path @@ -464,7 +468,22 @@ util.all(m._isempty() for m in self._dirs.values()))) def __str__(self): - return '<treemanifest dir=%s>' % self._dir + return ('<treemanifest dir=%s, node=%s>' % + (self._dir, revlog.hex(self._node))) + + def dir(self): + '''The directory that this tree manifest represents, including a + trailing '/'. Empty string for the repo root directory.''' + return self._dir + + def node(self): + '''This node of this instance. nullid for unsaved instances. Should + be updated when the instance is read or written from a revlog. + ''' + return self._node + + def setnode(self, node): + self._node = node def iteritems(self): for p, n in sorted(self._dirs.items() + self._files.items()): @@ -557,6 +576,7 @@ def setflag(self, f, flags): """Set the flags (symlink, executable) for path f.""" + assert 'd' not in flags dir, subpath = _splittopdir(f) if dir: if dir not in self._dirs: @@ -567,6 +587,7 @@ def copy(self): copy = treemanifest(self._dir) + copy._node = self._node for d in self._dirs: copy._dirs[d] = self._dirs[d].copy() copy._files = dict.copy(self._files) @@ -737,11 +758,18 @@ _diff(self, m2) return result - def parse(self, text): + def parse(self, text, readsubtree): for f, n, fl in _parse(text): - self[f] = n - if fl: - self.setflag(f, fl) + if fl == 'd': + f = f + '/' + self._dirs[f] = readsubtree(self._subpath(f), n) + else: + # Use __setitem__ and setflag rather than assigning directly + # to _files and _flags, thereby letting us parse flat manifests + # as well as tree manifests. + self[f] = n + if fl: + self.setflag(f, fl) def text(self, usemanifestv2=False): """Get the full data of this manifest as a bytestring.""" @@ -749,8 +777,26 @@ return _text(((f, self[f], flags(f)) for f in self.keys()), usemanifestv2) + def dirtext(self, usemanifestv2=False): + """Get the full data of this directory as a bytestring. Make sure that + any submanifests have been written first, so their nodeids are correct. + """ + flags = self.flags + dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs] + files = [(f, self._files[f], flags(f)) for f in self._files] + return _text(sorted(dirs + files), usemanifestv2) + + def writesubtrees(self, m1, m2, writesubtree): + emptytree = treemanifest() + for d, subm in self._dirs.iteritems(): + subp1 = m1._dirs.get(d, emptytree)._node + subp2 = m2._dirs.get(d, emptytree)._node + if subp1 == revlog.nullid: + subp1, subp2 = subp2, subp1 + writesubtree(subm, subp1, subp2) + class manifest(revlog.revlog): - def __init__(self, opener): + def __init__(self, opener, dir=''): # During normal operations, we expect to deal with not more than four # revs at a time (such as during commit --amend). When rebasing large # stacks of commits, the number can go up, hence the config knob below. @@ -760,17 +806,22 @@ opts = getattr(opener, 'options', None) if opts is not None: cachesize = opts.get('manifestcachesize', cachesize) - usetreemanifest = opts.get('usetreemanifest', usetreemanifest) + usetreemanifest = opts.get('treemanifest', usetreemanifest) usemanifestv2 = opts.get('manifestv2', usemanifestv2) self._mancache = util.lrucachedict(cachesize) - revlog.revlog.__init__(self, opener, "00manifest.i") self._treeinmem = usetreemanifest self._treeondisk = usetreemanifest self._usemanifestv2 = usemanifestv2 + indexfile = "00manifest.i" + if dir: + assert self._treeondisk + indexfile = "meta/" + dir + "00manifest.i" + revlog.revlog.__init__(self, opener, indexfile) + self._dir = dir def _newmanifest(self, data=''): if self._treeinmem: - return treemanifest('', data) + return treemanifest(self._dir, data) return manifestdict(data) def _slowreaddelta(self, node): @@ -793,7 +844,13 @@ return self._newmanifest(d) def readfast(self, node): - '''use the faster of readdelta or read''' + '''use the faster of readdelta or read + + This will return a manifest which is either only the files + added/modified relative to p1, or all files in the + manifest. Which one is returned depends on the codepath used + to retrieve the data. + ''' r = self.rev(node) deltaparent = self.deltaparent(r) if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r): @@ -806,8 +863,17 @@ if node in self._mancache: return self._mancache[node][0] text = self.revision(node) - arraytext = array.array('c', text) - m = self._newmanifest(text) + if self._treeondisk: + def readsubtree(dir, subm): + sublog = manifest(self.opener, dir) + return sublog.read(subm) + m = self._newmanifest() + m.parse(text, readsubtree) + m.setnode(node) + arraytext = None + else: + m = self._newmanifest(text) + arraytext = array.array('c', text) self._mancache[node] = (m, arraytext) return m @@ -845,10 +911,34 @@ # just encode a fulltext of the manifest and pass that # through to the revlog layer, and let it handle the delta # process. - text = m.text(self._usemanifestv2) - arraytext = array.array('c', text) - n = self.addrevision(text, transaction, link, p1, p2) + if self._treeondisk: + m1 = self.read(p1) + m2 = self.read(p2) + n = self._addtree(m, transaction, link, m1, m2) + arraytext = None + else: + text = m.text(self._usemanifestv2) + n = self.addrevision(text, transaction, link, p1, p2) + arraytext = array.array('c', text) self._mancache[n] = (m, arraytext) return n + + def _addtree(self, m, transaction, link, m1, m2): + def writesubtree(subm, subp1, subp2): + sublog = manifest(self.opener, subm.dir()) + sublog.add(subm, transaction, link, subp1, subp2, None, None) + m.writesubtrees(m1, m2, writesubtree) + text = m.dirtext(self._usemanifestv2) + # If the manifest is unchanged compared to one parent, + # don't write a new revision + if text == m1.dirtext(self._usemanifestv2): + n = m1.node() + elif text == m2.dirtext(self._usemanifestv2): + n = m2.node() + else: + n = self.addrevision(text, transaction, link, m1.node(), m2.node()) + # Save nodeid so parent manifest can calculate its nodeid + m.setnode(n) + return n
--- a/mercurial/obsolete.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/obsolete.py Fri May 15 11:52:09 2015 -0500 @@ -1110,13 +1110,17 @@ @cachefor('unstable') def _computeunstableset(repo): """the set of non obsolete revisions with obsolete parents""" - # revset is not efficient enough here - # we do (obsolete()::) - obsolete() by hand - obs = getrevs(repo, 'obsolete') - if not obs: - return set() - cl = repo.changelog - return set(r for r in cl.descendants(obs) if r not in obs) + revs = [(ctx.rev(), ctx) for ctx in + repo.set('(not public()) and (not obsolete())')] + revs.sort(key=lambda x:x[0]) + unstable = set() + for rev, ctx in revs: + # A rev is unstable if one of its parent is obsolete or unstable + # this works since we traverse following growing rev order + if util.any((x.obsolete() or (x.rev() in unstable)) + for x in ctx.parents()): + unstable.add(rev) + return unstable @cachefor('suspended') def _computesuspendedset(repo): @@ -1139,19 +1143,18 @@ public = phases.public cl = repo.changelog torev = cl.nodemap.get - obs = getrevs(repo, 'obsolete') - for rev in repo: + for ctx in repo.set('(not public()) and (not obsolete())'): + rev = ctx.rev() # We only evaluate mutable, non-obsolete revision - if (public < phase(repo, rev)) and (rev not in obs): - node = cl.node(rev) - # (future) A cache of precursors may worth if split is very common - for pnode in allprecursors(repo.obsstore, [node], - ignoreflags=bumpedfix): - prev = torev(pnode) # unfiltered! but so is phasecache - if (prev is not None) and (phase(repo, prev) <= public): - # we have a public precursors - bumped.add(rev) - break # Next draft! + node = ctx.node() + # (future) A cache of precursors may worth if split is very common + for pnode in allprecursors(repo.obsstore, [node], + ignoreflags=bumpedfix): + prev = torev(pnode) # unfiltered! but so is phasecache + if (prev is not None) and (phase(repo, prev) <= public): + # we have a public precursors + bumped.add(rev) + break # Next draft! return bumped @cachefor('divergent')
--- a/mercurial/pathutil.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/pathutil.py Fri May 15 11:52:09 2015 -0500 @@ -152,7 +152,19 @@ break name = dirname - raise util.Abort(_("%s not under root '%s'") % (myname, root)) + # A common mistake is to use -R, but specify a file relative to the repo + # instead of cwd. Detect that case, and provide a hint to the user. + hint = None + try: + if cwd != root: + canonpath(root, root, myname, auditor) + hint = (_("consider using '--cwd %s'") + % os.path.relpath(root, cwd)) + except util.Abort: + pass + + raise util.Abort(_("%s not under root '%s'") % (myname, root), + hint=hint) def normasprefix(path): '''normalize the specified path as path prefix
--- a/mercurial/repoview.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/repoview.py Fri May 15 11:52:09 2015 -0500 @@ -115,16 +115,15 @@ """ wlock = fh = None try: - try: - wlock = repo.wlock(wait=False) - # write cache to file - newhash = cachehash(repo, hideable) - fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True) - _writehiddencache(fh, newhash, hidden) - except (IOError, OSError): - repo.ui.debug('error writing hidden changesets cache') - except error.LockHeld: - repo.ui.debug('cannot obtain lock to write hidden changesets cache') + wlock = repo.wlock(wait=False) + # write cache to file + newhash = cachehash(repo, hideable) + fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True) + _writehiddencache(fh, newhash, hidden) + except (IOError, OSError): + repo.ui.debug('error writing hidden changesets cache') + except error.LockHeld: + repo.ui.debug('cannot obtain lock to write hidden changesets cache') finally: if fh: fh.close()
--- a/mercurial/revset.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/revset.py Fri May 15 11:52:09 2015 -0500 @@ -25,23 +25,25 @@ cl = repo.changelog def iterate(): - revqueue, revsnode = None, None + revs.sort(reverse=True) + irevs = iter(revs) h = [] - - revs.sort(reverse=True) - revqueue = util.deque(revs) - if revqueue: - revsnode = revqueue.popleft() - heapq.heappush(h, -revsnode) + try: + inputrev = irevs.next() + heapq.heappush(h, -inputrev) + except StopIteration: + return seen = set() while h: current = -heapq.heappop(h) + if current == inputrev: + try: + inputrev = irevs.next() + heapq.heappush(h, -inputrev) + except StopIteration: + pass if current not in seen: - if revsnode and current == revsnode: - if revqueue: - revsnode = revqueue.popleft() - heapq.heappush(h, -revsnode) seen.add(current) yield current for parent in cl.parentrevs(current)[:cut]: @@ -334,11 +336,6 @@ return baseset([x]) return baseset() -def symbolset(repo, subset, x): - if x in symbols: - raise error.ParseError(_("can't use %s here") % x) - return stringset(repo, subset, x) - def rangeset(repo, subset, x, y): m = getset(repo, fullreposet(repo), x) n = getset(repo, fullreposet(repo), y) @@ -1684,7 +1681,7 @@ Changesets in set with no parent changeset in set. """ s = getset(repo, fullreposet(repo), x) - subset = baseset([r for r in s if r in subset]) + subset = subset & s# baseset([r for r in s if r in subset]) cs = _children(repo, subset, s) return subset - cs @@ -2088,7 +2085,7 @@ "range": rangeset, "dagrange": dagrange, "string": stringset, - "symbol": symbolset, + "symbol": stringset, "and": andset, "or": orset, "not": notset, @@ -2948,6 +2945,64 @@ If the ascending attribute is set, that means the two structures are ordered in either an ascending or descending way. Therefore, we can add them maintaining the order by iterating over both at the same time + + >>> xs = baseset([0, 3, 2]) + >>> ys = baseset([5, 2, 4]) + + >>> rs = addset(xs, ys) + >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last() + (True, True, False, True, 0, 4) + >>> rs = addset(xs, baseset([])) + >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last() + (True, True, False, 0, 2) + >>> rs = addset(baseset([]), baseset([])) + >>> bool(rs), 0 in rs, rs.first(), rs.last() + (False, False, None, None) + + iterate unsorted: + >>> rs = addset(xs, ys) + >>> [x for x in rs] # without _genlist + [0, 3, 2, 5, 4] + >>> assert not rs._genlist + >>> len(rs) + 5 + >>> [x for x in rs] # with _genlist + [0, 3, 2, 5, 4] + >>> assert rs._genlist + + iterate ascending: + >>> rs = addset(xs, ys, ascending=True) + >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist + ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5]) + >>> assert not rs._asclist + >>> len(rs) # BROKEN + 6 + >>> [x for x in rs], [x for x in rs.fastasc()] # BROKEN with _asclist + ([0, 2, 2, 3, 4, 5], [0, 2, 2, 3, 4, 5]) + >>> assert rs._asclist + + iterate descending: + >>> rs = addset(xs, ys, ascending=False) + >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist + ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0]) + >>> assert not rs._asclist + >>> len(rs) # BROKEN + 6 + >>> [x for x in rs], [x for x in rs.fastdesc()] # BROKEN with _asclist + ([5, 4, 3, 2, 2, 0], [5, 4, 3, 2, 2, 0]) + >>> assert rs._asclist + + iterate ascending without fastasc: + >>> rs = addset(xs, generatorset(ys), ascending=True) + >>> assert rs.fastasc is None + >>> [x for x in rs] # BROKEN + [0, 2, 2, 3, 4, 5] + + iterate descending without fastdesc: + >>> rs = addset(generatorset(xs), ys, ascending=False) + >>> assert rs.fastdesc is None + >>> [x for x in rs] # BROKEN + [5, 4, 3, 2, 2, 0] """ def __init__(self, revs1, revs2, ascending=None): self._r1 = revs1 @@ -3048,10 +3103,6 @@ val1 = None val2 = None - - choice = max - if ascending: - choice = min try: # Consume both iterators in an ordered way until one is # empty @@ -3143,7 +3194,12 @@ self.__contains__ = self._desccontains def __nonzero__(self): - for r in self: + # Do not use 'for r in self' because it will enforce the iteration + # order (default ascending), possibly unrolling a whole descending + # iterator. + if self._genlist: + return True + for r in self._consumegen(): return True return False
--- a/mercurial/scmutil.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/scmutil.py Fri May 15 11:52:09 2015 -0500 @@ -1011,6 +1011,12 @@ " for more information")) return requirements +def writerequires(opener, requirements): + reqfile = opener("requires", "w") + for r in sorted(requirements): + reqfile.write("%s\n" % r) + reqfile.close() + class filecachesubentry(object): def __init__(self, path, stat): self.path = path
--- a/mercurial/store.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/store.py Fri May 15 11:52:09 2015 -0500 @@ -187,7 +187,7 @@ def _hashencode(path, dotencode): digest = _sha(path).hexdigest() - le = lowerencode(path).split('/')[1:] + le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/' parts = _auxencode(le, dotencode) basename = parts[-1] _root, ext = os.path.splitext(basename)
--- a/mercurial/subrepo.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/subrepo.py Fri May 15 11:52:09 2015 -0500 @@ -1711,7 +1711,7 @@ modified, added, removed = [], [], [] self._gitupdatestat() if rev2: - command = ['diff-tree', rev1, rev2] + command = ['diff-tree', '-r', rev1, rev2] else: command = ['diff-index', rev1] out = self._gitcommand(command)
--- a/mercurial/tags.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/tags.py Fri May 15 11:52:09 2015 -0500 @@ -509,26 +509,25 @@ return try: + f = repo.vfs.open(_fnodescachefile, 'ab') try: - f = repo.vfs.open(_fnodescachefile, 'ab') - try: - # if the file has been truncated - actualoffset = f.tell() - if actualoffset < self._dirtyoffset: - self._dirtyoffset = actualoffset - data = self._raw[self._dirtyoffset:] - f.seek(self._dirtyoffset) - f.truncate() - repo.ui.log('tagscache', - 'writing %d bytes to %s\n' % ( - len(data), _fnodescachefile)) - f.write(data) - self._dirtyoffset = None - finally: - f.close() - except (IOError, OSError), inst: + # if the file has been truncated + actualoffset = f.tell() + if actualoffset < self._dirtyoffset: + self._dirtyoffset = actualoffset + data = self._raw[self._dirtyoffset:] + f.seek(self._dirtyoffset) + f.truncate() repo.ui.log('tagscache', - "couldn't write %s: %s\n" % ( - _fnodescachefile, inst)) + 'writing %d bytes to %s\n' % ( + len(data), _fnodescachefile)) + f.write(data) + self._dirtyoffset = None + finally: + f.close() + except (IOError, OSError), inst: + repo.ui.log('tagscache', + "couldn't write %s: %s\n" % ( + _fnodescachefile, inst)) finally: lock.release()
--- a/mercurial/templatefilters.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templatefilters.py Fri May 15 11:52:09 2015 -0500 @@ -326,6 +326,8 @@ """ if util.safehasattr(thing, '__iter__') and not isinstance(thing, str): return "".join([stringify(t) for t in thing if t is not None]) + if thing is None: + return "" return str(thing) def strip(text):
--- a/mercurial/templatekw.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templatekw.py Fri May 15 11:52:09 2015 -0500 @@ -210,7 +210,7 @@ """ repo = args['ctx']._repo bookmarks = args['ctx'].bookmarks() - current = repo._bookmarkcurrent + current = repo._activebookmark makemap = lambda v: {'bookmark': v, 'current': current} f = _showlist('bookmark', bookmarks, **args) return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark']) @@ -221,15 +221,21 @@ childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()] return showlist('children', childrevs, element='child', **args) +# Deprecated, but kept alive for help generation a purpose. def showcurrentbookmark(**args): """:currentbookmark: String. The active bookmark, if it is + associated with the changeset (DEPRECATED)""" + return showactivebookmark(**args) + +def showactivebookmark(**args): + """:activetbookmark: String. The active bookmark, if it is associated with the changeset""" import bookmarks as bookmarks # to avoid circular import issues repo = args['repo'] - if bookmarks.iscurrent(repo): - current = repo._bookmarkcurrent - if current in args['ctx'].bookmarks(): - return current + if bookmarks.isactivewdirparent(repo): + active = repo._activebookmark + if active in args['ctx'].bookmarks(): + return active return '' def showdate(repo, ctx, templ, **args): @@ -418,12 +424,14 @@ # cache - a cache dictionary for the whole templater run # revcache - a cache dictionary for the current revision keywords = { + 'activebookmark': showactivebookmark, 'author': showauthor, 'bisect': showbisect, 'branch': showbranch, 'branches': showbranches, 'bookmarks': showbookmarks, 'children': showchildren, + # currentbookmark is deprecated 'currentbookmark': showcurrentbookmark, 'date': showdate, 'desc': showdescription,
--- a/mercurial/templater.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templater.py Fri May 15 11:52:09 2015 -0500 @@ -20,6 +20,7 @@ "|": (5, None, ("|", 5)), "%": (6, None, ("%", 6)), ")": (0, None, None), + "integer": (0, ("integer",), None), "symbol": (0, ("symbol",), None), "string": (0, ("string",), None), "rawstring": (0, ("rawstring",), None), @@ -59,6 +60,20 @@ pos += 1 else: raise error.ParseError(_("unterminated string"), s) + elif c.isdigit() or c == '-': + s = pos + if c == '-': # simply take negate operator as part of integer + pos += 1 + if pos >= end or not program[pos].isdigit(): + raise error.ParseError(_("integer literal without digits"), s) + pos += 1 + while pos < end: + d = program[pos] + if not d.isdigit(): + break + pos += 1 + yield ('integer', program[s:pos], s) + pos -= 1 elif c.isalnum() or c in '_': s = pos pos += 1 @@ -100,12 +115,12 @@ parseres, pos = p.parse(pd) parsed.append(parseres) - return [compileexp(e, context) for e in parsed] + return [compileexp(e, context, methods) for e in parsed] -def compileexp(exp, context): +def compileexp(exp, context, curmethods): t = exp[0] - if t in methods: - return methods[t](exp, context) + if t in curmethods: + return curmethods[t](exp, context) raise error.ParseError(_("unknown method '%s'") % t) # template evaluation @@ -135,6 +150,9 @@ return context._load(exp[1]) raise error.ParseError(_("expected template specifier")) +def runinteger(context, mapping, data): + return int(data) + def runstring(context, mapping, data): return data.decode("string-escape") @@ -157,7 +175,7 @@ return v def buildfilter(exp, context): - func, data = compileexp(exp[1], context) + func, data = compileexp(exp[1], context, methods) filt = getfilter(exp[2], context) return (runfilter, (func, data, filt)) @@ -179,7 +197,7 @@ "keyword '%s'") % (filt.func_name, dt)) def buildmap(exp, context): - func, data = compileexp(exp[1], context) + func, data = compileexp(exp[1], context, methods) ctmpl = gettemplate(exp[2], context) return (runmap, (func, data, ctmpl)) @@ -208,7 +226,7 @@ def buildfunc(exp, context): n = getsymbol(exp[1]) - args = [compileexp(x, context) for x in getlist(exp[2])] + args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])] if n in funcs: f = funcs[n] return (f, args) @@ -551,8 +569,7 @@ num = int(stringify(args[0][0](context, mapping, args[0][1]))) except ValueError: # i18n: "word" is a keyword - raise error.ParseError( - _("Use strings like '3' for numbers passed to word function")) + raise error.ParseError(_("word expects an integer index")) text = stringify(args[1][0](context, mapping, args[1][1])) if len(args) == 3: splitter = stringify(args[2][0](context, mapping, args[2][1])) @@ -565,17 +582,23 @@ else: return tokens[num] -methods = { +# methods to interpret function arguments or inner expressions (e.g. {_(x)}) +exprmethods = { + "integer": lambda e, c: (runinteger, e[1]), "string": lambda e, c: (runstring, e[1]), "rawstring": lambda e, c: (runrawstring, e[1]), "symbol": lambda e, c: (runsymbol, e[1]), - "group": lambda e, c: compileexp(e[1], c), + "group": lambda e, c: compileexp(e[1], c, exprmethods), # ".": buildmember, "|": buildfilter, "%": buildmap, "func": buildfunc, } +# methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"}) +methods = exprmethods.copy() +methods["integer"] = exprmethods["symbol"] # '{1}' as variable + funcs = { "date": date, "diff": diff, @@ -618,14 +641,25 @@ for j in _flatten(i): yield j -def parsestring(s, quoted=True): - '''unwrap quotes if quoted is True''' - if quoted: - if len(s) < 2 or s[0] != s[-1]: - raise SyntaxError(_('unmatched quotes')) - return s[1:-1] - - return s +def unquotestring(s): + '''unwrap quotes''' + if len(s) < 2 or s[0] != s[-1]: + raise SyntaxError(_('unmatched quotes')) + # de-backslash-ify only <\">. it is invalid syntax in non-string part of + # template, but we are likely to escape <"> in quoted string and it was + # accepted before, thanks to issue4290. <\\"> is unmodified because it + # is ambiguous and it was processed as such before 2.8.1. + # + # template result + # --------- ------------------------ + # {\"\"} parse error + # "{""}" {""} -> <> + # "{\"\"}" {""} -> <> + # {"\""} {"\""} -> <"> + # '{"\""}' {"\""} -> <"> + # "{"\""}" parse error (don't care) + q = s[0] + return s[1:-1].replace('\\\\' + q, '\\\\\\' + q).replace('\\' + q, q) class engine(object): '''template expansion engine. @@ -709,7 +743,7 @@ raise util.Abort(_("style '%s' not found") % mapfile, hint=_("available styles: %s") % stylelist()) - conf = config.config() + conf = config.config(includepaths=templatepaths()) conf.read(mapfile) for key, val in conf[''].items(): @@ -717,7 +751,7 @@ raise SyntaxError(_('%s: missing value') % conf.source('', key)) if val[0] in "'\"": try: - self.cache[key] = parsestring(val) + self.cache[key] = unquotestring(val) except SyntaxError, inst: raise SyntaxError('%s: %s' % (conf.source('', key), inst.args[0]))
--- a/mercurial/templates/gitweb/fileannotate.tmpl Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/gitweb/fileannotate.tmpl Fri May 15 11:52:09 2015 -0500 @@ -39,19 +39,23 @@ <table cellspacing="0"> <tr> <td>author</td> - <td>{author|obfuscate}</td></tr> + <td>{author|obfuscate}</td> +</tr> <tr> <td></td> - <td class="date age">{date|rfc822date}</td></tr> + <td class="date age">{date|rfc822date}</td> +</tr> {branch%filerevbranch} <tr> <td>changeset {rev}</td> - <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr> + <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> +</tr> {parent%fileannotateparent} {child%fileannotatechild} <tr> <td>permissions</td> - <td style="font-family:monospace">{permissions|permissions}</td></tr> + <td style="font-family:monospace">{permissions|permissions}</td> +</tr> </table> </div>
--- a/mercurial/templates/gitweb/filecomparison.tmpl Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/gitweb/filecomparison.tmpl Fri May 15 11:52:09 2015 -0500 @@ -39,7 +39,8 @@ {branch%filerevbranch} <tr> <td>changeset {rev}</td> - <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr> + <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> +</tr> {parent%filecompparent} {child%filecompchild} </table>
--- a/mercurial/templates/gitweb/filediff.tmpl Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/gitweb/filediff.tmpl Fri May 15 11:52:09 2015 -0500 @@ -39,7 +39,8 @@ {branch%filerevbranch} <tr> <td>changeset {rev}</td> - <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr> + <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> +</tr> {parent%filediffparent} {child%filediffchild} </table>
--- a/mercurial/templates/gitweb/filerevision.tmpl Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/gitweb/filerevision.tmpl Fri May 15 11:52:09 2015 -0500 @@ -39,19 +39,23 @@ <table cellspacing="0"> <tr> <td>author</td> - <td>{author|obfuscate}</td></tr> + <td>{author|obfuscate}</td> +</tr> <tr> <td></td> - <td class="date age">{date|rfc822date}</td></tr> + <td class="date age">{date|rfc822date}</td> +</tr> {branch%filerevbranch} <tr> <td>changeset {rev}</td> - <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr> + <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> +</tr> {parent%filerevparent} {child%filerevchild} <tr> <td>permissions</td> - <td style="font-family:monospace">{permissions|permissions}</td></tr> + <td style="font-family:monospace">{permissions|permissions}</td> +</tr> </table> </div>
--- a/mercurial/templates/map-cmdline.bisect Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/map-cmdline.bisect Fri May 15 11:52:09 2015 -0500 @@ -1,25 +1,14 @@ -changeset = 'changeset: {rev}:{node|short}\nbisect: {bisect}\n{branches}{bookmarks}{tags}{parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n' -changeset_quiet = '{bisect|shortbisect} {rev}:{node|short}\n' -changeset_verbose = 'changeset: {rev}:{node|short}\nbisect: {bisect}\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}\nbisect: {bisect}\n{branches}{bookmarks}{tags}{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' +%include map-cmdline.default + +changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n' +changeset_quiet = '{lshortbisect} {rev}:{node|short}\n' +changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n' +changeset_debug = '{fullcset}{lbisect}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n' + +# We take the zeroth word in order to omit "(implicit)" in the label +bisectlabel = ' bisect.{word('0', bisect)}' + +lbisect ='{label("log.bisect{if(bisect, bisectlabel)}", + "bisect: {bisect}\n")}' +lshortbisect ='{label("log.bisect{if(bisect, bisectlabel)}", + "{bisect|shortbisect}")}'
--- a/mercurial/templates/map-cmdline.phases Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/map-cmdline.phases Fri May 15 11:52:09 2015 -0500 @@ -1,73 +1,3 @@ -# Base templates. Due to name clashes with existing keywords, we have -# to replace some keywords with 'lkeyword', for 'labelled keyword' +%include map-cmdline.default changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n' -changeset_quiet = '{lnode}' changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n' -changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n' - -# File templates -lfiles = '{if(files, - label("ui.note log.files", - "files: {files}\n"))}' - -lfile_mods = '{if(file_mods, - label("ui.debug log.files", - "files: {file_mods}\n"))}' - -lfile_adds = '{if(file_adds, - label("ui.debug log.files", - "files+: {file_adds}\n"))}' - -lfile_dels = '{if(file_dels, - label("ui.debug log.files", - "files-: {file_dels}\n"))}' - -lfile_copies_switch = '{if(file_copies_switch, - label("ui.note log.copies", - "copies: {file_copies_switch - % ' {name} ({source})'}\n"))}' - -# General templates -cset = '{label("log.changeset changeset.{phase}", - "changeset: {rev}:{node|short}")}\n' - -lphase = '{label("log.phase", - "phase: {phase}")}\n' - -fullcset = '{label("log.changeset changeset.{phase}", - "changeset: {rev}:{node}")}\n' - -parent = '{label("log.parent changeset.{phase}", - "parent: {rev}:{node|formatnode}")}\n' - -lnode = '{label("log.node", - "{rev}:{node|short}")}\n' - -manifest = '{label("ui.debug log.manifest", - "manifest: {rev}:{node}")}\n' - -branch = '{label("log.branch", - "branch: {branch}")}\n' - -tag = '{label("log.tag", - "tag: {tag}")}\n' - -bookmark = '{label("log.bookmark", - "bookmark: {bookmark}")}\n' - -user = '{label("log.user", - "user: {author}")}\n' - -summary = '{if(desc|strip, "{label('log.summary', - 'summary: {desc|firstline}')}\n")}' - -ldate = '{label("log.date", - "date: {date|date}")}\n' - -extra = '{label("ui.debug log.extra", - "extra: {key}={value|stringescape}")}\n' - -description = '{if(desc|strip, "{label('ui.note log.description', - 'description:')} - {label('ui.note log.description', - '{desc|strip}')}\n\n")}'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templates/map-cmdline.status Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,25 @@ +%include map-cmdline.default + +# Override base templates +changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n' +changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n' +changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{extras}{description}{lfiles}\n' + +# Override the file templates +lfiles = '{if(files, + label('ui.note log.files', + 'files:\n'))}{lfile_mods}{lfile_adds}{lfile_copies_switch}{lfile_dels}' + +# Exclude copied files, will display those in lfile_copies_switch +lfile_adds = '{file_adds % "{ifcontains(file, file_copies_switch, + '', + '{lfile_add}')}"}' +lfile_add = '{label("status.added", "A {file}\n")}' + +lfile_copies_switch = '{file_copies_switch % "{lfile_copy_orig}{lfile_copy_dest}"' +lfile_copy_orig = '{label("status.added", "A {name}\n")}' +lfile_copy_dest = '{label("status.copied", " {source}\n")}' + +lfile_mods = '{file_mods % "{label('status.modified', 'M {file}\n')}"}' + +lfile_dels = '{file_dels % "{label('status.removed', 'R {file}\n')}"}'
--- a/mercurial/templates/spartan/fileannotate.tmpl Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/spartan/fileannotate.tmpl Fri May 15 11:52:09 2015 -0500 @@ -22,12 +22,14 @@ <table> <tr> <td class="metatag">changeset {rev}:</td> - <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr> + <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> +</tr> {parent%fileannotateparent} {child%fileannotatechild} <tr> <td class="metatag">author:</td> - <td>{author|obfuscate}</td></tr> + <td>{author|obfuscate}</td> +</tr> <tr> <td class="metatag">date:</td> <td class="date age">{date|rfc822date}</td>
--- a/mercurial/templates/spartan/filerevision.tmpl Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/templates/spartan/filerevision.tmpl Fri May 15 11:52:09 2015 -0500 @@ -22,18 +22,22 @@ <table> <tr> <td class="metatag">changeset {rev}:</td> - <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr> + <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> +</tr> {parent%filerevparent} {child%filerevchild} <tr> <td class="metatag">author:</td> - <td>{author|obfuscate}</td></tr> + <td>{author|obfuscate}</td> +</tr> <tr> <td class="metatag">date:</td> - <td class="date age">{date|rfc822date}</td></tr> + <td class="date age">{date|rfc822date}</td> +</tr> <tr> <td class="metatag">permissions:</td> - <td>{permissions|permissions}</td></tr> + <td>{permissions|permissions}</td> +</tr> <tr> <td class="metatag">description:</td> <td>{desc|strip|escape|websub|addbreaks|nonempty}</td>
--- a/mercurial/util.h Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/util.h Fri May 15 11:52:09 2015 -0500 @@ -57,66 +57,6 @@ #endif /* PY_MAJOR_VERSION */ -/* Backports from 2.6 */ -#if PY_VERSION_HEX < 0x02060000 - -#define Py_TYPE(ob) (ob)->ob_type -#define Py_SIZE(ob) (ob)->ob_size -#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, - -/* Shamelessly stolen from bytesobject.h */ -#define PyBytesObject PyStringObject -#define PyBytes_Type PyString_Type - -#define PyBytes_Check PyString_Check -#define PyBytes_CheckExact PyString_CheckExact -#define PyBytes_CHECK_INTERNED PyString_CHECK_INTERNED -#define PyBytes_AS_STRING PyString_AS_STRING -#define PyBytes_GET_SIZE PyString_GET_SIZE -#define Py_TPFLAGS_BYTES_SUBCLASS Py_TPFLAGS_STRING_SUBCLASS - -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_FromString PyString_FromString -#define PyBytes_FromFormatV PyString_FromFormatV -#define PyBytes_FromFormat PyString_FromFormat -#define PyBytes_Size PyString_Size -#define PyBytes_AsString PyString_AsString -#define PyBytes_Repr PyString_Repr -#define PyBytes_Concat PyString_Concat -#define PyBytes_ConcatAndDel PyString_ConcatAndDel -#define _PyBytes_Resize _PyString_Resize -#define _PyBytes_Eq _PyString_Eq -#define PyBytes_Format PyString_Format -#define _PyBytes_FormatLong _PyString_FormatLong -#define PyBytes_DecodeEscape PyString_DecodeEscape -#define _PyBytes_Join _PyString_Join -#define PyBytes_Decode PyString_Decode -#define PyBytes_Encode PyString_Encode -#define PyBytes_AsEncodedObject PyString_AsEncodedObject -#define PyBytes_AsEncodedString PyString_AsEncodedString -#define PyBytes_AsDecodedObject PyString_AsDecodedObject -#define PyBytes_AsDecodedString PyString_AsDecodedString -#define PyBytes_AsStringAndSize PyString_AsStringAndSize -#define _PyBytes_InsertThousandsGrouping _PyString_InsertThousandsGrouping - -#endif /* PY_VERSION_HEX */ - -#if (PY_VERSION_HEX < 0x02050000) -/* Definitions to get compatibility with python 2.4 and earlier which - does not have Py_ssize_t. See also PEP 353. - Note: msvc (8 or earlier) does not have ssize_t, so we use Py_ssize_t. -*/ -typedef int Py_ssize_t; -typedef Py_ssize_t (*lenfunc)(PyObject *); -typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); -#define PyInt_FromSsize_t PyInt_FromLong - -#if !defined(PY_SSIZE_T_MIN) -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif -#endif - #ifdef _WIN32 #ifdef _MSC_VER /* msvc 6.0 has problems */
--- a/mercurial/util.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/util.py Fri May 15 11:52:09 2015 -0500 @@ -1003,15 +1003,13 @@ f2 = testfile + ".hgtmp2" fd = None try: - try: - oslink(f1, f2) - except OSError: - return False - + oslink(f1, f2) # nlinks() may behave differently for files on Windows shares if # the file is open. fd = posixfile(f2) return nlinks(f2) > 1 + except OSError: + return False finally: if fd is not None: fd.close()
--- a/mercurial/windows.py Thu May 14 21:35:06 2015 -0700 +++ b/mercurial/windows.py Fri May 15 11:52:09 2015 -0500 @@ -139,7 +139,7 @@ return pconvert(os.path.normpath(path)) def normcase(path): - return encoding.upper(path) + return encoding.upper(path) # NTFS compares via upper() # see posix.py for definitions normcasespec = encoding.normcasespecs.upper @@ -162,6 +162,18 @@ _quotere = None _needsshellquote = None def shellquote(s): + r""" + >>> shellquote(r'C:\Users\xyz') + '"C:\\Users\\xyz"' + >>> shellquote(r'C:\Users\xyz/mixed') + '"C:\\Users\\xyz/mixed"' + >>> # Would be safe not to quote too, since it is all double backslashes + >>> shellquote(r'C:\\Users\\xyz') + '"C:\\\\Users\\\\xyz"' + >>> # But this must be quoted + >>> shellquote(r'C:\\Users\\xyz/abc') + '"C:\\\\Users\\\\xyz/abc"' + """ global _quotere if _quotere is None: _quotere = re.compile(r'(\\*)("|\\$)')
--- a/setup.py Thu May 14 21:35:06 2015 -0700 +++ b/setup.py Fri May 15 11:52:09 2015 -0500 @@ -5,8 +5,8 @@ # 'python setup.py --help' for more options import sys, platform -if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'): - raise SystemExit("Mercurial requires Python 2.4 or later.") +if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'): + raise SystemExit("Mercurial requires Python 2.6 or later.") if sys.version_info[0] >= 3: def b(s): @@ -106,25 +106,24 @@ tmpdir = tempfile.mkdtemp(prefix='hg-install-') devnull = oldstderr = None try: - try: - fname = os.path.join(tmpdir, 'funcname.c') - f = open(fname, 'w') - f.write('int main(void) {\n') - f.write(' %s();\n' % funcname) - f.write('}\n') - f.close() - # Redirect stderr to /dev/null to hide any error messages - # from the compiler. - # This will have to be changed if we ever have to check - # for a function on Windows. - devnull = open('/dev/null', 'w') - oldstderr = os.dup(sys.stderr.fileno()) - os.dup2(devnull.fileno(), sys.stderr.fileno()) - objects = cc.compile([fname], output_dir=tmpdir) - cc.link_executable(objects, os.path.join(tmpdir, "a.out")) - except Exception: - return False + fname = os.path.join(tmpdir, 'funcname.c') + f = open(fname, 'w') + f.write('int main(void) {\n') + f.write(' %s();\n' % funcname) + f.write('}\n') + f.close() + # Redirect stderr to /dev/null to hide any error messages + # from the compiler. + # This will have to be changed if we ever have to check + # for a function on Windows. + devnull = open('/dev/null', 'w') + oldstderr = os.dup(sys.stderr.fileno()) + os.dup2(devnull.fileno(), sys.stderr.fileno()) + objects = cc.compile([fname], output_dir=tmpdir) + cc.link_executable(objects, os.path.join(tmpdir, "a.out")) return True + except Exception: + return False finally: if oldstderr is not None: os.dup2(oldstderr, sys.stderr.fileno()) @@ -408,11 +407,12 @@ # Persist executable bit (apply it to group and other if user # has it) if st[stat.ST_MODE] & stat.S_IXUSR: - setmode = 0755 + setmode = int('0755', 8) else: - setmode = 0644 - os.chmod(dst, (stat.S_IMODE(st[stat.ST_MODE]) & ~0777) | - setmode) + setmode = int('0644', 8) + m = stat.S_IMODE(st[stat.ST_MODE]) + m = (m & ~int('0777', 8)) | setmode + os.chmod(dst, m) file_util.copy_file = copyfileandsetmode try: install_lib.run(self) @@ -483,6 +483,11 @@ common_depends = ['mercurial/util.h'] +osutil_ldflags = [] + +if sys.platform == 'darwin': + osutil_ldflags += ['-framework', 'ApplicationServices'] + extmodules = [ Extension('mercurial.base85', ['mercurial/base85.c'], depends=common_depends), @@ -497,21 +502,11 @@ 'mercurial/parsers.c', 'mercurial/pathencode.c'], depends=common_depends), + Extension('mercurial.osutil', ['mercurial/osutil.c'], + extra_link_args=osutil_ldflags, + depends=common_depends), ] -osutil_ldflags = [] - -if sys.platform == 'darwin': - osutil_ldflags += ['-framework', 'ApplicationServices'] - -# disable osutil.c under windows + python 2.4 (issue1364) -if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'): - pymodules.append('mercurial.pure.osutil') -else: - extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'], - extra_link_args=osutil_ldflags, - depends=common_depends)) - try: from distutils import cygwinccompiler @@ -572,6 +567,8 @@ version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines() if version: version = version[0] + if sys.version_info[0] == 3: + version = version.decode('utf-8') xcode4 = (version.startswith('Xcode') and StrictVersion(version.split()[1]) >= StrictVersion('4.0')) xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
--- a/tests/heredoctest.py Thu May 14 21:35:06 2015 -0700 +++ b/tests/heredoctest.py Fri May 15 11:52:09 2015 -0500 @@ -5,7 +5,7 @@ while lines: l = lines.pop(0) if l.startswith('SALT'): - print l[:-1] + print(l[:-1]) elif l.startswith('>>> '): snippet = l[4:] while lines and lines[0].startswith('... '): @@ -13,6 +13,6 @@ snippet += l[4:] c = compile(snippet, '<heredoc>', 'single') try: - exec c in globalvars - except Exception, inst: - print repr(inst) + exec(c, globalvars) + except Exception as inst: + print(repr(inst))
--- a/tests/hghave.py Thu May 14 21:35:06 2015 -0700 +++ b/tests/hghave.py Fri May 15 11:52:09 2015 -0500 @@ -225,12 +225,11 @@ os.close(fh) name = tempfile.mktemp(dir='.', prefix=tempprefix) try: - try: - util.oslink(fn, name) - os.unlink(name) - return True - except OSError: - return False + util.oslink(fn, name) + os.unlink(name) + return True + except OSError: + return False finally: os.unlink(fn)
--- a/tests/killdaemons.py Thu May 14 21:35:06 2015 -0700 +++ b/tests/killdaemons.py Fri May 15 11:52:09 2015 -0500 @@ -64,7 +64,7 @@ os.kill(pid, 0) logfn('# Daemon process %d is stuck - really killing it' % pid) os.kill(pid, signal.SIGKILL) - except OSError, err: + except OSError as err: if err.errno != errno.ESRCH: raise
--- a/tests/run-tests.py Thu May 14 21:35:06 2015 -0700 +++ b/tests/run-tests.py Fri May 15 11:52:09 2015 -0500 @@ -41,6 +41,8 @@ # completes fairly quickly, includes both shell and Python scripts, and # includes some scripts that run daemon processes.) +from __future__ import print_function + from distutils import version import difflib import errno @@ -49,6 +51,7 @@ import shutil import subprocess import signal +import socket import sys import tempfile import time @@ -56,10 +59,15 @@ import re import threading import killdaemons as killmod -import Queue as queue +try: + import Queue as queue +except ImportError: + import queue from xml.dom import minidom import unittest +osenvironb = getattr(os, 'environb', os.environ) + try: import json except ImportError: @@ -70,6 +78,9 @@ processlock = threading.Lock() +if sys.version_info > (3, 0, 0): + xrange = range # we use xrange in one place, and we'd rather not use range + # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing # zombies but it's pretty harmless even if we do. @@ -78,6 +89,18 @@ wifexited = getattr(os, "WIFEXITED", lambda x: False) +def checkportisavailable(port): + """return true if a port seems free to bind on localhost""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('localhost', port)) + s.close() + return True + except socket.error as exc: + if not exc.errno == errno.EADDRINUSE: + raise + return False + closefds = os.name == 'posix' def Popen4(cmd, wd, timeout, env=None): processlock.acquire() @@ -104,10 +127,10 @@ return p -PYTHON = sys.executable.replace('\\', '/') -IMPL_PATH = 'PYTHONPATH' +PYTHON = sys.executable.replace('\\', '/').encode('utf-8') +IMPL_PATH = b'PYTHONPATH' if 'java' in sys.platform: - IMPL_PATH = 'JYTHONPATH' + IMPL_PATH = b'JYTHONPATH' defaults = { 'jobs': ('HGTEST_JOBS', 1), @@ -122,15 +145,15 @@ try: path = os.path.expanduser(os.path.expandvars(filename)) f = open(path, "rb") - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise if warn: - print "warning: no such %s file: %s" % (listtype, filename) + print("warning: no such %s file: %s" % (listtype, filename)) continue for line in f.readlines(): - line = line.split('#', 1)[0].strip() + line = line.split(b'#', 1)[0].strip() if line: entries[line] = filename @@ -240,8 +263,8 @@ if not os.path.basename(options.with_hg) == 'hg': sys.stderr.write('warning: --with-hg should specify an hg script\n') if options.local: - testdir = os.path.dirname(os.path.realpath(sys.argv[0])) - hgbin = os.path.join(os.path.dirname(testdir), 'hg') + testdir = os.path.dirname(os.path.realpath(sys.argv[0]).encode('utf-8')) + hgbin = os.path.join(os.path.dirname(testdir), b'hg') if os.name != 'nt' and not os.access(hgbin, os.X_OK): parser.error('--local specified, but %r not found or not executable' % hgbin) @@ -301,17 +324,22 @@ shutil.copy(src, dst) os.remove(src) +_unified_diff = difflib.unified_diff +if sys.version_info[0] > 2: + import functools + _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff) + def getdiff(expected, output, ref, err): servefail = False lines = [] - for line in difflib.unified_diff(expected, output, ref, err): - if line.startswith('+++') or line.startswith('---'): - line = line.replace('\\', '/') - if line.endswith(' \n'): - line = line[:-2] + '\n' + for line in _unified_diff(expected, output, ref, err): + if line.startswith(b'+++') or line.startswith(b'---'): + line = line.replace(b'\\', b'/') + if line.endswith(b' \n'): + line = line[:-2] + b'\n' lines.append(line) if not servefail and line.startswith( - '+ abort: child process failed to start'): + b'+ abort: child process failed to start'): servefail = True return servefail, lines @@ -326,7 +354,7 @@ # Bytes that break XML even in a CDATA block: control characters 0-31 # sans \t, \n and \r -CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]") +CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]") def cdatasafe(data): """Make a string safe to include in a CDATA block. @@ -336,21 +364,20 @@ replaces illegal bytes with ? and adds a space between the ]] so that it won't break the CDATA block. """ - return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>') + return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>') def log(*msg): """Log something to stdout. Arguments are strings to print. """ - iolock.acquire() - if verbose: - print verbose, - for m in msg: - print m, - print - sys.stdout.flush() - iolock.release() + with iolock: + if verbose: + print(verbose, end=' ') + for m in msg: + print(m, end=' ') + print() + sys.stdout.flush() def terminate(proc): """Terminate subprocess (with fallback for Python versions < 2.6)""" @@ -408,11 +435,11 @@ shell is the shell to execute tests in. """ - self.path = path - self.name = os.path.basename(path) + self.bname = os.path.basename(path) + self.name = self.bname.decode('utf-8') self._testdir = os.path.dirname(path) - self.errpath = os.path.join(self._testdir, '%s.err' % self.name) + self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname) self._threadtmp = tmpdir self._keeptmpdir = keeptmpdir @@ -421,7 +448,7 @@ self._startport = startport self._extraconfigopts = extraconfigopts or [] self._py3kwarnings = py3kwarnings - self._shell = shell + self._shell = shell.encode('utf-8') self._aborted = False self._daemonpids = [] @@ -442,6 +469,11 @@ else: self._refout = [] + # needed to get base class __repr__ running + @property + def _testMethodName(self): + return self.name + def __str__(self): return self.name @@ -457,7 +489,7 @@ try: os.mkdir(self._threadtmp) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise @@ -469,7 +501,7 @@ if os.path.exists(self.errpath): try: os.remove(self.errpath) - except OSError, e: + except OSError as e: # We might have raced another test to clean up a .err # file, so ignore ENOENT when removing a previous .err # file. @@ -499,20 +531,20 @@ except KeyboardInterrupt: self._aborted = True raise - except SkipTest, e: + except SkipTest as e: result.addSkip(self, str(e)) # The base class will have already counted this as a # test we "ran", but we want to exclude skipped tests # from those we count towards those run. result.testsRun -= 1 - except IgnoreTest, e: + except IgnoreTest as e: result.addIgnore(self, str(e)) # As with skips, ignores also should be excluded from # the number of tests executed. result.testsRun -= 1 - except WarnTest, e: + except WarnTest as e: result.addWarn(self, str(e)) - except self.failureException, e: + except self.failureException as e: # This differs from unittest in that we don't capture # the stack trace. This is for historical reasons and # this decision could be revisited in the future, @@ -620,7 +652,7 @@ f.write(line) f.close() - vlog("# Ret was:", self._ret) + vlog("# Ret was:", self._ret, '(%s)' % self.name) def _run(self, env): # This should be implemented in child classes to run tests. @@ -638,20 +670,20 @@ occur. """ r = [ - (r':%s\b' % self._startport, ':$HGPORT'), - (r':%s\b' % (self._startport + 1), ':$HGPORT1'), - (r':%s\b' % (self._startport + 2), ':$HGPORT2'), - (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$', - r'\1 (glob)'), + (br':%d\b' % self._startport, b':$HGPORT'), + (br':%d\b' % (self._startport + 1), b':$HGPORT1'), + (br':%d\b' % (self._startport + 2), b':$HGPORT2'), + (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$', + br'\1 (glob)'), ] if os.name == 'nt': r.append( - (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or - c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c - for c in self._testtmp), '$TESTTMP')) + (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or + c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c + for c in self._testtmp), b'$TESTTMP')) else: - r.append((re.escape(self._testtmp), '$TESTTMP')) + r.append((re.escape(self._testtmp), b'$TESTTMP')) return r @@ -663,8 +695,8 @@ env["HGPORT"] = str(self._startport) env["HGPORT1"] = str(self._startport + 1) env["HGPORT2"] = str(self._startport + 2) - env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc') - env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids') + env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc') + env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids') env["HGEDITOR"] = ('"' + sys.executable + '"' + ' -c "import sys; sys.exit(0)"') env["HGMERGE"] = "internal:merge" @@ -695,27 +727,27 @@ def _createhgrc(self, path): """Create an hgrc file for this test.""" hgrc = open(path, 'wb') - hgrc.write('[ui]\n') - hgrc.write('slash = True\n') - hgrc.write('interactive = False\n') - hgrc.write('mergemarkers = detailed\n') - hgrc.write('promptecho = True\n') - hgrc.write('[defaults]\n') - hgrc.write('backout = -d "0 0"\n') - hgrc.write('commit = -d "0 0"\n') - hgrc.write('shelve = --date "0 0"\n') - hgrc.write('tag = -d "0 0"\n') - hgrc.write('[devel]\n') - hgrc.write('all = true\n') - hgrc.write('[largefiles]\n') - hgrc.write('usercache = %s\n' % - (os.path.join(self._testtmp, '.cache/largefiles'))) + hgrc.write(b'[ui]\n') + hgrc.write(b'slash = True\n') + hgrc.write(b'interactive = False\n') + hgrc.write(b'mergemarkers = detailed\n') + hgrc.write(b'promptecho = True\n') + hgrc.write(b'[defaults]\n') + hgrc.write(b'backout = -d "0 0"\n') + hgrc.write(b'commit = -d "0 0"\n') + hgrc.write(b'shelve = --date "0 0"\n') + hgrc.write(b'tag = -d "0 0"\n') + hgrc.write(b'[devel]\n') + hgrc.write(b'all = true\n') + hgrc.write(b'[largefiles]\n') + hgrc.write(b'usercache = %s\n' % + (os.path.join(self._testtmp, b'.cache/largefiles'))) for opt in self._extraconfigopts: section, key = opt.split('.', 1) assert '=' in key, ('extra config opt %s must ' 'have an = for assignment' % opt) - hgrc.write('[%s]\n%s\n' % (section, key)) + hgrc.write(b'[%s]\n%s\n' % (section, key)) hgrc.close() def fail(self, msg): @@ -777,11 +809,11 @@ @property def refpath(self): - return os.path.join(self._testdir, '%s.out' % self.name) + return os.path.join(self._testdir, b'%s.out' % self.bname) def _run(self, env): - py3kswitch = self._py3kwarnings and ' -3' or '' - cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path) + py3kswitch = self._py3kwarnings and b' -3' or b'' + cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path) vlog("# Running", cmd) normalizenewlines = os.name == 'nt' result = self._runcommand(cmd, env, @@ -795,25 +827,29 @@ # Windows, but check-code.py wants a glob on these lines unconditionally. Don't # warn if that is the case for anything matching these lines. checkcodeglobpats = [ - re.compile(r'^pushing to \$TESTTMP/.*[^)]$'), - re.compile(r'^moving \S+/.*[^)]$'), - re.compile(r'^pulling from \$TESTTMP/.*[^)]$') + re.compile(br'^pushing to \$TESTTMP/.*[^)]$'), + re.compile(br'^moving \S+/.*[^)]$'), + re.compile(br'^pulling from \$TESTTMP/.*[^)]$') ] +bchr = chr +if sys.version_info[0] == 3: + bchr = lambda x: bytes([x]) + class TTest(Test): """A "t test" is a test backed by a .t file.""" SKIPPED_PREFIX = 'skipped: ' FAILED_PREFIX = 'hghave check failed: ' - NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search + NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search - ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub - ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)) - ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'}) + ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub + ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256)) + ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) @property def refpath(self): - return os.path.join(self._testdir, self.name) + return os.path.join(self._testdir, self.bname) def _run(self, env): f = open(self.path, 'rb') @@ -823,13 +859,13 @@ salt, script, after, expected = self._parsetest(lines) # Write out the generated script. - fname = '%s.sh' % self._testtmp + fname = b'%s.sh' % self._testtmp f = open(fname, 'wb') for l in script: f.write(l) f.close() - cmd = '%s "%s"' % (self._shell, fname) + cmd = b'%s "%s"' % (self._shell, fname) vlog("# Running", cmd) exitcode, output = self._runcommand(cmd, env) @@ -846,16 +882,16 @@ def _hghave(self, reqs): # TODO do something smarter when all other uses of hghave are gone. - tdir = self._testdir.replace('\\', '/') - proc = Popen4('%s -c "%s/hghave %s"' % - (self._shell, tdir, ' '.join(reqs)), + tdir = self._testdir.replace(b'\\', b'/') + proc = Popen4(b'%s -c "%s/hghave %s"' % + (self._shell, tdir, b' '.join(reqs)), self._testtmp, 0, self._getenv()) stdout, stderr = proc.communicate() ret = proc.wait() if wifexited(ret): ret = os.WEXITSTATUS(ret) if ret == 2: - print stdout + print(stdout) sys.exit(1) return ret == 0 @@ -864,12 +900,12 @@ # We generate a shell script which outputs unique markers to line # up script results with our source. These markers include input # line number and the last return code. - salt = "SALT" + str(time.time()) + salt = b"SALT%d" % time.time() def addsalt(line, inpython): if inpython: - script.append('%s %d 0\n' % (salt, line)) + script.append(b'%s %d 0\n' % (salt, line)) else: - script.append('echo %s %s $?\n' % (salt, line)) + script.append(b'echo %s %d $?\n' % (salt, line)) script = [] @@ -892,42 +928,42 @@ inpython = False if self._debug: - script.append('set -x\n') + script.append(b'set -x\n') if os.getenv('MSYSTEM'): - script.append('alias pwd="pwd -W"\n') + script.append(b'alias pwd="pwd -W"\n') for n, l in enumerate(lines): - if not l.endswith('\n'): - l += '\n' - if l.startswith('#require'): + if not l.endswith(b'\n'): + l += b'\n' + if l.startswith(b'#require'): lsplit = l.split() - if len(lsplit) < 2 or lsplit[0] != '#require': + if len(lsplit) < 2 or lsplit[0] != b'#require': after.setdefault(pos, []).append(' !!! invalid #require\n') if not self._hghave(lsplit[1:]): - script = ["exit 80\n"] + script = [b"exit 80\n"] break after.setdefault(pos, []).append(l) - elif l.startswith('#if'): + elif l.startswith(b'#if'): lsplit = l.split() - if len(lsplit) < 2 or lsplit[0] != '#if': + if len(lsplit) < 2 or lsplit[0] != b'#if': after.setdefault(pos, []).append(' !!! invalid #if\n') if skipping is not None: after.setdefault(pos, []).append(' !!! nested #if\n') skipping = not self._hghave(lsplit[1:]) after.setdefault(pos, []).append(l) - elif l.startswith('#else'): + elif l.startswith(b'#else'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = not skipping after.setdefault(pos, []).append(l) - elif l.startswith('#endif'): + elif l.startswith(b'#endif'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = None after.setdefault(pos, []).append(l) elif skipping: after.setdefault(pos, []).append(l) - elif l.startswith(' >>> '): # python inlines + elif l.startswith(b' >>> '): # python inlines after.setdefault(pos, []).append(l) prepos = pos pos = n @@ -935,39 +971,39 @@ # We've just entered a Python block. Add the header. inpython = True addsalt(prepos, False) # Make sure we report the exit code. - script.append('%s -m heredoctest <<EOF\n' % PYTHON) + script.append(b'%s -m heredoctest <<EOF\n' % PYTHON) addsalt(n, True) script.append(l[2:]) - elif l.startswith(' ... '): # python inlines + elif l.startswith(b' ... '): # python inlines after.setdefault(prepos, []).append(l) script.append(l[2:]) - elif l.startswith(' $ '): # commands + elif l.startswith(b' $ '): # commands if inpython: - script.append('EOF\n') + script.append(b'EOF\n') inpython = False after.setdefault(pos, []).append(l) prepos = pos pos = n addsalt(n, False) cmd = l[4:].split() - if len(cmd) == 2 and cmd[0] == 'cd': - l = ' $ cd %s || exit 1\n' % cmd[1] + if len(cmd) == 2 and cmd[0] == b'cd': + l = b' $ cd %s || exit 1\n' % cmd[1] script.append(l[4:]) - elif l.startswith(' > '): # continuations + elif l.startswith(b' > '): # continuations after.setdefault(prepos, []).append(l) script.append(l[4:]) - elif l.startswith(' '): # results + elif l.startswith(b' '): # results # Queue up a list of expected results. expected.setdefault(pos, []).append(l[2:]) else: if inpython: - script.append('EOF\n') + script.append(b'EOF\n') inpython = False # Non-command/result. Queue up for merged output. after.setdefault(pos, []).append(l) if inpython: - script.append('EOF\n') + script.append(b'EOF\n') if skipping is not None: after.setdefault(pos, []).append(' !!! missing #endif\n') addsalt(n + 1, False) @@ -988,8 +1024,8 @@ lout, lcmd = l.split(salt, 1) if lout: - if not lout.endswith('\n'): - lout += ' (no-eol)\n' + if not lout.endswith(b'\n'): + lout += b' (no-eol)\n' # Find the expected output at the current position. el = None @@ -1008,12 +1044,12 @@ log('\ninfo, unknown linematch result: %r\n' % r) r = False if r: - postout.append(' ' + el) + postout.append(b' ' + el) else: if self.NEEDESCAPE(lout): - lout = TTest._stringescape('%s (esc)\n' % - lout.rstrip('\n')) - postout.append(' ' + lout) # Let diff deal with it. + lout = TTest._stringescape(b'%s (esc)\n' % + lout.rstrip(b'\n')) + postout.append(b' ' + lout) # Let diff deal with it. if r != '': # If line failed. warnonly = 3 # for sure not elif warnonly == 1: # Is "not yet" and line is warn only. @@ -1023,7 +1059,7 @@ # Add on last return code. ret = int(lcmd.split()[1]) if ret != 0: - postout.append(' [%s]\n' % ret) + postout.append(b' [%d]\n' % ret) if pos in after: # Merge in non-active test bits. postout += after.pop(pos) @@ -1042,8 +1078,8 @@ try: # use \Z to ensure that the regex matches to the end of the string if os.name == 'nt': - return re.match(el + r'\r?\n\Z', l) - return re.match(el + r'\n\Z', l) + return re.match(el + br'\r?\n\Z', l) + return re.match(el + br'\n\Z', l) except re.error: # el is an invalid regex return False @@ -1052,28 +1088,28 @@ def globmatch(el, l): # The only supported special characters are * and ? plus / which also # matches \ on windows. Escaping of these characters is supported. - if el + '\n' == l: + if el + b'\n' == l: if os.altsep: # matching on "/" is not needed for this line for pat in checkcodeglobpats: if pat.match(el): return True - return '-glob' + return b'-glob' return True i, n = 0, len(el) - res = '' + res = b'' while i < n: - c = el[i] + c = el[i:i + 1] i += 1 - if c == '\\' and i < n and el[i] in '*?\\/': + if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/': res += el[i - 1:i + 1] i += 1 - elif c == '*': - res += '.*' - elif c == '?': - res += '.' - elif c == '/' and os.altsep: - res += '[/\\\\]' + elif c == b'*': + res += b'.*' + elif c == b'?': + res += b'.' + elif c == b'/' and os.altsep: + res += b'[/\\\\]' else: res += re.escape(c) return TTest.rematch(res, l) @@ -1083,19 +1119,23 @@ if el == l: # perfect match (fast) return True if el: - if el.endswith(" (esc)\n"): - el = el[:-7].decode('string-escape') + '\n' - if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l: + if el.endswith(b" (esc)\n"): + if sys.version_info[0] == 3: + el = el[:-7].decode('unicode_escape') + '\n' + el = el.encode('utf-8') + else: + el = el[:-7].decode('string-escape') + '\n' + if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: return True - if el.endswith(" (re)\n"): + if el.endswith(b" (re)\n"): return TTest.rematch(el[:-6], l) - if el.endswith(" (glob)\n"): + if el.endswith(b" (glob)\n"): # ignore '(glob)' added to l by 'replacements' - if l.endswith(" (glob)\n"): - l = l[:-8] + "\n" + if l.endswith(b" (glob)\n"): + l = l[:-8] + b"\n" return TTest.globmatch(el[:-8], l) - if os.altsep and l.replace('\\', '/') == el: - return '+glob' + if os.altsep and l.replace(b'\\', b'/') == el: + return b'+glob' return False @staticmethod @@ -1160,6 +1200,7 @@ self.warned = [] self.times = [] + self._firststarttime = None # Data stored for the benefit of generating xunit reports. self.successes = [] self.faildata = {} @@ -1170,18 +1211,16 @@ if self._options.first: self.stop() else: - iolock.acquire() - if not self._options.nodiff: - self.stream.write('\nERROR: %s output changed\n' % test) + with iolock: + if not self._options.nodiff: + self.stream.write('\nERROR: %s output changed\n' % test) - self.stream.write('!') - self.stream.flush() - iolock.release() + self.stream.write('!') + self.stream.flush() def addSuccess(self, test): - iolock.acquire() - super(TestResult, self).addSuccess(test) - iolock.release() + with iolock: + super(TestResult, self).addSuccess(test) self.successes.append(test) def addError(self, test, err): @@ -1192,26 +1231,24 @@ # Polyfill. def addSkip(self, test, reason): self.skipped.append((test, reason)) - iolock.acquire() - if self.showAll: - self.stream.writeln('skipped %s' % reason) - else: - self.stream.write('s') - self.stream.flush() - iolock.release() + with iolock: + if self.showAll: + self.stream.writeln('skipped %s' % reason) + else: + self.stream.write('s') + self.stream.flush() def addIgnore(self, test, reason): self.ignored.append((test, reason)) - iolock.acquire() - if self.showAll: - self.stream.writeln('ignored %s' % reason) - else: - if reason != 'not retesting' and reason != "doesn't match keyword": - self.stream.write('i') + with iolock: + if self.showAll: + self.stream.writeln('ignored %s' % reason) else: - self.testsRun += 1 - self.stream.flush() - iolock.release() + if reason not in ('not retesting', "doesn't match keyword"): + self.stream.write('i') + else: + self.testsRun += 1 + self.stream.flush() def addWarn(self, test, reason): self.warned.append((test, reason)) @@ -1219,13 +1256,12 @@ if self._options.first: self.stop() - iolock.acquire() - if self.showAll: - self.stream.writeln('warned %s' % reason) - else: - self.stream.write('~') - self.stream.flush() - iolock.release() + with iolock: + if self.showAll: + self.stream.writeln('warned %s' % reason) + else: + self.stream.write('~') + self.stream.flush() def addOutputMismatch(self, test, ret, got, expected): """Record a mismatch in test output for a particular test.""" @@ -1239,38 +1275,45 @@ failed = False lines = [] - iolock.acquire() - if self._options.nodiff: - pass - elif self._options.view: - os.system("%s %s %s" % - (self._options.view, test.refpath, test.errpath)) - else: - servefail, lines = getdiff(expected, got, - test.refpath, test.errpath) - if servefail: - self.addFailure( - test, - 'server failed to start (HGPORT=%s)' % test._startport) + with iolock: + if self._options.nodiff: + pass + elif self._options.view: + v = self._options.view + if sys.version_info[0] == 3: + v = v.encode('utf-8') + os.system(b"%s %s %s" % + (v, test.refpath, test.errpath)) else: - self.stream.write('\n') - for line in lines: - self.stream.write(line) - self.stream.flush() + servefail, lines = getdiff(expected, got, + test.refpath, test.errpath) + if servefail: + self.addFailure( + test, + 'server failed to start (HGPORT=%s)' % test._startport) + else: + self.stream.write('\n') + for line in lines: + if sys.version_info[0] > 2: + self.stream.flush() + self.stream.buffer.write(line) + self.stream.buffer.flush() + else: + self.stream.write(line) + self.stream.flush() - # handle interactive prompt without releasing iolock - if self._options.interactive: - self.stream.write('Accept this change? [n] ') - answer = sys.stdin.readline().strip() - if answer.lower() in ('y', 'yes'): - if test.name.endswith('.t'): - rename(test.errpath, test.path) - else: - rename(test.errpath, '%s.out' % test.path) - accepted = True - if not accepted and not failed: - self.faildata[test.name] = ''.join(lines) - iolock.release() + # handle interactive prompt without releasing iolock + if self._options.interactive: + self.stream.write('Accept this change? [n] ') + answer = sys.stdin.readline().strip() + if answer.lower() in ('y', 'yes'): + if test.name.endswith('.t'): + rename(test.errpath, test.path) + else: + rename(test.errpath, '%s.out' % test.path) + accepted = True + if not accepted and not failed: + self.faildata[test.name] = b''.join(lines) return accepted @@ -1282,6 +1325,8 @@ # This module has one limitation. It can only work for Linux user # and not for Windows. test.started = os.times() + if self._firststarttime is None: # thread racy but irrelevant + self._firststarttime = test.started[4] def stopTest(self, test, interrupted=False): super(TestResult, self).stopTest(test) @@ -1290,14 +1335,19 @@ starttime = test.started endtime = test.stopped - self.times.append((test.name, endtime[2] - starttime[2], - endtime[3] - starttime[3], endtime[4] - starttime[4])) + origin = self._firststarttime + self.times.append((test.name, + endtime[2] - starttime[2], # user space CPU time + endtime[3] - starttime[3], # sys space CPU time + endtime[4] - starttime[4], # real time + starttime[4] - origin, # start date in run context + endtime[4] - origin, # end date in run context + )) if interrupted: - iolock.acquire() - self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( - test.name, self.times[-1][3])) - iolock.release() + with iolock: + self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( + test.name, self.times[-1][3])) class TestSuite(unittest.TestSuite): """Custom unittest TestSuite that knows how to execute Mercurial tests.""" @@ -1352,14 +1402,14 @@ def get(): num_tests[0] += 1 if getattr(test, 'should_reload', False): - return self._loadtest(test.name, num_tests[0]) + return self._loadtest(test.bname, num_tests[0]) return test if not os.path.exists(test.path): result.addSkip(test, "Doesn't exist") continue if not (self._whitelist and test.name in self._whitelist): - if self._blacklist and test.name in self._blacklist: + if self._blacklist and test.bname in self._blacklist: result.addSkip(test, 'blacklisted') continue @@ -1369,7 +1419,7 @@ if self._keywords: f = open(test.path, 'rb') - t = f.read().lower() + test.name.lower() + t = f.read().lower() + test.bname.lower() f.close() ignored = False for k in self._keywords.lower().split(): @@ -1460,101 +1510,92 @@ skipped = len(result.skipped) ignored = len(result.ignored) - iolock.acquire() - self.stream.writeln('') - - if not self._runner.options.noskips: - for test, msg in result.skipped: - self.stream.writeln('Skipped %s: %s' % (test.name, msg)) - for test, msg in result.warned: - self.stream.writeln('Warned %s: %s' % (test.name, msg)) - for test, msg in result.failures: - self.stream.writeln('Failed %s: %s' % (test.name, msg)) - for test, msg in result.errors: - self.stream.writeln('Errored %s: %s' % (test.name, msg)) + with iolock: + self.stream.writeln('') - if self._runner.options.xunit: - xuf = open(self._runner.options.xunit, 'wb') - try: - timesd = dict( - (test, real) for test, cuser, csys, real in result.times) - doc = minidom.Document() - s = doc.createElement('testsuite') - s.setAttribute('name', 'run-tests') - s.setAttribute('tests', str(result.testsRun)) - s.setAttribute('errors', "0") # TODO - s.setAttribute('failures', str(failed)) - s.setAttribute('skipped', str(skipped + ignored)) - doc.appendChild(s) - for tc in result.successes: - t = doc.createElement('testcase') - t.setAttribute('name', tc.name) - t.setAttribute('time', '%.3f' % timesd[tc.name]) - s.appendChild(t) - for tc, err in sorted(result.faildata.iteritems()): - t = doc.createElement('testcase') - t.setAttribute('name', tc) - t.setAttribute('time', '%.3f' % timesd[tc]) - # createCDATASection expects a unicode or it will convert - # using default conversion rules, which will fail if - # string isn't ASCII. - err = cdatasafe(err).decode('utf-8', 'replace') - cd = doc.createCDATASection(err) - t.appendChild(cd) - s.appendChild(t) - xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) - finally: - xuf.close() + if not self._runner.options.noskips: + for test, msg in result.skipped: + self.stream.writeln('Skipped %s: %s' % (test.name, msg)) + for test, msg in result.warned: + self.stream.writeln('Warned %s: %s' % (test.name, msg)) + for test, msg in result.failures: + self.stream.writeln('Failed %s: %s' % (test.name, msg)) + for test, msg in result.errors: + self.stream.writeln('Errored %s: %s' % (test.name, msg)) - if self._runner.options.json: - if json is None: - raise ImportError("json module not installed") - jsonpath = os.path.join(self._runner._testdir, 'report.json') - fp = open(jsonpath, 'w') - try: - timesd = {} - for test, cuser, csys, real in result.times: - timesd[test] = (real, cuser, csys) - - outcome = {} - for tc in result.successes: - testresult = {'result': 'success', - 'time': ('%0.3f' % timesd[tc.name][0]), - 'cuser': ('%0.3f' % timesd[tc.name][1]), - 'csys': ('%0.3f' % timesd[tc.name][2])} - outcome[tc.name] = testresult - - for tc, err in sorted(result.faildata.iteritems()): - testresult = {'result': 'failure', - 'time': ('%0.3f' % timesd[tc][0]), - 'cuser': ('%0.3f' % timesd[tc][1]), - 'csys': ('%0.3f' % timesd[tc][2])} - outcome[tc] = testresult + if self._runner.options.xunit: + xuf = open(self._runner.options.xunit, 'wb') + try: + timesd = dict((t[0], t[3]) for t in result.times) + doc = minidom.Document() + s = doc.createElement('testsuite') + s.setAttribute('name', 'run-tests') + s.setAttribute('tests', str(result.testsRun)) + s.setAttribute('errors', "0") # TODO + s.setAttribute('failures', str(failed)) + s.setAttribute('skipped', str(skipped + ignored)) + doc.appendChild(s) + for tc in result.successes: + t = doc.createElement('testcase') + t.setAttribute('name', tc.name) + t.setAttribute('time', '%.3f' % timesd[tc.name]) + s.appendChild(t) + for tc, err in sorted(result.faildata.items()): + t = doc.createElement('testcase') + t.setAttribute('name', tc) + t.setAttribute('time', '%.3f' % timesd[tc]) + # createCDATASection expects a unicode or it will + # convert using default conversion rules, which will + # fail if string isn't ASCII. + err = cdatasafe(err).decode('utf-8', 'replace') + cd = doc.createCDATASection(err) + t.appendChild(cd) + s.appendChild(t) + xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) + finally: + xuf.close() - for tc, reason in result.skipped: - testresult = {'result': 'skip', - 'time': ('%0.3f' % timesd[tc.name][0]), - 'cuser': ('%0.3f' % timesd[tc.name][1]), - 'csys': ('%0.3f' % timesd[tc.name][2])} - outcome[tc.name] = testresult - - jsonout = json.dumps(outcome, sort_keys=True, indent=4) - fp.writelines(("testreport =", jsonout)) - finally: - fp.close() + if self._runner.options.json: + if json is None: + raise ImportError("json module not installed") + jsonpath = os.path.join(self._runner._testdir, 'report.json') + fp = open(jsonpath, 'w') + try: + timesd = {} + for tdata in result.times: + test = tdata[0] + timesd[test] = tdata[1:] - self._runner._checkhglib('Tested') + outcome = {} + groups = [('success', ((tc, None) + for tc in result.successes)), + ('failure', result.failures), + ('skip', result.skipped)] + for res, testcases in groups: + for tc, __ in testcases: + tres = {'result': res, + 'time': ('%0.3f' % timesd[tc.name][2]), + 'cuser': ('%0.3f' % timesd[tc.name][0]), + 'csys': ('%0.3f' % timesd[tc.name][1]), + 'start': ('%0.3f' % timesd[tc.name][3]), + 'end': ('%0.3f' % timesd[tc.name][4])} + outcome[tc.name] = tres + jsonout = json.dumps(outcome, sort_keys=True, indent=4) + fp.writelines(("testreport =", jsonout)) + finally: + fp.close() - self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.' - % (result.testsRun, - skipped + ignored, warned, failed)) - if failed: - self.stream.writeln('python hash seed: %s' % - os.environ['PYTHONHASHSEED']) - if self._runner.options.time: - self.printtimes(result.times) + self._runner._checkhglib('Tested') - iolock.release() + self.stream.writeln( + '# Ran %d tests, %d skipped, %d warned, %d failed.' + % (result.testsRun, + skipped + ignored, warned, failed)) + if failed: + self.stream.writeln('python hash seed: %s' % + os.environ['PYTHONHASHSEED']) + if self._runner.options.time: + self.printtimes(result.times) return result @@ -1562,11 +1603,13 @@ # iolock held by run self.stream.writeln('# Producing time report') times.sort(key=lambda t: (t[3])) - cols = '%7.3f %7.3f %7.3f %s' - self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real', - 'Test')) - for test, cuser, csys, real in times: - self.stream.writeln(cols % (cuser, csys, real, test)) + cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s' + self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' % + ('start', 'end', 'cuser', 'csys', 'real', 'Test')) + for tdata in times: + test = tdata[0] + cuser, csys, real, start, end = tdata[1:6] + self.stream.writeln(cols % (start, end, cuser, csys, real, test)) class TestRunner(object): """Holds context for executing tests. @@ -1576,19 +1619,19 @@ # Programs required to run tests. REQUIREDTOOLS = [ - os.path.basename(sys.executable), - 'diff', - 'grep', - 'unzip', - 'gunzip', - 'bunzip2', - 'sed', + os.path.basename(sys.executable).encode('utf-8'), + b'diff', + b'grep', + b'unzip', + b'gunzip', + b'bunzip2', + b'sed', ] # Maps file extensions to test class. TESTTYPES = [ - ('.py', PythonTest), - ('.t', TTest), + (b'.py', PythonTest), + (b'.t', TTest), ] def __init__(self): @@ -1603,13 +1646,16 @@ self._coveragefile = None self._createdfiles = [] self._hgpath = None + self._portoffset = 0 + self._ports = {} def run(self, args, parser=None): """Run the test suite.""" - oldmask = os.umask(022) + oldmask = os.umask(0o22) try: parser = parser or getparser() options, args = parseargs(args, parser) + args = [a.encode('utf-8') for a in args] self.options = options self._checktools() @@ -1623,22 +1669,26 @@ random.shuffle(tests) else: # keywords for slow tests - slow = 'svn gendoc check-code-hg'.split() + slow = {b'svn': 10, + b'gendoc': 10, + b'check-code-hg': 100, + } def sortkey(f): # run largest tests first, as they tend to take the longest try: val = -os.stat(f).st_size - except OSError, e: + except OSError as e: if e.errno != errno.ENOENT: raise return -1e9 # file does not exist, tell early - for kw in slow: + for kw, mul in slow.iteritems(): if kw in f: - val *= 10 + val *= mul return val tests.sort(key=sortkey) - self._testdir = os.environ['TESTDIR'] = os.getcwd() + self._testdir = osenvironb[b'TESTDIR'] = getattr( + os, 'getcwdb', os.getcwd)() if 'PYTHONHASHSEED' not in os.environ: # use a random python hash seed all the time @@ -1647,12 +1697,12 @@ if self.options.tmpdir: self.options.keep_tmpdir = True - tmpdir = self.options.tmpdir + tmpdir = self.options.tmpdir.encode('utf-8') if os.path.exists(tmpdir): # Meaning of tmpdir has changed since 1.3: we used to create # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if # tmpdir already exists. - print "error: temp dir %r already exists" % tmpdir + print("error: temp dir %r already exists" % tmpdir) return 1 # Automatically removing tmpdir sounds convenient, but could @@ -1666,15 +1716,26 @@ if os.name == 'nt': # without this, we get the default temp dir location, but # in all lowercase, which causes troubles with paths (issue3490) - d = os.getenv('TMP') - tmpdir = tempfile.mkdtemp('', 'hgtests.', d) - self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir) + d = osenvironb.get(b'TMP', None) + # FILE BUG: mkdtemp works only on unicode in Python 3 + tmpdir = tempfile.mkdtemp('', 'hgtests.', + d and d.decode('utf-8')).encode('utf-8') + + self._hgtmp = osenvironb[b'HGTMP'] = ( + os.path.realpath(tmpdir)) if self.options.with_hg: self._installdir = None - self._bindir = os.path.dirname(os.path.realpath( - self.options.with_hg)) - self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin') + whg = self.options.with_hg + # If --with-hg is not specified, we have bytes already, + # but if it was specified in python3 we get a str, so we + # have to encode it back into a bytes. + if sys.version_info[0] == 3: + if not isinstance(whg, bytes): + whg = whg.encode('utf-8') + self._bindir = os.path.dirname(os.path.realpath(whg)) + assert isinstance(self._bindir, bytes) + self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') os.makedirs(self._tmpbindir) # This looks redundant with how Python initializes sys.path from @@ -1684,25 +1745,30 @@ # ... which means it's not really redundant at all. self._pythondir = self._bindir else: - self._installdir = os.path.join(self._hgtmp, "install") - self._bindir = os.environ["BINDIR"] = \ - os.path.join(self._installdir, "bin") + self._installdir = os.path.join(self._hgtmp, b"install") + self._bindir = osenvironb[b"BINDIR"] = \ + os.path.join(self._installdir, b"bin") self._tmpbindir = self._bindir - self._pythondir = os.path.join(self._installdir, "lib", "python") + self._pythondir = os.path.join(self._installdir, b"lib", b"python") - os.environ["BINDIR"] = self._bindir - os.environ["PYTHON"] = PYTHON + osenvironb[b"BINDIR"] = self._bindir + osenvironb[b"PYTHON"] = PYTHON - runtestdir = os.path.abspath(os.path.dirname(__file__)) - path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep) + fileb = __file__.encode('utf-8') + runtestdir = os.path.abspath(os.path.dirname(fileb)) + if sys.version_info[0] == 3: + sepb = os.pathsep.encode('utf-8') + else: + sepb = os.pathsep + path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb) if os.path.islink(__file__): # test helper will likely be at the end of the symlink - realfile = os.path.realpath(__file__) + realfile = os.path.realpath(fileb) realdir = os.path.abspath(os.path.dirname(realfile)) path.insert(2, realdir) if self._tmpbindir != self._bindir: path = [self._tmpbindir] + path - os.environ["PATH"] = os.pathsep.join(path) + osenvironb[b"PATH"] = sepb.join(path) # Include TESTDIR in PYTHONPATH so that out-of-tree extensions # can run .../tests/run-tests.py test-foo where test-foo @@ -1713,20 +1779,20 @@ # it, in case external libraries are only available via current # PYTHONPATH. (In particular, the Subversion bindings on OS X # are in /opt/subversion.) - oldpypath = os.environ.get(IMPL_PATH) + oldpypath = osenvironb.get(IMPL_PATH) if oldpypath: pypath.append(oldpypath) - os.environ[IMPL_PATH] = os.pathsep.join(pypath) + osenvironb[IMPL_PATH] = sepb.join(pypath) if self.options.pure: os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure" - self._coveragefile = os.path.join(self._testdir, '.coverage') + self._coveragefile = os.path.join(self._testdir, b'.coverage') vlog("# Using TESTDIR", self._testdir) vlog("# Using HGTMP", self._hgtmp) vlog("# Using PATH", os.environ["PATH"]) - vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) + vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH]) try: return self._runtests(tests) or 0 @@ -1745,13 +1811,13 @@ proc = Popen4('hg st --rev "%s" -man0 .' % self.options.changed, None, 0) stdout, stderr = proc.communicate() - args = stdout.strip('\0').split('\0') + args = stdout.strip(b'\0').split(b'\0') else: - args = os.listdir('.') + args = os.listdir(b'.') return [t for t in args - if os.path.basename(t).startswith('test-') - and (t.endswith('.py') or t.endswith('.t'))] + if os.path.basename(t).startswith(b'test-') + and (t.endswith(b'.py') or t.endswith(b'.t'))] def _runtests(self, tests): try: @@ -1768,20 +1834,23 @@ break tests.pop(0) if not tests: - print "running all tests" + print("running all tests") tests = orig tests = [self._gettest(t, i) for i, t in enumerate(tests)] failed = False warned = False + kws = self.options.keywords + if kws is not None and sys.version_info[0] == 3: + kws = kws.encode('utf-8') suite = TestSuite(self._testdir, jobs=self.options.jobs, whitelist=self.options.whitelisted, blacklist=self.options.blacklist, retest=self.options.retest, - keywords=self.options.keywords, + keywords=kws, loop=self.options.loop, runs_per_test=self.options.runs_per_test, tests=tests, loadtest=self._gettest) @@ -1800,13 +1869,31 @@ self._outputcoverage() except KeyboardInterrupt: failed = True - print "\ninterrupted!" + print("\ninterrupted!") if failed: return 1 if warned: return 80 + def _getport(self, count): + port = self._ports.get(count) # do we have a cached entry? + if port is None: + port = self.options.port + self._portoffset + portneeded = 3 + # above 100 tries we just give up and let test reports failure + for tries in xrange(100): + allfree = True + for idx in xrange(portneeded): + if not checkportisavailable(port + idx): + allfree = False + break + self._portoffset += portneeded + if allfree: + break + self._ports[count] = port + return port + def _gettest(self, test, count): """Obtain a Test by looking at its filename. @@ -1822,13 +1909,13 @@ break refpath = os.path.join(self._testdir, test) - tmpdir = os.path.join(self._hgtmp, 'child%d' % count) + tmpdir = os.path.join(self._hgtmp, b'child%d' % count) t = testcls(refpath, tmpdir, keeptmpdir=self.options.keep_tmpdir, debug=self.options.debug, timeout=self.options.timeout, - startport=self.options.port + count * 3, + startport=self._getport(count), extraconfigopts=self.options.extra_config_opt, py3kwarnings=self.options.py3k_warnings, shell=self.options.shell) @@ -1852,7 +1939,7 @@ def _usecorrectpython(self): """Configure the environment to use the appropriate Python in tests.""" # Tests must use the same interpreter as us or bad things will happen. - pyexename = sys.platform == 'win32' and 'python.exe' or 'python' + pyexename = sys.platform == 'win32' and b'python.exe' or b'python' if getattr(os, 'symlink', None): vlog("# Making python executable in test path a symlink to '%s'" % sys.executable) @@ -1861,14 +1948,14 @@ if os.readlink(mypython) == sys.executable: return os.unlink(mypython) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise if self._findprogram(pyexename) != sys.executable: try: os.symlink(sys.executable, mypython) self._createdfiles.append(mypython) - except OSError, err: + except OSError as err: # child processes may race, which is harmless if err.errno != errno.EEXIST: raise @@ -1881,7 +1968,7 @@ path.remove(exedir) os.environ['PATH'] = os.pathsep.join([exedir] + path) if not self._findprogram(pyexename): - print "WARNING: Cannot find %s in search path" % pyexename + print("WARNING: Cannot find %s in search path" % pyexename) def _installhg(self): """Install hg into the test environment. @@ -1889,47 +1976,51 @@ This will also configure hg with the appropriate testing settings. """ vlog("# Performing temporary installation of HG") - installerrs = os.path.join("tests", "install.err") + installerrs = os.path.join(b"tests", b"install.err") compiler = '' if self.options.compiler: compiler = '--compiler ' + self.options.compiler if self.options.pure: - pure = "--pure" + pure = b"--pure" else: - pure = "" + pure = b"" py3 = '' - if sys.version_info[0] == 3: - py3 = '--c2to3' # Run installer in hg root script = os.path.realpath(sys.argv[0]) + exe = sys.executable + if sys.version_info[0] == 3: + py3 = b'--c2to3' + compiler = compiler.encode('utf-8') + script = script.encode('utf-8') + exe = exe.encode('utf-8') hgroot = os.path.dirname(os.path.dirname(script)) self._hgroot = hgroot os.chdir(hgroot) - nohome = '--home=""' + nohome = b'--home=""' if os.name == 'nt': # The --home="" trick works only on OS where os.sep == '/' # because of a distutils convert_path() fast-path. Avoid it at # least on Windows for now, deal with .pydistutils.cfg bugs # when they happen. - nohome = '' - cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all' - ' build %(compiler)s --build-base="%(base)s"' - ' install --force --prefix="%(prefix)s"' - ' --install-lib="%(libdir)s"' - ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' - % {'exe': sys.executable, 'py3': py3, 'pure': pure, - 'compiler': compiler, - 'base': os.path.join(self._hgtmp, "build"), - 'prefix': self._installdir, 'libdir': self._pythondir, - 'bindir': self._bindir, - 'nohome': nohome, 'logfile': installerrs}) + nohome = b'' + cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all' + b' build %(compiler)s --build-base="%(base)s"' + b' install --force --prefix="%(prefix)s"' + b' --install-lib="%(libdir)s"' + b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' + % {b'exe': exe, b'py3': py3, b'pure': pure, + b'compiler': compiler, + b'base': os.path.join(self._hgtmp, b"build"), + b'prefix': self._installdir, b'libdir': self._pythondir, + b'bindir': self._bindir, + b'nohome': nohome, b'logfile': installerrs}) # setuptools requires install directories to exist. def makedirs(p): try: os.makedirs(p) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise makedirs(self._pythondir) @@ -1942,7 +2033,10 @@ else: f = open(installerrs, 'rb') for line in f: - sys.stdout.write(line) + if sys.version_info[0] > 2: + sys.stdout.buffer.write(line) + else: + sys.stdout.write(line) f.close() sys.exit(1) os.chdir(self._testdir) @@ -1960,21 +2054,21 @@ f.write(line + '\n') f.close() - hgbat = os.path.join(self._bindir, 'hg.bat') + hgbat = os.path.join(self._bindir, b'hg.bat') if os.path.isfile(hgbat): # hg.bat expects to be put in bin/scripts while run-tests.py # installation layout put it in bin/ directly. Fix it f = open(hgbat, 'rb') data = f.read() f.close() - if '"%~dp0..\python" "%~dp0hg" %*' in data: - data = data.replace('"%~dp0..\python" "%~dp0hg" %*', - '"%~dp0python" "%~dp0hg" %*') + if b'"%~dp0..\python" "%~dp0hg" %*' in data: + data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*', + b'"%~dp0python" "%~dp0hg" %*') f = open(hgbat, 'wb') f.write(data) f.close() else: - print 'WARNING: cannot fix hg.bat reference to python.exe' + print('WARNING: cannot fix hg.bat reference to python.exe') if self.options.anycoverage: custom = os.path.join(self._testdir, 'sitecustomize.py') @@ -1987,7 +2081,7 @@ covdir = os.path.join(self._installdir, '..', 'coverage') try: os.mkdir(covdir) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise @@ -2001,7 +2095,7 @@ # The pythondir has been inferred from --with-hg flag. # We cannot expect anything sensible here. return - expecthg = os.path.join(self._pythondir, 'mercurial') + expecthg = os.path.join(self._pythondir, b'mercurial') actualhg = self._gethgpath() if os.path.abspath(actualhg) != os.path.abspath(expecthg): sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' @@ -2013,10 +2107,15 @@ if self._hgpath is not None: return self._hgpath - cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"' - pipe = os.popen(cmd % PYTHON) + cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"' + cmd = cmd % PYTHON + if sys.version_info[0] > 2: + cmd = cmd.decode('utf-8') + pipe = os.popen(cmd) try: self._hgpath = pipe.read().strip() + if sys.version_info[0] == 3: + self._hgpath = self._hgpath.encode('utf-8') finally: pipe.close() @@ -2052,7 +2151,13 @@ def _findprogram(self, program): """Search PATH for a executable program""" - for p in os.environ.get('PATH', os.defpath).split(os.pathsep): + if sys.version_info[0] > 2: + dpb = os.defpath.encode('utf-8') + sepb = os.pathsep.encode('utf-8') + else: + dpb = os.defpath + sepb = os.pathsep + for p in osenvironb.get(b'PATH', dpb).split(sepb): name = os.path.join(p, program) if os.name == 'nt' or os.access(name, os.X_OK): return name @@ -2067,7 +2172,7 @@ if found: vlog("# Found prerequisite", p, "at", found) else: - print "WARNING: Did not find prerequisite tool: %s " % p + print("WARNING: Did not find prerequisite tool: %s " % p) if __name__ == '__main__': runner = TestRunner()
--- a/tests/test-bisect3.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-bisect3.t Fri May 15 11:52:09 2015 -0500 @@ -230,3 +230,20 @@ I 2:e1355ee1f23e G 1:ce7c85e06a9f G 0:b4e73ffab476 + + $ hg --config extensions.color= --color=debug log --quiet --style bisect + [log.bisect| ] 14:cbf2f3105bbf + [log.bisect| ] 13:e07efca37c43 + [log.bisect bisect.bad|B] 12:98c6b56349c0 + [log.bisect bisect.bad|B] 11:03f491376e63 + [log.bisect bisect.bad|B] 10:c012b15e2409 + [log.bisect bisect.untested|U] 9:2197c557e14c + [log.bisect bisect.untested|U] 8:e74a86251f58 + [log.bisect bisect.skipped|S] 7:a5f87041c899 + [log.bisect bisect.good|G] 6:7d997bedcd8d + [log.bisect bisect.good|G] 5:2dd1875f1028 + [log.bisect bisect.good|G] 4:2a1daef14cd4 + [log.bisect bisect.ignored|I] 3:8417d459b90c + [log.bisect bisect.ignored|I] 2:e1355ee1f23e + [log.bisect bisect.good|G] 1:ce7c85e06a9f + [log.bisect bisect.good|G] 0:b4e73ffab476
--- a/tests/test-check-code.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-check-code.t Fri May 15 11:52:09 2015 -0500 @@ -104,9 +104,6 @@ ./non-py24.py:6: > z = next(x) no next(foo) in Python 2.4 and 2.5, use foo.next() instead - ./non-py24.py:18: - > try: - no try/except/finally in Python 2.4 ./non-py24.py:35: > try: no yield inside try/finally in Python 2.4
--- a/tests/test-command-template.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-command-template.t Fri May 15 11:52:09 2015 -0500 @@ -95,7 +95,7 @@ 8 Add a commit with empty description, to ensure that the templates -following below omit it properly. +below will omit the description line. $ echo c >> c $ hg add c @@ -108,32 +108,52 @@ $ hg log --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg log -T phases > phases.out - $ diff -u log.out phases.out | grep "phase:" + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +phase: draft + @@ -6,0 +8 @@ +phase: draft + @@ -11,0 +14 @@ +phase: draft + @@ -17,0 +21 @@ +phase: draft + @@ -24,0 +29 @@ +phase: draft + @@ -31,0 +37 @@ +phase: draft + @@ -36,0 +43 @@ +phase: draft + @@ -41,0 +49 @@ +phase: draft + @@ -46,0 +55 @@ +phase: draft + @@ -51,0 +61 @@ +phase: draft $ hg log -v > log.out $ hg log -v --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg log -v -T phases > phases.out - $ diff -u log.out phases.out | grep phase: + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +phase: draft + @@ -7,0 +9 @@ +phase: draft + @@ -15,0 +18 @@ +phase: draft + @@ -24,0 +28 @@ +phase: draft + @@ -33,0 +38 @@ +phase: draft + @@ -43,0 +49 @@ +phase: draft + @@ -50,0 +57 @@ +phase: draft + @@ -58,0 +66 @@ +phase: draft + @@ -66,0 +75 @@ +phase: draft + @@ -77,0 +87 @@ +phase: draft $ hg log -q > log.out @@ -160,32 +180,52 @@ $ hg --color=debug log --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg --color=debug log -T phases > phases.out - $ diff -u log.out phases.out | grep phase: + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +[log.phase|phase: draft] + @@ -6,0 +8 @@ +[log.phase|phase: draft] + @@ -11,0 +14 @@ +[log.phase|phase: draft] + @@ -17,0 +21 @@ +[log.phase|phase: draft] + @@ -24,0 +29 @@ +[log.phase|phase: draft] + @@ -31,0 +37 @@ +[log.phase|phase: draft] + @@ -36,0 +43 @@ +[log.phase|phase: draft] + @@ -41,0 +49 @@ +[log.phase|phase: draft] + @@ -46,0 +55 @@ +[log.phase|phase: draft] + @@ -51,0 +61 @@ +[log.phase|phase: draft] $ hg --color=debug -v log > log.out $ hg --color=debug -v log --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg --color=debug -v log -T phases > phases.out - $ diff -u log.out phases.out | grep phase: + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +[log.phase|phase: draft] + @@ -7,0 +9 @@ +[log.phase|phase: draft] + @@ -15,0 +18 @@ +[log.phase|phase: draft] + @@ -24,0 +28 @@ +[log.phase|phase: draft] + @@ -33,0 +38 @@ +[log.phase|phase: draft] + @@ -43,0 +49 @@ +[log.phase|phase: draft] + @@ -50,0 +57 @@ +[log.phase|phase: draft] + @@ -58,0 +66 @@ +[log.phase|phase: draft] + @@ -66,0 +75 @@ +[log.phase|phase: draft] + @@ -77,0 +87 @@ +[log.phase|phase: draft] $ hg --color=debug -q log > log.out @@ -952,11 +992,11 @@ $ hg log --style notexist abort: style 'notexist' not found - (available styles: bisect, changelog, compact, default, phases, xml) + (available styles: bisect, changelog, compact, default, phases, status, xml) [255] $ hg log -T list - available styles: bisect, changelog, compact, default, phases, xml + available styles: bisect, changelog, compact, default, phases, status, xml abort: specify a template [255] @@ -1898,6 +1938,8 @@ Age filter: + $ hg init unstable-hash + $ cd unstable-hash $ hg log --template '{date|age}\n' > /dev/null || exit 1 >>> from datetime import datetime, timedelta @@ -1911,6 +1953,15 @@ $ hg log -l1 --template '{date|age}\n' 7 years from now + $ cd .. + $ rm -rf unstable-hash + +Add a dummy commit to make up for the instability of the above: + + $ echo a > a + $ hg add a + $ hg ci -m future + Count filter: $ hg log -l1 --template '{node|count} {node|short|count}\n' @@ -1953,6 +2004,476 @@ abort: template filter 'upper' is not compatible with keyword 'date' [255] +Add a commit that does all possible modifications at once + + $ echo modify >> third + $ touch b + $ hg add b + $ hg mv fourth fifth + $ hg rm a + $ hg ci -m "Modify, add, remove, rename" + +Check the status template + + $ cat <<EOF >> $HGRCPATH + > [extensions] + > color= + > EOF + + $ hg log -T status -r 10 + changeset: 10:0f9759ec227a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Modify, add, remove, rename + files: + M third + A b + A fifth + R a + R fourth + + $ hg log -T status -C -r 10 + changeset: 10:0f9759ec227a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Modify, add, remove, rename + files: + M third + A b + A fifth + fourth + R a + R fourth + + $ hg log -T status -C -r 10 -v + changeset: 10:0f9759ec227a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + description: + Modify, add, remove, rename + + files: + M third + A b + A fifth + fourth + R a + R fourth + + $ hg log -T status -C -r 10 --debug + changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c + tag: tip + phase: secret + parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066 + parent: -1:0000000000000000000000000000000000000000 + manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + extra: branch=default + description: + Modify, add, remove, rename + + files: + M third + A b + A fifth + fourth + R a + R fourth + + $ hg log -T status -C -r 10 --quiet + 10:0f9759ec227a + $ hg --color=debug log -T status -r 10 + [log.changeset changeset.secret|changeset: 10:0f9759ec227a] + [log.tag|tag: tip] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [log.summary|summary: Modify, add, remove, rename] + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 + [log.changeset changeset.secret|changeset: 10:0f9759ec227a] + [log.tag|tag: tip] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [log.summary|summary: Modify, add, remove, rename] + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.copied| fourth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 -v + [log.changeset changeset.secret|changeset: 10:0f9759ec227a] + [log.tag|tag: tip] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [ui.note log.description|description:] + [ui.note log.description|Modify, add, remove, rename] + + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.copied| fourth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 --debug + [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c] + [log.tag|tag: tip] + [log.phase|phase: secret] + [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|Modify, add, remove, rename] + + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.copied| fourth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 --quiet + [log.node|10:0f9759ec227a] + +Check the bisect template + + $ hg bisect -g 1 + $ hg bisect -b 3 --noupdate + Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests) + $ hg log -T bisect -r 0:4 + changeset: 0:1e4e1b8f71e0 + bisect: good (implicit) + user: User Name <user@hostname> + date: Mon Jan 12 13:46:40 1970 +0000 + summary: line 1 + + changeset: 1:b608e9d1a3f0 + bisect: good + user: A. N. Other <other@place> + date: Tue Jan 13 17:33:20 1970 +0000 + summary: other 1 + + changeset: 2:97054abb4ab8 + bisect: untested + user: other@place + date: Wed Jan 14 21:20:00 1970 +0000 + summary: no person + + changeset: 3:10e46f2dcbf4 + bisect: bad + user: person + date: Fri Jan 16 01:06:40 1970 +0000 + summary: no user, no domain + + changeset: 4:bbe44766e73d + bisect: bad (implicit) + branch: foo + user: person + date: Sat Jan 17 04:53:20 1970 +0000 + summary: new branch + + $ hg log --debug -T bisect -r 0:4 + changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f + bisect: good (implicit) + phase: public + parent: -1:0000000000000000000000000000000000000000 + parent: -1:0000000000000000000000000000000000000000 + manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 + user: User Name <user@hostname> + date: Mon Jan 12 13:46:40 1970 +0000 + files+: a + extra: branch=default + description: + line 1 + line 2 + + + changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 + bisect: good + phase: public + parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f + parent: -1:0000000000000000000000000000000000000000 + manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55 + user: A. N. Other <other@place> + date: Tue Jan 13 17:33:20 1970 +0000 + files+: b + extra: branch=default + description: + other 1 + other 2 + + other 3 + + + changeset: 2:97054abb4ab824450e9164180baf491ae0078465 + bisect: untested + phase: public + parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 + parent: -1:0000000000000000000000000000000000000000 + manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1 + user: other@place + date: Wed Jan 14 21:20:00 1970 +0000 + files+: c + extra: branch=default + description: + no person + + + changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 + bisect: bad + phase: public + parent: 2:97054abb4ab824450e9164180baf491ae0078465 + parent: -1:0000000000000000000000000000000000000000 + manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc + user: person + date: Fri Jan 16 01:06:40 1970 +0000 + files: c + extra: branch=default + description: + no user, no domain + + + changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74 + bisect: bad (implicit) + branch: foo + phase: draft + parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 + parent: -1:0000000000000000000000000000000000000000 + manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc + user: person + date: Sat Jan 17 04:53:20 1970 +0000 + extra: branch=foo + description: + new branch + + + $ hg log -v -T bisect -r 0:4 + changeset: 0:1e4e1b8f71e0 + bisect: good (implicit) + user: User Name <user@hostname> + date: Mon Jan 12 13:46:40 1970 +0000 + files: a + description: + line 1 + line 2 + + + changeset: 1:b608e9d1a3f0 + bisect: good + user: A. N. Other <other@place> + date: Tue Jan 13 17:33:20 1970 +0000 + files: b + description: + other 1 + other 2 + + other 3 + + + changeset: 2:97054abb4ab8 + bisect: untested + user: other@place + date: Wed Jan 14 21:20:00 1970 +0000 + files: c + description: + no person + + + changeset: 3:10e46f2dcbf4 + bisect: bad + user: person + date: Fri Jan 16 01:06:40 1970 +0000 + files: c + description: + no user, no domain + + + changeset: 4:bbe44766e73d + bisect: bad (implicit) + branch: foo + user: person + date: Sat Jan 17 04:53:20 1970 +0000 + description: + new branch + + + $ hg --color=debug log -T bisect -r 0:4 + [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0] + [log.bisect bisect.good|bisect: good (implicit)] + [log.user|user: User Name <user@hostname>] + [log.date|date: Mon Jan 12 13:46:40 1970 +0000] + [log.summary|summary: line 1] + + [log.changeset changeset.public|changeset: 1:b608e9d1a3f0] + [log.bisect bisect.good|bisect: good] + [log.user|user: A. N. Other <other@place>] + [log.date|date: Tue Jan 13 17:33:20 1970 +0000] + [log.summary|summary: other 1] + + [log.changeset changeset.public|changeset: 2:97054abb4ab8] + [log.bisect bisect.untested|bisect: untested] + [log.user|user: other@place] + [log.date|date: Wed Jan 14 21:20:00 1970 +0000] + [log.summary|summary: no person] + + [log.changeset changeset.public|changeset: 3:10e46f2dcbf4] + [log.bisect bisect.bad|bisect: bad] + [log.user|user: person] + [log.date|date: Fri Jan 16 01:06:40 1970 +0000] + [log.summary|summary: no user, no domain] + + [log.changeset changeset.draft|changeset: 4:bbe44766e73d] + [log.bisect bisect.bad|bisect: bad (implicit)] + [log.branch|branch: foo] + [log.user|user: person] + [log.date|date: Sat Jan 17 04:53:20 1970 +0000] + [log.summary|summary: new branch] + + $ hg --color=debug log --debug -T bisect -r 0:4 + [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f] + [log.bisect bisect.good|bisect: good (implicit)] + [log.phase|phase: public] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0] + [log.user|user: User Name <user@hostname>] + [log.date|date: Mon Jan 12 13:46:40 1970 +0000] + [ui.debug log.files|files+: a] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|line 1 + line 2] + + + [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965] + [log.bisect bisect.good|bisect: good] + [log.phase|phase: public] + [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55] + [log.user|user: A. N. Other <other@place>] + [log.date|date: Tue Jan 13 17:33:20 1970 +0000] + [ui.debug log.files|files+: b] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|other 1 + other 2 + + other 3] + + + [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465] + [log.bisect bisect.untested|bisect: untested] + [log.phase|phase: public] + [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1] + [log.user|user: other@place] + [log.date|date: Wed Jan 14 21:20:00 1970 +0000] + [ui.debug log.files|files+: c] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|no person] + + + [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47] + [log.bisect bisect.bad|bisect: bad] + [log.phase|phase: public] + [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc] + [log.user|user: person] + [log.date|date: Fri Jan 16 01:06:40 1970 +0000] + [ui.debug log.files|files: c] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|no user, no domain] + + + [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74] + [log.bisect bisect.bad|bisect: bad (implicit)] + [log.branch|branch: foo] + [log.phase|phase: draft] + [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc] + [log.user|user: person] + [log.date|date: Sat Jan 17 04:53:20 1970 +0000] + [ui.debug log.extra|extra: branch=foo] + [ui.note log.description|description:] + [ui.note log.description|new branch] + + + $ hg --color=debug log -v -T bisect -r 0:4 + [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0] + [log.bisect bisect.good|bisect: good (implicit)] + [log.user|user: User Name <user@hostname>] + [log.date|date: Mon Jan 12 13:46:40 1970 +0000] + [ui.note log.files|files: a] + [ui.note log.description|description:] + [ui.note log.description|line 1 + line 2] + + + [log.changeset changeset.public|changeset: 1:b608e9d1a3f0] + [log.bisect bisect.good|bisect: good] + [log.user|user: A. N. Other <other@place>] + [log.date|date: Tue Jan 13 17:33:20 1970 +0000] + [ui.note log.files|files: b] + [ui.note log.description|description:] + [ui.note log.description|other 1 + other 2 + + other 3] + + + [log.changeset changeset.public|changeset: 2:97054abb4ab8] + [log.bisect bisect.untested|bisect: untested] + [log.user|user: other@place] + [log.date|date: Wed Jan 14 21:20:00 1970 +0000] + [ui.note log.files|files: c] + [ui.note log.description|description:] + [ui.note log.description|no person] + + + [log.changeset changeset.public|changeset: 3:10e46f2dcbf4] + [log.bisect bisect.bad|bisect: bad] + [log.user|user: person] + [log.date|date: Fri Jan 16 01:06:40 1970 +0000] + [ui.note log.files|files: c] + [ui.note log.description|description:] + [ui.note log.description|no user, no domain] + + + [log.changeset changeset.draft|changeset: 4:bbe44766e73d] + [log.bisect bisect.bad|bisect: bad (implicit)] + [log.branch|branch: foo] + [log.user|user: person] + [log.date|date: Sat Jan 17 04:53:20 1970 +0000] + [ui.note log.description|description:] + [ui.note log.description|new branch] + + + $ hg bisect --reset + Error on syntax: $ echo 'x = "f' >> t @@ -2242,6 +2763,39 @@ hg: parse error: date expects a date information [255] +Test integer literal: + + $ hg log -Ra -r0 -T '{(0)}\n' + 0 + $ hg log -Ra -r0 -T '{(123)}\n' + 123 + $ hg log -Ra -r0 -T '{(-4)}\n' + -4 + $ hg log -Ra -r0 -T '{(-)}\n' + hg: parse error at 2: integer literal without digits + [255] + $ hg log -Ra -r0 -T '{(-a)}\n' + hg: parse error at 2: integer literal without digits + [255] + +top-level integer literal is interpreted as symbol (i.e. variable name): + + $ hg log -Ra -r0 -T '{1}\n' + + $ hg log -Ra -r0 -T '{if("t", "{1}")}\n' + + $ hg log -Ra -r0 -T '{1|stringify}\n' + + +unless explicit symbol is expected: + + $ hg log -Ra -r0 -T '{desc|1}\n' + hg: parse error: expected a symbol, got 'integer' + [255] + $ hg log -Ra -r0 -T '{1()}\n' + hg: parse error: expected a symbol, got 'integer' + [255] + Test string escaping: $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n' @@ -2273,6 +2827,25 @@ <>\n<]> <>\n< +Test exception in quoted template. single backslash before quotation mark is +stripped before parsing: + + $ cat <<'EOF' > escquotetmpl + > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n" + > EOF + $ cd latesttag + $ hg log -r 2 --style ../escquotetmpl + " \" \" \\" head1 + + $ hg log -r 2 -T esc --config templates.esc='{\"invalid\"}\n' + hg: parse error at 1: syntax error + [255] + $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"' + valid + $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'" + valid + $ cd .. + Test leading backslashes: $ cd latesttag @@ -2517,12 +3090,12 @@ 2 bar* foo 1 0 - $ hg log --template "{rev} {currentbookmark}\n" + $ hg log --template "{rev} {activebookmark}\n" 2 bar 1 0 $ hg bookmarks --inactive bar - $ hg log --template "{rev} {currentbookmark}\n" + $ hg log --template "{rev} {activebookmark}\n" 2 1 0 @@ -2547,7 +3120,9 @@ Test splitlines $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}" - @ foo future + @ foo Modify, add, remove, rename + | + o foo future | o foo third | @@ -2581,6 +3156,8 @@ o | o + | + o o |\ @@ -2606,7 +3183,9 @@ Test word function (including index out of bounds graceful failure) $ hg log -Gv -R a --template "{word('1', desc)}" - @ + @ add, + | + o | o | @@ -2630,7 +3209,9 @@ Test word third parameter used as splitter $ hg log -Gv -R a --template "{word('0', desc, 'o')}" - @ future + @ M + | + o future | o third | @@ -2661,8 +3242,13 @@ hg: parse error: word expects two or three arguments, got 7 [255] +Test word for integer literal + + $ hg log -R a --template "{word(2, desc)}\n" -r0 + line + Test word for invalid numbers - $ hg log -Gv -R a --template "{word(2, desc)}" - hg: parse error: Use strings like '3' for numbers passed to word function + $ hg log -Gv -R a --template "{word('a', desc)}" + hg: parse error: word expects an integer index [255]
--- a/tests/test-commit.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-commit.t Fri May 15 11:52:09 2015 -0500 @@ -302,7 +302,7 @@ $ cd commitmsg $ echo changed > changed $ echo removed > removed - $ hg book currentbookmark + $ hg book activebookmark $ hg ci -qAm init $ hg rm removed @@ -317,7 +317,7 @@ HG: -- HG: user: test HG: branch 'default' - HG: bookmark 'currentbookmark' + HG: bookmark 'activebookmark' HG: added added HG: changed changed HG: removed removed @@ -354,7 +354,7 @@ HG: -- HG: user: test HG: branch 'default' - HG: bookmark 'currentbookmark' + HG: bookmark 'activebookmark' HG: subrepo sub HG: added .hgsub HG: added added @@ -376,22 +376,22 @@ > [committemplate] > changeset.commit.normal = HG: this is "commit.normal" template > HG: {extramsg} - > {if(currentbookmark, - > "HG: bookmark '{currentbookmark}' is activated\n", + > {if(activebookmark, + > "HG: bookmark '{activebookmark}' is activated\n", > "HG: no bookmark is activated\n")}{subrepos % > "HG: subrepo '{subrepo}' is changed\n"} > > changeset.commit = HG: this is "commit" template > HG: {extramsg} - > {if(currentbookmark, - > "HG: bookmark '{currentbookmark}' is activated\n", + > {if(activebookmark, + > "HG: bookmark '{activebookmark}' is activated\n", > "HG: no bookmark is activated\n")}{subrepos % > "HG: subrepo '{subrepo}' is changed\n"} > > changeset = HG: this is customized commit template > HG: {extramsg} - > {if(currentbookmark, - > "HG: bookmark '{currentbookmark}' is activated\n", + > {if(activebookmark, + > "HG: bookmark '{activebookmark}' is activated\n", > "HG: no bookmark is activated\n")}{subrepos % > "HG: subrepo '{subrepo}' is changed\n"} > EOF @@ -404,7 +404,7 @@ $ HGEDITOR=cat hg commit -S -q HG: this is "commit.normal" template HG: Leave message empty to abort commit. - HG: bookmark 'currentbookmark' is activated + HG: bookmark 'activebookmark' is activated HG: subrepo 'sub' is changed HG: subrepo 'sub2' is changed abort: empty commit message @@ -416,7 +416,7 @@ > # now, "changeset.commit" should be chosen for "hg commit" > EOF - $ hg bookmark --inactive currentbookmark + $ hg bookmark --inactive activebookmark $ hg forget .hgsub $ HGEDITOR=cat hg commit -q HG: this is "commit" template @@ -544,6 +544,18 @@ 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re) 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re) +Test making empty commits + $ hg commit --config ui.allowemptycommit=True -m "empty commit" + $ hg log -r . -v --stat + changeset: 2:d809f3644287 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + description: + empty commit + + + verify pathauditor blocks evil filepaths $ cat > evil-commit.py <<EOF > from mercurial import ui, hg, context, node @@ -568,7 +580,7 @@ #endif $ hg rollback -f - repository tip rolled back to revision 1 (undo commit) + repository tip rolled back to revision 2 (undo commit) $ cat > evil-commit.py <<EOF > from mercurial import ui, hg, context, node > notrc = "HG~1/hgrc" @@ -586,7 +598,7 @@ [255] $ hg rollback -f - repository tip rolled back to revision 1 (undo commit) + repository tip rolled back to revision 2 (undo commit) $ cat > evil-commit.py <<EOF > from mercurial import ui, hg, context, node > notrc = "HG8B6C~2/hgrc"
--- a/tests/test-globalopts.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-globalopts.t Fri May 15 11:52:09 2015 -0500 @@ -88,6 +88,7 @@ [255] $ hg -R b ann a/a abort: a/a not under root '$TESTTMP/b' (glob) + (consider using '--cwd b') [255] $ hg log abort: no repository found in '$TESTTMP' (.hg not found)!
--- a/tests/test-hybridencode.py Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-hybridencode.py Fri May 15 11:52:09 2015 -0500 @@ -460,3 +460,9 @@ 'VWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxx' 'xxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwww' 'wwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i') + +print "paths outside data/ can be encoded" +show('metadata/dir/00manifest.i') +show('metadata/12345678/12345678/12345678/12345678/12345678/' + '12345678/12345678/12345678/12345678/12345678/12345678/' + '12345678/12345678/00manifest.i')
--- a/tests/test-hybridencode.py.out Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-hybridencode.py.out Fri May 15 11:52:09 2015 -0500 @@ -491,3 +491,10 @@ A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTUVWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxxxxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i' B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx93352aa50377751d9e5ebdf52da1e6e69a6887a6.i' +paths outside data/ can be encoded +A = 'metadata/dir/00manifest.i' +B = 'metadata/dir/00manifest.i' + +A = 'metadata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manifest.i' +B = 'dh/ata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manife0a4da1f89aa2aa9eb0896eb451288419049781b4.i' +
--- a/tests/test-largefiles-misc.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-largefiles-misc.t Fri May 15 11:52:09 2015 -0500 @@ -1017,7 +1017,7 @@ $ hg -R no-largefiles -q pull --rebase Invoking status precommit hook - ? normal3 + M normal3 (test reverting)
--- a/tests/test-log.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-log.t Fri May 15 11:52:09 2015 -0500 @@ -148,7 +148,7 @@ $ hg log -f -l1 --style something abort: style 'something' not found - (available styles: bisect, changelog, compact, default, phases, xml) + (available styles: bisect, changelog, compact, default, phases, status, xml) [255] -f, phases style
--- a/tests/test-manifestv2.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-manifestv2.t Fri May 15 11:52:09 2015 -0500 @@ -1,4 +1,69 @@ -Check that entry is added to .hg/requires +Create repo with old manifest + + $ hg init existing + $ cd existing + $ echo footext > foo + $ hg add foo + $ hg commit -m initial + +We're using v1, so no manifestv2 entry is in requires yet. + + $ grep manifestv2 .hg/requires + [1] + +Let's clone this with manifestv2 enabled to switch to the new format for +future commits. + + $ cd .. + $ hg clone --pull existing new --config experimental.manifestv2=1 + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd new + +Check that entry was added to .hg/requires. + + $ grep manifestv2 .hg/requires + manifestv2 + +Make a new commit. + + $ echo newfootext > foo + $ hg commit -m new + +Check that the manifest actually switched to v2. + + $ hg debugdata -m 0 + foo\x0021e958b1dca695a60ee2e9cf151753204ee0f9e9 (esc) + + $ hg debugdata -m 1 + \x00 (esc) + \x00foo\x00 (esc) + I\xab\x7f\xb8(\x83\xcas\x15\x9d\xc2\xd3\xd3:5\x08\xbad5_ (esc) + +Check that manifestv2 is used if the requirement is present, even if it's +disabled in the config. + + $ echo newerfootext > foo + $ hg --config experimental.manifestv2=False commit -m newer + + $ hg debugdata -m 2 + \x00 (esc) + \x00foo\x00 (esc) + \xa6\xb1\xfb\xef]\x91\xa1\x19`\xf3.#\x90S\xf8\x06 \xe2\x19\x00 (esc) + +Check that we can still read v1 manifests. + + $ hg files -r 0 + foo + + $ cd .. + +Check that entry is added to .hg/requires on repo creation $ hg --config experimental.manifestv2=True init repo $ cd repo
--- a/tests/test-module-imports.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-module-imports.t Fri May 15 11:52:09 2015 -0500 @@ -20,10 +20,7 @@ hidden by deduplication algorithm in the cycle detector, so fixing these may expose other cycles. - $ hg locate 'mercurial/**.py' | sed 's-\\-/-g' | xargs python "$import_checker" - mercurial/crecord.py mixed imports - stdlib: fcntl, termios - relative: curses + $ hg locate 'mercurial/**.py' | sed 's-\\-/-g' | python "$import_checker" - mercurial/dispatch.py mixed imports stdlib: commands relative: error, extensions, fancyopts, hg, hook, util
--- a/tests/test-mq-qpush-fail.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-mq-qpush-fail.t Fri May 15 11:52:09 2015 -0500 @@ -34,7 +34,23 @@ $ $PYTHON -c 'print "\xe9"' > message $ cat .hg/patches/bad-patch >> message $ mv message .hg/patches/bad-patch - $ hg qpush -a && echo 'qpush succeeded?!' + $ cat > $TESTTMP/wrapplayback.py <<EOF + > import os + > from mercurial import extensions, transaction + > def wrapplayback(orig, + > journal, report, opener, vfsmap, entries, backupentries, + > unlink=True): + > orig(journal, report, opener, vfsmap, entries, backupentries, unlink) + > # Touching files truncated at "transaction.abort" causes + > # forcible re-loading invalidated filecache properties + > # (including repo.changelog) + > for f, o, _ignore in entries: + > if o or not unlink: + > os.utime(opener.join(f), (0.0, 0.0)) + > def extsetup(ui): + > extensions.wrapfunction(transaction, '_playback', wrapplayback) + > EOF + $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py && echo 'qpush succeeded?!' applying patch1 applying patch2 applying bad-patch
--- a/tests/test-rebase-scenario-global.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-rebase-scenario-global.t Fri May 15 11:52:09 2015 -0500 @@ -397,7 +397,7 @@ abort: can't remove original changesets with unrebased descendants (use --keep to keep original changesets) [255] - $ hg rebase -r '2::8' -d 1 --keep + $ hg rebase -r '2::8' -d 1 -k rebasing 2:c9e50f6cdc55 "C" rebasing 3:ffd453c31098 "D" rebasing 6:3d8a618087a7 "G"
--- a/tests/test-revset.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-revset.t Fri May 15 11:52:09 2015 -0500 @@ -1,5 +1,27 @@ $ HGENCODING=utf-8 $ export HGENCODING + $ cat > testrevset.py << EOF + > import mercurial.revset + > + > baseset = mercurial.revset.baseset + > + > def r3232(repo, subset, x): + > """"simple revset that return [3,2,3,2] + > + > revisions duplicated on purpose. + > """ + > if 3 not in subset: + > if 2 in subset: + > return baseset([2,2]) + > return baseset() + > return baseset([3,3,2,2]) + > + > mercurial.revset.symbols['r3232'] = r3232 + > EOF + $ cat >> $HGRCPATH << EOF + > [extensions] + > testrevset=$TESTTMP/testrevset.py + > EOF $ try() { > hg debugrevspec --debug "$@" @@ -281,7 +303,7 @@ hg: parse error: date requires a string [255] $ log 'date' - hg: parse error: can't use date here + abort: unknown revision 'date'! [255] $ log 'date(' hg: parse error at 5: not a prefix: end @@ -289,11 +311,40 @@ $ log 'date(tip)' abort: invalid date: 'tip' [255] - $ log '"date"' + $ log '0:date' + abort: unknown revision 'date'! + [255] + $ log '::"date"' abort: unknown revision 'date'! [255] + $ hg book date -r 4 + $ log '0:date' + 0 + 1 + 2 + 3 + 4 + $ log '::date' + 0 + 1 + 2 + 4 + $ log '::"date"' + 0 + 1 + 2 + 4 $ log 'date(2005) and 1::' 4 + $ hg book -d date + +Test that symbols only get parsed as functions if there's an opening +parenthesis. + + $ hg book only -r 9 + $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark + 8 + 9 ancestor can accept 0 or more arguments @@ -311,6 +362,9 @@ 0 $ log 'ancestor(1,2,3,4,5)' 1 + +test ancestors + $ log 'ancestors(5)' 0 1 @@ -318,6 +372,12 @@ 5 $ log 'ancestor(ancestors(5))' 0 + $ log '::r3232()' + 0 + 1 + 2 + 3 + $ log 'author(bob)' 2 $ log 'author("re:bob|test")' @@ -796,6 +856,54 @@ 4 5 +test that `or` operation skips duplicated revisions from right-hand side + + $ try 'reverse(1::5) or ancestors(4)' + (or + (func + ('symbol', 'reverse') + (dagrange + ('symbol', '1') + ('symbol', '5'))) + (func + ('symbol', 'ancestors') + ('symbol', '4'))) + * set: + <addset + <baseset [5, 3, 1]>, + <filteredset + <filteredset + <fullreposet+ 0:9>>>> + 5 + 3 + 1 + 0 + 2 + 4 + $ try 'sort(ancestors(4) or reverse(1::5))' + (func + ('symbol', 'sort') + (or + (func + ('symbol', 'ancestors') + ('symbol', '4')) + (func + ('symbol', 'reverse') + (dagrange + ('symbol', '1') + ('symbol', '5'))))) + * set: + <addset+ + <generatorset+>, + <filteredset + <baseset [5, 3, 1]>>> + 0 + 1 + 2 + 3 + 4 + 5 + check that conversion to only works $ try --optimize '::3 - ::1' (minus
--- a/tests/test-run-tests.py Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-run-tests.py Fri May 15 11:52:09 2015 -0500 @@ -3,6 +3,7 @@ run-test.t only checks positive matches and can not see warnings (both by design) """ +from __future__ import print_function import os, re # this is hack to make sure no escape characters are inserted into the output @@ -11,27 +12,37 @@ import doctest run_tests = __import__('run-tests') +def prn(ex): + m = ex.args[0] + if isinstance(m, str): + print(m) + else: + print(m.decode('utf-8')) + def lm(expected, output): r"""check if output matches expected does it generally work? - >>> lm('H*e (glob)\n', 'Here\n') + >>> lm(b'H*e (glob)\n', b'Here\n') True fail on bad test data - >>> try: lm('a\n','a') - ... except AssertionError, ex: print ex + >>> try: lm(b'a\n',b'a') + ... except AssertionError as ex: print(ex) missing newline - >>> try: lm('single backslash\n', 'single \backslash\n') - ... except AssertionError, ex: print ex + >>> try: lm(b'single backslash\n', b'single \backslash\n') + ... except AssertionError as ex: prn(ex) single backslash or unknown char """ - assert expected.endswith('\n') and output.endswith('\n'), 'missing newline' - assert not re.search(r'[^ \w\\/\r\n()*?]', expected + output), \ - 'single backslash or unknown char' + assert (expected.endswith(b'\n') + and output.endswith(b'\n')), 'missing newline' + assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \ + b'single backslash or unknown char' match = run_tests.TTest.linematch(expected, output) if isinstance(match, str): return 'special: ' + match + elif isinstance(match, bytes): + return 'special: ' + match.decode('utf-8') else: return bool(match) # do not return match object @@ -43,15 +54,15 @@ >>> os.altsep = True valid match on windows - >>> lm('g/a*/d (glob)\n', 'g\\abc/d\n') + >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n') True direct matching, glob unnecessary - >>> lm('g/b (glob)\n', 'g/b\n') + >>> lm(b'g/b (glob)\n', b'g/b\n') 'special: -glob' missing glob - >>> lm('/g/c/d/fg\n', '\\g\\c\\d/fg\n') + >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n') 'special: +glob' restore os.altsep @@ -67,15 +78,15 @@ >>> os.altsep = False backslash does not match slash - >>> lm('h/a* (glob)\n', 'h\\ab\n') + >>> lm(b'h/a* (glob)\n', b'h\\ab\n') False direct matching glob can not be recognized - >>> lm('h/b (glob)\n', 'h/b\n') + >>> lm(b'h/b (glob)\n', b'h/b\n') True missing glob can not not be recognized - >>> lm('/h/c/df/g/\n', '\\h/c\\df/g\\\n') + >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n') False restore os.altsep
--- a/tests/test-run-tests.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-run-tests.t Fri May 15 11:52:09 2015 -0500 @@ -39,8 +39,8 @@ > EOF >>> fh = open('test-failure-unicode.t', 'wb') - >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) - >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) + >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None + >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None $ $TESTDIR/run-tests.py --with-hg=`which hg` @@ -258,8 +258,8 @@ failures in parallel with --first should only print one failure >>> f = open('test-nothing.t', 'w') - >>> f.write('foo\n' * 1024) - >>> f.write(' $ sleep 1') + >>> f.write('foo\n' * 1024) and None + >>> f.write(' $ sleep 1') and None $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 --first --- $TESTTMP/test-failure*.t (glob) @@ -398,8 +398,8 @@ . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. # Producing time report - cuser csys real Test - \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) + start end cuser csys real Test + \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) test for --time with --job enabled ==================================== @@ -408,8 +408,8 @@ . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. # Producing time report - cuser csys real Test - \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) + start end cuser csys real Test + \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) Skips ================ @@ -480,23 +480,75 @@ "test-failure.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) "result": "failure", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) "time": "\s*[\d\.]{4,5}" (re) }, ? (re) "test-skip.t": { "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) "result": "skip", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) "time": "\s*[\d\.]{4,5}" (re) }, ? (re) "test-success.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) "result": "success", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) "time": "\s*[\d\.]{4,5}" (re) } } (no-eol) +Test that failed test accepted through interactive are properly reported: + + $ cp test-failure.t backup + $ echo y | $TESTDIR/run-tests.py --with-hg=`which hg` --json -i + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,4 +1,4 @@ + $ echo babar + - rataxes + + babar + This is a noop statement so that + this test is still more bytes than success. + Accept this change? [n] ..s + Skipped test-skip.t: skipped + # Ran 2 tests, 1 skipped, 0 warned, 0 failed. + + $ cat report.json + testreport ={ + "test-failure.t": [\{] (re) + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) + "result": "success", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + }, ? (re) + "test-skip.t": { + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) + "result": "skip", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + }, ? (re) + "test-success.t": [\{] (re) + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) + "result": "success", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + } + } (no-eol) + $ mv backup test-failure.t + #endif backslash on end of line with glob matching is handled properly
--- a/tests/test-subrepo-deep-nested-change.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-subrepo-deep-nested-change.t Fri May 15 11:52:09 2015 -0500 @@ -309,17 +309,17 @@ Exclude normal files from main and sub-sub repo - $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf.tgz + $ hg --config extensions.largefiles= archive -S -X '**.txt' -p '.' ../archive_lf.tgz $ tar -tzf ../archive_lf.tgz | sort - archive_lf/.hgsub - archive_lf/.hgsubstate - archive_lf/large.bin - archive_lf/main - archive_lf/sub1/.hgsub - archive_lf/sub1/.hgsubstate - archive_lf/sub1/sub1 - archive_lf/sub1/sub2/large.bin - archive_lf/sub1/sub2/sub2 + .hgsub + .hgsubstate + large.bin + main + sub1/.hgsub + sub1/.hgsubstate + sub1/sub1 + sub1/sub2/large.bin + sub1/sub2/sub2 Include normal files from within a largefiles subrepo
--- a/tests/test-subrepo-git.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-subrepo-git.t Fri May 15 11:52:09 2015 -0500 @@ -325,13 +325,13 @@ ../archive_x/s ../archive_x/s/g - $ hg -R ../tc archive -S ../archive.tgz 2>/dev/null - $ tar -tzf ../archive.tgz | sort - archive/.hg_archival.txt - archive/.hgsub - archive/.hgsubstate - archive/a - archive/s/g + $ hg -R ../tc archive -S ../archive.tgz --prefix '.' 2>/dev/null + $ tar -tzf ../archive.tgz | sort | grep -v pax_global_header + .hg_archival.txt + .hgsub + .hgsubstate + a + s/g create nested repo @@ -1105,5 +1105,21 @@ ? s/c.c ? s/cpp.cpp ? s/foobar.orig + $ hg revert --all -q + +make sure we show changed files, rather than changed subtrees + $ mkdir s/foo + $ touch s/foo/bwuh + $ hg add s/foo/bwuh + $ hg commit -S -m "add bwuh" + committing subrepository s + $ hg status -S --change . + M .hgsubstate + A s/foo/bwuh + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + ? s/snake.python.orig $ cd ..
--- a/tests/test-subrepo-recursion.t Thu May 14 21:35:06 2015 -0700 +++ b/tests/test-subrepo-recursion.t Fri May 15 11:52:09 2015 -0500 @@ -312,7 +312,7 @@ Test archiving to zip file (unzip output is unstable): - $ hg archive --subrepos ../archive.zip + $ hg archive --subrepos --prefix '.' ../archive.zip \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) @@ -340,6 +340,23 @@ archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) \r (no-eol) (esc) +(unzip date formating is unstable, we do not care about it and glob it out) + + $ unzip -l ../archive.zip + Archive: ../archive.zip + Length Date Time Name + --------- ---------- ----- ---- + 172 ?????????? 00:00 .hg_archival.txt (glob) + 10 ?????????? 00:00 .hgsub (glob) + 45 ?????????? 00:00 .hgsubstate (glob) + 3 ?????????? 00:00 x.txt (glob) + 10 ?????????? 00:00 foo/.hgsub (glob) + 45 ?????????? 00:00 foo/.hgsubstate (glob) + 9 ?????????? 00:00 foo/y.txt (glob) + 9 ?????????? 00:00 foo/bar/z.txt (glob) + --------- ------- + 303 8 files + Test archiving a revision that references a subrepo that is not yet cloned: @@ -363,7 +380,7 @@ $ cd ../empty #if hardlink - $ hg archive --subrepos -r tip ../archive.tar.gz + $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) @@ -413,7 +430,7 @@ #else Note there's a slight output glitch on non-hardlink systems: the last "linking" progress topic never gets closed, leading to slight output corruption on that platform. - $ hg archive --subrepos -r tip ../archive.tar.gz + $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) @@ -437,14 +454,14 @@ Archive + subrepos uses '/' for all component separators $ tar -tzf ../archive.tar.gz | sort - archive/.hg_archival.txt - archive/.hgsub - archive/.hgsubstate - archive/foo/.hgsub - archive/foo/.hgsubstate - archive/foo/bar/z.txt - archive/foo/y.txt - archive/x.txt + .hg_archival.txt + .hgsub + .hgsubstate + foo/.hgsub + foo/.hgsubstate + foo/bar/z.txt + foo/y.txt + x.txt The newly cloned subrepos contain no working copy:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-treemanifest.t Fri May 15 11:52:09 2015 -0500 @@ -0,0 +1,278 @@ + +Set up repo + + $ hg --config experimental.treemanifest=True init repo + $ cd repo + +Requirements get set on init + + $ grep treemanifest .hg/requires + treemanifest + +Without directories, looks like any other repo + + $ echo 0 > a + $ echo 0 > b + $ hg ci -Aqm initial + $ hg debugdata -m 0 + a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + +Submanifest is stored in separate revlog + + $ mkdir dir1 + $ echo 1 > dir1/a + $ echo 1 > dir1/b + $ echo 1 > e + $ hg ci -Aqm 'add dir1' + $ hg debugdata -m 1 + a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc) + e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) + $ hg debugdata .hg/store/meta/dir1/00manifest.i 0 + a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) + b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) + +Can add nested directories + + $ mkdir dir1/dir1 + $ echo 2 > dir1/dir1/a + $ echo 2 > dir1/dir1/b + $ mkdir dir1/dir2 + $ echo 2 > dir1/dir2/a + $ echo 2 > dir1/dir2/b + $ hg ci -Aqm 'add dir1/dir1' + $ hg files -r . + a + b + dir1/a + dir1/b + dir1/dir1/a + dir1/dir1/b + dir1/dir2/a + dir1/dir2/b + e + +Revision is not created for unchanged directory + + $ mkdir dir2 + $ echo 3 > dir2/a + $ hg add dir2 + adding dir2/a + $ hg debugindex .hg/store/meta/dir1/00manifest.i > before + $ hg ci -qm 'add dir2' + $ hg debugindex .hg/store/meta/dir1/00manifest.i > after + $ diff before after + $ rm before after + +Removing directory does not create an revlog entry + + $ hg rm dir1/dir1 + removing dir1/dir1/a + removing dir1/dir1/b + $ hg debugindex .hg/store/meta/dir1/dir1/00manifest.i > before + $ hg ci -qm 'remove dir1/dir1' + $ hg debugindex .hg/store/meta/dir1/dir1/00manifest.i > after + $ diff before after + $ rm before after + +Check that hg files (calls treemanifest.walk()) works + + $ hg co 'desc("add dir2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg files -r . dir1 + dir1/a + dir1/b + dir1/dir1/a + dir1/dir1/b + dir1/dir2/a + dir1/dir2/b + +Check that status between revisions works (calls treemanifest.matches()) + + $ hg status --rev 'desc("add dir1")' --rev . dir1 + A dir1/dir1/a + A dir1/dir1/b + A dir1/dir2/a + A dir1/dir2/b + +Merge creates 2-parent revision of directory revlog + + $ echo 5 > dir1/a + $ hg ci -Aqm 'modify dir1/a' + $ hg co '.^' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 6 > dir1/b + $ hg ci -Aqm 'modify dir1/b' + $ hg merge 'desc("modify dir1/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'conflict-free merge involving dir1/' + $ cat dir1/a + 5 + $ cat dir1/b + 6 + $ hg debugindex .hg/store/meta/dir1/00manifest.i + rev offset length base linkrev nodeid p1 p2 + 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000 + 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000 + 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000 + 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000 + 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000 + 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72 + +Merge keeping directory from parent 1 does not create revlog entry. (Note that +dir1's manifest does change, but only because dir1/a's filelog changes.) + + $ hg co 'desc("add dir2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 8 > dir2/a + $ hg ci -m 'modify dir2/a' + created new head + + $ hg debugindex .hg/store/meta/dir2/00manifest.i > before + $ hg merge 'desc("modify dir1/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg revert -r 'desc("modify dir2/a")' . + reverting dir1/a (glob) + $ hg ci -m 'merge, keeping parent 1' + $ hg debugindex .hg/store/meta/dir2/00manifest.i > after + $ diff before after + $ rm before after + +Merge keeping directory from parent 2 does not create revlog entry. (Note that +dir2's manifest does change, but only because dir2/a's filelog changes.) + + $ hg co 'desc("modify dir2/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg debugindex .hg/store/meta/dir1/00manifest.i > before + $ hg merge 'desc("modify dir1/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg revert -r 'desc("modify dir1/a")' . + reverting dir2/a (glob) + $ hg ci -m 'merge, keeping parent 2' + created new head + $ hg debugindex .hg/store/meta/dir1/00manifest.i > after + $ diff before after + $ rm before after + +Create flat source repo for tests with mixed flat/tree manifests + + $ cd .. + $ hg init repo-flat + $ cd repo-flat + +Create a few commits with flat manifest + + $ echo 0 > a + $ echo 0 > b + $ echo 0 > e + $ for d in dir1 dir1/dir1 dir1/dir2 dir2 + > do + > mkdir $d + > echo 0 > $d/a + > echo 0 > $d/b + > done + $ hg ci -Aqm initial + + $ echo 1 > a + $ echo 1 > dir1/a + $ echo 1 > dir1/dir1/a + $ hg ci -Aqm 'modify on branch 1' + + $ hg co 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 2 > b + $ echo 2 > dir1/b + $ echo 2 > dir1/dir1/b + $ hg ci -Aqm 'modify on branch 2' + + $ hg merge 1 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'merge of flat manifests to new flat manifest' + +Create clone with tree manifests enabled + + $ cd .. + $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 17 changes to 11 files + updating to branch default + 11 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd repo-mixed + $ test -f .hg/store/meta + [1] + $ grep treemanifest .hg/requires + treemanifest + +Commit should store revlog per directory + + $ hg co 1 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 3 > a + $ echo 3 > dir1/a + $ echo 3 > dir1/dir1/a + $ hg ci -m 'first tree' + created new head + $ find .hg/store/meta | sort + .hg/store/meta + .hg/store/meta/dir1 + .hg/store/meta/dir1/00manifest.i + .hg/store/meta/dir1/dir1 + .hg/store/meta/dir1/dir1/00manifest.i + .hg/store/meta/dir1/dir2 + .hg/store/meta/dir1/dir2/00manifest.i + .hg/store/meta/dir2 + .hg/store/meta/dir2/00manifest.i + +Merge of two trees + + $ hg co 2 + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 1 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'merge of flat manifests to new tree manifest' + created new head + $ hg diff -r 3 + +Parent of tree root manifest should be flat manifest, and two for merge + + $ hg debugindex -m + rev offset length base linkrev nodeid p1 p2 + 0 0 80 0 0 40536115ed9e 000000000000 000000000000 + 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000 + 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000 + 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255 + 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000 + 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255 + + +Status across flat/tree boundary should work + + $ hg status --rev '.^' --rev . + M a + M dir1/a + M dir1/dir1/a + + +Turning off treemanifest config has no effect + + $ hg debugindex .hg/store/meta/dir1/00manifest.i + rev offset length base linkrev nodeid p1 p2 + 0 0 125 0 4 63c9c0557d24 000000000000 000000000000 + 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000 + $ echo 2 > dir1/a + $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a' + $ hg debugindex .hg/store/meta/dir1/00manifest.i + rev offset length base linkrev nodeid p1 p2 + 0 0 125 0 4 63c9c0557d24 000000000000 000000000000 + 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000 + 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000