Mercurial > hg
changeset 25075:d1bd0fd07ee6
merge with stable
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Thu, 14 May 2015 16:28:28 -0500 |
parents | 0021ad4c2309 (diff) bd98d073a34f (current diff) |
children | 14bf7679fb68 |
files | hgext/rebase.py |
diffstat | 85 files changed, 2315 insertions(+), 1017 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Sun May 10 10:57:24 2015 -0400 +++ b/Makefile Thu May 14 16:28:28 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 Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/buildrpm Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/check-code.py Thu May 14 16:28:28 2015 -0500 @@ -238,7 +238,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 +248,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 +280,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 Thu May 14 16:28:28 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 Thu May 14 16:28:28 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 Thu May 14 16:28:28 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 Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/dockerrpm Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/import-checker.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/mercurial.spec Thu May 14 16:28:28 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 Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/revsetbenchmarks.txt Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/wix/guids.wxi Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/contrib/wix/templates.wxs Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/hgext/bugzilla.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/hgext/churn.py Thu May 14 16:28:28 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/hgcia.py Sun May 10 10:57:24 2015 -0400 +++ b/hgext/hgcia.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/hgext/keyword.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/hgext/largefiles/overrides.py Thu May 14 16:28:28 2015 -0500 @@ -985,7 +985,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/mq.py Sun May 10 10:57:24 2015 -0400 +++ b/hgext/mq.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/hgext/notify.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/hgext/rebase.py Thu May 14 16:28:28 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: - 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'))
--- a/hgext/shelve.py Sun May 10 10:57:24 2015 -0400 +++ b/hgext/shelve.py Thu May 14 16:28:28 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('/', '_')
--- a/hgext/strip.py Sun May 10 10:57:24 2015 -0400 +++ b/hgext/strip.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/archival.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/bookmarks.py Thu May 14 16:28:28 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): @@ -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,16 +128,16 @@ 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 + current = repo._activebookmark if current == mark: return @@ -149,42 +148,37 @@ 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 + 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,8 +201,8 @@ check out and where to move the active bookmark from, if needed.''' movemarkfrom = None if checkout is None: - curmark = repo._bookmarkcurrent - if iscurrent(repo): + curmark = repo._activebookmark + if isactivewdirparent(repo): movemarkfrom = repo['.'].node() elif curmark: ui.status(_("updating to active bookmark %s\n") % curmark) @@ -219,7 +213,7 @@ deletefrom = parents marks = repo._bookmarks update = False - cur = repo._bookmarkcurrent + cur = repo._activebookmark if not cur: return False
--- a/mercurial/bundlerepo.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/bundlerepo.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/changegroup.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/changelog.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/cmdutil.py Thu May 14 16:28:28 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 + currentbookmark = 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 = currentbookmark 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/commands.py Thu May 14 16:28:28 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): @@ -4213,7 +4213,7 @@ cmdutil.bailifchanged(repo) base = opts["base"] - wlock = lock = tr = None + wlock = dsguard = lock = tr = None msgs = [] ret = 0 @@ -4221,7 +4221,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 +4262,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 +4700,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 +4717,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 +5047,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 +5912,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 +6403,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/context.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/context.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/crecord.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/dirs.c Thu May 14 16:28:28 2015 -0500 @@ -9,7 +9,6 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> -#include <string.h> #include "util.h" /* @@ -33,19 +32,23 @@ { const char *s = PyString_AS_STRING(path); - const char *ret = strchr(s + pos, '/'); - return (ret != NULL) ? (ret - s) : -1; + while (pos != -1) { + if (s[pos] == '/') + break; + pos -= 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(path, pos - 1)) != -1) { PyObject *val; /* It's likely that every prefix already has an entry @@ -53,18 +56,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 +69,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 +83,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,11 +95,11 @@ static int _delpath(PyObject *dirs, PyObject *path) { - Py_ssize_t pos = -1; + Py_ssize_t pos = PyString_GET_SIZE(path); PyObject *key = NULL; int ret = -1; - while ((pos = _finddir(path, pos + 1)) != -1) { + while ((pos = _finddir(path, pos - 1)) != -1) { PyObject *val; key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos); @@ -126,9 +114,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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/filemerge.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/help/config.txt Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/help/templates.txt Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/hg.py Thu May 14 16:28:28 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/ignore.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/ignore.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/localrepo.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/manifest.c Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/manifest.py Thu May 14 16:28:28 2015 -0500 @@ -760,7 +760,7 @@ 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") @@ -793,7 +793,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):
--- a/mercurial/obsolete.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/obsolete.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/pathutil.py Thu May 14 16:28:28 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/revset.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/revset.py Thu May 14 16:28:28 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, @@ -2947,6 +2944,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 @@ -3047,10 +3102,6 @@ val1 = None val2 = None - - choice = max - if ascending: - choice = min try: # Consume both iterators in an ordered way until one is # empty @@ -3142,7 +3193,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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/scmutil.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/store.py Thu May 14 16:28:28 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/' parts = _auxencode(le, dotencode) basename = parts[-1] _root, ext = os.path.splitext(basename)
--- a/mercurial/subrepo.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/subrepo.py Thu May 14 16:28:28 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/templatefilters.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templatefilters.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templatekw.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templater.py Thu May 14 16:28:28 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. @@ -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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/gitweb/fileannotate.tmpl Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/gitweb/filecomparison.tmpl Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/gitweb/filediff.tmpl Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/gitweb/filerevision.tmpl Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/map-cmdline.bisect Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/map-cmdline.phases Thu May 14 16:28:28 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 Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/spartan/fileannotate.tmpl Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/templates/spartan/filerevision.tmpl Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/util.h Thu May 14 16:28:28 2015 -0500 @@ -101,22 +101,6 @@ #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/windows.py Sun May 10 10:57:24 2015 -0400 +++ b/mercurial/windows.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/setup.py Thu May 14 16:28:28 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): @@ -408,11 +408,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 +484,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 +503,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 +568,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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/heredoctest.py Thu May 14 16:28:28 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/killdaemons.py Sun May 10 10:57:24 2015 -0400 +++ b/tests/killdaemons.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/run-tests.py Thu May 14 16:28:28 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 @@ -1170,18 +1210,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 +1230,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 +1255,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 +1274,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 @@ -1290,14 +1332,16 @@ starttime = test.started endtime = test.stopped - self.times.append((test.name, endtime[2] - starttime[2], - endtime[3] - starttime[3], endtime[4] - starttime[4])) + 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 + )) 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 +1396,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 +1413,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 +1504,91 @@ 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])} + outcome[tc.name] = tres - 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) + jsonout = json.dumps(outcome, sort_keys=True, indent=4) + fp.writelines(("testreport =", jsonout)) + finally: + fp.close() + + 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 @@ -1565,7 +1599,9 @@ 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: + for tdata in times: + test = tdata[0] + cuser, csys, real = tdata[1:4] self.stream.writeln(cols % (cuser, csys, real, test)) class TestRunner(object): @@ -1576,19 +1612,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 +1639,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 +1662,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 +1690,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 +1709,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 +1738,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 +1772,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 +1804,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 +1827,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 +1862,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 +1902,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 +1932,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 +1941,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 +1961,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 +1969,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 +2026,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 +2047,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 +2074,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 +2088,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 +2100,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 +2144,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 +2165,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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-bisect3.t Thu May 14 16:28:28 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-command-template.t Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-command-template.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-commit.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-globalopts.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-hybridencode.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-hybridencode.py.out Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-largefiles-misc.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-log.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-manifestv2.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-module-imports.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-mq-qpush-fail.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-rebase-scenario-global.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-revset.t Thu May 14 16:28:28 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")' @@ -773,6 +833,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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-run-tests.py Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-run-tests.t Thu May 14 16:28:28 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) @@ -497,6 +497,46 @@ } } (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) + "result": "success", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + }, ? (re) + "test-skip.t": { + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "result": "skip", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + }, ? (re) + "test-success.t": [\{] (re) + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "result": "success", ? (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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-subrepo-deep-nested-change.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-subrepo-git.t Thu May 14 16:28:28 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 Sun May 10 10:57:24 2015 -0400 +++ b/tests/test-subrepo-recursion.t Thu May 14 16:28:28 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: