# HG changeset patch # User Matt Mackall # Date 1432936855 18000 # Node ID 7d24a41200d32b48ff63638c9309877c2e187c91 # Parent 724421cb4745000f1a16e4e9d97c6e200ae32c4b# Parent 8594d0b3018ed0eb3d747cbfd068d70bc1bb58a4 merge with stable diff -r 8594d0b3018e -r 7d24a41200d3 Makefile --- a/Makefile Thu May 28 20:30:20 2015 -0700 +++ b/Makefile Fri May 29 17:00:55 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 diff -r 8594d0b3018e -r 7d24a41200d3 contrib/builddeb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/builddeb Fri May 29 17:00:55 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 diff -r 8594d0b3018e -r 7d24a41200d3 contrib/buildrpm --- a/contrib/buildrpm Thu May 28 20:30:20 2015 -0700 +++ b/contrib/buildrpm Fri May 29 17:00:55 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 diff -r 8594d0b3018e -r 7d24a41200d3 contrib/check-code.py --- a/contrib/check-code.py Thu May 28 20:30:20 2015 -0700 +++ b/contrib/check-code.py Fri May 29 17:00:55 2015 -0500 @@ -217,14 +217,6 @@ (r'(\w|\)),\w', "missing whitespace after ,"), (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"), - (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n' - r'((?:\n|\1\s.*\n)+?))+\1finally:', - 'no try/except/finally in Python 2.4'), - (r'(? +Description: Mercurial (probably nightly) package built by upstream. diff -r 8594d0b3018e -r 7d24a41200d3 contrib/docker/debian-jessie --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/docker/debian-jessie Fri May 29 17:00:55 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 diff -r 8594d0b3018e -r 7d24a41200d3 contrib/dockerdeb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerdeb Fri May 29 17:00:55 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" diff -r 8594d0b3018e -r 7d24a41200d3 contrib/dockerlib.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerlib.sh Fri May 29 17:00:55 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 - +} diff -r 8594d0b3018e -r 7d24a41200d3 contrib/dockerrpm --- a/contrib/dockerrpm Thu May 28 20:30:20 2015 -0700 +++ b/contrib/dockerrpm Fri May 29 17:00:55 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 $* diff -r 8594d0b3018e -r 7d24a41200d3 contrib/hg-ssh --- a/contrib/hg-ssh Thu May 28 20:30:20 2015 -0700 +++ b/contrib/hg-ssh Fri May 29 17:00:55 2015 -0500 @@ -64,7 +64,7 @@ if readonly: cmd += [ '--config', - 'hooks.prechangegroup.hg-ssh=python:__main__.rejectpush', + 'hooks.pretxnopen.hg-ssh=python:__main__.rejectpush', '--config', 'hooks.prepushkey.hg-ssh=python:__main__.rejectpush' ] diff -r 8594d0b3018e -r 7d24a41200d3 contrib/import-checker.py --- a/contrib/import-checker.py Thu May 28 20:30:20 2015 -0700 +++ b/contrib/import-checker.py Fri May 29 17:00:55 2015 -0500 @@ -26,6 +26,72 @@ return '.'.join(p for p in parts if p != 'pure') return '.'.join(parts) +def fromlocalfunc(modulename, localmods): + """Get a function to examine which locally defined module the + target source imports via a specified name. + + `modulename` is an `dotted_name_of_path()`-ed source file path, + which may have `.__init__` at the end of it, of the target source. + + `localmods` is a dict (or set), of which key is an absolute + `dotted_name_of_path()`-ed source file path of locally defined (= + Mercurial specific) modules. + + This function assumes that module names not existing in + `localmods` are ones of Python standard libarary. + + This function returns the function, which takes `name` argument, + and returns `(absname, dottedpath, hassubmod)` tuple if `name` + matches against locally defined module. Otherwise, it returns + False. + + It is assumed that `name` doesn't have `.__init__`. + + `absname` is an absolute module name of specified `name` + (e.g. "hgext.convert"). This can be used to compose prefix for sub + modules or so. + + `dottedpath` is a `dotted_name_of_path()`-ed source file path + (e.g. "hgext.convert.__init__") of `name`. This is used to look + module up in `localmods` again. + + `hassubmod` is whether it may have sub modules under it (for + convenient, even though this is also equivalent to "absname != + dottednpath") + + >>> localmods = {'foo.__init__': True, 'foo.foo1': True, + ... 'foo.bar.__init__': True, 'foo.bar.bar1': True, + ... 'baz.__init__': True, 'baz.baz1': True } + >>> fromlocal = fromlocalfunc('foo.xxx', localmods) + >>> # relative + >>> fromlocal('foo1') + ('foo.foo1', 'foo.foo1', False) + >>> fromlocal('bar') + ('foo.bar', 'foo.bar.__init__', True) + >>> fromlocal('bar.bar1') + ('foo.bar.bar1', 'foo.bar.bar1', False) + >>> # absolute + >>> fromlocal('baz') + ('baz', 'baz.__init__', True) + >>> fromlocal('baz.baz1') + ('baz.baz1', 'baz.baz1', False) + >>> # unknown = maybe standard library + >>> fromlocal('os') + False + """ + prefix = '.'.join(modulename.split('.')[:-1]) + if prefix: + prefix += '.' + def fromlocal(name): + # check relative name at first + for n in prefix + name, name: + if n in localmods: + return (n, n, False) + dottedpath = n + '.__init__' + if dottedpath in localmods: + return (n, dottedpath, True) + return False + return fromlocal def list_stdlib_modules(): """List the modules present in the stdlib. @@ -104,38 +170,94 @@ stdlib_modules = set(list_stdlib_modules()) -def imported_modules(source, ignore_nested=False): +def imported_modules(source, modulename, localmods, ignore_nested=False): """Given the source of a file as a string, yield the names imported by that file. Args: source: The python source to examine as a string. + modulename: of specified python source (may have `__init__`) + localmods: dict of locally defined module names (may have `__init__`) ignore_nested: If true, import statements that do not start in column zero will be ignored. Returns: - A list of module names imported by the given source. + A list of absolute module names imported by the given source. + >>> modulename = 'foo.xxx' + >>> localmods = {'foo.__init__': True, + ... 'foo.foo1': True, 'foo.foo2': True, + ... 'foo.bar.__init__': True, 'foo.bar.bar1': True, + ... 'baz.__init__': True, 'baz.baz1': True } + >>> # standard library (= not locally defined ones) + >>> sorted(imported_modules( + ... 'from stdlib1 import foo, bar; import stdlib2', + ... modulename, localmods)) + [] + >>> # relative importing >>> sorted(imported_modules( - ... 'import foo ; from baz import bar; import foo.qux')) - ['baz.bar', 'foo', 'foo.qux'] + ... 'import foo1; from bar import bar1', + ... modulename, localmods)) + ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1'] + >>> sorted(imported_modules( + ... 'from bar.bar1 import name1, name2, name3', + ... modulename, localmods)) + ['foo.bar.bar1'] + >>> # absolute importing + >>> sorted(imported_modules( + ... 'from baz import baz1, name1', + ... modulename, localmods)) + ['baz.__init__', 'baz.baz1'] + >>> # mixed importing, even though it shouldn't be recommended + >>> sorted(imported_modules( + ... 'import stdlib, foo1, baz', + ... modulename, localmods)) + ['baz.__init__', 'foo.foo1'] + >>> # ignore_nested >>> sorted(imported_modules( ... '''import foo ... def wat(): ... import bar - ... ''', ignore_nested=True)) - ['foo'] + ... ''', modulename, localmods)) + ['foo.__init__', 'foo.bar.__init__'] + >>> sorted(imported_modules( + ... '''import foo + ... def wat(): + ... import bar + ... ''', modulename, localmods, ignore_nested=True)) + ['foo.__init__'] """ + fromlocal = fromlocalfunc(modulename, localmods) for node in ast.walk(ast.parse(source)): if ignore_nested and getattr(node, 'col_offset', 0) > 0: continue if isinstance(node, ast.Import): for n in node.names: - yield n.name + found = fromlocal(n.name) + if not found: + # this should import standard library + continue + yield found[1] elif isinstance(node, ast.ImportFrom): - prefix = node.module + '.' + found = fromlocal(node.module) + if not found: + # this should import standard library + continue + + absname, dottedpath, hassubmod = found + yield dottedpath + if not hassubmod: + # examination of "node.names" should be redundant + # e.g.: from mercurial.node import nullid, nullrev + continue + + prefix = absname + '.' for n in node.names: - yield prefix + n.name + found = fromlocal(prefix + n.name) + if not found: + # this should be a function or a property of "node.module" + continue + yield found[1] def verify_stdlib_on_own_line(source): """Given some python source, verify that stdlib imports are done @@ -171,8 +293,6 @@ while visit: path = visit.pop(0) for i in sorted(imports.get(path[-1], [])): - if i not in stdlib_modules and not i.startswith('mercurial.'): - i = mod.rsplit('.', 1)[0] + '.' + i if len(path) < shortest.get(i, 1000): shortest[i] = len(path) if i in path: @@ -194,10 +314,12 @@ def find_cycles(imports): """Find cycles in an already-loaded import graph. - >>> imports = {'top.foo': ['bar', 'os.path', 'qux'], - ... 'top.bar': ['baz', 'sys'], - ... 'top.baz': ['foo'], - ... 'top.qux': ['foo']} + All module names recorded in `imports` should be absolute one. + + >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'], + ... 'top.bar': ['top.baz', 'sys'], + ... 'top.baz': ['top.foo'], + ... 'top.qux': ['top.foo']} >>> print '\\n'.join(sorted(find_cycles(imports))) top.bar -> top.baz -> top.foo -> top.bar top.foo -> top.qux -> top.foo @@ -215,17 +337,23 @@ 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)) + imported_modules(src, modname, localmods, ignore_nested=True)) for error in verify_stdlib_on_own_line(src): any_errors = True print source_path, error diff -r 8594d0b3018e -r 7d24a41200d3 contrib/mercurial.spec --- a/contrib/mercurial.spec Thu May 28 20:30:20 2015 -0700 +++ b/contrib/mercurial.spec Fri May 29 17:00:55 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 diff -r 8594d0b3018e -r 7d24a41200d3 contrib/packagelib.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/packagelib.sh Fri May 29 17:00:55 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 +} diff -r 8594d0b3018e -r 7d24a41200d3 contrib/revsetbenchmarks.txt --- a/contrib/revsetbenchmarks.txt Thu May 28 20:30:20 2015 -0700 +++ b/contrib/revsetbenchmarks.txt Fri May 29 17:00:55 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() diff -r 8594d0b3018e -r 7d24a41200d3 contrib/synthrepo.py --- a/contrib/synthrepo.py Thu May 28 20:30:20 2015 -0700 +++ b/contrib/synthrepo.py Fri May 29 17:00:55 2015 -0500 @@ -41,6 +41,10 @@ from mercurial.i18n import _ from mercurial.node import nullrev, nullid, short +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' cmdtable = {} diff -r 8594d0b3018e -r 7d24a41200d3 contrib/wix/guids.wxi --- a/contrib/wix/guids.wxi Thu May 28 20:30:20 2015 -0700 +++ b/contrib/wix/guids.wxi Fri May 29 17:00:55 2015 -0500 @@ -28,6 +28,7 @@ + diff -r 8594d0b3018e -r 7d24a41200d3 contrib/wix/templates.wxs --- a/contrib/wix/templates.wxs Thu May 28 20:30:20 2015 -0700 +++ b/contrib/wix/templates.wxs Fri May 29 17:00:55 2015 -0500 @@ -12,6 +12,7 @@ + @@ -36,6 +37,13 @@ + + + + + + + diff -r 8594d0b3018e -r 7d24a41200d3 hgext/acl.py --- a/hgext/acl.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/acl.py Fri May 29 17:00:55 2015 -0500 @@ -195,6 +195,10 @@ from mercurial import util, match import getpass, urllib +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' def _getusers(ui, group): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/blackbox.py --- a/hgext/blackbox.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/blackbox.py Fri May 29 17:00:55 2015 -0500 @@ -35,6 +35,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' lastblackbox = None diff -r 8594d0b3018e -r 7d24a41200d3 hgext/bugzilla.py --- a/hgext/bugzilla.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/bugzilla.py Fri May 29 17:00:55 2015 -0500 @@ -279,9 +279,13 @@ 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 +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' class bzaccess(object): @@ -876,8 +880,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() diff -r 8594d0b3018e -r 7d24a41200d3 hgext/censor.py --- a/hgext/censor.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/censor.py Fri May 29 17:00:55 2015 -0500 @@ -31,6 +31,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('censor', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/children.py --- a/hgext/children.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/children.py Fri May 29 17:00:55 2015 -0500 @@ -20,6 +20,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('children', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/churn.py --- a/hgext/churn.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/churn.py Fri May 29 17:00:55 2015 -0500 @@ -9,17 +9,20 @@ '''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 cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. 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) diff -r 8594d0b3018e -r 7d24a41200d3 hgext/color.py --- a/hgext/color.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/color.py Fri May 29 17:00:55 2015 -0500 @@ -84,7 +84,7 @@ resolve.unresolved = red bold resolve.resolved = green bold - bookmarks.current = green + bookmarks.active = green branches.active = none branches.closed = black bold @@ -162,6 +162,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # start and stop parameters for effects @@ -309,7 +313,7 @@ 'grep.filename': 'magenta', 'grep.user': 'magenta', 'grep.date': 'magenta', - 'bookmarks.current': 'green', + 'bookmarks.active': 'green', 'branches.active': 'none', 'branches.closed': 'black bold', 'branches.current': 'green', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/convert/__init__.py --- a/hgext/convert/__init__.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/convert/__init__.py Fri May 29 17:00:55 2015 -0500 @@ -15,6 +15,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # Commands definition was moved elsewhere to ease demandload job. diff -r 8594d0b3018e -r 7d24a41200d3 hgext/convert/filemap.py --- a/hgext/convert/filemap.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/convert/filemap.py Fri May 29 17:00:55 2015 -0500 @@ -332,7 +332,7 @@ mp1 = self.parentmap[p1] if mp1 == SKIPREV or mp1 in knownparents: continue - isancestor = util.any(p2 for p2 in parents + isancestor = any(p2 for p2 in parents if p1 != p2 and mp1 != self.parentmap[p2] and mp1 in self.wantedancestors[p2]) if not isancestor and not hasbranchparent and len(parents) > 1: diff -r 8594d0b3018e -r 7d24a41200d3 hgext/eol.py --- a/hgext/eol.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/eol.py Fri May 29 17:00:55 2015 -0500 @@ -95,6 +95,10 @@ from mercurial import util, config, extensions, match, error import re, os +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # Matches a lone LF, i.e., one that is not part of CRLF. diff -r 8594d0b3018e -r 7d24a41200d3 hgext/extdiff.py --- a/hgext/extdiff.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/extdiff.py Fri May 29 17:00:55 2015 -0500 @@ -67,6 +67,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' def snapshot(ui, repo, files, node, tmproot): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/factotum.py --- a/hgext/factotum.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/factotum.py Fri May 29 17:00:55 2015 -0500 @@ -67,21 +67,20 @@ while True: fd = os.open('%s/rpc' % _mountpoint, os.O_RDWR) try: - try: - os.write(fd, 'start %s' % params) - l = os.read(fd, ERRMAX).split() - if l[0] == 'ok': - os.write(fd, 'read') - status, user, passwd = os.read(fd, ERRMAX).split(None, 2) - if status == 'ok': - if passwd.startswith("'"): - if passwd.endswith("'"): - passwd = passwd[1:-1].replace("''", "'") - else: - raise util.Abort(_('malformed password string')) - return (user, passwd) - except (OSError, IOError): - raise util.Abort(_('factotum not responding')) + os.write(fd, 'start %s' % params) + l = os.read(fd, ERRMAX).split() + if l[0] == 'ok': + os.write(fd, 'read') + status, user, passwd = os.read(fd, ERRMAX).split(None, 2) + if status == 'ok': + if passwd.startswith("'"): + if passwd.endswith("'"): + passwd = passwd[1:-1].replace("''", "'") + else: + raise util.Abort(_('malformed password string')) + return (user, passwd) + except (OSError, IOError): + raise util.Abort(_('factotum not responding')) finally: os.close(fd) getkey(self, params) diff -r 8594d0b3018e -r 7d24a41200d3 hgext/fetch.py --- a/hgext/fetch.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/fetch.py Fri May 29 17:00:55 2015 -0500 @@ -15,6 +15,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('fetch', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/gpg.py --- a/hgext/gpg.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/gpg.py Fri May 29 17:00:55 2015 -0500 @@ -12,6 +12,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' class gpg(object): @@ -255,7 +259,7 @@ if not opts["force"]: msigs = match.exact(repo.root, '', ['.hgsigs']) - if util.any(repo.status(match=msigs, unknown=True, ignored=True)): + if any(repo.status(match=msigs, unknown=True, ignored=True)): raise util.Abort(_("working copy of .hgsigs is changed "), hint=_("please commit .hgsigs manually")) diff -r 8594d0b3018e -r 7d24a41200d3 hgext/graphlog.py --- a/hgext/graphlog.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/graphlog.py Fri May 29 17:00:55 2015 -0500 @@ -20,6 +20,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('glog', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/hgcia.py --- a/hgext/hgcia.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/hgcia.py Fri May 29 17:00:55 2015 -0500 @@ -43,11 +43,15 @@ 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 from xml.sax import saxutils +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' socket_timeout = 30 # seconds @@ -206,7 +210,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 diff -r 8594d0b3018e -r 7d24a41200d3 hgext/hgk.py --- a/hgext/hgk.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/hgk.py Fri May 29 17:00:55 2015 -0500 @@ -41,6 +41,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('debug-diff-tree', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/highlight/__init__.py --- a/hgext/highlight/__init__.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/highlight/__init__.py Fri May 29 17:00:55 2015 -0500 @@ -24,6 +24,10 @@ import highlight from mercurial.hgweb import webcommands, webutil, common from mercurial import extensions, encoding +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' def filerevision_highlight(orig, web, tmpl, fctx): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/histedit.py --- a/hgext/histedit.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/histedit.py Fri May 29 17:00:55 2015 -0500 @@ -181,6 +181,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # i18n: command names and abbreviations must remain untranslated @@ -707,15 +711,15 @@ if force and not outg: raise util.Abort(_('--force only allowed with --outgoing')) if cont: - if util.any((outg, abort, revs, freeargs, rules, editplan)): + if any((outg, abort, revs, freeargs, rules, editplan)): raise util.Abort(_('no arguments allowed with --continue')) goal = 'continue' elif abort: - if util.any((outg, revs, freeargs, rules, editplan)): + if any((outg, revs, freeargs, rules, editplan)): raise util.Abort(_('no arguments allowed with --abort')) goal = 'abort' elif editplan: - if util.any((outg, revs, freeargs)): + if any((outg, revs, freeargs)): raise util.Abort(_('only --commands argument allowed with ' '--edit-plan')) goal = 'edit-plan' diff -r 8594d0b3018e -r 7d24a41200d3 hgext/keyword.py --- a/hgext/keyword.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/keyword.py Fri May 29 17:00:55 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 _ @@ -91,6 +91,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # hg commands that do not act on keywords @@ -191,8 +195,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 +460,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, diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/__init__.py --- a/hgext/largefiles/__init__.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/__init__.py Fri May 29 17:00:55 2015 -0500 @@ -112,6 +112,10 @@ import reposetup import uisetup as uisetupmod +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' reposetup = reposetup.reposetup diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/lfcommands.py --- a/hgext/largefiles/lfcommands.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/lfcommands.py Fri May 29 17:00:55 2015 -0500 @@ -16,6 +16,9 @@ from mercurial.i18n import _ from mercurial.lock import release +from hgext.convert import convcmd +from hgext.convert import filemap + import lfutil import basestore @@ -70,12 +73,6 @@ success = False dstwlock = dstlock = None try: - # Lock destination to prevent modification while it is converted to. - # Don't need to lock src because we are just reading from its history - # which can't change. - dstwlock = rdst.wlock() - dstlock = rdst.lock() - # Get a list of all changesets in the source. The easy way to do this # is to simply walk the changelog, using changelog.nodesbetween(). # Take a look at mercurial/revlog.py:639 for more details. @@ -84,6 +81,12 @@ rsrc.heads())[0]) revmap = {node.nullid: node.nullid} if tolfile: + # Lock destination to prevent modification while it is converted to. + # Don't need to lock src because we are just reading from its + # history which can't change. + dstwlock = rdst.wlock() + dstlock = rdst.lock() + lfiles = set() normalfiles = set() if not pats: @@ -118,74 +121,55 @@ rdst.requirements.add('largefiles') rdst._writerequirements() else: - for ctx in ctxs: - ui.progress(_('converting revisions'), ctx.rev(), - unit=_('revision'), total=rsrc['tip'].rev()) - _addchangeset(ui, rsrc, rdst, ctx, revmap) + class lfsource(filemap.filemap_source): + def __init__(self, ui, source): + super(lfsource, self).__init__(ui, source, None) + self.filemapper.rename[lfutil.shortname] = '.' + + def getfile(self, name, rev): + realname, realrev = rev + f = super(lfsource, self).getfile(name, rev) + + if (not realname.startswith(lfutil.shortnameslash) + or f[0] is None): + return f + + # Substitute in the largefile data for the hash + hash = f[0].strip() + path = lfutil.findfile(rsrc, hash) - ui.progress(_('converting revisions'), None) + if path is None: + raise util.Abort(_("missing largefile for \'%s\' in %s") + % (realname, realrev)) + fp = open(path, 'rb') + + try: + return (fp.read(), f[1]) + finally: + fp.close() + + class converter(convcmd.converter): + def __init__(self, ui, source, dest, revmapfile, opts): + src = lfsource(ui, source) + + super(converter, self).__init__(ui, src, dest, revmapfile, + opts) + + found, missing = downloadlfiles(ui, rsrc) + if missing != 0: + raise util.Abort(_("all largefiles must be present locally")) + + convcmd.converter = converter + convcmd.convert(ui, src, dest) success = True finally: - rdst.dirstate.clear() - release(dstlock, dstwlock) + if tolfile: + rdst.dirstate.clear() + release(dstlock, dstwlock) if not success: # we failed, remove the new directory shutil.rmtree(rdst.root) -def _addchangeset(ui, rsrc, rdst, ctx, revmap): - # Convert src parents to dst parents - parents = _convertparents(ctx, revmap) - - # Generate list of changed files - files = _getchangedfiles(ctx, parents) - - def getfilectx(repo, memctx, f): - if lfutil.standin(f) in files: - # if the file isn't in the manifest then it was removed - # or renamed, raise IOError to indicate this - try: - fctx = ctx.filectx(lfutil.standin(f)) - except error.LookupError: - return None - renamed = fctx.renamed() - if renamed: - renamed = lfutil.splitstandin(renamed[0]) - - hash = fctx.data().strip() - path = lfutil.findfile(rsrc, hash) - - # If one file is missing, likely all files from this rev are - if path is None: - cachelfiles(ui, rsrc, ctx.node()) - path = lfutil.findfile(rsrc, hash) - - if path is None: - raise util.Abort( - _("missing largefile \'%s\' from revision %s") - % (f, node.hex(ctx.node()))) - - data = '' - fd = None - try: - fd = open(path, 'rb') - data = fd.read() - finally: - if fd: - fd.close() - return context.memfilectx(repo, f, data, 'l' in fctx.flags(), - 'x' in fctx.flags(), renamed) - else: - return _getnormalcontext(repo, ctx, f, revmap) - - dstfiles = [] - for file in files: - if lfutil.isstandin(file): - dstfiles.append(lfutil.splitstandin(file)) - else: - dstfiles.append(file) - # Commit - _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap) - def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles, matcher, size, lfiletohash): # Convert src parents to dst parents diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/lfutil.py Fri May 29 17:00:55 2015 -0500 @@ -238,16 +238,18 @@ if path: link(storepath(repo, hash), path) -def getstandinmatcher(repo, pats=[], opts={}): - '''Return a match object that applies pats to the standin directory''' +def getstandinmatcher(repo, rmatcher=None): + '''Return a match object that applies rmatcher to the standin directory''' standindir = repo.wjoin(shortname) - if pats: - pats = [os.path.join(standindir, pat) for pat in pats] + if rmatcher and not rmatcher.always(): + pats = [os.path.join(standindir, pat) for pat in rmatcher.files()] + match = scmutil.match(repo[None], pats) + # if pats is empty, it would incorrectly always match, so clear _always + match._always = False else: # no patterns: relative to repo root - pats = [standindir] + match = scmutil.match(repo[None], [standindir]) # no warnings about missing files or directories - match = scmutil.match(repo[None], pats, opts) match.bad = lambda f, msg: None return match @@ -255,7 +257,7 @@ '''Return a matcher that accepts standins corresponding to the files accepted by rmatcher. Pass the list of files in the matcher as the paths specified by the user.''' - smatcher = getstandinmatcher(repo, rmatcher.files()) + smatcher = getstandinmatcher(repo, rmatcher) isstandin = smatcher.matchfn def composedmatchfn(f): return isstandin(f) and rmatcher.matchfn(splitstandin(f)) @@ -364,10 +366,10 @@ def islfilesrepo(repo): if ('largefiles' in repo.requirements and - util.any(shortnameslash in f[0] for f in repo.store.datafiles())): + any(shortnameslash in f[0] for f in repo.store.datafiles())): return True - return util.any(openlfdirstate(repo.ui, repo, False)) + return any(openlfdirstate(repo.ui, repo, False)) class storeprotonotcapable(Exception): def __init__(self, storetypes): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/overrides.py Fri May 29 17:00:55 2015 -0500 @@ -27,7 +27,7 @@ m = copy.copy(match) lfile = lambda f: lfutil.standin(f) in manifest m._files = filter(lfile, m._files) - m._fmap = set(m._files) + m._fileroots = set(m._files) m._always = False origmatchfn = m.matchfn m.matchfn = lambda f: lfile(f) and origmatchfn(f) @@ -42,7 +42,7 @@ notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in manifest or f in excluded) m._files = filter(notlfile, m._files) - m._fmap = set(m._files) + m._fileroots = set(m._files) m._always = False origmatchfn = m.matchfn m.matchfn = lambda f: notlfile(f) and origmatchfn(f) @@ -358,7 +358,7 @@ and repo.wvfs.isdir(standin): m._files.append(standin) - m._fmap = set(m._files) + m._fileroots = set(m._files) m._always = False origmatchfn = m.matchfn def lfmatchfn(f): @@ -578,14 +578,13 @@ nolfiles = False installnormalfilesmatchfn(repo[None].manifest()) try: - try: - result = orig(ui, repo, pats, opts, rename) - except util.Abort, e: - if str(e) != _('no files to copy'): - raise e - else: - nonormalfiles = True - result = 0 + result = orig(ui, repo, pats, opts, rename) + except util.Abort, e: + if str(e) != _('no files to copy'): + raise e + else: + nonormalfiles = True + result = 0 finally: restorematchfn() @@ -608,86 +607,85 @@ os.makedirs(makestandin(dest)) try: - try: - # When we call orig below it creates the standins but we don't add - # them to the dir state until later so lock during that time. - wlock = repo.wlock() + # When we call orig below it creates the standins but we don't add + # them to the dir state until later so lock during that time. + wlock = repo.wlock() - manifest = repo[None].manifest() - def overridematch(ctx, pats=[], opts={}, globbed=False, - default='relpath'): - newpats = [] - # The patterns were previously mangled to add the standin - # directory; we need to remove that now - for pat in pats: - if match_.patkind(pat) is None and lfutil.shortname in pat: - newpats.append(pat.replace(lfutil.shortname, '')) - else: - newpats.append(pat) - match = oldmatch(ctx, newpats, opts, globbed, default) - m = copy.copy(match) - lfile = lambda f: lfutil.standin(f) in manifest - m._files = [lfutil.standin(f) for f in m._files if lfile(f)] - m._fmap = set(m._files) - origmatchfn = m.matchfn - m.matchfn = lambda f: (lfutil.isstandin(f) and - (f in manifest) and - origmatchfn(lfutil.splitstandin(f)) or - None) - return m - oldmatch = installmatchfn(overridematch) - listpats = [] + manifest = repo[None].manifest() + def overridematch(ctx, pats=[], opts={}, globbed=False, + default='relpath'): + newpats = [] + # The patterns were previously mangled to add the standin + # directory; we need to remove that now for pat in pats: - if match_.patkind(pat) is not None: - listpats.append(pat) + if match_.patkind(pat) is None and lfutil.shortname in pat: + newpats.append(pat.replace(lfutil.shortname, '')) else: - listpats.append(makestandin(pat)) + newpats.append(pat) + match = oldmatch(ctx, newpats, opts, globbed, default) + m = copy.copy(match) + lfile = lambda f: lfutil.standin(f) in manifest + m._files = [lfutil.standin(f) for f in m._files if lfile(f)] + m._fileroots = set(m._files) + origmatchfn = m.matchfn + m.matchfn = lambda f: (lfutil.isstandin(f) and + (f in manifest) and + origmatchfn(lfutil.splitstandin(f)) or + None) + return m + oldmatch = installmatchfn(overridematch) + listpats = [] + for pat in pats: + if match_.patkind(pat) is not None: + listpats.append(pat) + else: + listpats.append(makestandin(pat)) - try: - origcopyfile = util.copyfile - copiedfiles = [] - def overridecopyfile(src, dest): - if (lfutil.shortname in src and - dest.startswith(repo.wjoin(lfutil.shortname))): - destlfile = dest.replace(lfutil.shortname, '') - if not opts['force'] and os.path.exists(destlfile): - raise IOError('', - _('destination largefile already exists')) - copiedfiles.append((src, dest)) - origcopyfile(src, dest) - - util.copyfile = overridecopyfile - result += orig(ui, repo, listpats, opts, rename) - finally: - util.copyfile = origcopyfile - - lfdirstate = lfutil.openlfdirstate(ui, repo) - for (src, dest) in copiedfiles: + try: + origcopyfile = util.copyfile + copiedfiles = [] + def overridecopyfile(src, dest): if (lfutil.shortname in src and dest.startswith(repo.wjoin(lfutil.shortname))): - srclfile = src.replace(repo.wjoin(lfutil.standin('')), '') - destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '') - destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.' - if not os.path.isdir(destlfiledir): - os.makedirs(destlfiledir) - if rename: - os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile)) + destlfile = dest.replace(lfutil.shortname, '') + if not opts['force'] and os.path.exists(destlfile): + raise IOError('', + _('destination largefile already exists')) + copiedfiles.append((src, dest)) + origcopyfile(src, dest) + + util.copyfile = overridecopyfile + result += orig(ui, repo, listpats, opts, rename) + finally: + util.copyfile = origcopyfile - # The file is gone, but this deletes any empty parent - # directories as a side-effect. - util.unlinkpath(repo.wjoin(srclfile), True) - lfdirstate.remove(srclfile) - else: - util.copyfile(repo.wjoin(srclfile), - repo.wjoin(destlfile)) + lfdirstate = lfutil.openlfdirstate(ui, repo) + for (src, dest) in copiedfiles: + if (lfutil.shortname in src and + dest.startswith(repo.wjoin(lfutil.shortname))): + srclfile = src.replace(repo.wjoin(lfutil.standin('')), '') + destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '') + destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.' + if not os.path.isdir(destlfiledir): + os.makedirs(destlfiledir) + if rename: + os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile)) - lfdirstate.add(destlfile) - lfdirstate.write() - except util.Abort, e: - if str(e) != _('no files to copy'): - raise e - else: - nolfiles = True + # The file is gone, but this deletes any empty parent + # directories as a side-effect. + util.unlinkpath(repo.wjoin(srclfile), True) + lfdirstate.remove(srclfile) + else: + util.copyfile(repo.wjoin(srclfile), + repo.wjoin(destlfile)) + + lfdirstate.add(destlfile) + lfdirstate.write() + except util.Abort, e: + if str(e) != _('no files to copy'): + raise e + else: + nolfiles = True finally: restorematchfn() wlock.release() @@ -744,7 +742,7 @@ return f m._files = [tostandin(f) for f in m._files] m._files = [f for f in m._files if f is not None] - m._fmap = set(m._files) + m._fileroots = set(m._files) origmatchfn = m.matchfn def matchfn(f): if lfutil.isstandin(f): @@ -985,7 +983,7 @@ for subpath in sorted(ctx.substate): sub = ctx.sub(subpath) submatch = match_.narrowmatcher(subpath, match) - sub.archive(archiver, os.path.join(prefix, repo._path) + '/', submatch) + sub.archive(archiver, prefix + repo._path + '/', submatch) # If a largefile is modified, the change is not reflected in its # standin until a commit. cmdutil.bailifchanged() raises an exception diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/proto.py Fri May 29 17:00:55 2015 -0500 @@ -31,17 +31,16 @@ tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) try: - try: - proto.getfile(tmpfp) - tmpfp._fp.seek(0) - if sha != lfutil.hexsha1(tmpfp._fp): - raise IOError(0, _('largefile contents do not match hash')) - tmpfp.close() - lfutil.linktousercache(repo, sha) - except IOError, e: - repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % - (sha, e.strerror)) - return wireproto.pushres(1) + proto.getfile(tmpfp) + tmpfp._fp.seek(0) + if sha != lfutil.hexsha1(tmpfp._fp): + raise IOError(0, _('largefile contents do not match hash')) + tmpfp.close() + lfutil.linktousercache(repo, sha) + except IOError, e: + repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % + (sha, e.strerror)) + return wireproto.pushres(1) finally: tmpfp.discard() diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/remotestore.py --- a/hgext/largefiles/remotestore.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/remotestore.py Fri May 29 17:00:55 2015 -0500 @@ -36,13 +36,12 @@ self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash)) fd = None try: - try: - fd = lfutil.httpsendfile(self.ui, filename) - except IOError, e: - raise util.Abort( - _('remotestore: could not open file %s: %s') - % (filename, str(e))) + fd = lfutil.httpsendfile(self.ui, filename) return self._put(hash, fd) + except IOError, e: + raise util.Abort( + _('remotestore: could not open file %s: %s') + % (filename, str(e))) finally: if fd: fd.close() diff -r 8594d0b3018e -r 7d24a41200d3 hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/largefiles/reposetup.py Fri May 29 17:00:55 2015 -0500 @@ -365,7 +365,7 @@ repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook) def checkrequireslfiles(ui, repo, **kwargs): - if 'largefiles' not in repo.requirements and util.any( + if 'largefiles' not in repo.requirements and any( lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()): repo.requirements.add('largefiles') repo._writerequirements() diff -r 8594d0b3018e -r 7d24a41200d3 hgext/mq.py --- a/hgext/mq.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/mq.py Fri May 29 17:00:55 2015 -0500 @@ -76,6 +76,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # force load strip extension formerly included in mq and import some utility @@ -298,7 +302,7 @@ self.haspatch = diffstart > 1 self.plainmode = (plainmode or '# HG changeset patch' not in self.comments and - util.any(c.startswith('Date: ') or + any(c.startswith('Date: ') or c.startswith('From: ') for c in self.comments)) @@ -376,14 +380,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 +814,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 +826,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 +1690,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 +1745,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 diff -r 8594d0b3018e -r 7d24a41200d3 hgext/notify.py --- a/hgext/notify.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/notify.py Fri May 29 17:00:55 2015 -0500 @@ -134,13 +134,14 @@ ''' import email, socket, time -# On python2.4 you have to import this by name or they fail to -# 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 +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # template for single changeset can include email headers. @@ -190,8 +191,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) diff -r 8594d0b3018e -r 7d24a41200d3 hgext/pager.py --- a/hgext/pager.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/pager.py Fri May 29 17:00:55 2015 -0500 @@ -55,40 +55,16 @@ ''' -import atexit, sys, os, signal, subprocess, errno, shlex +import atexit, sys, os, signal, subprocess from mercurial import commands, dispatch, util, extensions, cmdutil from mercurial.i18n import _ +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' -def _pagerfork(ui, p): - if not util.safehasattr(os, 'fork'): - sys.stdout = util.popen(p, 'wb') - if ui._isatty(sys.stderr): - sys.stderr = sys.stdout - return - fdin, fdout = os.pipe() - pid = os.fork() - if pid == 0: - os.close(fdin) - os.dup2(fdout, sys.stdout.fileno()) - if ui._isatty(sys.stderr): - os.dup2(fdout, sys.stderr.fileno()) - os.close(fdout) - return - os.dup2(fdin, sys.stdin.fileno()) - os.close(fdin) - os.close(fdout) - try: - os.execvp('/bin/sh', ['/bin/sh', '-c', p]) - except OSError, e: - if e.errno == errno.ENOENT: - # no /bin/sh, try executing the pager directly - args = shlex.split(p) - os.execvp(args[0], args) - else: - raise - def _pagersubprocess(ui, p): pager = subprocess.Popen(p, shell=True, bufsize=-1, close_fds=util.closefds, stdin=subprocess.PIPE, @@ -110,13 +86,7 @@ pager.wait() def _runpager(ui, p): - # The subprocess module shipped with Python <= 2.4 is buggy (issue3533). - # The compat version is buggy on Windows (issue3225), but has been shipping - # with hg for a long time. Preserve existing functionality. - if sys.version_info >= (2, 5): - _pagersubprocess(ui, p) - else: - _pagerfork(ui, p) + _pagersubprocess(ui, p) def uisetup(ui): if '--debugger' in sys.argv or not ui.formatted(): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/patchbomb.py --- a/hgext/patchbomb.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/patchbomb.py Fri May 29 17:00:55 2015 -0500 @@ -59,10 +59,6 @@ import os, errno, socket, tempfile, cStringIO import email -# On python2.4 you have to import these by name or they fail to -# load. This was not a problem on Python 2.7. -import email.Generator -import email.MIMEMultipart from mercurial import cmdutil, commands, hg, mail, patch, util from mercurial import scmutil @@ -71,6 +67,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' def prompt(ui, prompt, default=None, rest=':'): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/progress.py --- a/hgext/progress.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/progress.py Fri May 29 17:00:55 2015 -0500 @@ -40,6 +40,10 @@ import threading from mercurial.i18n import _ +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' from mercurial import encoding diff -r 8594d0b3018e -r 7d24a41200d3 hgext/purge.py --- a/hgext/purge.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/purge.py Fri May 29 17:00:55 2015 -0500 @@ -30,6 +30,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('purge|clean', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/rebase.py --- a/hgext/rebase.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/rebase.py Fri May 29 17:00:55 2015 -0500 @@ -29,6 +29,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' def _savegraft(ctx, extra): @@ -67,7 +71,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 +362,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 +502,7 @@ if (activebookmark and repo['.'].node() == repo._bookmarks[activebookmark]): - bookmarks.setcurrent(repo, activebookmark) + bookmarks.activate(repo, activebookmark) finally: release(lock, wlock) @@ -530,10 +534,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 +555,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 +895,7 @@ repair.strip(repo.ui, repo, strippoints) if activebookmark and activebookmark in repo._bookmarks: - bookmarks.setcurrent(repo, activebookmark) + bookmarks.activate(repo, activebookmark) clearstatus(repo) repo.ui.warn(_('rebase aborted\n')) @@ -1057,7 +1059,7 @@ hg.update(repo, dest) if bookmarks.update(repo, [movemarkfrom], repo['.'].node()): ui.status(_("updating bookmark %s\n") - % repo._bookmarkcurrent) + % repo._activebookmark) else: if opts.get('tool'): raise util.Abort(_('--tool can only be used with --rebase')) @@ -1118,4 +1120,3 @@ _("use 'hg rebase --continue' or 'hg rebase --abort'")]) # ensure rebased rev are not hidden extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible) - diff -r 8594d0b3018e -r 7d24a41200d3 hgext/record.py --- a/hgext/record.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/record.py Fri May 29 17:00:55 2015 -0500 @@ -13,6 +13,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @@ -50,7 +54,12 @@ This command is not available when committing a merge.''' opts["interactive"] = True - commands.commit(ui, repo, *pats, **opts) + backup = ui.backupconfig('experimental', 'crecord') + try: + ui.setconfig('experimental', 'crecord', False, 'record') + commands.commit(ui, repo, *pats, **opts) + finally: + ui.restoreconfig(backup) def qrefresh(origfn, ui, repo, *pats, **opts): if not opts['interactive']: @@ -92,8 +101,13 @@ opts['checkname'] = False mq.new(ui, repo, patch, *pats, **opts) - cmdutil.dorecord(ui, repo, committomq, 'qnew', False, - cmdutil.recordfilter, *pats, **opts) + backup = ui.backupconfig('experimental', 'crecord') + try: + ui.setconfig('experimental', 'crecord', False, 'record') + cmdutil.dorecord(ui, repo, committomq, 'qnew', False, + cmdutil.recordfilter, *pats, **opts) + finally: + ui.restoreconfig(backup) def qnew(origfn, ui, repo, patch, *args, **opts): if opts['interactive']: diff -r 8594d0b3018e -r 7d24a41200d3 hgext/relink.py --- a/hgext/relink.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/relink.py Fri May 29 17:00:55 2015 -0500 @@ -13,6 +13,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('relink', [], _('[ORIGIN]')) diff -r 8594d0b3018e -r 7d24a41200d3 hgext/schemes.py --- a/hgext/schemes.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/schemes.py Fri May 29 17:00:55 2015 -0500 @@ -44,6 +44,10 @@ from mercurial import extensions, hg, templater, util from mercurial.i18n import _ +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' diff -r 8594d0b3018e -r 7d24a41200d3 hgext/share.py --- a/hgext/share.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/share.py Fri May 29 17:00:55 2015 -0500 @@ -12,6 +12,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' @command('share', diff -r 8594d0b3018e -r 7d24a41200d3 hgext/shelve.py --- a/hgext/shelve.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/shelve.py Fri May 29 17:00:55 2015 -0500 @@ -21,6 +21,7 @@ shelve". """ +import collections from mercurial.i18n import _ from mercurial.node import nullid, nullrev, bin, hex from mercurial import changegroup, cmdutil, scmutil, phases, commands @@ -32,6 +33,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' class shelvedfile(object): @@ -143,7 +148,7 @@ Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) - visit = util.deque() + visit = collections.deque() visit.append(ctx) while visit: ctx = visit.popleft() @@ -163,7 +168,7 @@ # we never need the user, so we use a generic user for all shelve operations user = 'shelve@localhost' - label = repo._bookmarkcurrent or parent.branch() or 'default' + label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') @@ -284,17 +289,15 @@ """subcommand that deletes a specific shelve""" if not pats: raise util.Abort(_('no shelved changes specified!')) - wlock = None + wlock = repo.wlock() try: - wlock = repo.wlock() - try: - for name in pats: - for suffix in 'hg patch'.split(): - shelvedfile(repo, name, suffix).unlink() - except OSError, err: - if err.errno != errno.ENOENT: - raise - raise util.Abort(_("shelved change '%s' not found") % name) + for name in pats: + for suffix in 'hg patch'.split(): + shelvedfile(repo, name, suffix).unlink() + except OSError, err: + if err.errno != errno.ENOENT: + raise + raise util.Abort(_("shelved change '%s' not found") % name) finally: lockmod.release(wlock) @@ -363,6 +366,17 @@ finally: fp.close() +def singlepatchcmds(ui, repo, pats, opts, subcommand): + """subcommand that displays a single shelf""" + if len(pats) != 1: + raise util.Abort(_("--%s expects a single shelf") % subcommand) + shelfname = pats[0] + + if not shelvedfile(repo, shelfname, 'patch').exists(): + raise util.Abort(_("cannot find shelf %s") % shelfname) + + listcmd(ui, repo, pats, opts) + def checkparents(repo, state): """check parent while resuming an unshelve""" if state.parents != repo.dirstate.parents(): @@ -658,8 +672,7 @@ ('p', 'patch', None, _('show patch')), ('i', 'interactive', None, - _('interactive mode, only works while creating a shelve' - '(EXPERIMENTAL)')), + _('interactive mode, only works while creating a shelve')), ('', 'stat', None, _('output diffstat-style summary of changes'))] + commands.walkopts, _('hg shelve [OPTION]... [FILE]...')) @@ -693,21 +706,21 @@ cmdutil.checkunfinished(repo) allowables = [ - ('addremove', 'create'), # 'create' is pseudo action - ('cleanup', 'cleanup'), -# ('date', 'create'), # ignored for passing '--date "0 0"' in tests - ('delete', 'delete'), - ('edit', 'create'), - ('list', 'list'), - ('message', 'create'), - ('name', 'create'), - ('patch', 'list'), - ('stat', 'list'), + ('addremove', set(['create'])), # 'create' is pseudo action + ('cleanup', set(['cleanup'])), +# ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests + ('delete', set(['delete'])), + ('edit', set(['create'])), + ('list', set(['list'])), + ('message', set(['create'])), + ('name', set(['create'])), + ('patch', set(['patch', 'list'])), + ('stat', set(['stat', 'list'])), ] def checkopt(opt): if opts[opt]: for i, allowable in allowables: - if opts[i] and opt != allowable: + if opts[i] and opt not in allowable: raise util.Abort(_("options '--%s' and '--%s' may not be " "used together") % (opt, i)) return True @@ -719,11 +732,11 @@ return deletecmd(ui, repo, pats) elif checkopt('list'): return listcmd(ui, repo, pats, opts) + elif checkopt('patch'): + return singlepatchcmds(ui, repo, pats, opts, subcommand='patch') + elif checkopt('stat'): + return singlepatchcmds(ui, repo, pats, opts, subcommand='stat') else: - for i in ('patch', 'stat'): - if opts[i]: - raise util.Abort(_("option '--%s' may not be " - "used when shelving a change") % (i,)) return createcmd(ui, repo, pats, opts) def extsetup(ui): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/strip.py --- a/hgext/strip.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/strip.py Fri May 29 17:00:55 2015 -0500 @@ -11,6 +11,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' def checksubstate(repo, baserev=None): @@ -60,8 +64,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) diff -r 8594d0b3018e -r 7d24a41200d3 hgext/transplant.py --- a/hgext/transplant.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/transplant.py Fri May 29 17:00:55 2015 -0500 @@ -26,6 +26,10 @@ cmdtable = {} command = cmdutil.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' class transplantentry(object): diff -r 8594d0b3018e -r 7d24a41200d3 hgext/win32mbcs.py --- a/hgext/win32mbcs.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/win32mbcs.py Fri May 29 17:00:55 2015 -0500 @@ -48,6 +48,10 @@ import os, sys from mercurial.i18n import _ from mercurial import util, encoding +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' _encoding = None # see extsetup diff -r 8594d0b3018e -r 7d24a41200d3 hgext/win32text.py --- a/hgext/win32text.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/win32text.py Fri May 29 17:00:55 2015 -0500 @@ -46,6 +46,10 @@ from mercurial import util import re +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # regexp for single LF without CR preceding. diff -r 8594d0b3018e -r 7d24a41200d3 hgext/zeroconf/__init__.py --- a/hgext/zeroconf/__init__.py Thu May 28 20:30:20 2015 -0700 +++ b/hgext/zeroconf/__init__.py Fri May 29 17:00:55 2015 -0500 @@ -31,6 +31,10 @@ from mercurial import extensions from mercurial.hgweb import server as servermod +# Note for extension authors: ONLY specify testedwith = 'internal' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. testedwith = 'internal' # publish diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/ancestor.py --- a/mercurial/ancestor.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/ancestor.py Fri May 29 17:00:55 2015 -0500 @@ -5,8 +5,8 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import collections import heapq -import util from node import nullrev def commonancestorsheads(pfunc, *nodes): @@ -314,7 +314,7 @@ parentrevs = self._parentrevs stoprev = self._stoprev - visit = util.deque(revs) + visit = collections.deque(revs) while visit: for parent in parentrevs(visit.popleft()): diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/archival.py --- a/mercurial/archival.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/archival.py Fri May 29 17:00:55 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 @@ -50,7 +54,7 @@ def guesskind(dest): for kind, extensions in exts.iteritems(): - if util.any(dest.endswith(ext) for ext in extensions): + if any(dest.endswith(ext) for ext in extensions): return kind return None diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/bookmarks.py --- a/mercurial/bookmarks.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/bookmarks.py Fri May 29 17:00:55 2015 -0500 @@ -8,7 +8,7 @@ import os from mercurial.i18n import _ from mercurial.node import hex, bin -from mercurial import encoding, error, util, obsolete, lock as lockmod +from mercurial import encoding, util, obsolete, lock as lockmod import errno class bmstore(dict): @@ -22,7 +22,7 @@ {hash}\s{name}\n (the same format as localtags) in .hg/bookmarks. The mapping is stored as {name: nodeid}. - This class does NOT handle the "current" bookmark state at this + This class does NOT handle the "active" bookmark state at this time. """ @@ -83,8 +83,8 @@ def _writerepo(self, repo): """Factored out for extensibility""" - if repo._bookmarkcurrent not in self: - unsetcurrent(repo) + if repo._activebookmark not in self: + deactivate(repo) wlock = repo.wlock() try: @@ -106,13 +106,12 @@ for name, node in self.iteritems(): fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name))) -def readcurrent(repo): - '''Get the current bookmark - - If we use gittish branches we have a current bookmark that - we are on. This function returns the name of the bookmark. It - is stored in .hg/bookmarks.current - ''' +def readactive(repo): + """ + Get the active bookmark. We can have an active bookmark that updates + itself as we commit. This function returns the name of that bookmark. + It is stored in .hg/bookmarks.current + """ mark = None try: file = repo.vfs('bookmarks.current') @@ -129,17 +128,17 @@ file.close() return mark -def setcurrent(repo, mark): - '''Set the name of the bookmark that we are currently on - - Set the name of the bookmark that we are on (hg update ). +def activate(repo, mark): + """ + Set the given bookmark to be 'active', meaning that this bookmark will + follow new commits that are made. The name is recorded in .hg/bookmarks.current - ''' + """ if mark not in repo._bookmarks: raise AssertionError('bookmark %s does not exist!' % mark) - current = repo._bookmarkcurrent - if current == mark: + active = repo._activebookmark + if active == mark: return wlock = repo.wlock() @@ -149,42 +148,36 @@ file.close() finally: wlock.release() - repo._bookmarkcurrent = mark + repo._activebookmark = mark -def unsetcurrent(repo): +def deactivate(repo): + """ + Unset the active bookmark in this reposiotry. + """ wlock = repo.wlock() try: - try: - repo.vfs.unlink('bookmarks.current') - repo._bookmarkcurrent = None - except OSError, inst: - if inst.errno != errno.ENOENT: - raise + repo.vfs.unlink('bookmarks.current') + repo._activebookmark = None + except OSError, inst: + if inst.errno != errno.ENOENT: + raise finally: wlock.release() -def iscurrent(repo, mark=None, parents=None): - '''Tell whether the current bookmark is also active +def isactivewdirparent(repo): + """ + Tell whether the 'active' bookmark (the one that follows new commits) + points to one of the parents of the current working directory (wdir). - I.e., the bookmark listed in .hg/bookmarks.current also points to a - parent of the working directory. - ''' - if not mark: - mark = repo._bookmarkcurrent - if not parents: - parents = [p.node() for p in repo[None].parents()] + While this is normally the case, it can on occasion be false; for example, + immediately after a pull, the active bookmark can be moved to point + to a place different than the wdir. This is solved by running `hg update`. + """ + mark = repo._activebookmark marks = repo._bookmarks + parents = [p.node() for p in repo[None].parents()] return (mark in marks and marks[mark] in parents) -def updatecurrentbookmark(repo, oldnode, curbranch): - try: - return update(repo, oldnode, repo.branchtip(curbranch)) - except error.RepoLookupError: - if curbranch == "default": # no default branch! - return update(repo, oldnode, repo.lookup("tip")) - else: - raise util.Abort(_("branch %s not found") % curbranch) - def deletedivergent(repo, deletefrom, bm): '''Delete divergent versions of bm on nodes in deletefrom. @@ -207,33 +200,33 @@ check out and where to move the active bookmark from, if needed.''' movemarkfrom = None if checkout is None: - curmark = repo._bookmarkcurrent - if iscurrent(repo): + activemark = repo._activebookmark + if isactivewdirparent(repo): movemarkfrom = repo['.'].node() - elif curmark: - ui.status(_("updating to active bookmark %s\n") % curmark) - checkout = curmark + elif activemark: + ui.status(_("updating to active bookmark %s\n") % activemark) + checkout = activemark return (checkout, movemarkfrom) def update(repo, parents, node): deletefrom = parents marks = repo._bookmarks update = False - cur = repo._bookmarkcurrent - if not cur: + active = repo._activebookmark + if not active: return False - if marks[cur] in parents: + if marks[active] in parents: new = repo[node] divs = [repo[b] for b in marks - if b.split('@', 1)[0] == cur.split('@', 1)[0]] + if b.split('@', 1)[0] == active.split('@', 1)[0]] anc = repo.changelog.ancestors([new.rev()]) deletefrom = [b.node() for b in divs if b.rev() in anc or b == new] - if validdest(repo, repo[marks[cur]], new): - marks[cur] = new.node() + if validdest(repo, repo[marks[active]], new): + marks[active] = new.node() update = True - if deletedivergent(repo, deletefrom, cur): + if deletedivergent(repo, deletefrom, active): update = True if update: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/bundle2.py --- a/mercurial/bundle2.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/bundle2.py Fri May 29 17:00:55 2015 -0500 @@ -173,6 +173,16 @@ _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]') +def outdebug(ui, message): + """debug regarding output stream (bundling)""" + if ui.configbool('devel', 'bundle2.debug', False): + ui.debug('bundle2-output: %s\n' % message) + +def indebug(ui, message): + """debug on input stream (unbundling)""" + if ui.configbool('devel', 'bundle2.debug', False): + ui.debug('bundle2-input: %s\n' % message) + def validateparttype(parttype): """raise ValueError if a parttype contains invalid character""" if _parttypeforbidden.search(parttype): @@ -310,13 +320,24 @@ # - replace this is a init function soon. # - exception catching unbundler.params - iterparts = unbundler.iterparts() + if repo.ui.debugflag: + msg = ['bundle2-input-bundle:'] + if unbundler.params: + msg.append(' %i params') + if op.gettransaction is None: + msg.append(' no-transaction') + else: + msg.append(' with-transaction') + msg.append('\n') + repo.ui.debug(''.join(msg)) + iterparts = enumerate(unbundler.iterparts()) part = None + nbpart = 0 try: - for part in iterparts: + for nbpart, part in iterparts: _processpart(op, part) - except Exception, exc: - for part in iterparts: + except BaseException, exc: + for nbpart, part in iterparts: # consume the bundle content part.seek(0, 2) # Small hack to let caller code distinguish exceptions from bundle2 @@ -330,6 +351,9 @@ salvaged = op.reply.salvageoutput() exc._bundle2salvagedoutput = salvaged raise + finally: + repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart) + return op def _processpart(op, part): @@ -337,23 +361,43 @@ The part is guaranteed to have been fully consumed when the function exits (even if an exception is raised).""" + status = 'unknown' # used by debug output try: try: handler = parthandlermapping.get(part.type) if handler is None: + status = 'unsupported-type' raise error.UnsupportedPartError(parttype=part.type) - op.ui.debug('found a handler for part %r\n' % part.type) + indebug(op.ui, 'found a handler for part %r' % part.type) unknownparams = part.mandatorykeys - handler.params if unknownparams: unknownparams = list(unknownparams) unknownparams.sort() + status = 'unsupported-params (%s)' % unknownparams raise error.UnsupportedPartError(parttype=part.type, params=unknownparams) + status = 'supported' except error.UnsupportedPartError, exc: if part.mandatory: # mandatory parts raise - op.ui.debug('ignoring unsupported advisory part %s\n' % exc) + indebug(op.ui, 'ignoring unsupported advisory part %s' % exc) return # skip to part processing + finally: + if op.ui.debugflag: + msg = ['bundle2-input-part: "%s"' % part.type] + if not part.mandatory: + msg.append(' (advisory)') + nbmp = len(part.mandatorykeys) + nbap = len(part.params) - nbmp + if nbmp or nbap: + msg.append(' (params:') + if nbmp: + msg.append(' %i mandatory' % nbmp) + if nbap: + msg.append(' %i advisory' % nbmp) + msg.append(')') + msg.append(' %s\n' % status) + op.ui.debug(''.join(msg)) # handler is called outside the above try block so that we don't # risk catching KeyErrors from anything other than the @@ -464,20 +508,26 @@ # methods used to generate the bundle2 stream def getchunks(self): - self.ui.debug('start emission of %s stream\n' % self._magicstring) + if self.ui.debugflag: + msg = ['bundle2-output-bundle: "%s",' % self._magicstring] + if self._params: + msg.append(' (%i params)' % len(self._params)) + msg.append(' %i parts total\n' % len(self._parts)) + self.ui.debug(''.join(msg)) + outdebug(self.ui, 'start emission of %s stream' % self._magicstring) yield self._magicstring param = self._paramchunk() - self.ui.debug('bundle parameter: %s\n' % param) + outdebug(self.ui, 'bundle parameter: %s' % param) yield _pack(_fstreamparamsize, len(param)) if param: yield param - self.ui.debug('start of parts\n') + outdebug(self.ui, 'start of parts') for part in self._parts: - self.ui.debug('bundle part: "%s"\n' % part.type) - for chunk in part.getchunks(): + outdebug(self.ui, 'bundle part: "%s"' % part.type) + for chunk in part.getchunks(ui=self.ui): yield chunk - self.ui.debug('end of bundle\n') + outdebug(self.ui, 'end of bundle') yield _pack(_fpartheadersize, 0) def _paramchunk(self): @@ -555,7 +605,7 @@ if unbundlerclass is None: raise util.Abort(_('unknown bundle version %s') % version) unbundler = unbundlerclass(ui, fp) - ui.debug('start processing of %s stream\n' % header) + indebug(ui, 'start processing of %s stream' % header) return unbundler class unbundle20(unpackermixin): @@ -572,7 +622,7 @@ @util.propertycache def params(self): """dictionary of stream level parameters""" - self.ui.debug('reading bundle2 stream parameters\n') + indebug(self.ui, 'reading bundle2 stream parameters') params = {} paramssize = self._unpack(_fstreamparamsize)[0] if paramssize < 0: @@ -605,7 +655,7 @@ # Some logic will be later added here to try to process the option for # a dict of known parameter. if name[0].islower(): - self.ui.debug("ignoring unknown parameter %r\n" % name) + indebug(self.ui, "ignoring unknown parameter %r" % name) else: raise error.UnsupportedPartError(params=(name,)) @@ -614,14 +664,14 @@ """yield all parts contained in the stream""" # make sure param have been loaded self.params - self.ui.debug('start extraction of bundle2 parts\n') + indebug(self.ui, 'start extraction of bundle2 parts') headerblock = self._readpartheader() while headerblock is not None: part = unbundlepart(self.ui, headerblock, self._fp) yield part part.seek(0, 2) headerblock = self._readpartheader() - self.ui.debug('end of bundle2 stream\n') + indebug(self.ui, 'end of bundle2 stream') def _readpartheader(self): """reads a part header size and return the bytes blob @@ -631,7 +681,7 @@ if headersize < 0: raise error.BundleValueError('negative part header size: %i' % headersize) - self.ui.debug('part header size: %i\n' % headersize) + indebug(self.ui, 'part header size: %i' % headersize) if headersize: return self._readexact(headersize) return None @@ -718,15 +768,39 @@ params.append((name, value)) # methods used to generates the bundle2 stream - def getchunks(self): + def getchunks(self, ui): if self._generated is not None: raise RuntimeError('part can only be consumed once') self._generated = False + + if ui.debugflag: + msg = ['bundle2-output-part: "%s"' % self.type] + if not self.mandatory: + msg.append(' (advisory)') + nbmp = len(self.mandatoryparams) + nbap = len(self.advisoryparams) + if nbmp or nbap: + msg.append(' (params:') + if nbmp: + msg.append(' %i mandatory' % nbmp) + if nbap: + msg.append(' %i advisory' % nbmp) + msg.append(')') + if not self.data: + msg.append(' empty payload') + elif util.safehasattr(self.data, 'next'): + msg.append(' streamed payload') + else: + msg.append(' %i bytes payload' % len(self.data)) + msg.append('\n') + ui.debug(''.join(msg)) + #### header if self.mandatory: parttype = self.type.upper() else: parttype = self.type.lower() + outdebug(ui, 'part %s: "%s"' % (self.id, parttype)) ## parttype header = [_pack(_fparttypesize, len(parttype)), parttype, _pack(_fpartid, self.id), @@ -755,27 +829,33 @@ header.append(value) ## finalize header headerchunk = ''.join(header) + outdebug(ui, 'header chunk size: %i' % len(headerchunk)) yield _pack(_fpartheadersize, len(headerchunk)) yield headerchunk ## payload try: for chunk in self._payloadchunks(): + outdebug(ui, 'payload chunk size: %i' % len(chunk)) yield _pack(_fpayloadsize, len(chunk)) yield chunk - except Exception, exc: + except BaseException, exc: # backup exception data for later + ui.debug('bundle2-input-stream-interrupt: encoding exception %s' + % exc) exc_info = sys.exc_info() msg = 'unexpected error: %s' % exc interpart = bundlepart('error:abort', [('message', msg)], mandatory=False) interpart.id = 0 yield _pack(_fpayloadsize, -1) - for chunk in interpart.getchunks(): + for chunk in interpart.getchunks(ui=ui): yield chunk + outdebug(ui, 'closing payload chunk') # abort current part payload yield _pack(_fpayloadsize, 0) raise exc_info[0], exc_info[1], exc_info[2] # end of payload + outdebug(ui, 'closing payload chunk') yield _pack(_fpayloadsize, 0) self._generated = True @@ -817,20 +897,25 @@ if headersize < 0: raise error.BundleValueError('negative part header size: %i' % headersize) - self.ui.debug('part header size: %i\n' % headersize) + indebug(self.ui, 'part header size: %i\n' % headersize) if headersize: return self._readexact(headersize) return None def __call__(self): - self.ui.debug('bundle2 stream interruption, looking for a part.\n') + + self.ui.debug('bundle2-input-stream-interrupt:' + ' opening out of band context\n') + indebug(self.ui, 'bundle2 stream interruption, looking for a part.') headerblock = self._readpartheader() if headerblock is None: - self.ui.debug('no part found during interruption.\n') + indebug(self.ui, 'no part found during interruption.') return part = unbundlepart(self.ui, headerblock, self._fp) op = interruptoperation(self.ui) _processpart(op, part) + self.ui.debug('bundle2-input-stream-interrupt:' + ' closing out of band context\n') class interruptoperation(object): """A limited operation to be use by part handler during interruption @@ -910,7 +995,7 @@ pos = self._chunkindex[chunknum][0] payloadsize = self._unpack(_fpayloadsize)[0] - self.ui.debug('payload chunk size: %i\n' % payloadsize) + indebug(self.ui, 'payload chunk size: %i' % payloadsize) while payloadsize: if payloadsize == flaginterrupt: # interruption detection, the handler will now read a @@ -928,7 +1013,7 @@ super(unbundlepart, self).tell())) yield result payloadsize = self._unpack(_fpayloadsize)[0] - self.ui.debug('payload chunk size: %i\n' % payloadsize) + indebug(self.ui, 'payload chunk size: %i' % payloadsize) def _findchunk(self, pos): '''for a given payload position, return a chunk number and offset''' @@ -943,16 +1028,16 @@ """read the header and setup the object""" typesize = self._unpackheader(_fparttypesize)[0] self.type = self._fromheader(typesize) - self.ui.debug('part type: "%s"\n' % self.type) + indebug(self.ui, 'part type: "%s"' % self.type) self.id = self._unpackheader(_fpartid)[0] - self.ui.debug('part id: "%s"\n' % self.id) + indebug(self.ui, 'part id: "%s"' % self.id) # extract mandatory bit from type self.mandatory = (self.type != self.type.lower()) self.type = self.type.lower() ## reading parameters # param count mancount, advcount = self._unpackheader(_fpartparamcount) - self.ui.debug('part parameters: %i\n' % (mancount + advcount)) + indebug(self.ui, 'part parameters: %i' % (mancount + advcount)) # param size fparamsizes = _makefpartparamsizes(mancount + advcount) paramsizes = self._unpackheader(fparamsizes) @@ -982,9 +1067,12 @@ data = self._payloadstream.read() else: data = self._payloadstream.read(size) + self._pos += len(data) if size is None or len(data) < size: + if not self.consumed and self._pos: + self.ui.debug('bundle2-input-part: total payload size %i\n' + % self._pos) self.consumed = True - self._pos += len(data) return data def tell(self): @@ -1015,6 +1103,8 @@ raise util.Abort(_('Seek failed\n')) self._pos = newpos +# These are only the static capabilities. +# Check the 'getrepocaps' function for the rest. capabilities = {'HG20': (), 'listkeys': (), 'pushkey': (), diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/bundlerepo.py Fri May 29 17:00:55 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) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/changegroup.py --- a/mercurial/changegroup.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/changegroup.py Fri May 29 17:00:55 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) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/changelog.py --- a/mercurial/changelog.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/changelog.py Fri May 29 17:00:55 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: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/cmdutil.py Fri May 29 17:00:55 2015 -0500 @@ -17,6 +17,18 @@ import crecord as crecordmod import lock as lockmod +def ishunk(x): + hunkclasses = (crecordmod.uihunk, patch.recordhunk) + return isinstance(x, hunkclasses) + +def newandmodified(chunks, originalchunks): + newlyaddedandmodifiedfiles = set() + for chunk in chunks: + if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \ + originalchunks: + newlyaddedandmodifiedfiles.add(chunk.header.filename()) + return newlyaddedandmodifiedfiles + def parsealiases(cmd): return cmd.lstrip("^").split("|") @@ -33,7 +45,7 @@ setattr(ui, 'write', wrap) return oldwrite -def filterchunks(ui, originalhunks, usecurses, testfile): +def filterchunks(ui, originalhunks, usecurses, testfile, operation=None): if usecurses: if testfile: recordfn = crecordmod.testdecorator(testfile, @@ -41,17 +53,24 @@ else: recordfn = crecordmod.chunkselector - return crecordmod.filterpatch(ui, originalhunks, recordfn) + return crecordmod.filterpatch(ui, originalhunks, recordfn, operation) else: - return patch.filterpatch(ui, originalhunks) - -def recordfilter(ui, originalhunks): + return patch.filterpatch(ui, originalhunks, operation) + +def recordfilter(ui, originalhunks, operation=None): + """ Prompts the user to filter the originalhunks and return a list of + selected hunks. + *operation* is used for ui purposes to indicate the user + what kind of filtering they are doing: reverting, commiting, shelving, etc. + *operation* has to be a translated string. + """ usecurses = ui.configbool('experimental', 'crecord', False) testfile = ui.config('experimental', 'crecordtest', None) oldwrite = setupwrapcolorwrite(ui) try: - newchunks = filterchunks(ui, originalhunks, usecurses, testfile) + newchunks = filterchunks(ui, originalhunks, usecurses, testfile, + operation) finally: ui.write = oldwrite return newchunks @@ -59,8 +78,6 @@ def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts): import merge as mergemod - hunkclasses = (crecordmod.uihunk, patch.recordhunk) - ishunk = lambda x: isinstance(x, hunkclasses) if not ui.interactive(): raise util.Abort(_('running non-interactively, use %s instead') % @@ -107,11 +124,7 @@ # We need to keep a backup of files that have been newly added and # modified during the recording process because there is a previous # version without the edit in the workdir - newlyaddedandmodifiedfiles = set() - for chunk in chunks: - if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \ - originalchunks: - newlyaddedandmodifiedfiles.add(chunk.header.filename()) + newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks) contenders = set() for h in chunks: try: @@ -450,14 +463,17 @@ """opens the changelog, manifest, a filelog or a given revlog""" cl = opts['changelog'] mf = opts['manifest'] + dir = opts['dir'] msg = None if cl and mf: msg = _('cannot specify --changelog and --manifest at the same time') + elif cl and dir: + msg = _('cannot specify --changelog and --dir at the same time') elif cl or mf: if file_: msg = _('cannot specify filename with --changelog or --manifest') elif not repo: - msg = _('cannot specify --changelog or --manifest ' + msg = _('cannot specify --changelog or --manifest or --dir ' 'without a repository') if msg: raise util.Abort(msg) @@ -466,6 +482,13 @@ if repo: if cl: r = repo.unfiltered().changelog + elif dir: + if 'treemanifest' not in repo.requirements: + raise util.Abort(_("--dir can only be used on repos with " + "treemanifest enabled")) + dirlog = repo.dirlog(file_) + if len(dirlog): + r = dirlog elif mf: r = repo.manifest elif file_: @@ -818,6 +841,7 @@ msg = _('applied to working directory') rejects = False + dsguard = None try: cmdline_message = logmessage(ui, opts) @@ -859,7 +883,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 +920,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 +967,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 +1474,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 +1508,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': @@ -1731,7 +1762,8 @@ if not revs: return [] wanted = set() - slowpath = match.anypats() or (match.files() and opts.get('removed')) + slowpath = match.anypats() or ((match.isexact() or match.prefix()) and + opts.get('removed')) fncache = {} change = repo.changectx @@ -1743,8 +1775,7 @@ if match.always(): # No files, no patterns. Display all revs. wanted = revs - - if not slowpath and match.files(): + elif not slowpath: # We only have to read through the filelog to find wanted revisions try: @@ -1812,7 +1843,7 @@ # Now that wanted is correctly initialized, we can iterate over the # revision range, yielding only revisions in wanted. def iterate(): - if follow and not match.files(): + if follow and match.always(): ff = _followfilter(repo, onlyfirst=opts.get('follow_first')) def want(rev): return ff.match(rev) and rev in wanted @@ -1825,13 +1856,12 @@ for windowsize in increasingwindows(): nrevs = [] for i in xrange(windowsize): - try: - rev = it.next() - if want(rev): - nrevs.append(rev) - except (StopIteration): + rev = next(it, None) + if rev is None: stopiteration = True break + elif want(rev): + nrevs.append(rev) for rev in sorted(nrevs): fns = fncache.get(rev) ctx = change(rev) @@ -1916,10 +1946,7 @@ # --follow with FILE behaviour depends on revs... it = iter(revs) startrev = it.next() - try: - followdescendants = startrev < it.next() - except (StopIteration): - followdescendants = False + followdescendants = startrev < next(it, startrev) # branch and only_branch are really aliases and must be handled at # the same time @@ -1931,7 +1958,8 @@ # platforms without shell expansion (windows). wctx = repo[None] match, pats = scmutil.matchandpats(wctx, pats, opts) - slowpath = match.anypats() or (match.files() and opts.get('removed')) + slowpath = match.anypats() or ((match.isexact() or match.prefix()) and + opts.get('removed')) if not slowpath: for f in match.files(): if follow and f not in wctx: @@ -2113,15 +2141,11 @@ if not opts.get('rev'): revs.sort(reverse=True) if limit is not None: - count = 0 limitedrevs = [] - it = iter(revs) - while count < limit: - try: - limitedrevs.append(it.next()) - except (StopIteration): + for idx, r in enumerate(revs): + if limit <= idx: break - count += 1 + limitedrevs.append(r) revs = revset.baseset(limitedrevs) return revs, expr, filematcher @@ -2287,12 +2311,16 @@ fm.write('path', fmt, m.rel(f)) ret = 0 - if subrepos: - for subpath in sorted(ctx.substate): + for subpath in sorted(ctx.substate): + def matchessubrepo(subpath): + return (m.always() or m.exact(subpath) + or any(f.startswith(subpath + '/') for f in m.files())) + + if subrepos or matchessubrepo(subpath): sub = ctx.sub(subpath) try: submatch = matchmod.narrowmatcher(subpath, m) - if sub.printfiles(ui, submatch, fm, fmt) == 0: + if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0: ret = 0 except error.LookupError: ui.status(_("skipping missing subrepository: %s\n") @@ -2336,7 +2364,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 +2492,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 +2509,13 @@ # First, do a regular commit to record all changes in the working # directory (if there are any) ui.callhooks = False - currentbookmark = repo._bookmarkcurrent + activebookmark = repo._activebookmark try: - repo._bookmarkcurrent = None + repo._activebookmark = None opts['message'] = 'temporary amend commit for %s' % old node = commit(ui, repo, commitfunc, pats, opts) finally: - repo._bookmarkcurrent = currentbookmark + repo._activebookmark = activebookmark ui.callhooks = True ctx = repo[node] @@ -2637,6 +2666,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 +2675,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 +2749,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]) @@ -3103,17 +3131,22 @@ else: normal = repo.dirstate.normal + newlyaddedandmodifiedfiles = set() if interactive: # Prompt the user for changes to revert torevert = [repo.wjoin(f) for f in actions['revert'][0]] m = scmutil.match(ctx, torevert, {}) - diff = patch.diff(repo, None, ctx.node(), m) + diffopts = patch.difffeatureopts(repo.ui, whitespace=True) + diffopts.nodates = True + diffopts.git = True + diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts) originalchunks = patch.parsepatch(diff) try: chunks = recordfilter(repo.ui, originalchunks) except patch.PatchError, err: raise util.Abort(_('error parsing patch: %s') % err) + newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks) # Apply changes fp = cStringIO.StringIO() for c in chunks: @@ -3137,8 +3170,10 @@ repo.dirstate.normallookup(f) for f in actions['add'][0]: - checkout(f) - repo.dirstate.add(f) + # Don't checkout modified files, they are already created by the diff + if f not in newlyaddedandmodifiedfiles: + checkout(f) + repo.dirstate.add(f) normal = repo.dirstate.normallookup if node == parent and p2 == nullid: @@ -3259,3 +3294,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() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/commands.py --- a/mercurial/commands.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/commands.py Fri May 29 17:00:55 2015 -0500 @@ -40,6 +40,12 @@ # @command decorator. inferrepo = '' +# label constants +# until 3.5, bookmarks.current was the advertised name, not +# bookmarks.active, so we must use both to avoid breaking old +# custom styles +activebookmarklabel = 'bookmarks.active bookmarks.current' + # common command options globalopts = [ @@ -979,8 +985,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 +1000,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 +1011,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 +1020,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,9 +1041,9 @@ if len(marks) == 0 and not fm: ui.status(_("no bookmarks set\n")) for bmark, n in sorted(marks.iteritems()): - current = repo._bookmarkcurrent - if bmark == current: - prefix, label = '*', 'bookmarks.current' + active = repo._activebookmark + if bmark == active: + prefix, label = '*', activebookmarklabel else: prefix, label = ' ', '' @@ -1048,7 +1054,7 @@ pad = " " * (25 - encoding.colwidth(bmark)) fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s', repo.changelog.rev(n), hexfn(n), label=label) - fm.data(active=(bmark == current)) + fm.data(active=(bmark == active)) fm.plain('\n') fm.end() @@ -1080,7 +1086,9 @@ change. Use the command :hg:`update` to switch to an existing branch. Use - :hg:`commit --close-branch` to mark this branch as closed. + :hg:`commit --close-branch` to mark this branch head as closed. + When all heads of the branch are closed, the branch will be + considered closed. Returns 0 on success. """ @@ -1107,8 +1115,13 @@ scmutil.checknewlabel(repo, label, 'branch') repo.dirstate.setbranch(label) ui.status(_('marked working directory as branch %s\n') % label) - ui.status(_('(branches are permanent and global, ' - 'did you want a bookmark?)\n')) + + # find any open named branches aside from default + others = [n for n, h, t, c in repo.branchmap().iterbranches() + if n != "default" and not c] + if not others: + ui.status(_('(branches are permanent and global, ' + 'did you want a bookmark?)\n')) finally: wlock.release() @@ -1413,7 +1426,7 @@ [('A', 'addremove', None, _('mark new/missing files as added/removed before committing')), ('', 'close-branch', None, - _('mark a branch as closed, hiding it from the branch list')), + _('mark a branch head as closed')), ('', 'amend', None, _('amend the parent of the working directory')), ('s', 'secret', None, _('use the secret phase for committing')), ('e', 'edit', None, _('invoke editor on commit messages')), @@ -1439,6 +1452,10 @@ commit fails, you will find a backup of your message in ``.hg/last-message.txt``. + The --close-branch flag can be used to mark the current branch + head closed. When all heads of a branch are closed, the branch + will be considered closed and no longer listed. + The --amend flag can be used to amend the parent of the working directory with a new commit that contains the changes in the parent in addition to those currently reported by :hg:`status`, @@ -1506,7 +1523,7 @@ match, extra=extra) - current = repo._bookmarkcurrent + active = repo._activebookmark marks = old.bookmarks() node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts) if node == old.node(): @@ -1518,8 +1535,8 @@ newmarks = repo._bookmarks for bm in marks: newmarks[bm] = node - if bm == current: - bookmarks.setcurrent(repo, bm) + if bm == active: + bookmarks.activate(repo, bm) newmarks.write() else: def commitfunc(ui, repo, message, match, opts): @@ -2056,7 +2073,8 @@ @command('debugdata', [('c', 'changelog', False, _('open changelog')), - ('m', 'manifest', False, _('open manifest'))], + ('m', 'manifest', False, _('open manifest')), + ('', 'dir', False, _('open directory manifest'))], _('-c|-m|FILE REV')) def debugdata(ui, repo, file_, rev=None, **opts): """dump the contents of a data file revision""" @@ -2163,8 +2181,8 @@ '''parse and apply a fileset specification''' ctx = scmutil.revsingle(repo, opts.get('rev'), None) if ui.verbose: - tree = fileset.parse(expr)[0] - ui.note(tree, "\n") + tree = fileset.parse(expr) + ui.note(fileset.prettyformat(tree), "\n") for f in ctx.getfileset(expr): ui.write("%s\n" % f) @@ -2227,6 +2245,7 @@ @command('debugindex', [('c', 'changelog', False, _('open changelog')), ('m', 'manifest', False, _('open manifest')), + ('', 'dir', False, _('open directory manifest')), ('f', 'format', 0, _('revlog format'), _('FORMAT'))], _('[-f FORMAT] -c|-m|FILE'), optionalrepo=True) @@ -2545,26 +2564,25 @@ try: tr = repo.transaction('debugobsolete') try: - try: - date = opts.get('date') - if date: - date = util.parsedate(date) - else: - date = None - prec = parsenodeid(precursor) - parents = None - if opts['record_parents']: - if prec not in repo.unfiltered(): - raise util.Abort('cannot used --record-parents on ' - 'unknown changesets') - parents = repo.unfiltered()[prec].parents() - parents = tuple(p.node() for p in parents) - repo.obsstore.create(tr, prec, succs, opts['flags'], - parents=parents, date=date, - metadata=metadata) - tr.close() - except ValueError, exc: - raise util.Abort(_('bad obsmarker input: %s') % exc) + date = opts.get('date') + if date: + date = util.parsedate(date) + else: + date = None + prec = parsenodeid(precursor) + parents = None + if opts['record_parents']: + if prec not in repo.unfiltered(): + raise util.Abort('cannot used --record-parents on ' + 'unknown changesets') + parents = repo.unfiltered()[prec].parents() + parents = tuple(p.node() for p in parents) + repo.obsstore.create(tr, prec, succs, opts['flags'], + parents=parents, date=date, + metadata=metadata) + tr.close() + except ValueError, exc: + raise util.Abort(_('bad obsmarker input: %s') % exc) finally: tr.release() finally: @@ -2730,6 +2748,7 @@ @command('debugrevlog', [('c', 'changelog', False, _('open changelog')), ('m', 'manifest', False, _('open manifest')), + ('', 'dir', False, _('open directory manifest')), ('d', 'dump', False, _('dump index data'))], _('-c|-m|FILE'), optionalrepo=True) @@ -2916,7 +2935,7 @@ expansion. """ if ui.verbose: - tree = revset.parse(expr)[0] + tree = revset.parse(expr) ui.note(revset.prettyformat(tree), "\n") newtree = revset.findaliases(ui, tree) if newtree != tree: @@ -4045,8 +4064,8 @@ parents = ctx.parents() changed = "" if default or id or num: - if (util.any(repo.status()) - or util.any(ctx.sub(s).dirty() for s in ctx.substate)): + if (any(repo.status()) + or any(ctx.sub(s).dirty() for s in ctx.substate)): changed = '+' if default or id: output = ["%s%s" % @@ -4213,7 +4232,7 @@ cmdutil.bailifchanged(repo) base = opts["base"] - wlock = lock = tr = None + wlock = dsguard = lock = tr = None msgs = [] ret = 0 @@ -4221,7 +4240,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 +4281,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 +4719,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 +4736,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()] @@ -4952,11 +4969,11 @@ ('f', 'force', False, _('allow to move boundary backward')), ('r', 'rev', [], _('target revision'), _('REV')), ], - _('[-p|-d|-s] [-f] [-r] REV...')) + _('[-p|-d|-s] [-f] [-r] [REV...]')) def phase(ui, repo, *revs, **opts): """set or show the current phase name - With no argument, show the phase name of specified revisions. + With no argument, show the phase name of the current revision(s). With one of -p/--public, -d/--draft or -s/--secret, change the phase value of the specified revisions. @@ -4981,7 +4998,9 @@ revs = list(revs) revs.extend(opts['rev']) if not revs: - raise util.Abort(_('no revisions specified')) + # display both parents as the second parent phase can influence + # the phase of a merge commit + revs = [c.rev() for c in repo[None].parents()] revs = scmutil.revrange(repo, revs) @@ -5049,7 +5068,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()) @@ -5520,7 +5539,7 @@ hint = _("uncommitted merge, use --all to discard all changes," " or 'hg update -C .' to abort the merge") raise util.Abort(msg, hint=hint) - dirty = util.any(repo.status()) + dirty = any(repo.status()) node = ctx.node() if node != parent: if dirty: @@ -5872,7 +5891,7 @@ """summarize working directory state This generates a brief summary of the working directory state, - including parents, branch, commit status, and available updates. + including parents, branch, commit status, phase and available updates. With the --remote option, this will check the default paths for incoming and outgoing changes. This can be time-consuming. @@ -5914,15 +5933,15 @@ ui.status(m, label='log.branch') if marks: - current = repo._bookmarkcurrent + active = repo._activebookmark # i18n: column positioning for "hg summary" ui.write(_('bookmarks:'), label='log.bookmark') - if current is not None: - if current in marks: - ui.write(' *' + current, label='bookmarks.current') - marks.remove(current) + if active is not None: + if active in marks: + ui.write(' *' + active, label=activebookmarklabel) + marks.remove(active) else: - ui.write(' [%s]' % current, label='bookmarks.current') + ui.write(' [%s]' % active, label=activebookmarklabel) for m in marks: ui.write(' ' + m, label='log.bookmark') ui.write('\n', label='log.bookmark') @@ -6000,6 +6019,25 @@ ui.write(_('update: %d new changesets, %d branch heads (merge)\n') % (new, len(bheads))) + t = [] + draft = len(repo.revs('draft()')) + if draft: + t.append(_('%d draft') % draft) + secret = len(repo.revs('secret()')) + if secret: + t.append(_('%d secret') % secret) + + if parents: + parentphase = max(p.phase() for p in parents) + else: + parentphase = phases.public + + if draft or secret: + ui.status(_('phases: %s (%s)\n') % (', '.join(t), + phases.phasenames[parentphase])) + else: + ui.note(_('phases: (%s)\n') % phases.phasenames[parentphase]) + cmdutil.summaryhooks(ui, repo) if opts.get('remote'): @@ -6323,7 +6361,7 @@ Update the repository's working directory to the specified changeset. If no changeset is specified, update to the tip of the - current named branch and move the current bookmark (see :hg:`help + current named branch and move the active bookmark (see :hg:`help bookmarks`). Update sets the working directory's parent revision to the specified @@ -6376,7 +6414,7 @@ cmdutil.clearunfinished(repo) - # with no argument, we also move the current bookmark, if any + # with no argument, we also move the active bookmark, if any rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev) # if we defined a bookmark, we have to remember the original bookmark name @@ -6405,15 +6443,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 diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/config.py --- a/mercurial/config.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/config.py Fri May 29 17:00:55 2015 -0500 @@ -10,10 +10,11 @@ import os, errno class config(object): - def __init__(self, data=None): + def __init__(self, data=None, includepaths=[]): self._data = {} self._source = {} self._unset = [] + self._includepaths = includepaths if data: for k in data._data: self._data[k] = data[k].copy() @@ -110,13 +111,17 @@ item = None cont = False m = includere.match(l) - if m: - inc = util.expandpath(m.group(1)) - base = os.path.dirname(src) - inc = os.path.normpath(os.path.join(base, inc)) - if include: + + if m and include: + expanded = util.expandpath(m.group(1)) + includepaths = [os.path.dirname(src)] + self._includepaths + + for base in includepaths: + inc = os.path.normpath(os.path.join(base, expanded)) + try: include(inc, remap=remap, sections=sections) + break except IOError, inst: if inst.errno != errno.ENOENT: raise error.ParseError(_("cannot include %s (%s)") diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/context.py --- a/mercurial/context.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/context.py Fri May 29 17:00:55 2015 -0500 @@ -251,11 +251,13 @@ def sub(self, path): return subrepo.subrepo(self, path) - def match(self, pats=[], include=None, exclude=None, default='glob'): + def match(self, pats=[], include=None, exclude=None, default='glob', + listsubrepos=False): r = self._repo return matchmod.match(r.root, r.getcwd(), pats, include, exclude, default, - auditor=r.auditor, ctx=self) + auditor=r.auditor, ctx=self, + listsubrepos=listsubrepos) def diff(self, ctx2=None, match=None, **opts): """Returns a diff generator for the given contexts and matcher""" @@ -338,7 +340,7 @@ def makememctx(repo, parents, text, user, date, branch, files, store, - editor=None): + editor=None, extra=None): def getfilectx(repo, memctx, path): data, mode, copied = store.getfile(path) if data is None: @@ -346,7 +348,8 @@ islink, isexec = mode return memfilectx(repo, path, data, islink=islink, isexec=isexec, copied=copied, memctx=memctx) - extra = {} + if extra is None: + extra = {} if branch: extra['branch'] = encoding.fromlocal(branch) ctx = memctx(repo, parents, text, files, getfilectx, user, @@ -459,7 +462,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) @@ -595,8 +598,8 @@ def bad(fn, msg): # The manifest doesn't know about subrepos, so don't complain about # paths into valid subrepos. - if util.any(fn == s or fn.startswith(s + '/') - for s in self.substate): + if any(fn == s or fn.startswith(s + '/') + for s in self.substate): return oldbad(fn, _('no such file in rev %s') % self) match.bad = bad @@ -1443,17 +1446,20 @@ finally: wlock.release() - def match(self, pats=[], include=None, exclude=None, default='glob'): + def match(self, pats=[], include=None, exclude=None, default='glob', + listsubrepos=False): r = self._repo # Only a case insensitive filesystem needs magic to translate user input # to actual case in the filesystem. if not util.checkcase(r.root): return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include, - exclude, default, r.auditor, self) + exclude, default, r.auditor, self, + listsubrepos=listsubrepos) return matchmod.match(r.root, r.getcwd(), pats, include, exclude, default, - auditor=r.auditor, ctx=self) + auditor=r.auditor, ctx=self, + listsubrepos=listsubrepos) def _filtersuspectsymlink(self, files): if not files or self._repo.dirstate._checklink: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/copies.py --- a/mercurial/copies.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/copies.py Fri May 29 17:00:55 2015 -0500 @@ -5,15 +5,9 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import util +import util, pathutil import heapq -def _dirname(f): - s = f.rfind("/") - if s == -1: - return "" - return f[:s] - def _findlimit(repo, a, b): """ Find the last revision that needs to be checked to ensure that a full @@ -376,6 +370,7 @@ # generate a directory move map d1, d2 = c1.dirs(), c2.dirs() + # Hack for adding '', which is not otherwise added, to d1 and d2 d1.addpath('/') d2.addpath('/') invalid = set() @@ -384,7 +379,7 @@ # examine each file copy for a potential directory move, which is # when all the files in a directory are moved to a new directory for dst, src in fullcopy.iteritems(): - dsrc, ddst = _dirname(src), _dirname(dst) + dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst) if dsrc in invalid: # already seen to be uninteresting continue diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/crecord.py --- a/mercurial/crecord.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/crecord.py Fri May 29 17:00:55 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: @@ -424,9 +425,11 @@ def __repr__(self): return '' % (self.filename(), self.fromline) -def filterpatch(ui, chunks, chunkselector): +def filterpatch(ui, chunks, chunkselector, operation=None): """interactively filter patch chunks into applied-only chunks""" + if operation is None: + operation = _('confirm') chunks = list(chunks) # convert chunks list into structure suitable for displaying/modifying # with curses. create a list of headers only. diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/dagparser.py --- a/mercurial/dagparser.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/dagparser.py Fri May 29 17:00:55 2015 -0500 @@ -176,10 +176,7 @@ chiter = (c for c in desc) def nextch(): - try: - return chiter.next() - except StopIteration: - return '\0' + return next(chiter, '\0') def nextrun(c, allow): s = '' diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/demandimport.py --- a/mercurial/demandimport.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/demandimport.py Fri May 29 17:00:55 2015 -0500 @@ -25,6 +25,8 @@ ''' import __builtin__, os, sys +from contextlib import contextmanager + _origimport = __import__ nothing = object() @@ -179,3 +181,16 @@ def disable(): "disable global demand-loading of modules" __builtin__.__import__ = _origimport + +@contextmanager +def deactivated(): + "context manager for disabling demandimport in 'with' blocks" + demandenabled = isenabled() + if demandenabled: + disable() + + try: + yield + finally: + if demandenabled: + enable() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/dirs.c --- a/mercurial/dirs.c Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/dirs.c Fri May 29 17:00:55 2015 -0500 @@ -9,7 +9,6 @@ #define PY_SSIZE_T_CLEAN #include -#include #include "util.h" /* @@ -29,23 +28,25 @@ PyObject *dict; } dirsObject; -static inline Py_ssize_t _finddir(PyObject *path, Py_ssize_t pos) +static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos) { - const char *s = PyString_AS_STRING(path); + while (pos != -1) { + if (path[pos] == '/') + break; + pos -= 1; + } - const char *ret = strchr(s + pos, '/'); - return (ret != NULL) ? (ret - s) : -1; + return pos; } static int _addpath(PyObject *dirs, PyObject *path) { - char *cpath = PyString_AS_STRING(path); - Py_ssize_t len = PyString_GET_SIZE(path); - Py_ssize_t pos = -1; + const char *cpath = PyString_AS_STRING(path); + Py_ssize_t pos = PyString_GET_SIZE(path); PyObject *key = NULL; int ret = -1; - while ((pos = _finddir(path, pos + 1)) != -1) { + while ((pos = _finddir(cpath, pos - 1)) != -1) { PyObject *val; /* It's likely that every prefix already has an entry @@ -53,18 +54,10 @@ deallocating a string for each prefix we check. */ if (key != NULL) ((PyStringObject *)key)->ob_shash = -1; - else if (pos != 0) { - /* pos >= 1, which means that len >= 2. This is - guaranteed to produce a non-interned string. */ - key = PyString_FromStringAndSize(cpath, len); - if (key == NULL) - goto bail; - } else { - /* pos == 0, which means we need to increment the dir - count for the empty string. We need to make sure we - don't muck around with interned strings, so throw it - away later. */ - key = PyString_FromString(""); + else { + /* Force Python to not reuse a small shared string. */ + key = PyString_FromStringAndSize(cpath, + pos < 2 ? 2 : pos); if (key == NULL) goto bail; } @@ -74,11 +67,7 @@ val = PyDict_GetItem(dirs, key); if (val != NULL) { PyInt_AS_LONG(val) += 1; - if (pos != 0) - PyString_AS_STRING(key)[pos] = '/'; - else - key = NULL; - continue; + break; } /* Force Python to not reuse a small shared int. */ @@ -92,9 +81,6 @@ Py_DECREF(val); if (ret == -1) goto bail; - - /* Clear the key out since we've already exposed it to Python - and can't mutate it further. */ Py_CLEAR(key); } ret = 0; @@ -107,14 +93,15 @@ static int _delpath(PyObject *dirs, PyObject *path) { - Py_ssize_t pos = -1; + char *cpath = PyString_AS_STRING(path); + Py_ssize_t pos = PyString_GET_SIZE(path); PyObject *key = NULL; int ret = -1; - while ((pos = _finddir(path, pos + 1)) != -1) { + while ((pos = _finddir(cpath, pos - 1)) != -1) { PyObject *val; - key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos); + key = PyString_FromStringAndSize(cpath, pos); if (key == NULL) goto bail; @@ -126,9 +113,11 @@ goto bail; } - if (--PyInt_AS_LONG(val) <= 0 && - PyDict_DelItem(dirs, key) == -1) - goto bail; + if (--PyInt_AS_LONG(val) <= 0) { + if (PyDict_DelItem(dirs, key) == -1) + goto bail; + } else + break; Py_CLEAR(key); } ret = 0; diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/dirstate.py --- a/mercurial/dirstate.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/dirstate.py Fri May 29 17:00:55 2015 -0500 @@ -7,8 +7,9 @@ from node import nullid from i18n import _ -import scmutil, util, ignore, osutil, parsers, encoding, pathutil +import scmutil, util, osutil, parsers, encoding, pathutil import os, stat, errno +import match as matchmod propertycache = util.propertycache filecache = scmutil.filecache @@ -47,6 +48,7 @@ self._ui = ui self._filecache = {} self._parentwriters = 0 + self._filename = 'dirstate' def beginparentchange(self): '''Marks the beginning of a set of changes that involve changing @@ -121,7 +123,7 @@ @propertycache def _pl(self): try: - fp = self._opener("dirstate") + fp = self._opener(self._filename) st = fp.read(40) fp.close() l = len(st) @@ -143,13 +145,20 @@ @rootcache('.hgignore') def _ignore(self): - files = [self._join('.hgignore')] + files = [] + if os.path.exists(self._join('.hgignore')): + files.append(self._join('.hgignore')) for name, path in self._ui.configitems("ui"): if name == 'ignore' or name.startswith('ignore.'): # we need to use os.path.join here rather than self._join # because path is arbitrary and user-specified files.append(os.path.join(self._rootdir, util.expandpath(path))) - return ignore.ignore(self._root, files, self._ui.warn) + + if not files: + return util.never + + pats = ['include:%s' % f for f in files] + return matchmod.match(self._root, '', [], pats, warn=self._ui.warn) @propertycache def _slash(self): @@ -317,7 +326,11 @@ self._map = {} self._copymap = {} try: - st = self._opener.read("dirstate") + fp = self._opener.open(self._filename) + try: + st = fp.read() + finally: + fp.close() except IOError, err: if err.errno != errno.ENOENT: raise @@ -584,7 +597,7 @@ import time # to avoid useless import time.sleep(delaywrite) - st = self._opener("dirstate", "w", atomictemp=True) + st = self._opener(self._filename, "w", atomictemp=True) # use the modification time of the newly created temporary file as the # filesystem's notion of 'now' now = util.fstat(st).st_mtime @@ -746,7 +759,7 @@ if match.isexact(): # match.exact exact = True dirignore = util.always # skip step 2 - elif match.files() and not match.anypats(): # match.match, no patterns + elif match.prefix(): # match.match, no patterns skipstep3 = True if not exact and self._checkcase: @@ -972,7 +985,7 @@ # fast path -- filter the other way around, since typically files is # much smaller than dmap return [f for f in files if f in dmap] - if not match.anypats() and util.all(fn in dmap for fn in files): + if match.prefix() and all(fn in dmap for fn in files): # fast path -- all the values are known to be files, so just return # that return list(files) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/dispatch.py --- a/mercurial/dispatch.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/dispatch.py Fri May 29 17:00:55 2015 -0500 @@ -11,6 +11,7 @@ import util, commands, hg, fancyopts, extensions, hook, error import cmdutil, encoding import ui as uimod +import demandimport class request(object): def __init__(self, args, ui=None, repo=None, fin=None, fout=None, @@ -137,10 +138,11 @@ # This import can be slow for fancy debuggers, so only # do it when absolutely necessary, i.e. when actual # debugging has been requested - try: - debugmod = __import__(debugger) - except ImportError: - pass # Leave debugmod = pdb + with demandimport.deactivated(): + try: + debugmod = __import__(debugger) + except ImportError: + pass # Leave debugmod = pdb debugtrace[debugger] = debugmod.set_trace debugmortem[debugger] = debugmod.post_mortem @@ -193,8 +195,15 @@ ui.warn(_("hg: %s\n") % inst.args[1]) commands.help_(ui, 'shortlist') except error.OutOfBandError, inst: - ui.warn(_("abort: remote error:\n")) - ui.warn(''.join(inst.args)) + if inst.args: + msg = _("abort: remote error:\n") + else: + msg = _("abort: remote error\n") + ui.warn(msg) + if inst.args: + ui.warn(''.join(inst.args)) + if inst.hint: + ui.warn('(%s)\n' % inst.hint) except error.RepoError, inst: ui.warn(_("abort: %s!\n") % inst) if inst.hint: @@ -891,7 +900,7 @@ format = ui.config('profiling', 'format', default='text') field = ui.config('profiling', 'sort', default='inlinetime') limit = ui.configint('profiling', 'limit', default=30) - climit = ui.configint('profiling', 'nested', default=5) + climit = ui.configint('profiling', 'nested', default=0) if format not in ['text', 'kcachegrind']: ui.warn(_("unrecognized profiling format '%s'" @@ -921,6 +930,30 @@ stats.sort(field) stats.pprint(limit=limit, file=fp, climit=climit) +def flameprofile(ui, func, fp): + try: + from flamegraph import flamegraph + except ImportError: + raise util.Abort(_( + 'flamegraph not available - install from ' + 'https://github.com/evanhempel/python-flamegraph')) + freq = ui.configint('profiling', 'freq', default=1000) + filter_ = None + collapse_recursion = True + thread = flamegraph.ProfileThread(fp, 1.0 / freq, + filter_, collapse_recursion) + start_time = time.clock() + try: + thread.start() + func() + finally: + thread.stop() + thread.join() + print 'Collected %d stack frames (%d unique) in %2.2f seconds.' % ( + time.clock() - start_time, thread.num_frames(), + thread.num_frames(unique=True)) + + def statprofile(ui, func, fp): try: import statprof @@ -952,7 +985,7 @@ profiler = os.getenv('HGPROF') if profiler is None: profiler = ui.config('profiling', 'type', default='ls') - if profiler not in ('ls', 'stat'): + if profiler not in ('ls', 'stat', 'flame'): ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler) profiler = 'ls' @@ -967,6 +1000,8 @@ try: if profiler == 'ls': return lsprofile(ui, checkargs, fp) + elif profiler == 'flame': + return flameprofile(ui, checkargs, fp) else: return statprofile(ui, checkargs, fp) finally: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/error.py --- a/mercurial/error.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/error.py Fri May 29 17:00:55 2015 -0500 @@ -13,7 +13,12 @@ # Do not import anything here, please -class RevlogError(Exception): +class HintException(Exception): + def __init__(self, *args, **kw): + Exception.__init__(self, *args) + self.hint = kw.get('hint') + +class RevlogError(HintException): pass class FilteredIndexError(IndexError): @@ -46,11 +51,9 @@ class InterventionRequired(Exception): """Exception raised when a command requires human intervention.""" -class Abort(Exception): +class Abort(HintException): """Raised if a command needs to print an error and exit.""" - def __init__(self, *args, **kw): - Exception.__init__(self, *args) - self.hint = kw.get('hint') + pass class HookAbort(Abort): """raised when a validation hook fails, aborting an operation @@ -64,6 +67,10 @@ class OutOfBandError(Exception): """Exception raised when a remote repo reports failure""" + def __init__(self, *args, **kw): + Exception.__init__(self, *args) + self.hint = kw.get('hint') + class ParseError(Exception): """Raised when parsing config files and {rev,file}sets (msg[, pos])""" @@ -76,10 +83,8 @@ self.function = function self.symbols = symbols -class RepoError(Exception): - def __init__(self, *args, **kw): - Exception.__init__(self, *args) - self.hint = kw.get('hint') +class RepoError(HintException): + pass class RepoLookupError(RepoError): pass diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/exchange.py --- a/mercurial/exchange.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/exchange.py Fri May 29 17:00:55 2015 -0500 @@ -5,10 +5,11 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import time from i18n import _ from node import hex, nullid import errno, urllib -import util, scmutil, changegroup, base85, error +import util, scmutil, changegroup, base85, error, store import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey import lock as lockmod @@ -304,6 +305,20 @@ unfi = pushop.repo.unfiltered() remotephases = pushop.remote.listkeys('phases') publishing = remotephases.get('publishing', False) + if (pushop.ui.configbool('ui', '_usedassubrepo', False) + and remotephases # server supports phases + and not pushop.outgoing.missing # no changesets to be pushed + and publishing): + # When: + # - this is a subrepo push + # - and remote support phase + # - and no changeset are to be pushed + # - and remote is publishing + # We may be in issue 3871 case! + # We drop the possible phase synchronisation done by + # courtesy to publish changesets possibly locally draft + # on the remote. + remotephases = {'publishing': 'True'} ana = phases.analyzeremotephases(pushop.repo, pushop.fallbackheads, remotephases) @@ -536,7 +551,8 @@ return pushop.stepsdone.add('obsmarkers') if pushop.outobsmarkers: - buildobsmarkerspart(bundler, pushop.outobsmarkers) + markers = sorted(pushop.outobsmarkers) + buildobsmarkerspart(bundler, markers) @b2partsgenerator('bookmarks') def _pushb2bookmarks(pushop, bundler): @@ -751,7 +767,7 @@ pushop.stepsdone.add('obsmarkers') if pushop.outobsmarkers: rslts = [] - remotedata = obsolete._pushkeyescape(pushop.outobsmarkers) + remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers)) for key in sorted(remotedata, reverse=True): # reverse sort to ensure we end with dump0 data = remotedata[key] @@ -1180,7 +1196,7 @@ # bundle10 case usebundle2 = False if bundlecaps is not None: - usebundle2 = util.any((cap.startswith('HG2') for cap in bundlecaps)) + usebundle2 = any((cap.startswith('HG2') for cap in bundlecaps)) if not usebundle2: if bundlecaps and not kwargs.get('cg', True): raise ValueError(_('request for bundle10 must include changegroup')) @@ -1257,6 +1273,7 @@ heads = repo.heads() subset = [c.node() for c in repo.set('::%ln', heads)] markers = repo.obsstore.relevantmarkers(subset) + markers = sorted(markers) buildobsmarkerspart(bundler, markers) def check_heads(repo, their_heads, context): @@ -1313,7 +1330,7 @@ def recordout(output): r.newpart('output', data=output, mandatory=False) tr.close() - except Exception, exc: + except BaseException, exc: exc.duringunbundle2 = True if captureoutput and r is not None: parts = exc._bundle2salvagedoutput = r.salvageoutput() @@ -1330,3 +1347,131 @@ if recordout is not None: recordout(repo.ui.popbuffer()) return r + +# This is it's own function so extensions can override it. +def _walkstreamfiles(repo): + return repo.store.walk() + +def generatestreamclone(repo): + """Emit content for a streaming clone. + + This is a generator of raw chunks that constitute a streaming clone. + + The stream begins with a line of 2 space-delimited integers containing the + number of entries and total bytes size. + + Next, are N entries for each file being transferred. Each file entry starts + as a line with the file name and integer size delimited by a null byte. + The raw file data follows. Following the raw file data is the next file + entry, or EOF. + + When used on the wire protocol, an additional line indicating protocol + success will be prepended to the stream. This function is not responsible + for adding it. + + This function will obtain a repository lock to ensure a consistent view of + the store is captured. It therefore may raise LockError. + """ + entries = [] + total_bytes = 0 + # Get consistent snapshot of repo, lock during scan. + lock = repo.lock() + try: + repo.ui.debug('scanning\n') + for name, ename, size in _walkstreamfiles(repo): + if size: + entries.append((name, size)) + total_bytes += size + finally: + lock.release() + + repo.ui.debug('%d files, %d bytes to transfer\n' % + (len(entries), total_bytes)) + yield '%d %d\n' % (len(entries), total_bytes) + + sopener = repo.svfs + oldaudit = sopener.mustaudit + debugflag = repo.ui.debugflag + sopener.mustaudit = False + + try: + for name, size in entries: + if debugflag: + repo.ui.debug('sending %s (%d bytes)\n' % (name, size)) + # partially encode name over the wire for backwards compat + yield '%s\0%d\n' % (store.encodedir(name), size) + if size <= 65536: + fp = sopener(name) + try: + data = fp.read(size) + finally: + fp.close() + yield data + else: + for chunk in util.filechunkiter(sopener(name), limit=size): + yield chunk + finally: + sopener.mustaudit = oldaudit + +def consumestreamclone(repo, fp): + """Apply the contents from a streaming clone file. + + This takes the output from "streamout" and applies it to the specified + repository. + + Like "streamout," the status line added by the wire protocol is not handled + by this function. + """ + lock = repo.lock() + try: + repo.ui.status(_('streaming all changes\n')) + l = fp.readline() + try: + total_files, total_bytes = map(int, l.split(' ', 1)) + except (ValueError, TypeError): + raise error.ResponseError( + _('unexpected response from remote server:'), l) + repo.ui.status(_('%d files to transfer, %s of data\n') % + (total_files, util.bytecount(total_bytes))) + handled_bytes = 0 + repo.ui.progress(_('clone'), 0, total=total_bytes) + start = time.time() + + tr = repo.transaction(_('clone')) + try: + for i in xrange(total_files): + # XXX doesn't support '\n' or '\r' in filenames + l = fp.readline() + try: + name, size = l.split('\0', 1) + size = int(size) + except (ValueError, TypeError): + raise error.ResponseError( + _('unexpected response from remote server:'), l) + if repo.ui.debugflag: + repo.ui.debug('adding %s (%s)\n' % + (name, util.bytecount(size))) + # for backwards compat, name was partially encoded + ofp = repo.svfs(store.decodedir(name), 'w') + for chunk in util.filechunkiter(fp, limit=size): + handled_bytes += len(chunk) + repo.ui.progress(_('clone'), handled_bytes, + total=total_bytes) + ofp.write(chunk) + ofp.close() + tr.close() + finally: + tr.release() + + # Writing straight to files circumvented the inmemory caches + repo.invalidate() + + elapsed = time.time() - start + if elapsed <= 0: + elapsed = 0.001 + repo.ui.progress(_('clone'), None) + repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') % + (util.bytecount(total_bytes), elapsed, + util.bytecount(total_bytes / elapsed))) + finally: + lock.release() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/filemerge.py --- a/mercurial/filemerge.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/filemerge.py Fri May 29 17:00:55 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) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/fileset.py --- a/mercurial/fileset.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/fileset.py Fri May 29 17:00:55 2015 -0500 @@ -81,7 +81,10 @@ def parse(expr): p = parser.parser(tokenize, elements) - return p.parse(expr) + tree, pos = p.parse(expr) + if pos != len(expr): + raise error.ParseError(_("invalid token"), pos) + return tree def getstring(x, err): if x and (x[0] == 'string' or x[0] == 'symbol'): @@ -491,9 +494,7 @@ ] def getfileset(ctx, expr): - tree, pos = parse(expr) - if (pos != len(expr)): - raise error.ParseError(_("invalid token"), pos) + tree = parse(expr) # do we need status info? if (_intree(['modified', 'added', 'removed', 'deleted', @@ -516,5 +517,8 @@ return getset(matchctx(ctx, subset, status), tree) +def prettyformat(tree): + return parser.prettyformat(tree, ('string', 'symbol')) + # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/hbisect.py --- a/mercurial/hbisect.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/hbisect.py Fri May 29 17:00:55 2015 -0500 @@ -8,6 +8,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import collections import os import error from i18n import _ @@ -71,7 +72,7 @@ # build children dict children = {} - visit = util.deque([badrev]) + visit = collections.deque([badrev]) candidates = [] while visit: rev = visit.popleft() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/help/config.txt --- a/mercurial/help/config.txt Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/help/config.txt Fri May 29 17:00:55 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 % diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/help/hgignore.txt --- a/mercurial/help/hgignore.txt Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/help/hgignore.txt Fri May 29 17:00:55 2015 -0500 @@ -68,6 +68,10 @@ and a regexp pattern of the form ``\.c$`` will do the same. To root a regexp pattern, start it with ``^``. +Subdirectories can have their own .hgignore settings by adding +``subinclude:path/to/subdir/.hgignore`` to the root ``.hgignore``. See +:hg:`help patterns` for details on ``subinclude:`` and ``include:``. + .. note:: Patterns specified in other than ``.hgignore`` are always rooted. diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/help/patterns.txt --- a/mercurial/help/patterns.txt Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/help/patterns.txt Fri May 29 17:00:55 2015 -0500 @@ -30,6 +30,12 @@ feeds. Each string read from the file is itself treated as a file pattern. +To read a set of patterns from a file, use ``include:`` or ``subinclude:``. +``include:`` will use all the patterns from the given file and treat them as if +they had been passed in manually. ``subinclude:`` will only apply the patterns +against files that are under the subinclude file's directory. See :hg:`help +hgignore` for details on the format of these files. + All patterns, except for ``glob:`` specified in command line (not for ``-I`` or ``-X`` options), can match also against directories: files under matched directories are treated as matched. @@ -60,3 +66,9 @@ listfile0:list.txt read list from list.txt with null byte delimiters See also :hg:`help filesets`. + +Include examples:: + + include:path/to/mypatternfile reads patterns to be applied to all paths + subinclude:path/to/subignorefile reads patterns specifically for paths in the + subdirectory diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/help/subrepos.txt --- a/mercurial/help/subrepos.txt Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/help/subrepos.txt Fri May 29 17:00:55 2015 -0500 @@ -109,8 +109,10 @@ elements. Subversion subrepositories are currently silently ignored. :files: files does not recurse into subrepos unless -S/--subrepos is - specified. Git and Subversion subrepositories are currently - silently ignored. + specified. However, if you specify the full path of a file or + directory in a subrepo, it will be displayed even without + -S/--subrepos being specified. Git and Subversion subrepositories + are currently silently ignored. :forget: forget currently only handles exact file matches in subrepos. Git and Subversion subrepositories are currently silently ignored. diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/help/templates.txt --- a/mercurial/help/templates.txt Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/help/templates.txt Fri May 29 17:00:55 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:: @@ -90,9 +90,9 @@ $ hg log -r 0 --template "{join(extras, '\n')}\n" -- Mark the current bookmark with '*':: +- Mark the active bookmark with '*':: - $ hg log --template "{bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n" + $ hg log --template "{bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n" - Mark the working copy parent with '@':: @@ -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" diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/hg.py --- a/mercurial/hg.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/hg.py Fri May 29 17:00:55 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: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/hgweb/hgwebdir_mod.py Fri May 29 17:00:55 2015 -0500 @@ -176,71 +176,70 @@ def run_wsgi(self, req): try: - try: - self.refresh() + self.refresh() - virtual = req.env.get("PATH_INFO", "").strip('/') - tmpl = self.templater(req) - ctype = tmpl('mimetype', encoding=encoding.encoding) - ctype = templater.stringify(ctype) + virtual = req.env.get("PATH_INFO", "").strip('/') + tmpl = self.templater(req) + ctype = tmpl('mimetype', encoding=encoding.encoding) + ctype = templater.stringify(ctype) - # a static file - if virtual.startswith('static/') or 'static' in req.form: - if virtual.startswith('static/'): - fname = virtual[7:] - else: - fname = req.form['static'][0] - static = self.ui.config("web", "static", None, - untrusted=False) - if not static: - tp = self.templatepath or templater.templatepaths() - if isinstance(tp, str): - tp = [tp] - static = [os.path.join(p, 'static') for p in tp] - staticfile(static, fname, req) - return [] + # a static file + if virtual.startswith('static/') or 'static' in req.form: + if virtual.startswith('static/'): + fname = virtual[7:] + else: + fname = req.form['static'][0] + static = self.ui.config("web", "static", None, + untrusted=False) + if not static: + tp = self.templatepath or templater.templatepaths() + if isinstance(tp, str): + tp = [tp] + static = [os.path.join(p, 'static') for p in tp] + staticfile(static, fname, req) + return [] - # top-level index - elif not virtual: - req.respond(HTTP_OK, ctype) - return self.makeindex(req, tmpl) + # top-level index + elif not virtual: + req.respond(HTTP_OK, ctype) + return self.makeindex(req, tmpl) - # nested indexes and hgwebs + # nested indexes and hgwebs - repos = dict(self.repos) - virtualrepo = virtual - while virtualrepo: - real = repos.get(virtualrepo) - if real: - req.env['REPO_NAME'] = virtualrepo - try: - # ensure caller gets private copy of ui - repo = hg.repository(self.ui.copy(), real) - return hgweb(repo).run_wsgi(req) - except IOError, inst: - msg = inst.strerror - raise ErrorResponse(HTTP_SERVER_ERROR, msg) - except error.RepoError, inst: - raise ErrorResponse(HTTP_SERVER_ERROR, str(inst)) + repos = dict(self.repos) + virtualrepo = virtual + while virtualrepo: + real = repos.get(virtualrepo) + if real: + req.env['REPO_NAME'] = virtualrepo + try: + # ensure caller gets private copy of ui + repo = hg.repository(self.ui.copy(), real) + return hgweb(repo).run_wsgi(req) + except IOError, inst: + msg = inst.strerror + raise ErrorResponse(HTTP_SERVER_ERROR, msg) + except error.RepoError, inst: + raise ErrorResponse(HTTP_SERVER_ERROR, str(inst)) - up = virtualrepo.rfind('/') - if up < 0: - break - virtualrepo = virtualrepo[:up] + up = virtualrepo.rfind('/') + if up < 0: + break + virtualrepo = virtualrepo[:up] - # browse subdirectories - subdir = virtual + '/' - if [r for r in repos if r.startswith(subdir)]: - req.respond(HTTP_OK, ctype) - return self.makeindex(req, tmpl, subdir) + # browse subdirectories + subdir = virtual + '/' + if [r for r in repos if r.startswith(subdir)]: + req.respond(HTTP_OK, ctype) + return self.makeindex(req, tmpl, subdir) - # prefixes not found - req.respond(HTTP_NOT_FOUND, ctype) - return tmpl("notfound", repo=virtual) + # prefixes not found + req.respond(HTTP_NOT_FOUND, ctype) + return tmpl("notfound", repo=virtual) - except ErrorResponse, err: - req.respond(err, ctype) - return tmpl('error', error=err.message or '') + except ErrorResponse, err: + req.respond(err, ctype) + return tmpl('error', error=err.message or '') finally: tmpl = None diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/hgweb/webcommands.py Fri May 29 17:00:55 2015 -0500 @@ -130,6 +130,8 @@ parent=webutil.parents(fctx), child=webutil.children(fctx), rename=webutil.renamelink(fctx), + tags=webutil.nodetagsdict(web.repo, fctx.node()), + bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()), permissions=fctx.manifest().flags(f)) @webcommand('file') @@ -221,7 +223,7 @@ revdef = 'reverse(%s)' % query try: - tree, pos = revset.parse(revdef) + tree = revset.parse(revdef) except ParseError: # can't parse to a revset tree return MODE_KEYWORD, query @@ -230,7 +232,7 @@ # no revset syntax used return MODE_KEYWORD, query - if util.any((token, (value or '')[:3]) == ('string', 're:') + if any((token, (value or '')[:3]) == ('string', 're:') for token, value, pos in revset.tokenize(revdef)): return MODE_KEYWORD, query @@ -546,6 +548,7 @@ archives=web.archivelist(hex(node)), tags=webutil.nodetagsdict(web.repo, node), bookmarks=webutil.nodebookmarksdict(web.repo, node), + branch=webutil.nodebranchnodefault(ctx), inbranch=webutil.nodeinbranch(web.repo, ctx), branches=webutil.nodebranchdict(web.repo, ctx)) @@ -808,6 +811,8 @@ branch=webutil.nodebranchnodefault(ctx), parent=webutil.parents(ctx), child=webutil.children(ctx), + tags=webutil.nodetagsdict(web.repo, n), + bookmarks=webutil.nodebookmarksdict(web.repo, n), diff=diffs) diff = webcommand('diff')(filediff) @@ -880,6 +885,8 @@ branch=webutil.nodebranchnodefault(ctx), parent=webutil.parents(fctx), child=webutil.children(fctx), + tags=webutil.nodetagsdict(web.repo, ctx.node()), + bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()), leftrev=leftrev, leftnode=hex(leftnode), rightrev=rightrev, @@ -946,6 +953,8 @@ branch=webutil.nodebranchnodefault(fctx), parent=webutil.parents(fctx), child=webutil.children(fctx), + tags=webutil.nodetagsdict(web.repo, fctx.node()), + bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()), permissions=fctx.manifest().flags(f)) @webcommand('filelog') diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/hgweb/webutil.py Fri May 29 17:00:55 2015 -0500 @@ -331,7 +331,7 @@ archives=web.archivelist(ctx.hex()), tags=nodetagsdict(web.repo, ctx.node()), bookmarks=nodebookmarksdict(web.repo, ctx.node()), - branch=nodebranchnodefault(ctx), + branch=showbranch, inbranch=nodeinbranch(web.repo, ctx), branches=nodebranchdict(web.repo, ctx)) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/hook.py --- a/mercurial/hook.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/hook.py Fri May 29 17:00:55 2015 -0500 @@ -35,10 +35,7 @@ if modpath and modfile: sys.path = sys.path[:] + [modpath] modname = modfile - demandimportenabled = demandimport.isenabled() - if demandimportenabled: - demandimport.disable() - try: + with demandimport.deactivated(): try: obj = __import__(modname) except ImportError: @@ -59,9 +56,6 @@ raise util.Abort(_('%s hook is invalid ' '(import of "%s" failed)') % (hname, modname)) - finally: - if demandimportenabled: - demandimport.enable() sys.path = oldpaths try: for p in funcname.split('.')[1:]: @@ -79,27 +73,24 @@ starttime = time.time() try: - try: - # redirect IO descriptors to the ui descriptors so hooks - # that write directly to these don't mess up the command - # protocol when running through the command server - old = sys.stdout, sys.stderr, sys.stdin - sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin + # redirect IO descriptors to the ui descriptors so hooks + # that write directly to these don't mess up the command + # protocol when running through the command server + old = sys.stdout, sys.stderr, sys.stdin + sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin - r = obj(ui=ui, repo=repo, hooktype=name, **args) - except KeyboardInterrupt: + r = obj(ui=ui, repo=repo, hooktype=name, **args) + except Exception, exc: + if isinstance(exc, util.Abort): + ui.warn(_('error: %s hook failed: %s\n') % + (hname, exc.args[0])) + else: + ui.warn(_('error: %s hook raised an exception: ' + '%s\n') % (hname, exc)) + if throw: raise - except Exception, exc: - if isinstance(exc, util.Abort): - ui.warn(_('error: %s hook failed: %s\n') % - (hname, exc.args[0])) - else: - ui.warn(_('error: %s hook raised an exception: ' - '%s\n') % (hname, exc)) - if throw: - raise - ui.traceback() - return True + ui.traceback() + return True finally: sys.stdout, sys.stderr, sys.stdin = old duration = time.time() - starttime diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/httpconnection.py --- a/mercurial/httpconnection.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/httpconnection.py Fri May 29 17:00:55 2015 -0500 @@ -70,11 +70,7 @@ gdict[setting] = val # Find the best match - if '://' in uri: - scheme, hostpath = uri.split('://', 1) - else: - # Python 2.4.1 doesn't provide the full URI - scheme, hostpath = 'http', uri + scheme, hostpath = uri.split('://', 1) bestuser = None bestlen = 0 bestauth = None diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/httppeer.py --- a/mercurial/httppeer.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/httppeer.py Fri May 29 17:00:55 2015 -0500 @@ -198,16 +198,15 @@ headers = {'Content-Type': 'application/mercurial-0.1'} try: - try: - r = self._call(cmd, data=fp, headers=headers, **args) - vals = r.split('\n', 1) - if len(vals) < 2: - raise error.ResponseError(_("unexpected response:"), r) - return vals - except socket.error, err: - if err.args[0] in (errno.ECONNRESET, errno.EPIPE): - raise util.Abort(_('push failed: %s') % err.args[1]) - raise util.Abort(err.args[1]) + r = self._call(cmd, data=fp, headers=headers, **args) + vals = r.split('\n', 1) + if len(vals) < 2: + raise error.ResponseError(_("unexpected response:"), r) + return vals + except socket.error, err: + if err.args[0] in (errno.ECONNRESET, errno.EPIPE): + raise util.Abort(_('push failed: %s') % err.args[1]) + raise util.Abort(err.args[1]) finally: fp.close() os.unlink(tempname) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/ignore.py --- a/mercurial/ignore.py Thu May 28 20:30:20 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -# ignore.py - ignored file handling for mercurial -# -# Copyright 2007 Matt Mackall -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -from i18n import _ -import util, match -import re - -_commentre = None - -def ignorepats(lines): - '''parse lines (iterable) of .hgignore text, returning a tuple of - (patterns, parse errors). These patterns should be given to compile() - to be validated and converted into a match function.''' - syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'} - syntax = 'relre:' - patterns = [] - warnings = [] - - for line in lines: - if "#" in line: - global _commentre - if not _commentre: - _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*') - # remove comments prefixed by an even number of escapes - line = _commentre.sub(r'\1', line) - # fixup properly escaped comments that survived the above - line = line.replace("\\#", "#") - line = line.rstrip() - if not line: - continue - - if line.startswith('syntax:'): - s = line[7:].strip() - try: - syntax = syntaxes[s] - except KeyError: - warnings.append(_("ignoring invalid syntax '%s'") % s) - continue - pat = syntax + line - for s, rels in syntaxes.iteritems(): - if line.startswith(rels): - pat = line - break - elif line.startswith(s+':'): - pat = rels + line[len(s) + 1:] - break - patterns.append(pat) - - return patterns, warnings - -def readpats(root, files, warn): - '''return a dict mapping ignore-file-name to list-of-patterns''' - - pats = {} - 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)) - return [(f, pats[f]) for f in files if f in pats] - -def ignore(root, files, warn): - '''return matcher covering patterns in 'files'. - - the files parsed for patterns include: - .hgignore in the repository root - any additional files specified in the [ui] section of ~/.hgrc - - trailing white space is dropped. - the escape character is backslash. - comments start with #. - empty lines are skipped. - - lines can be of the following formats: - - syntax: regexp # defaults following lines to non-rooted regexps - syntax: glob # defaults following lines to non-rooted globs - re:pattern # non-rooted regular expression - glob:pattern # non-rooted glob - pattern # pattern of the current default type''' - - pats = readpats(root, files, warn) - - allpats = [] - for f, patlist in pats: - allpats.extend(patlist) - if not allpats: - return util.never - - try: - ignorefunc = match.match(root, '', [], allpats) - except util.Abort: - # Re-raise an exception where the src is the right file - for f, patlist in pats: - try: - match.match(root, '', [], patlist) - except util.Abort, inst: - raise util.Abort('%s: %s' % (f, inst[0])) - - return ignorefunc diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/localrepo.py --- a/mercurial/localrepo.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/localrepo.py Fri May 29 17:00:55 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] @@ -464,6 +460,9 @@ def manifest(self): return manifest.manifest(self.svfs) + def dirlog(self, dir): + return self.manifest.dirlog(dir) + @repofilecache('dirstate') def dirstate(self): warned = [0] @@ -628,7 +627,7 @@ if not local: m = matchmod.exact(self.root, '', ['.hgtags']) - if util.any(self.status(match=m, unknown=True, ignored=True)): + if any(self.status(match=m, unknown=True, ignored=True)): raise util.Abort(_('working copy of .hgtags is changed'), hint=_('please commit .hgtags manually')) @@ -945,7 +944,7 @@ return None def transaction(self, desc, report=None): - if (self.ui.configbool('devel', 'all') + if (self.ui.configbool('devel', 'all-warnings') or self.ui.configbool('devel', 'check-locks')): l = self._lockref and self._lockref() if l is None or not l.held: @@ -1250,7 +1249,7 @@ # We do not need to check for non-waiting lock aquisition. Such # acquisition would not cause dead-lock as they would just fail. - if wait and (self.ui.configbool('devel', 'all') + if wait and (self.ui.configbool('devel', 'all-warnings') or self.ui.configbool('devel', 'check-locks')): l = self._lockref and self._lockref() if l is not None and l.held: @@ -1382,7 +1381,7 @@ wctx = self[None] merge = len(wctx.parents()) > 1 - if not force and merge and not match.always(): + if not force and merge and match.ispartial(): raise util.Abort(_('cannot partially commit a merge ' '(do not specify files or patterns)')) @@ -1445,7 +1444,7 @@ status.removed.insert(0, '.hgsubstate') # make sure all explicit patterns are matched - if not force and match.files(): + if not force and (match.isexact() or match.prefix()): matched = set(status.modified + status.added + status.removed) for f in match.files(): @@ -1467,9 +1466,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(): @@ -1522,7 +1522,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) @@ -1755,89 +1755,55 @@ """ return util.hooks() - def stream_in(self, remote, requirements): + def stream_in(self, remote, remotereqs): + # Save remote branchmap. We will use it later + # to speed up branchcache creation + rbranchmap = None + if remote.capable("branchmap"): + rbranchmap = remote.branchmap() + + fp = remote.stream_out() + l = fp.readline() + try: + resp = int(l) + except ValueError: + raise error.ResponseError( + _('unexpected response from remote server:'), l) + if resp == 1: + raise util.Abort(_('operation forbidden by server')) + elif resp == 2: + raise util.Abort(_('locking the remote repository failed')) + elif resp != 0: + raise util.Abort(_('the server sent an unknown error code')) + + self.applystreamclone(remotereqs, rbranchmap, fp) + return len(self.heads()) + 1 + + def applystreamclone(self, remotereqs, remotebranchmap, fp): + """Apply stream clone data to this repository. + + "remotereqs" is a set of requirements to handle the incoming data. + "remotebranchmap" is the result of a branchmap lookup on the remote. It + can be None. + "fp" is a file object containing the raw stream data, suitable for + feeding into exchange.consumestreamclone. + """ lock = self.lock() try: - # Save remote branchmap. We will use it later - # to speed up branchcache creation - rbranchmap = None - if remote.capable("branchmap"): - rbranchmap = remote.branchmap() - - fp = remote.stream_out() - l = fp.readline() - try: - resp = int(l) - except ValueError: - raise error.ResponseError( - _('unexpected response from remote server:'), l) - if resp == 1: - raise util.Abort(_('operation forbidden by server')) - elif resp == 2: - raise util.Abort(_('locking the remote repository failed')) - elif resp != 0: - raise util.Abort(_('the server sent an unknown error code')) - self.ui.status(_('streaming all changes\n')) - l = fp.readline() - try: - total_files, total_bytes = map(int, l.split(' ', 1)) - except (ValueError, TypeError): - raise error.ResponseError( - _('unexpected response from remote server:'), l) - self.ui.status(_('%d files to transfer, %s of data\n') % - (total_files, util.bytecount(total_bytes))) - handled_bytes = 0 - self.ui.progress(_('clone'), 0, total=total_bytes) - start = time.time() - - tr = self.transaction(_('clone')) - try: - for i in xrange(total_files): - # XXX doesn't support '\n' or '\r' in filenames - l = fp.readline() - try: - name, size = l.split('\0', 1) - size = int(size) - except (ValueError, TypeError): - raise error.ResponseError( - _('unexpected response from remote server:'), l) - if self.ui.debugflag: - self.ui.debug('adding %s (%s)\n' % - (name, util.bytecount(size))) - # for backwards compat, name was partially encoded - ofp = self.svfs(store.decodedir(name), 'w') - for chunk in util.filechunkiter(fp, limit=size): - handled_bytes += len(chunk) - self.ui.progress(_('clone'), handled_bytes, - total=total_bytes) - ofp.write(chunk) - ofp.close() - tr.close() - finally: - tr.release() - - # Writing straight to files circumvented the inmemory caches - self.invalidate() - - elapsed = time.time() - start - if elapsed <= 0: - elapsed = 0.001 - self.ui.progress(_('clone'), None) - self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') % - (util.bytecount(total_bytes), elapsed, - util.bytecount(total_bytes / elapsed))) + exchange.consumestreamclone(self, fp) # 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: + if remotebranchmap: rbheads = [] closed = [] - for bheads in rbranchmap.itervalues(): + for bheads in remotebranchmap.itervalues(): rbheads.extend(bheads) for h in bheads: r = self.changelog.rev(h) @@ -1848,7 +1814,7 @@ if rbheads: rtiprev = max((int(self.changelog.rev(node)) for node in rbheads)) - cache = branchmap.branchcache(rbranchmap, + cache = branchmap.branchcache(remotebranchmap, self[rtiprev].node(), rtiprev, closednodes=closed) @@ -1861,7 +1827,6 @@ cache.write(rview) break self.invalidate() - return len(self.heads()) + 1 finally: lock.release() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/mail.py --- a/mercurial/mail.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/mail.py Fri May 29 17:00:55 2015 -0500 @@ -9,10 +9,6 @@ import util, encoding, sslutil import os, smtplib, socket, quopri, time, sys import email -# On python2.4 you have to import these by name or they fail to -# load. This was not a problem on Python 2.7. -import email.Header -import email.MIMEText _oldheaderinit = email.Header.Header.__init__ def _unifiedheaderinit(self, *args, **kw): diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/manifest.c --- a/mercurial/manifest.c Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/manifest.c Fri May 29 17:00:55 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); diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/manifest.py --- a/mercurial/manifest.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/manifest.py Fri May 29 17:00:55 2015 -0500 @@ -219,7 +219,7 @@ files instead of over manifest files.''' files = match.files() return (len(files) < 100 and (match.isexact() or - (not match.anypats() and util.all(fn in self for fn in files)))) + (match.prefix() and all(fn in self for fn in files)))) def walk(self, match): '''Generates matching file names. @@ -441,32 +441,64 @@ else: return '', f +_noop = lambda: None + class treemanifest(object): def __init__(self, dir='', text=''): self._dir = dir + self._node = revlog.nullid + self._load = _noop + self._dirty = False self._dirs = {} # Using _lazymanifest here is a little slower than plain old dicts self._files = {} self._flags = {} - self.parse(text) + if text: + def readsubtree(subdir, subm): + raise AssertionError('treemanifest constructor only accepts ' + 'flat manifests') + self.parse(text, readsubtree) + self._dirty = True # Mark flat manifest dirty after parsing def _subpath(self, path): return self._dir + path def __len__(self): + self._load() size = len(self._files) for m in self._dirs.values(): size += m.__len__() return size def _isempty(self): + self._load() # for consistency; already loaded by all callers return (not self._files and (not self._dirs or - util.all(m._isempty() for m in self._dirs.values()))) + all(m._isempty() for m in self._dirs.values()))) def __str__(self): - return '' % self._dir + return ('' % + (self._dir, revlog.hex(self._node), + bool(self._load is _noop), + self._dirty)) + + def dir(self): + '''The directory that this tree manifest represents, including a + trailing '/'. Empty string for the repo root directory.''' + return self._dir + + def node(self): + '''This node of this instance. nullid for unsaved instances. Should + be updated when the instance is read or written from a revlog. + ''' + assert not self._dirty + return self._node + + def setnode(self, node): + self._node = node + self._dirty = False def iteritems(self): + self._load() for p, n in sorted(self._dirs.items() + self._files.items()): if p in self._files: yield self._subpath(p), n @@ -475,6 +507,7 @@ yield f, sn def iterkeys(self): + self._load() for p in sorted(self._dirs.keys() + self._files.keys()): if p in self._files: yield self._subpath(p) @@ -491,6 +524,7 @@ def __contains__(self, f): if f is None: return False + self._load() dir, subpath = _splittopdir(f) if dir: if dir not in self._dirs: @@ -500,6 +534,7 @@ return f in self._files def get(self, f, default=None): + self._load() dir, subpath = _splittopdir(f) if dir: if dir not in self._dirs: @@ -509,6 +544,7 @@ return self._files.get(f, default) def __getitem__(self, f): + self._load() dir, subpath = _splittopdir(f) if dir: return self._dirs[dir].__getitem__(subpath) @@ -516,6 +552,7 @@ return self._files[f] def flags(self, f): + self._load() dir, subpath = _splittopdir(f) if dir: if dir not in self._dirs: @@ -527,6 +564,7 @@ return self._flags.get(f, '') def find(self, f): + self._load() dir, subpath = _splittopdir(f) if dir: return self._dirs[dir].find(subpath) @@ -534,6 +572,7 @@ return self._files[f], self._flags.get(f, '') def __delitem__(self, f): + self._load() dir, subpath = _splittopdir(f) if dir: self._dirs[dir].__delitem__(subpath) @@ -544,9 +583,11 @@ del self._files[f] if f in self._flags: del self._flags[f] + self._dirty = True def __setitem__(self, f, n): assert n is not None + self._load() dir, subpath = _splittopdir(f) if dir: if dir not in self._dirs: @@ -554,9 +595,12 @@ self._dirs[dir].__setitem__(subpath, n) else: self._files[f] = n[:21] # to match manifestdict's behavior + self._dirty = True def setflag(self, f, flags): """Set the flags (symlink, executable) for path f.""" + assert 'd' not in flags + self._load() dir, subpath = _splittopdir(f) if dir: if dir not in self._dirs: @@ -564,19 +608,35 @@ self._dirs[dir].setflag(subpath, flags) else: self._flags[f] = flags + self._dirty = True def copy(self): copy = treemanifest(self._dir) - for d in self._dirs: - copy._dirs[d] = self._dirs[d].copy() - copy._files = dict.copy(self._files) - copy._flags = dict.copy(self._flags) + copy._node = self._node + copy._dirty = self._dirty + def _load(): + self._load() + for d in self._dirs: + copy._dirs[d] = self._dirs[d].copy() + copy._files = dict.copy(self._files) + copy._flags = dict.copy(self._flags) + copy._load = _noop + copy._load = _load + if self._load == _noop: + # Chaining _load if it's _noop is functionally correct, but the + # chain may end up excessively long (stack overflow), and + # will prevent garbage collection of 'self'. + copy._load() return copy def filesnotin(self, m2): '''Set of files in this manifest that are not in the other''' files = set() def _filesnotin(t1, t2): + if t1._node == t2._node and not t1._dirty and not t2._dirty: + return + t1._load() + t2._load() for d, m1 in t1._dirs.iteritems(): if d in t2._dirs: m2 = t2._dirs[d] @@ -599,6 +659,7 @@ return self._alldirs def hasdir(self, dir): + self._load() topdir, subdir = _splittopdir(dir) if topdir: if topdir in self._dirs: @@ -635,27 +696,20 @@ if not self.hasdir(fn): match.bad(fn, None) - def _walk(self, match, alldirs=False): - '''Recursively generates matching file names for walk(). - - Will visit all subdirectories if alldirs is True, otherwise it will - only visit subdirectories for which match.visitdir is True.''' - - if not alldirs: - # substring to strip trailing slash - visit = match.visitdir(self._dir[:-1] or '.') - if not visit: - return - alldirs = (visit == 'all') + def _walk(self, match): + '''Recursively generates matching file names for walk().''' + if not match.visitdir(self._dir[:-1] or '.'): + return # yield this dir's files and walk its submanifests + self._load() for p in sorted(self._dirs.keys() + self._files.keys()): if p in self._files: fullp = self._subpath(p) if match(fullp): yield fullp else: - for f in self._dirs[p]._walk(match, alldirs): + for f in self._dirs[p]._walk(match): yield f def matches(self, match): @@ -665,20 +719,15 @@ return self._matches(match) - def _matches(self, match, alldirs=False): + def _matches(self, match): '''recursively generate a new manifest filtered by the match argument. - - Will visit all subdirectories if alldirs is True, otherwise it will - only visit subdirectories for which match.visitdir is True.''' - + ''' ret = treemanifest(self._dir) - if not alldirs: - # substring to strip trailing slash - visit = match.visitdir(self._dir[:-1] or '.') - if not visit: - return ret - alldirs = (visit == 'all') + if not match.visitdir(self._dir[:-1] or '.'): + return ret + + self._load() for fn in self._files: fullp = self._subpath(fn) if not match(fullp): @@ -688,10 +737,12 @@ ret._flags[fn] = self._flags[fn] for dir, subm in self._dirs.iteritems(): - m = subm._matches(match, alldirs) + m = subm._matches(match) if not m._isempty(): ret._dirs[dir] = m + if not ret._isempty(): + ret._dirty = True return ret def diff(self, m2, clean=False): @@ -712,6 +763,10 @@ result = {} emptytree = treemanifest() def _diff(t1, t2): + if t1._node == t2._node and not t1._dirty and not t2._dirty: + return + t1._load() + t2._load() for d, m1 in t1._dirs.iteritems(): m2 = t2._dirs.get(d, emptytree) _diff(m1, m2) @@ -737,20 +792,71 @@ _diff(self, m2) return result - def parse(self, text): + def unmodifiedsince(self, m2): + return not self._dirty and not m2._dirty and self._node == m2._node + + def parse(self, text, readsubtree): for f, n, fl in _parse(text): - self[f] = n - if fl: - self.setflag(f, fl) + if fl == 'd': + f = f + '/' + self._dirs[f] = readsubtree(self._subpath(f), n) + elif '/' in f: + # This is a flat manifest, so use __setitem__ and setflag rather + # than assigning directly to _files and _flags, so we can + # assign a path in a subdirectory, and to mark dirty (compared + # to nullid). + self[f] = n + if fl: + self.setflag(f, fl) + else: + # Assigning to _files and _flags avoids marking as dirty, + # and should be a little faster. + self._files[f] = n + if fl: + self._flags[f] = fl def text(self, usemanifestv2=False): """Get the full data of this manifest as a bytestring.""" + self._load() flags = self.flags return _text(((f, self[f], flags(f)) for f in self.keys()), usemanifestv2) + def dirtext(self, usemanifestv2=False): + """Get the full data of this directory as a bytestring. Make sure that + any submanifests have been written first, so their nodeids are correct. + """ + self._load() + flags = self.flags + dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs] + files = [(f, self._files[f], flags(f)) for f in self._files] + return _text(sorted(dirs + files), usemanifestv2) + + def read(self, gettext, readsubtree): + def _load(): + # Mark as loaded already here, so __setitem__ and setflag() don't + # cause infinite loops when they try to load. + self._load = _noop + self.parse(gettext(), readsubtree) + self._dirty = False + self._load = _load + + def writesubtrees(self, m1, m2, writesubtree): + self._load() # for consistency; should never have any effect here + emptytree = treemanifest() + for d, subm in self._dirs.iteritems(): + subp1 = m1._dirs.get(d, emptytree)._node + subp2 = m2._dirs.get(d, emptytree)._node + if subp1 == revlog.nullid: + subp1, subp2 = subp2, subp1 + writesubtree(subm, subp1, subp2) + class manifest(revlog.revlog): - def __init__(self, opener): + def __init__(self, opener, dir='', dirlogcache=None): + '''The 'dir' and 'dirlogcache' arguments are for internal use by + manifest.manifest only. External users should create a root manifest + log with manifest.manifest(opener) and call dirlog() on it. + ''' # During normal operations, we expect to deal with not more than four # revs at a time (such as during commit --amend). When rebasing large # stacks of commits, the number can go up, hence the config knob below. @@ -760,19 +866,38 @@ opts = getattr(opener, 'options', None) if opts is not None: cachesize = opts.get('manifestcachesize', cachesize) - usetreemanifest = opts.get('usetreemanifest', usetreemanifest) + usetreemanifest = opts.get('treemanifest', usetreemanifest) usemanifestv2 = opts.get('manifestv2', usemanifestv2) self._mancache = util.lrucachedict(cachesize) - revlog.revlog.__init__(self, opener, "00manifest.i") self._treeinmem = usetreemanifest self._treeondisk = usetreemanifest self._usemanifestv2 = usemanifestv2 + indexfile = "00manifest.i" + if dir: + assert self._treeondisk + if not dir.endswith('/'): + dir = dir + '/' + indexfile = "meta/" + dir + "00manifest.i" + revlog.revlog.__init__(self, opener, indexfile) + self._dir = dir + # The dirlogcache is kept on the root manifest log + if dir: + self._dirlogcache = dirlogcache + else: + self._dirlogcache = {'': self} def _newmanifest(self, data=''): if self._treeinmem: - return treemanifest('', data) + return treemanifest(self._dir, data) return manifestdict(data) + def dirlog(self, dir): + assert self._treeondisk + if dir not in self._dirlogcache: + self._dirlogcache[dir] = manifest(self.opener, dir, + self._dirlogcache) + return self._dirlogcache[dir] + def _slowreaddelta(self, node): r0 = self.deltaparent(self.rev(node)) m0 = self.read(self.node(r0)) @@ -793,7 +918,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): @@ -805,9 +936,19 @@ return self._newmanifest() # don't upset local cache if node in self._mancache: return self._mancache[node][0] - text = self.revision(node) - arraytext = array.array('c', text) - m = self._newmanifest(text) + if self._treeondisk: + def gettext(): + return self.revision(node) + def readsubtree(dir, subm): + return self.dirlog(dir).read(subm) + m = self._newmanifest() + m.read(gettext, readsubtree) + m.setnode(node) + arraytext = None + else: + text = self.revision(node) + m = self._newmanifest(text) + arraytext = array.array('c', text) self._mancache[node] = (m, arraytext) return m @@ -845,10 +986,37 @@ # just encode a fulltext of the manifest and pass that # through to the revlog layer, and let it handle the delta # process. - text = m.text(self._usemanifestv2) - arraytext = array.array('c', text) - n = self.addrevision(text, transaction, link, p1, p2) + if self._treeondisk: + m1 = self.read(p1) + m2 = self.read(p2) + n = self._addtree(m, transaction, link, m1, m2) + arraytext = None + else: + text = m.text(self._usemanifestv2) + n = self.addrevision(text, transaction, link, p1, p2) + arraytext = array.array('c', text) self._mancache[n] = (m, arraytext) return n + + def _addtree(self, m, transaction, link, m1, m2): + # If the manifest is unchanged compared to one parent, + # don't write a new revision + if m.unmodifiedsince(m1) or m.unmodifiedsince(m2): + return m.node() + def writesubtree(subm, subp1, subp2): + sublog = self.dirlog(subm.dir()) + sublog.add(subm, transaction, link, subp1, subp2, None, None) + m.writesubtrees(m1, m2, writesubtree) + text = m.dirtext(self._usemanifestv2) + # Double-check whether contents are unchanged to one parent + if text == m1.dirtext(self._usemanifestv2): + n = m1.node() + elif text == m2.dirtext(self._usemanifestv2): + n = m2.node() + else: + n = self.addrevision(text, transaction, link, m1.node(), m2.node()) + # Save nodeid so parent manifest can calculate its nodeid + m.setnode(n) + return n diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/match.py --- a/mercurial/match.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/match.py Fri May 29 17:00:55 2015 -0500 @@ -21,33 +21,64 @@ except AttributeError: return m.match -def _expandsets(kindpats, ctx): +def _expandsets(kindpats, ctx, listsubrepos): '''Returns the kindpats list with the 'set' patterns expanded.''' fset = set() other = [] - for kind, pat in kindpats: + for kind, pat, source in kindpats: if kind == 'set': if not ctx: raise util.Abort("fileset expression with no context") s = ctx.getfileset(pat) fset.update(s) + + if listsubrepos: + for subpath in ctx.substate: + s = ctx.sub(subpath).getfileset(pat) + fset.update(subpath + '/' + f for f in s) + continue - other.append((kind, pat)) + other.append((kind, pat, source)) return fset, other +def _expandsubinclude(kindpats, root): + '''Returns the list of subinclude matchers and the kindpats without the + subincludes in it.''' + relmatchers = [] + other = [] + + for kind, pat, source in kindpats: + if kind == 'subinclude': + sourceroot = pathutil.dirname(util.normpath(source)) + pat = util.pconvert(pat) + path = pathutil.join(sourceroot, pat) + + newroot = pathutil.dirname(path) + relmatcher = match(newroot, '', [], ['include:%s' % path]) + + prefix = pathutil.canonpath(root, root, newroot) + if prefix: + prefix += '/' + relmatchers.append((prefix, relmatcher)) + else: + other.append((kind, pat, source)) + + return relmatchers, other + def _kindpatsalwaysmatch(kindpats): """"Checks whether the kindspats match everything, as e.g. 'relpath:.' does. """ - for kind, pat in kindpats: + for kind, pat, source in kindpats: if pat != '' or kind not in ['relpath', 'glob']: return False return True class match(object): def __init__(self, root, cwd, patterns, include=[], exclude=[], - default='glob', exact=False, auditor=None, ctx=None): + default='glob', exact=False, auditor=None, ctx=None, + listsubrepos=False, warn=None): """build an object to match a set of file patterns arguments: @@ -58,6 +89,7 @@ exclude - patterns to exclude (even if they are included) default - if a pattern in patterns has no explicit type, assume this one exact - patterns are actually filenames (include/exclude still apply) + warn - optional function used for printing warnings a pattern is one of: 'glob:' - a glob relative to cwd @@ -67,6 +99,9 @@ 'relpath:' - a path relative to cwd 'relre:' - a regexp that needn't match the start of a name 'set:' - a fileset expression + 'include:' - a file of patterns to read and include + 'subinclude:' - a file of patterns to match against files under + the same directory '' - a pattern of the specified default type """ @@ -76,15 +111,26 @@ self._anypats = bool(include or exclude) self._always = False self._pathrestricted = bool(include or exclude or patterns) + self._warn = warn + self._includeroots = set() + self._includedirs = set(['.']) + self._excluderoots = set() matchfns = [] if include: kindpats = self._normalize(include, 'glob', root, cwd, auditor) - self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)') + self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)', + listsubrepos, root) + self._includeroots.update(_roots(kindpats)) + self._includeroots.discard('.') + self._includedirs.update(util.dirs(self._includeroots)) matchfns.append(im) if exclude: kindpats = self._normalize(exclude, 'glob', root, cwd, auditor) - self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)') + self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)', + listsubrepos, root) + self._excluderoots.update(_roots(kindpats)) + self._excluderoots.discard('.') matchfns.append(lambda f: not em(f)) if exact: if isinstance(patterns, list): @@ -97,7 +143,8 @@ if not _kindpatsalwaysmatch(kindpats): self._files = _roots(kindpats) self._anypats = self._anypats or _anypats(kindpats) - self.patternspat, pm = _buildmatch(ctx, kindpats, '$') + self.patternspat, pm = _buildmatch(ctx, kindpats, '$', + listsubrepos, root) matchfns.append(pm) if not matchfns: @@ -113,7 +160,7 @@ return True self.matchfn = m - self._fmap = set(self._files) + self._fileroots = set(self._files) def __call__(self, fn): return self.matchfn(fn) @@ -161,21 +208,32 @@ @propertycache def _dirs(self): - return set(util.dirs(self._fmap)) | set(['.']) + return set(util.dirs(self._fileroots)) | set(['.']) def visitdir(self, dir): - '''Helps while traversing a directory tree. Returns the string 'all' if - the given directory and all subdirectories should be visited. Otherwise - returns True or False indicating whether the given directory should be - visited. If 'all' is returned, calling this method on a subdirectory - gives an undefined result.''' - if not self._fmap or self.exact(dir): - return 'all' - return dir in self._dirs + '''Decides whether a directory should be visited based on whether it + has potential matches in it or one of its subdirectories. This is + based on the match's primary, included, and excluded patterns. + + This function's behavior is undefined if it has returned False for + one of the dir's parent directories. + ''' + if dir in self._excluderoots: + return False + parentdirs = None + if (self._includeroots and dir not in self._includeroots and + dir not in self._includedirs): + parentdirs = list(util.finddirs(dir)) + if not any(parent in self._includeroots for parent in parentdirs): + return False + return (not self._fileroots or '.' in self._fileroots or + dir in self._fileroots or dir in self._dirs or + any(parentdir in self._fileroots + for parentdir in parentdirs or util.finddirs(dir))) def exact(self, f): '''Returns True if f is in .files().''' - return f in self._fmap + return f in self._fileroots def anypats(self): '''Matcher uses patterns or include/exclude.''' @@ -186,9 +244,20 @@ - optimization might be possible and necessary.''' return self._always + def ispartial(self): + '''True if the matcher won't always match. + + Although it's just the inverse of _always in this implementation, + an extenion such as narrowhg might make it return something + slightly different.''' + return not self._always + def isexact(self): return self.matchfn == self.exact + def prefix(self): + return not self.always() and not self.isexact() and not self.anypats() + def _normalize(self, patterns, default, root, cwd, auditor): '''Convert 'kind:pat' from the patterns list to tuples with kind and normalized and rooted patterns and with listfiles expanded.''' @@ -208,10 +277,25 @@ files = [f for f in files if f] except EnvironmentError: raise util.Abort(_("unable to read file list (%s)") % pat) - kindpats += self._normalize(files, default, root, cwd, auditor) + for k, p, source in self._normalize(files, default, root, cwd, + auditor): + kindpats.append((k, p, pat)) + continue + elif kind == 'include': + try: + includepats = readpatternfile(pat, self._warn) + for k, p, source in self._normalize(includepats, default, + root, cwd, auditor): + kindpats.append((k, p, source or pat)) + except util.Abort, inst: + raise util.Abort('%s: %s' % (pat, inst[0])) + except IOError, inst: + if self._warn: + self._warn(_("skipping unreadable pattern file " + "'%s': %s\n") % (pat, inst.strerror)) continue # else: re or relre - which cannot be normalized - kindpats.append((kind, pat)) + kindpats.append((kind, pat, '')) return kindpats def exact(root, cwd, files): @@ -264,11 +348,11 @@ # If the parent repo had a path to this subrepo and no patterns are # specified, this submatcher always matches. if not self._always and not matcher._anypats: - self._always = util.any(f == path for f in matcher._files) + self._always = any(f == path for f in matcher._files) self._anypats = matcher._anypats self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn) - self._fmap = set(self._files) + self._fileroots = set(self._files) def abs(self, f): return self._matcher.abs(self._path + "/" + f) @@ -285,26 +369,26 @@ """ def __init__(self, root, cwd, patterns, include, exclude, default, auditor, - ctx): + ctx, listsubrepos=False): init = super(icasefsmatcher, self).__init__ self._dsnormalize = ctx.repo().dirstate.normalize init(root, cwd, patterns, include, exclude, default, auditor=auditor, - ctx=ctx) + ctx=ctx, listsubrepos=listsubrepos) # m.exact(file) must be based off of the actual user input, otherwise # inexact case matches are treated as exact, and not noted without -v. if self._files: - self._fmap = set(_roots(self._kp)) + self._fileroots = set(_roots(self._kp)) def _normalize(self, patterns, default, root, cwd, auditor): self._kp = super(icasefsmatcher, self)._normalize(patterns, default, root, cwd, auditor) kindpats = [] - for kind, pats in self._kp: + for kind, pats, source in self._kp: if kind not in ('re', 'relre'): # regex can't be normalized pats = self._dsnormalize(pats) - kindpats.append((kind, pats)) + kindpats.append((kind, pats, source)) return kindpats def patkind(pattern, default=None): @@ -317,7 +401,7 @@ if ':' in pattern: kind, pat = pattern.split(':', 1) if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre', - 'listfile', 'listfile0', 'set'): + 'listfile', 'listfile0', 'set', 'include', 'subinclude'): return kind, pat return default, pattern @@ -418,24 +502,40 @@ return '.*' + pat return _globre(pat) + globsuffix -def _buildmatch(ctx, kindpats, globsuffix): +def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root): '''Return regexp string and a matcher function for kindpats. globsuffix is appended to the regexp of globs.''' - fset, kindpats = _expandsets(kindpats, ctx) - if not kindpats: - return "", fset.__contains__ + matchfuncs = [] + + subincludes, kindpats = _expandsubinclude(kindpats, root) + if subincludes: + def matchsubinclude(f): + for prefix, mf in subincludes: + if f.startswith(prefix) and mf(f[len(prefix):]): + return True + return False + matchfuncs.append(matchsubinclude) - regex, mf = _buildregexmatch(kindpats, globsuffix) + fset, kindpats = _expandsets(kindpats, ctx, listsubrepos) if fset: - return regex, lambda f: f in fset or mf(f) - return regex, mf + matchfuncs.append(fset.__contains__) + + regex = '' + if kindpats: + regex, mf = _buildregexmatch(kindpats, globsuffix) + matchfuncs.append(mf) + + if len(matchfuncs) == 1: + return regex, matchfuncs[0] + else: + return regex, lambda f: any(mf(f) for mf in matchfuncs) def _buildregexmatch(kindpats, globsuffix): """Build a match function from a list of kinds and kindpats, return regexp string and a matcher function.""" try: regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix) - for (k, p) in kindpats]) + for (k, p, s) in kindpats]) if len(regex) > 20000: raise OverflowError return regex, _rematcher(regex) @@ -450,25 +550,29 @@ regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix) return regex, lambda s: a(s) or b(s) except re.error: - for k, p in kindpats: + for k, p, s in kindpats: try: _rematcher('(?:%s)' % _regex(k, p, globsuffix)) except re.error: - raise util.Abort(_("invalid pattern (%s): %s") % (k, p)) + if s: + raise util.Abort(_("%s: invalid pattern (%s): %s") % + (s, k, p)) + else: + raise util.Abort(_("invalid pattern (%s): %s") % (k, p)) raise util.Abort(_("invalid pattern")) def _roots(kindpats): '''return roots and exact explicitly listed files from patterns - >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')]) + >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')]) ['g', 'g', '.'] - >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')]) + >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')]) ['r', 'p/p', '.'] - >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')]) + >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')]) ['.', '.', '.'] ''' r = [] - for kind, pat in kindpats: + for kind, pat, source in kindpats: if kind == 'glob': # find the non-glob prefix root = [] for p in pat.split('/'): @@ -483,6 +587,69 @@ return r def _anypats(kindpats): - for kind, pat in kindpats: + for kind, pat, source in kindpats: if kind in ('glob', 're', 'relglob', 'relre', 'set'): return True + +_commentre = None + +def readpatternfile(filepath, warn): + '''parse a pattern file, returning a list of + patterns. These patterns should be given to compile() + to be validated and converted into a match function. + + trailing white space is dropped. + the escape character is backslash. + comments start with #. + empty lines are skipped. + + lines can be of the following formats: + + syntax: regexp # defaults following lines to non-rooted regexps + syntax: glob # defaults following lines to non-rooted globs + re:pattern # non-rooted regular expression + glob:pattern # non-rooted glob + pattern # pattern of the current default type''' + + syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:', + 'include': 'include', 'subinclude': 'subinclude'} + syntax = 'relre:' + patterns = [] + + fp = open(filepath) + for line in fp: + if "#" in line: + global _commentre + if not _commentre: + _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*') + # remove comments prefixed by an even number of escapes + line = _commentre.sub(r'\1', line) + # fixup properly escaped comments that survived the above + line = line.replace("\\#", "#") + line = line.rstrip() + if not line: + continue + + if line.startswith('syntax:'): + s = line[7:].strip() + try: + syntax = syntaxes[s] + except KeyError: + if warn: + warn(_("%s: ignoring invalid syntax '%s'\n") % + (filepath, s)) + continue + + linesyntax = syntax + for s, rels in syntaxes.iteritems(): + if line.startswith(rels): + linesyntax = rels + line = line[len(rels):] + break + elif line.startswith(s+':'): + linesyntax = rels + line = line[len(s) + 1:] + break + patterns.append(linesyntax + line) + fp.close() + return patterns diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/merge.py --- a/mercurial/merge.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/merge.py Fri May 29 17:00:55 2015 -0500 @@ -605,7 +605,7 @@ # Consensus? if len(bids) == 1: # all bids are the same kind of method m, l = bids.items()[0] - if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1 + if all(a == l[0] for a in l[1:]): # len(bids) is > 1 repo.ui.note(" %s: consensus for %s\n" % (f, m)) actions[f] = l[0] continue @@ -617,7 +617,7 @@ # If there are gets and they all agree [how could they not?], do it. if 'g' in bids: ga0 = bids['g'][0] - if util.all(a == ga0 for a in bids['g'][1:]): + if all(a == ga0 for a in bids['g'][1:]): repo.ui.note(" %s: picking 'get' action\n" % f) actions[f] = ga0 continue diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/obsolete.py --- a/mercurial/obsolete.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/obsolete.py Fri May 29 17:00:55 2015 -0500 @@ -299,7 +299,7 @@ # Loop on markers stop = len(data) - _fm1fsize - ufixed = util.unpacker(_fm1fixed) + ufixed = struct.Struct(_fm1fixed).unpack while off <= stop: # read fixed part @@ -718,7 +718,7 @@ """List markers over pushkey""" if not repo.obsstore: return {} - return _pushkeyescape(repo.obsstore) + return _pushkeyescape(sorted(repo.obsstore)) def pushmarker(repo, key, old, new): """Push markers over pushkey""" @@ -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 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') diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/parser.py --- a/mercurial/parser.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/parser.py Fri May 29 17:00:55 2015 -0500 @@ -27,10 +27,7 @@ def _advance(self): 'advance the tokenizer' t = self.current - try: - self.current = self._iter.next() - except StopIteration: - pass + self.current = next(self._iter, None) return t def _match(self, m, pos): 'make sure the tokenizer matches an end condition' @@ -96,3 +93,95 @@ if self._methods: return self.eval(t) return t + +def _prettyformat(tree, leafnodes, level, lines): + if not isinstance(tree, tuple) or tree[0] in leafnodes: + lines.append((level, str(tree))) + else: + lines.append((level, '(%s' % tree[0])) + for s in tree[1:]: + _prettyformat(s, leafnodes, level + 1, lines) + lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')] + +def prettyformat(tree, leafnodes): + lines = [] + _prettyformat(tree, leafnodes, 0, lines) + output = '\n'.join((' ' * l + s) for l, s in lines) + return output + +def simplifyinfixops(tree, targetnodes): + """Flatten chained infix operations to reduce usage of Python stack + + >>> def f(tree): + ... print prettyformat(simplifyinfixops(tree, ('or',)), ('symbol',)) + >>> f(('or', + ... ('or', + ... ('symbol', '1'), + ... ('symbol', '2')), + ... ('symbol', '3'))) + (or + ('symbol', '1') + ('symbol', '2') + ('symbol', '3')) + >>> f(('func', + ... ('symbol', 'p1'), + ... ('or', + ... ('or', + ... ('func', + ... ('symbol', 'sort'), + ... ('list', + ... ('or', + ... ('or', + ... ('symbol', '1'), + ... ('symbol', '2')), + ... ('symbol', '3')), + ... ('negate', + ... ('symbol', 'rev')))), + ... ('and', + ... ('symbol', '4'), + ... ('group', + ... ('or', + ... ('or', + ... ('symbol', '5'), + ... ('symbol', '6')), + ... ('symbol', '7'))))), + ... ('symbol', '8')))) + (func + ('symbol', 'p1') + (or + (func + ('symbol', 'sort') + (list + (or + ('symbol', '1') + ('symbol', '2') + ('symbol', '3')) + (negate + ('symbol', 'rev')))) + (and + ('symbol', '4') + (group + (or + ('symbol', '5') + ('symbol', '6') + ('symbol', '7')))) + ('symbol', '8'))) + """ + if not isinstance(tree, tuple): + return tree + op = tree[0] + if op not in targetnodes: + return (op,) + tuple(simplifyinfixops(x, targetnodes) for x in tree[1:]) + + # walk down left nodes taking each right node. no recursion to left nodes + # because infix operators are left-associative, i.e. left tree is deep. + # e.g. '1 + 2 + 3' -> (+ (+ 1 2) 3) -> (+ 1 2 3) + simplified = [] + x = tree + while x[0] == op: + l, r = x[1:] + simplified.append(simplifyinfixops(r, targetnodes)) + x = l + simplified.append(simplifyinfixops(x, targetnodes)) + simplified.append(op) + return tuple(reversed(simplified)) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/parsers.c --- a/mercurial/parsers.c Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/parsers.c Fri May 29 17:00:55 2015 -0500 @@ -741,6 +741,22 @@ return PyString_AS_STRING(self->data) + pos * v1_hdrsize; } +static inline void index_get_parents(indexObject *self, Py_ssize_t rev, + int *ps) +{ + if (rev >= self->length - 1) { + PyObject *tuple = PyList_GET_ITEM(self->added, + rev - self->length + 1); + ps[0] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 5)); + ps[1] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 6)); + } else { + const char *data = index_deref(self, rev); + ps[0] = getbe32(data + 24); + ps[1] = getbe32(data + 28); + } +} + + /* * RevlogNG format (all in big endian, data may be inlined): * 6 bytes: offset @@ -1071,23 +1087,21 @@ phases[i] = phases[parent_2]; } -static PyObject *compute_phases(indexObject *self, PyObject *args) +static PyObject *compute_phases_map_sets(indexObject *self, PyObject *args) { PyObject *roots = Py_None; + PyObject *ret = NULL; PyObject *phaseslist = NULL; PyObject *phaseroots = NULL; - PyObject *rev = NULL; - PyObject *p1 = NULL; - PyObject *p2 = NULL; - Py_ssize_t addlen = self->added ? PyList_GET_SIZE(self->added) : 0; + PyObject *phaseset = NULL; + PyObject *phasessetlist = NULL; Py_ssize_t len = index_length(self) - 1; Py_ssize_t numphase = 0; Py_ssize_t minrevallphases = 0; Py_ssize_t minrevphase = 0; Py_ssize_t i = 0; - int parent_1, parent_2; char *phases = NULL; - const char *data; + long phase; if (!PyArg_ParseTuple(args, "O", &roots)) goto release_none; @@ -1100,50 +1114,71 @@ /* Put the phase information of all the roots in phases */ numphase = PyList_GET_SIZE(roots)+1; minrevallphases = len + 1; + phasessetlist = PyList_New(numphase); + if (phasessetlist == NULL) + goto release_none; + + PyList_SET_ITEM(phasessetlist, 0, Py_None); + Py_INCREF(Py_None); + for (i = 0; i < numphase-1; i++) { phaseroots = PyList_GET_ITEM(roots, i); + phaseset = PySet_New(NULL); + if (phaseset == NULL) + goto release_phasesetlist; + PyList_SET_ITEM(phasessetlist, i+1, phaseset); if (!PyList_Check(phaseroots)) - goto release_phases; + goto release_phasesetlist; minrevphase = add_roots_get_min(self, phaseroots, i+1, phases); if (minrevphase == -2) /* Error from add_roots_get_min */ - goto release_phases; + goto release_phasesetlist; minrevallphases = MIN(minrevallphases, minrevphase); } /* Propagate the phase information from the roots to the revs */ if (minrevallphases != -1) { - for (i = minrevallphases; i < self->raw_length; i++) { - data = index_deref(self, i); - set_phase_from_parents(phases, getbe32(data+24), getbe32(data+28), i); - } - for (i = 0; i < addlen; i++) { - rev = PyList_GET_ITEM(self->added, i); - p1 = PyTuple_GET_ITEM(rev, 5); - p2 = PyTuple_GET_ITEM(rev, 6); - if (!PyInt_Check(p1) || !PyInt_Check(p2)) { - PyErr_SetString(PyExc_TypeError, "revlog parents are invalid"); - goto release_phases; - } - parent_1 = (int)PyInt_AS_LONG(p1); - parent_2 = (int)PyInt_AS_LONG(p2); - set_phase_from_parents(phases, parent_1, parent_2, i+self->raw_length); + int parents[2]; + for (i = minrevallphases; i < len; i++) { + index_get_parents(self, i, parents); + set_phase_from_parents(phases, parents[0], parents[1], i); } } /* Transform phase list to a python list */ phaseslist = PyList_New(len); if (phaseslist == NULL) - goto release_phases; - for (i = 0; i < len; i++) - PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phases[i])); + goto release_phasesetlist; + for (i = 0; i < len; i++) { + phase = phases[i]; + /* We only store the sets of phase for non public phase, the public phase + * is computed as a difference */ + if (phase != 0) { + phaseset = PyList_GET_ITEM(phasessetlist, phase); + PySet_Add(phaseset, PyInt_FromLong(i)); + } + PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phase)); + } + ret = PyList_New(2); + if (ret == NULL) + goto release_phaseslist; + PyList_SET_ITEM(ret, 0, phaseslist); + PyList_SET_ITEM(ret, 1, phasessetlist); + /* We don't release phaseslist and phasessetlist as we return them to + * python */ + goto release_phases; + +release_phaseslist: + Py_XDECREF(phaseslist); +release_phasesetlist: + Py_XDECREF(phasessetlist); release_phases: free(phases); release_none: - return phaseslist; + return ret; } static PyObject *index_headrevs(indexObject *self, PyObject *args) { - Py_ssize_t i, len, addlen; + Py_ssize_t i, j, len; char *nothead = NULL; PyObject *heads = NULL; PyObject *filter = NULL; @@ -1186,9 +1221,9 @@ if (nothead == NULL) goto bail; - for (i = 0; i < self->raw_length; i++) { - const char *data; - int parent_1, parent_2, isfiltered; + for (i = 0; i < len; i++) { + int isfiltered; + int parents[2]; isfiltered = check_filter(filter, i); if (isfiltered == -1) { @@ -1202,49 +1237,11 @@ continue; } - data = index_deref(self, i); - parent_1 = getbe32(data + 24); - parent_2 = getbe32(data + 28); - - if (parent_1 >= 0) - nothead[parent_1] = 1; - if (parent_2 >= 0) - nothead[parent_2] = 1; - } - - addlen = self->added ? PyList_GET_SIZE(self->added) : 0; - - for (i = 0; i < addlen; i++) { - PyObject *rev = PyList_GET_ITEM(self->added, i); - PyObject *p1 = PyTuple_GET_ITEM(rev, 5); - PyObject *p2 = PyTuple_GET_ITEM(rev, 6); - long parent_1, parent_2; - int isfiltered; - - if (!PyInt_Check(p1) || !PyInt_Check(p2)) { - PyErr_SetString(PyExc_TypeError, - "revlog parents are invalid"); - goto bail; + index_get_parents(self, i, parents); + for (j = 0; j < 2; j++) { + if (parents[j] >= 0) + nothead[parents[j]] = 1; } - - isfiltered = check_filter(filter, i); - if (isfiltered == -1) { - PyErr_SetString(PyExc_TypeError, - "unable to check filter"); - goto bail; - } - - if (isfiltered) { - nothead[i] = 1; - continue; - } - - parent_1 = PyInt_AS_LONG(p1); - parent_2 = PyInt_AS_LONG(p2); - if (parent_1 >= 0) - nothead[parent_1] = 1; - if (parent_2 >= 0) - nothead[parent_2] = 1; } for (i = 0; i < len; i++) { @@ -1654,20 +1651,6 @@ } } -static inline void index_get_parents(indexObject *self, int rev, int *ps) -{ - if (rev >= self->length - 1) { - PyObject *tuple = PyList_GET_ITEM(self->added, - rev - self->length + 1); - ps[0] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 5)); - ps[1] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 6)); - } else { - const char *data = index_deref(self, rev); - ps[0] = getbe32(data + 24); - ps[1] = getbe32(data + 28); - } -} - typedef uint64_t bitmask; /* @@ -2278,8 +2261,8 @@ "clear the index caches"}, {"get", (PyCFunction)index_m_get, METH_VARARGS, "get an index entry"}, - {"computephases", (PyCFunction)compute_phases, METH_VARARGS, - "compute phases"}, + {"computephasesmapsets", (PyCFunction)compute_phases_map_sets, + METH_VARARGS, "compute phases"}, {"headrevs", (PyCFunction)index_headrevs, METH_VARARGS, "get head revisions"}, /* Can do filtering since 3.2 */ {"headrevsfiltered", (PyCFunction)index_headrevs, METH_VARARGS, diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/patch.py --- a/mercurial/patch.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/patch.py Fri May 29 17:00:55 2015 -0500 @@ -6,6 +6,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import collections import cStringIO, email, os, errno, re, posixpath, copy import tempfile, zlib, shutil # On python2.4 you have to import these by name or they fail to @@ -15,7 +16,6 @@ from i18n import _ from node import hex, short -import cStringIO import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error import pathutil @@ -830,7 +830,7 @@ self.hunks = [] def binary(self): - return util.any(h.startswith('index ') for h in self.header) + return any(h.startswith('index ') for h in self.header) def pretty(self, fp): for h in self.header: @@ -853,7 +853,7 @@ fp.write(''.join(self.header)) def allhunks(self): - return util.any(self.allhunks_re.match(h) for h in self.header) + return any(self.allhunks_re.match(h) for h in self.header) def files(self): match = self.diffgit_re.match(self.header[0]) @@ -872,7 +872,7 @@ return '
' % (' '.join(map(repr, self.files()))) def isnewfile(self): - return util.any(self.newfile_re.match(h) for h in self.header) + return any(self.newfile_re.match(h) for h in self.header) def special(self): # Special files are shown only at the header level and not at the hunk @@ -885,7 +885,7 @@ nocontent = len(self.header) == 2 emptynewfile = self.isnewfile() and nocontent return emptynewfile or \ - util.any(self.special_re.match(h) for h in self.header) + any(self.special_re.match(h) for h in self.header) class recordhunk(object): """patch hunk @@ -948,8 +948,10 @@ def __repr__(self): return '' % (self.filename(), self.fromline) -def filterpatch(ui, headers): +def filterpatch(ui, headers, operation=None): """Interactively filter patch chunks into applied-only chunks""" + if operation is None: + operation = _('record') def prompt(skipfile, skipall, query, chunk): """prompt query, and process base inputs @@ -2102,7 +2104,7 @@ def lrugetfilectx(): cache = {} - order = util.deque() + order = collections.deque() def getfilectx(f, ctx): fctx = ctx.filectx(f, filelog=cache.get(f)) if f not in cache: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/pathutil.py --- a/mercurial/pathutil.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/pathutil.py Fri May 29 17:00:55 2015 -0500 @@ -1,4 +1,4 @@ -import os, errno, stat +import os, errno, stat, posixpath import encoding import util @@ -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 @@ -175,3 +187,9 @@ return path + os.sep else: return path + +# forward two methods from posixpath that do what we need, but we'd +# rather not let our internals know that we're thinking in posix terms +# - instead we'll let them be oblivious. +join = posixpath.join +dirname = posixpath.dirname diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/phases.py --- a/mercurial/phases.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/phases.py Fri May 29 17:00:55 2015 -0500 @@ -155,6 +155,7 @@ # Cheap trick to allow shallow-copy without copy module self.phaseroots, self.dirty = _readroots(repo, phasedefaults) self._phaserevs = None + self._phasesets = None self.filterunknown(repo) self.opener = repo.svfs @@ -177,7 +178,7 @@ nativeroots = [] for phase in trackedphases: nativeroots.append(map(repo.changelog.rev, self.phaseroots[phase])) - return repo.changelog.computephases(nativeroots) + return repo.changelog.computephasesmapsets(nativeroots) def _computephaserevspure(self, repo): repo = repo.unfiltered() @@ -199,7 +200,8 @@ 'nativephaseskillswitch'): self._computephaserevspure(repo) else: - self._phaserevs = self._getphaserevsnative(repo) + res = self._getphaserevsnative(repo) + self._phaserevs, self._phasesets = res except AttributeError: self._computephaserevspure(repo) return self._phaserevs diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/repair.py --- a/mercurial/repair.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/repair.py Fri May 29 17:00:55 2015 -0500 @@ -7,7 +7,7 @@ # GNU General Public License version 2 or any later version. from mercurial import changegroup, exchange, util, bundle2 -from mercurial.node import short, hex +from mercurial.node import short from mercurial.i18n import _ import errno @@ -34,9 +34,7 @@ vfs.mkdir(backupdir) # Include a hash of all the nodes in the filename for uniqueness - hexbases = (hex(n) for n in bases) - hexheads = (hex(n) for n in heads) - allcommits = repo.set('%ls::%ls', hexbases, hexheads) + allcommits = repo.set('%ln::%ln', bases, heads) allhashes = sorted(c.hex() for c in allcommits) totalhash = util.sha1(''.join(allhashes)).hexdigest() name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix) @@ -151,6 +149,12 @@ mfst = repo.manifest + curtr = repo.currenttransaction() + if curtr is not None: + del curtr # avoid carrying reference to transaction for nothing + msg = _('programming error: cannot strip from inside a transaction') + raise util.Abort(msg, hint=_('contact your extension maintainer')) + tr = repo.transaction("strip") offset = len(tr.entries) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/repoview.py --- a/mercurial/repoview.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/repoview.py Fri May 29 17:00:55 2015 -0500 @@ -115,16 +115,15 @@ """ wlock = fh = None try: - try: - wlock = repo.wlock(wait=False) - # write cache to file - newhash = cachehash(repo, hideable) - fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True) - _writehiddencache(fh, newhash, hidden) - except (IOError, OSError): - repo.ui.debug('error writing hidden changesets cache') - except error.LockHeld: - repo.ui.debug('cannot obtain lock to write hidden changesets cache') + wlock = repo.wlock(wait=False) + # write cache to file + newhash = cachehash(repo, hideable) + fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True) + _writehiddencache(fh, newhash, hidden) + except (IOError, OSError): + repo.ui.debug('error writing hidden changesets cache') + except error.LockHeld: + repo.ui.debug('cannot obtain lock to write hidden changesets cache') finally: if fh: fh.close() @@ -197,7 +196,7 @@ Secret and hidden changeset should not pretend to be here.""" assert not repo.changelog.filteredrevs # fast check to avoid revset call on huge repo - if util.any(repo._phasecache.phaseroots[1:]): + if any(repo._phasecache.phaseroots[1:]): getphase = repo._phasecache.phase maymutable = filterrevs(repo, 'base') return frozenset(r for r in maymutable if getphase(repo, r)) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/revlog.py --- a/mercurial/revlog.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/revlog.py Fri May 29 17:00:55 2015 -0500 @@ -12,6 +12,7 @@ """ # import stuff from node for others to import from revlog +import collections from node import bin, hex, nullid, nullrev from i18n import _ import ancestor, mdiff, parsers, error, util, templatefilters @@ -485,7 +486,7 @@ # take all ancestors from heads that aren't in has missing = set() - visit = util.deque(r for r in heads if r not in has) + visit = collections.deque(r for r in heads if r not in has) while visit: r = visit.popleft() if r in missing: diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/revset.py --- a/mercurial/revset.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/revset.py Fri May 29 17:00:55 2015 -0500 @@ -25,23 +25,22 @@ 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) + inputrev = next(irevs, None) + if inputrev is not None: + heapq.heappush(h, -inputrev) seen = set() while h: current = -heapq.heappop(h) + if current == inputrev: + inputrev = next(irevs, None) + if inputrev is not None: + heapq.heappush(h, -inputrev) 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]: @@ -335,11 +334,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) @@ -362,10 +356,9 @@ def andset(repo, subset, x, y): return getset(repo, getset(repo, subset, x), y) -def orset(repo, subset, x, y): - xl = getset(repo, subset, x) - yl = getset(repo, subset - xl, y) - return xl + yl +def orset(repo, subset, *xs): + rs = [getset(repo, subset, x) for x in xs] + return _combinesets(rs) def notset(repo, subset, x): return subset - getset(repo, subset, x) @@ -1128,7 +1121,7 @@ def matches(r): c = repo[r] - return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(), + return any(kw in encoding.lower(t) for t in c.files() + [c.user(), c.description()]) return subset.filter(matches) @@ -1152,12 +1145,11 @@ result = [] it = iter(os) for x in xrange(lim): - try: - y = it.next() - if y in ss: - result.append(y) - except (StopIteration): + y = next(it, None) + if y is None: break + elif y in ss: + result.append(y) return baseset(result) def last(repo, subset, x): @@ -1180,12 +1172,11 @@ result = [] it = iter(os) for x in xrange(lim): - try: - y = it.next() - if y in ss: - result.append(y) - except (StopIteration): + y = next(it, None) + if y is None: break + elif y in ss: + result.append(y) return baseset(result) def maxrev(repo, subset, x): @@ -1487,6 +1478,20 @@ except error.RepoLookupError: return baseset() +# for internal use +def _notpublic(repo, subset, x): + getargs(x, 0, 0, "_notpublic takes no arguments") + if repo._phasecache._phasesets: + s = set() + for u in repo._phasecache._phasesets[1:]: + s.update(u) + return subset & s + else: + phase = repo._phasecache.phase + target = phases.public + condition = lambda r: phase(repo, r) != target + return subset.filter(condition, cache=False) + def public(repo, subset, x): """``public()`` Changeset in public phase.""" @@ -1685,7 +1690,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 @@ -1788,7 +1793,7 @@ return s.added or s.modified or s.removed if s.added: - return util.any(submatches(c.substate.keys())) + return any(submatches(c.substate.keys())) if s.modified: subs = set(c.p1().substate.keys()) @@ -1799,7 +1804,7 @@ return True if s.removed: - return util.any(submatches(c.p1().substate.keys())) + return any(submatches(c.p1().substate.keys())) return False @@ -1915,9 +1920,26 @@ s = getstring(x, "internal error") if not s: return baseset() - ls = [repo[r].rev() for r in s.split('\0')] - s = subset - return baseset([r for r in ls if r in s]) + # remove duplicates here. it's difficult for caller to deduplicate sets + # because different symbols can point to the same rev. + cl = repo.changelog + ls = [] + seen = set() + for t in s.split('\0'): + try: + # fast path for integer revision + r = int(t) + if str(r) != t or r not in cl: + raise ValueError + except ValueError: + r = repo[t].rev() + if r in seen: + continue + if (r in subset + or r == node.nullrev and isinstance(subset, fullreposet)): + ls.append(r) + seen.add(r) + return baseset(ls) # for internal use def _intlist(repo, subset, x): @@ -1993,6 +2015,7 @@ "parents": parents, "present": present, "public": public, + "_notpublic": _notpublic, "remote": remote, "removes": removes, "rev": rev, @@ -2067,6 +2090,7 @@ "parents", "present", "public", + "_notpublic", "remote", "removes", "rev", @@ -2089,7 +2113,7 @@ "range": rangeset, "dagrange": dagrange, "string": stringset, - "symbol": symbolset, + "symbol": stringset, "and": andset, "or": orset, "not": notset, @@ -2098,7 +2122,6 @@ "ancestor": ancestorspec, "parent": parentspec, "parentpost": p1, - "only": only, } def optimize(x, small): @@ -2153,14 +2176,45 @@ return w, (op, tb, ta) return w, (op, ta, tb) elif op == 'or': - wa, ta = optimize(x[1], False) - wb, tb = optimize(x[2], False) - if wb < wa: - wb, wa = wa, wb - return max(wa, wb), (op, ta, tb) + # fast path for machine-generated expression, that is likely to have + # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()' + ws, ts, ss = [], [], [] + def flushss(): + if not ss: + return + if len(ss) == 1: + w, t = ss[0] + else: + s = '\0'.join(t[1] for w, t in ss) + y = ('func', ('symbol', '_list'), ('string', s)) + w, t = optimize(y, False) + ws.append(w) + ts.append(t) + del ss[:] + for y in x[1:]: + w, t = optimize(y, False) + if t[0] == 'string' or t[0] == 'symbol': + ss.append((w, t)) + continue + flushss() + ws.append(w) + ts.append(t) + flushss() + if len(ts) == 1: + return ws[0], ts[0] # 'or' operation is fully optimized out + # we can't reorder trees by weight because it would change the order. + # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a") + # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0])) + return max(ws), (op,) + tuple(ts) elif op == 'not': - o = optimize(x[1], not small) - return o[0], (op, o[1]) + # Optimize not public() to _notpublic() because we have a fast version + if x[1] == ('func', ('symbol', 'public'), None): + newsym = ('func', ('symbol', '_notpublic'), None) + o = optimize(newsym, not small) + return o[0], o[1] + else: + o = optimize(x[1], not small) + return o[0], (op, o[1]) elif op == 'parentpost': o = optimize(x[1], small) return o[0], (op, o[1]) @@ -2369,7 +2423,7 @@ tree, pos = p.parse(defn) if pos != len(defn): raise error.ParseError(_('invalid token'), pos) - return tree + return parser.simplifyinfixops(tree, ('or',)) class revsetalias(object): # whether own `error` information is already shown or not. @@ -2497,7 +2551,10 @@ def parse(spec, lookup=None): p = parser.parser(tokenize, elements) - return p.parse(spec, lookup=lookup) + tree, pos = p.parse(spec, lookup=lookup) + if pos != len(spec): + raise error.ParseError(_("invalid token"), pos) + return parser.simplifyinfixops(tree, ('or',)) def posttreebuilthook(tree, repo): # hook for extensions to execute code on the optimized tree @@ -2509,9 +2566,7 @@ lookup = None if repo: lookup = repo.__contains__ - tree, pos = parse(spec, lookup) - if (pos != len(spec)): - raise error.ParseError(_("invalid token"), pos) + tree = parse(spec, lookup) if ui: tree = findaliases(ui, tree, showwarning=ui.warn) tree = foldconcat(tree) @@ -2622,19 +2677,7 @@ return ret def prettyformat(tree): - def _prettyformat(tree, level, lines): - if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'): - lines.append((level, str(tree))) - else: - lines.append((level, '(%s' % tree[0])) - for s in tree[1:]: - _prettyformat(s, level + 1, lines) - lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')] - - lines = [] - _prettyformat(tree, 0, lines) - output = '\n'.join((' '*l + s) for l, s in lines) - return output + return parser.prettyformat(tree, ('string', 'symbol')) def depth(tree): if isinstance(tree, tuple): @@ -2940,6 +2983,56 @@ def __repr__(self): return '<%s %r>' % (type(self).__name__, self._subset) +# this function will be removed, or merged to addset or orset, when +# - scmutil.revrange() can be rewritten to not combine calculated smartsets +# - or addset can handle more than two sets without balanced tree +def _combinesets(subsets): + """Create balanced tree of addsets representing union of given sets""" + if not subsets: + return baseset() + if len(subsets) == 1: + return subsets[0] + p = len(subsets) // 2 + xs = _combinesets(subsets[:p]) + ys = _combinesets(subsets[p:]) + return addset(xs, ys) + +def _iterordered(ascending, iter1, iter2): + """produce an ordered iteration from two iterators with the same order + + The ascending is used to indicated the iteration direction. + """ + choice = max + if ascending: + choice = min + + val1 = None + val2 = None + try: + # Consume both iterators in an ordered way until one is empty + while True: + if val1 is None: + val1 = iter1.next() + if val2 is None: + val2 = iter2.next() + next = choice(val1, val2) + yield next + if val1 == next: + val1 = None + if val2 == next: + val2 = None + except StopIteration: + # Flush any remaining values and consume the other one + it = iter2 + if val1 is not None: + yield val1 + it = iter1 + elif val2 is not None: + # might have been equality and both are empty + yield val2 + for val in it: + yield val + class addset(abstractsmartset): """Represent the addition of two sets @@ -2949,6 +3042,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) + 5 + >>> [x for x in rs], [x for x in rs.fastasc()] + ([0, 2, 3, 4, 5], [0, 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) + 5 + >>> [x for x in rs], [x for x in rs.fastdesc()] + ([5, 4, 3, 2, 0], [5, 4, 3, 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] + [0, 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] + [5, 4, 3, 2, 0] """ def __init__(self, revs1, revs2, ascending=None): self._r1 = revs1 @@ -2967,10 +3118,10 @@ @util.propertycache def _list(self): if not self._genlist: - self._genlist = baseset(self._iterator()) + self._genlist = baseset(iter(self)) return self._genlist - def _iterator(self): + def __iter__(self): """Iterate over both collections without repeating elements If the ascending attribute is not set, iterate over the first one and @@ -2981,35 +3132,41 @@ same time, yielding only one value at a time in the given order. """ if self._ascending is None: - def gen(): + if self._genlist: + return iter(self._genlist) + def arbitraryordergen(): for r in self._r1: yield r inr1 = self._r1.__contains__ for r in self._r2: if not inr1(r): yield r - gen = gen() - else: - iter1 = iter(self._r1) - iter2 = iter(self._r2) - gen = self._iterordered(self._ascending, iter1, iter2) - return gen - - def __iter__(self): - if self._ascending is None: - if self._genlist: - return iter(self._genlist) - return iter(self._iterator()) + return arbitraryordergen() + # try to use our own fast iterator if it exists self._trysetasclist() if self._ascending: - it = self.fastasc + attr = 'fastasc' else: - it = self.fastdesc - if it is None: - # consume the gen and try again - self._list - return iter(self) - return it() + attr = 'fastdesc' + it = getattr(self, attr) + if it is not None: + return it() + # maybe half of the component supports fast + # get iterator for _r1 + iter1 = getattr(self._r1, attr) + if iter1 is None: + # let's avoid side effect (not sure it matters) + iter1 = iter(sorted(self._r1, reverse=not self._ascending)) + else: + iter1 = iter1() + # get iterator for _r2 + iter2 = getattr(self._r2, attr) + if iter2 is None: + # let's avoid side effect (not sure it matters) + iter2 = iter(sorted(self._r2, reverse=not self._ascending)) + else: + iter2 = iter2() + return _iterordered(self._ascending, iter1, iter2) def _trysetasclist(self): """populate the _asclist attribute if possible and necessary""" @@ -3025,7 +3182,7 @@ iter2 = self._r2.fastasc if None in (iter1, iter2): return None - return lambda: self._iterordered(True, iter1(), iter2()) + return lambda: _iterordered(True, iter1(), iter2()) @property def fastdesc(self): @@ -3036,48 +3193,7 @@ iter2 = self._r2.fastdesc if None in (iter1, iter2): return None - return lambda: self._iterordered(False, iter1(), iter2()) - - def _iterordered(self, ascending, iter1, iter2): - """produce an ordered iteration from two iterators with the same order - - The ascending is used to indicated the iteration direction. - """ - choice = max - if ascending: - choice = min - - val1 = None - val2 = None - - choice = max - if ascending: - choice = min - try: - # Consume both iterators in an ordered way until one is - # empty - while True: - if val1 is None: - val1 = iter1.next() - if val2 is None: - val2 = iter2.next() - next = choice(val1, val2) - yield next - if val1 == next: - val1 = None - if val2 == next: - val2 = None - except StopIteration: - # Flush any remaining values and consume the other one - it = iter2 - if val1 is not None: - yield val1 - it = iter1 - elif val2 is not None: - # might have been equality and both are empty - yield val2 - for val in it: - yield val + return lambda: _iterordered(False, iter1(), iter2()) def __contains__(self, x): return x in self._r1 or x in self._r2 @@ -3144,7 +3260,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 @@ -3268,9 +3389,7 @@ for x in self._consumegen(): pass return self.first() - if self: - return it().next() - return None + return next(it(), None) def last(self): if self._ascending: @@ -3282,9 +3401,7 @@ for x in self._consumegen(): pass return self.first() - if self: - return it().next() - return None + return next(it(), None) def __repr__(self): d = {False: '-', True: '+'}[self._ascending] diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/scmutil.py --- a/mercurial/scmutil.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/scmutil.py Fri May 29 17:00:55 2015 -0500 @@ -803,7 +803,7 @@ pats = expandpats(pats or []) m = ctx.match(pats, opts.get('include'), opts.get('exclude'), - default) + default, listsubrepos=opts.get('subrepos')) def badfn(f, msg): ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg)) m.bad = badfn @@ -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 diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/setdiscovery.py --- a/mercurial/setdiscovery.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/setdiscovery.py Fri May 29 17:00:55 2015 -0500 @@ -40,6 +40,7 @@ classified with it (since all ancestors or descendants will be marked as well). """ +import collections from node import nullid, nullrev from i18n import _ import random @@ -65,7 +66,7 @@ else: heads = dag.heads() dist = {} - visit = util.deque(heads) + visit = collections.deque(heads) seen = set() factor = 1 while visit: @@ -168,7 +169,7 @@ ui.debug("all remote heads known locally\n") return (srvheadhashes, False, srvheadhashes,) - if sample and len(ownheads) <= initialsamplesize and util.all(yesno): + if sample and len(ownheads) <= initialsamplesize and all(yesno): ui.note(_("all local heads known remotely\n")) ownheadhashes = dag.externalizeall(ownheads) return (ownheadhashes, True, srvheadhashes,) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/sshpeer.py --- a/mercurial/sshpeer.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/sshpeer.py Fri May 29 17:00:55 2015 -0500 @@ -27,6 +27,15 @@ return s return "'%s'" % s.replace("'", "'\\''") +def _forwardoutput(ui, pipe): + """display all data currently available on pipe as remote output. + + This is non blocking.""" + s = util.readpipe(pipe) + if s: + for l in s.splitlines(): + ui.status(_("remote: "), l, '\n') + class sshpeer(wireproto.wirepeer): def __init__(self, ui, path, create=False): self._url = path @@ -108,10 +117,7 @@ return self._caps def readerr(self): - s = util.readpipe(self.pipee) - if s: - for l in s.splitlines(): - self.ui.status(_("remote: "), l, '\n') + _forwardoutput(self.ui, self.pipee) def _abort(self, exception): self.cleanup() @@ -195,16 +201,9 @@ def _recv(self): l = self.pipei.readline() if l == '\n': - err = [] - while True: - line = self.pipee.readline() - if line == '-\n': - break - err.extend([line]) - if len(err) > 0: - # strip the trailing newline added to the last line server-side - err[-1] = err[-1][:-1] - self._abort(error.OutOfBandError(*err)) + self.readerr() + msg = _('check previous remote output') + self._abort(error.OutOfBandError(hint=msg)) self.readerr() try: l = int(l) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/statichttprepo.py Fri May 29 17:00:55 2015 -0500 @@ -32,11 +32,7 @@ try: f = self.opener.open(req) data = f.read() - # Python 2.6+ defines a getcode() function, and 2.4 and - # 2.5 appear to always have an undocumented code attribute - # set. If we can't read either of those, fall back to 206 - # and hope for the best. - code = getattr(f, 'getcode', lambda : getattr(f, 'code', 206))() + code = f.code except urllib2.HTTPError, inst: num = inst.code == 404 and errno.ENOENT or None raise IOError(num, inst) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/store.py --- a/mercurial/store.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/store.py Fri May 29 17:00:55 2015 -0500 @@ -187,7 +187,7 @@ def _hashencode(path, dotencode): digest = _sha(path).hexdigest() - le = lowerencode(path).split('/')[1:] + le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/' parts = _auxencode(le, dotencode) basename = parts[-1] _root, ext = os.path.splitext(basename) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/subrepo.py --- a/mercurial/subrepo.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/subrepo.py Fri May 29 17:00:55 2015 -0500 @@ -500,7 +500,11 @@ """return file flags""" return '' - def printfiles(self, ui, m, fm, fmt): + def getfileset(self, expr): + """Resolve the fileset expression for this repo""" + return set() + + def printfiles(self, ui, m, fm, fmt, subrepos): """handle the files command for this subrepo""" return 1 @@ -592,21 +596,14 @@ def _storeclean(self, path): clean = True itercache = self._calcstorehash(path) - try: - for filehash in self._readstorehashcache(path): - if filehash != itercache.next(): - clean = False - break - except StopIteration: + for filehash in self._readstorehashcache(path): + if filehash != next(itercache, None): + clean = False + break + if clean: + # if not empty: # the cached and current pull states have a different size - clean = False - if clean: - try: - itercache.next() - # the cached and current pull states have a different size - clean = False - except StopIteration: - pass + clean = next(itercache, None) is None return clean def _calcstorehash(self, remotepath): @@ -907,7 +904,7 @@ return ctx.flags(name) @annotatesubrepoerror - def printfiles(self, ui, m, fm, fmt): + def printfiles(self, ui, m, fm, fmt, subrepos): # If the parent context is a workingctx, use the workingctx here for # consistency. if self._ctx.rev() is None: @@ -915,7 +912,27 @@ else: rev = self._state[1] ctx = self._repo[rev] - return cmdutil.files(ui, ctx, m, fm, fmt, True) + return cmdutil.files(ui, ctx, m, fm, fmt, subrepos) + + @annotatesubrepoerror + def getfileset(self, expr): + if self._ctx.rev() is None: + ctx = self._repo[None] + else: + rev = self._state[1] + ctx = self._repo[rev] + + files = ctx.getfileset(expr) + + for subpath in ctx.substate: + sub = ctx.sub(subpath) + + try: + files.extend(subpath + '/' + f for f in sub.getfileset(expr)) + except error.LookupError: + self.ui.status(_("skipping missing subrepository: %s\n") + % self.wvfs.reljoin(reporelpath(self), subpath)) + return files def walk(self, match): ctx = self._repo[None] @@ -1711,7 +1728,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) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/tags.py --- a/mercurial/tags.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/tags.py Fri May 29 17:00:55 2015 -0500 @@ -509,26 +509,25 @@ return try: + f = repo.vfs.open(_fnodescachefile, 'ab') try: - f = repo.vfs.open(_fnodescachefile, 'ab') - try: - # if the file has been truncated - actualoffset = f.tell() - if actualoffset < self._dirtyoffset: - self._dirtyoffset = actualoffset - data = self._raw[self._dirtyoffset:] - f.seek(self._dirtyoffset) - f.truncate() - repo.ui.log('tagscache', - 'writing %d bytes to %s\n' % ( - len(data), _fnodescachefile)) - f.write(data) - self._dirtyoffset = None - finally: - f.close() - except (IOError, OSError), inst: + # if the file has been truncated + actualoffset = f.tell() + if actualoffset < self._dirtyoffset: + self._dirtyoffset = actualoffset + data = self._raw[self._dirtyoffset:] + f.seek(self._dirtyoffset) + f.truncate() repo.ui.log('tagscache', - "couldn't write %s: %s\n" % ( - _fnodescachefile, inst)) + 'writing %d bytes to %s\n' % ( + len(data), _fnodescachefile)) + f.write(data) + self._dirtyoffset = None + finally: + f.close() + except (IOError, OSError), inst: + repo.ui.log('tagscache', + "couldn't write %s: %s\n" % ( + _fnodescachefile, inst)) finally: lock.release() diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templatefilters.py --- a/mercurial/templatefilters.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templatefilters.py Fri May 29 17:00:55 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): diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templatekw.py --- a/mercurial/templatekw.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templatekw.py Fri May 29 17:00:55 2015 -0500 @@ -206,12 +206,12 @@ def showbookmarks(**args): """:bookmarks: List of strings. Any bookmarks associated with the - changeset. + changeset. Also sets 'active', the name of the active bookmark. """ repo = args['ctx']._repo bookmarks = args['ctx'].bookmarks() - current = repo._bookmarkcurrent - makemap = lambda v: {'bookmark': v, 'current': current} + active = repo._activebookmark + makemap = lambda v: {'bookmark': v, 'active': active, 'current': active} 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, diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templater.py --- a/mercurial/templater.py Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templater.py Fri May 29 17:00:55 2015 -0500 @@ -20,6 +20,7 @@ "|": (5, None, ("|", 5)), "%": (6, None, ("%", 6)), ")": (0, None, None), + "integer": (0, ("integer",), None), "symbol": (0, ("symbol",), None), "string": (0, ("string",), None), "rawstring": (0, ("rawstring",), None), @@ -59,6 +60,20 @@ pos += 1 else: raise error.ParseError(_("unterminated string"), s) + elif c.isdigit() or c == '-': + s = pos + if c == '-': # simply take negate operator as part of integer + pos += 1 + if pos >= end or not program[pos].isdigit(): + raise error.ParseError(_("integer literal without digits"), s) + pos += 1 + while pos < end: + d = program[pos] + if not d.isdigit(): + break + pos += 1 + yield ('integer', program[s:pos], s) + pos -= 1 elif c.isalnum() or c in '_': s = pos pos += 1 @@ -100,12 +115,12 @@ parseres, pos = p.parse(pd) parsed.append(parseres) - return [compileexp(e, context) for e in parsed] + return [compileexp(e, context, methods) for e in parsed] -def compileexp(exp, context): +def compileexp(exp, context, curmethods): t = exp[0] - if t in methods: - return methods[t](exp, context) + if t in curmethods: + return curmethods[t](exp, context) raise error.ParseError(_("unknown method '%s'") % t) # template evaluation @@ -135,6 +150,9 @@ return context._load(exp[1]) raise error.ParseError(_("expected template specifier")) +def runinteger(context, mapping, data): + return int(data) + def runstring(context, mapping, data): return data.decode("string-escape") @@ -157,7 +175,7 @@ return v def buildfilter(exp, context): - func, data = compileexp(exp[1], context) + func, data = compileexp(exp[1], context, methods) filt = getfilter(exp[2], context) return (runfilter, (func, data, filt)) @@ -179,7 +197,7 @@ "keyword '%s'") % (filt.func_name, dt)) def buildmap(exp, context): - func, data = compileexp(exp[1], context) + func, data = compileexp(exp[1], context, methods) ctmpl = gettemplate(exp[2], context) return (runmap, (func, data, ctmpl)) @@ -208,7 +226,7 @@ def buildfunc(exp, context): n = getsymbol(exp[1]) - args = [compileexp(x, context) for x in getlist(exp[2])] + args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])] if n in funcs: f = funcs[n] return (f, args) @@ -551,8 +569,7 @@ num = int(stringify(args[0][0](context, mapping, args[0][1]))) except ValueError: # i18n: "word" is a keyword - raise error.ParseError( - _("Use strings like '3' for numbers passed to word function")) + raise error.ParseError(_("word expects an integer index")) text = stringify(args[1][0](context, mapping, args[1][1])) if len(args) == 3: splitter = stringify(args[2][0](context, mapping, args[2][1])) @@ -565,17 +582,23 @@ else: return tokens[num] -methods = { +# methods to interpret function arguments or inner expressions (e.g. {_(x)}) +exprmethods = { + "integer": lambda e, c: (runinteger, e[1]), "string": lambda e, c: (runstring, e[1]), "rawstring": lambda e, c: (runrawstring, e[1]), "symbol": lambda e, c: (runsymbol, e[1]), - "group": lambda e, c: compileexp(e[1], c), + "group": lambda e, c: compileexp(e[1], c, exprmethods), # ".": buildmember, "|": buildfilter, "%": buildmap, "func": buildfunc, } +# methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"}) +methods = exprmethods.copy() +methods["integer"] = exprmethods["symbol"] # '{1}' as variable + funcs = { "date": date, "diff": diff, @@ -618,14 +641,25 @@ for j in _flatten(i): yield j -def parsestring(s, quoted=True): - '''unwrap quotes if quoted is True''' - if quoted: - if len(s) < 2 or s[0] != s[-1]: - raise SyntaxError(_('unmatched quotes')) - return s[1:-1] - - return s +def unquotestring(s): + '''unwrap quotes''' + if len(s) < 2 or s[0] != s[-1]: + raise SyntaxError(_('unmatched quotes')) + # de-backslash-ify only <\">. it is invalid syntax in non-string part of + # template, but we are likely to escape <"> in quoted string and it was + # accepted before, thanks to issue4290. <\\"> is unmodified because it + # is ambiguous and it was processed as such before 2.8.1. + # + # template result + # --------- ------------------------ + # {\"\"} parse error + # "{""}" {""} -> <> + # "{\"\"}" {""} -> <> + # {"\""} {"\""} -> <"> + # '{"\""}' {"\""} -> <"> + # "{"\""}" parse error (don't care) + q = s[0] + return s[1:-1].replace('\\\\' + q, '\\\\\\' + q).replace('\\' + q, q) class engine(object): '''template expansion engine. @@ -709,7 +743,7 @@ raise util.Abort(_("style '%s' not found") % mapfile, hint=_("available styles: %s") % stylelist()) - conf = config.config() + conf = config.config(includepaths=templatepaths()) conf.read(mapfile) for key, val in conf[''].items(): @@ -717,7 +751,7 @@ raise SyntaxError(_('%s: missing value') % conf.source('', key)) if val[0] in "'\"": try: - self.cache[key] = parsestring(val) + self.cache[key] = unquotestring(val) except SyntaxError, inst: raise SyntaxError('%s: %s' % (conf.source('', key), inst.args[0])) diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/gitweb/fileannotate.tmpl --- a/mercurial/templates/gitweb/fileannotate.tmpl Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/gitweb/fileannotate.tmpl Fri May 29 17:00:55 2015 -0500 @@ -39,19 +39,23 @@ - + + - + + {branch%filerevbranch} - + + {parent%fileannotateparent} {child%fileannotatechild} - + +
author{author|obfuscate}
{author|obfuscate}
{date|rfc822date}
{date|rfc822date}
changeset {rev}{node|short}
{node|short}
permissions{permissions|permissions}
{permissions|permissions}
diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/gitweb/filecomparison.tmpl --- a/mercurial/templates/gitweb/filecomparison.tmpl Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/gitweb/filecomparison.tmpl Fri May 29 17:00:55 2015 -0500 @@ -39,7 +39,8 @@ {branch%filerevbranch} changeset {rev} - {node|short} + {node|short} + {parent%filecompparent} {child%filecompchild} diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/gitweb/filediff.tmpl --- a/mercurial/templates/gitweb/filediff.tmpl Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/gitweb/filediff.tmpl Fri May 29 17:00:55 2015 -0500 @@ -39,7 +39,8 @@ {branch%filerevbranch} changeset {rev} - {node|short} + {node|short} + {parent%filediffparent} {child%filediffchild} diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/gitweb/filerevision.tmpl --- a/mercurial/templates/gitweb/filerevision.tmpl Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/gitweb/filerevision.tmpl Fri May 29 17:00:55 2015 -0500 @@ -39,19 +39,23 @@ - + + - + + {branch%filerevbranch} - + + {parent%filerevparent} {child%filerevchild} - + +
author{author|obfuscate}
{author|obfuscate}
{date|rfc822date}
{date|rfc822date}
changeset {rev}{node|short}
{node|short}
permissions{permissions|permissions}
{permissions|permissions}
diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/gitweb/map --- a/mercurial/templates/gitweb/map Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/gitweb/map Fri May 29 17:00:55 2015 -0500 @@ -293,11 +293,16 @@ {desc|strip|firstline|escape|nonempty} + {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag} - file | diff | annotate {rename%filelogrename} - ' + file | + diff | + annotate + {rename%filelogrename} + + ' archiveentry = ' | {type|escape} ' indexentry = ' diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/map-cmdline.bisect --- a/mercurial/templates/map-cmdline.bisect Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/map-cmdline.bisect Fri May 29 17:00:55 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}")}' diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/map-cmdline.phases --- a/mercurial/templates/map-cmdline.phases Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/map-cmdline.phases Fri May 29 17:00:55 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")}' diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/map-cmdline.status --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templates/map-cmdline.status Fri May 29 17:00:55 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')}"}' diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/monoblue/map --- a/mercurial/templates/monoblue/map Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/monoblue/map Fri May 29 17:00:55 2015 -0500 @@ -65,7 +65,10 @@ drwxr-xr-x - {basename|escape} + + {basename|escape} + {emptydirs|escape} + files ' fileentry = ' @@ -244,7 +247,12 @@ filelogentry = ' {date|rfc822date} - {desc|strip|firstline|escape|nonempty} + + + {desc|strip|firstline|escape|nonempty} + {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag} + + file | diff | annotate {rename%filelogrename} diff -r 8594d0b3018e -r 7d24a41200d3 mercurial/templates/paper/fileannotate.tmpl --- a/mercurial/templates/paper/fileannotate.tmpl Thu May 28 20:30:20 2015 -0700 +++ b/mercurial/templates/paper/fileannotate.tmpl Fri May 29 17:00:55 2015 -0500 @@ -37,7 +37,7 @@
-

annotate {file|escape} @ {rev}:{node|short}

+

annotate {file|escape} @ {rev}:{node|short} {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}