# HG changeset patch # User Matt Mackall # Date 1437258758 18000 # Node ID 96a38d44ba093bd1d1ecfd34119e94056030278b # Parent 501c51d607922692bd6654cb3c24d9f1e31d7450# Parent eabba9c75061254ff62827f92df0f32491c74b3d merge with default for code freeze diff -r 501c51d60792 -r 96a38d44ba09 Makefile --- a/Makefile Fri Jul 03 18:10:58 2015 +0100 +++ b/Makefile Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/all-revsets.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/all-revsets.txt Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,137 @@ +# All revsets ever used with revsetbenchmarks.py script +# +# The goal of this file is to gather all revsets ever used for benchmarking +# revset's performance. It should be used to gather revsets that test a +# specific usecase or a specific implementation of revset predicates. +# If you are working on the smartset implementation itself, check +# 'base-revsets.txt'. +# +# Please update this file with any revsets you use for benchmarking a change so +# that future contributors can easily find and retest it when doing further +# modification. Feel free to highlight interresting variants if needed. + + +## Revset from this section are all extracted from changelog when this file was +# created. Feel free to dig and improve documentation. + +# Used in revision da05fe01170b +(20000::) - (20000) +# Used in revision 95af98616aa7 +parents(20000) +# Used in revision 186fd06283b4 +(_intlist('20000\x0020001')) and merge() +# Used in revision 911f5a6579d1 +p1(20000) +p2(10000) +# Used in revision b6dc3b79bb25 +0:: +# Used in revision faf4f63533ff +bookmark() +# Used in revision 22ba2c0825da +tip~25 +# Used in revision 0cf46b8298fe +bisect(range) +# Used in revision 5b65429721d5 +divergent() +# Used in revision 6261b9c549a2 +file(COPYING) +# Used in revision 44f471102f3a +follow(COPYING) +# Used in revision 8040a44aab1c +origin(tip) +# Used in revision bbf4f3dfd700 +rev(25) +# Used in revision a428db9ab61d +p1() +# Used in revision c1546d7400ef +min(0::) +# Used in revision 546fa6576815 +author(lmoscovicz) or author(mpm) +author(mpm) or author(lmoscovicz) +# Used in revision 9bfe68357c01 +public() and id("d82e2223f132") +# Used in revision ba89f7b542c9 +rev(25) +# Used in revision eb763217152a +rev(210000) +# Used in revision 69524a05a7fa +10:100 +parents(10):parents(100) +# Used in revision 6f1b8b3f12fd +100~5 +parents(100)~5 +(100~5)~5 +# Used in revision 7a42e5d4c418 +children(tip~100) +# Used in revision 7e8737e6ab08 +100^1 +parents(100)^1 +(100^1)^1 +# Used in revision 30e0dcd7c5ff +matching(100) +matching(parents(100)) +# Used in revision aafeaba22826 +0|1|2|3|4|5|6|7|8|9 +# Used in revision 33c7a94d4dd0 +tip:0 +# Used in revision 7d369fae098e +(0:100000) +# Used in revision b333ca94403d +0 + 1 + 2 + ... + 200 +0 + 1 + 2 + ... + 1000 +sort(0 + 1 + 2 + ... + 200) +sort(0 + 1 + 2 + ... + 1000) +# Used in revision 7fbef7932af9 +first(0 + 1 + 2 + ... + 1000) +# Used in revision ceaf04bb14ff +0:1000 +# Used in revision 262e6ad93885 +not public() +(tip~1000::) - public() +not public() and branch("default") +# Used in revision 15412bba5a68 +0::tip + +## all the revsets from this section have been taken from the former central file +# for revset's benchmarking, they are undocumented for this reason. +all() +draft() +::tip +draft() and ::tip +::tip and draft() +author(lmoscovicz) +author(mpm) +::p1(p1(tip)):: +public() +:10000 and public() +:10000 and draft() +(not public() - obsolete()) + +# The one below is used by rebase +(children(ancestor(tip~5, tip)) and ::(tip~5)):: + +# those two `roots(...)` inputs are close to what phase movement use. +roots((tip~100::) - (tip~100::tip)) +roots((0::) - (0::tip)) + +# more roots testing +roots(tip~100:) +roots(:42) +roots(not public()) +roots((0:tip)::) +roots(0::tip) +42:68 and roots(42:tip) +# Used in revision f140d6207cca +roots(0:tip) +# test disjoint set with multiple roots +roots((:42) + (tip~42:)) + +# Testing the behavior of "head()" in various situations +head() +head() - public() +draft() and head() +head() and author("mpm") + +# testing the mutable phases set +draft() +secret() diff -r 501c51d60792 -r 96a38d44ba09 contrib/base-revsets.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/base-revsets.txt Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,48 @@ +# Base Revsets to be used with revsetbenchmarks.py script +# +# The goal of this file is to gather a limited amount of revsets that allow a +# good coverage of the internal revsets mechanisms. Revsets included should not +# be selected for their individual implementation, but for what they reveal of +# the internal implementation of smartsets classes (and their interactions). +# +# Use and update this file when you change internal implementation of these +# smartsets classes. Please include a comment explaining what each of your +# addition is testing. Also check if your changes to the smartset class makes +# some of the tests inadequate and replace them with a new one testing the same +# behavior. +# +# If you want to benchmark revsets predicate itself, check 'all-revsets.txt'. +# +# The current content of this file is currently likely not reaching this goal +# entirely, feel free, to audit its content and comment on each revset to +# highlight what internal mechanisms they test. + +all() +draft() +::tip +draft() and ::tip +::tip and draft() +0::tip +roots(0::tip) +author(lmoscovicz) +author(mpm) +author(lmoscovicz) or author(mpm) +author(mpm) or author(lmoscovicz) +tip:0 +0:: +# 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() +draft() +:10000 and draft() +roots((0:tip)::) +(not public() - obsolete()) +(_intlist('20000\x0020001')) and merge() +parents(20000) +(20000::) - (20000) +# The one below is used by rebase +(children(ancestor(tip~5, tip)) and ::(tip~5)):: diff -r 501c51d60792 -r 96a38d44ba09 contrib/builddeb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/builddeb Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/buildrpm --- a/contrib/buildrpm Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/buildrpm Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/check-code.py --- a/contrib/check-code.py Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/check-code.py Sat Jul 18 17:32:38 2015 -0500 @@ -114,6 +114,7 @@ (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), (r'^stop\(\)', "don't use 'stop' as a shell function name"), (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"), + (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"), (r'^alias\b.*=', "don't use alias, use a function"), (r'if\s*!', "don't use '!' to negate exit status"), (r'/dev/u?random', "don't use entropy, use /dev/zero"), @@ -217,14 +218,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'(? +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +import re +import sys + +foundopts = {} +documented = {} + +configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"], ?""" + r"""['"](\S+)['"](,\s(?:default=)?(\S+?))?\)""") + +def main(args): + for f in args: + sect = '' + prevname = '' + confsect = '' + for l in open(f): + + # check topic-like bits + m = re.match('\s*``(\S+)``', l) + if m: + prevname = m.group(1) + if re.match('^\s*-+$', l): + sect = prevname + prevname = '' + + if sect and prevname: + name = sect + '.' + prevname + documented[name] = 1 + + # check docstring bits + m = re.match(r'^\s+\[(\S+)\]', l) + if m: + confsect = m.group(1) + continue + m = re.match(r'^\s+(?:#\s*)?([a-z._]+) = ', l) + if m: + name = confsect + '.' + m.group(1) + documented[name] = 1 + + # like the bugzilla extension + m = re.match(r'^\s*([a-z]+\.[a-z]+)$', l) + if m: + documented[m.group(1)] = 1 + + # quoted in help or docstrings + m = re.match(r'.*?``([-a-z_]+\.[-a-z_]+)``', l) + if m: + documented[m.group(1)] = 1 + + # look for ignore markers + m = re.search(r'# (?:internal|experimental|deprecated|developer)' + ' config: (\S+.\S+)$', l) + if m: + documented[m.group(1)] = 1 + + # look for code-like bits + m = re.search(configre, l) + if m: + ctype = m.group(1) + if not ctype: + ctype = 'str' + name = m.group(2) + "." + m.group(3) + default = m.group(5) + if default in (None, 'False', 'None', '0', '[]', '""', "''"): + default = '' + if re.match('[a-z.]+$', default): + default = '' + if name in foundopts and (ctype, default) != foundopts[name]: + print l + print "conflict on %s: %r != %r" % (name, (ctype, default), + foundopts[name]) + foundopts[name] = (ctype, default) + + for name in sorted(foundopts): + if name not in documented: + if not (name.startswith("devel.") or + name.startswith("experimental.") or + name.startswith("debug.")): + ctype, default = foundopts[name] + if default: + default = ' [%s]' % default + print "undocumented: %s (%s)%s" % (name, ctype, default) + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff -r 501c51d60792 -r 96a38d44ba09 contrib/debian/control --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/debian/control Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,9 @@ +Package: mercurial +Version: __VERSION__ +Section: vcs +Priority: optional +Architecture: all +Depends: python +Conflicts: mercurial-common +Maintainer: Mercurial Developers +Description: Mercurial (probably nightly) package built by upstream. diff -r 501c51d60792 -r 96a38d44ba09 contrib/docker/debian-jessie --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/docker/debian-jessie Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/dockerdeb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerdeb Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/dockerlib.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerlib.sh Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/dockerrpm --- a/contrib/dockerrpm Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/dockerrpm Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/hg-ssh --- a/contrib/hg-ssh Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/hg-ssh Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/import-checker.py --- a/contrib/import-checker.py Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/import-checker.py Sat Jul 18 17:32:38 2015 -0500 @@ -8,6 +8,33 @@ import BaseHTTPServer import zlib +# Whitelist of modules that symbols can be directly imported from. +allowsymbolimports = ( + '__future__', + 'mercurial.i18n', + 'mercurial.node', +) + +# Modules that must be aliased because they are commonly confused with +# common variables and can create aliasing and readability issues. +requirealias = { + 'ui': 'uimod', +} + +def usingabsolute(root): + """Whether absolute imports are being used.""" + if sys.version_info[0] >= 3: + return True + + for node in ast.walk(root): + if isinstance(node, ast.ImportFrom): + if node.module == '__future__': + for n in node.names: + if n.name == 'absolute_import': + return True + + return False + def dotted_name_of_path(path, trimpure=False): """Given a relative path to a source file, return its dotted module name. @@ -26,6 +53,85 @@ 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 + >>> fromlocal(None, 1) + ('foo', 'foo.__init__', True) + >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods) + >>> fromlocal2(None, 2) + ('foo', 'foo.__init__', True) + """ + prefix = '.'.join(modulename.split('.')[:-1]) + if prefix: + prefix += '.' + def fromlocal(name, level=0): + # name is None when relative imports are used. + if name is None: + # If relative imports are used, level must not be absolute. + assert level > 0 + candidates = ['.'.join(modulename.split('.')[:-level])] + else: + # Check relative name first. + candidates = [prefix + name, name] + + for n in candidates: + 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. @@ -86,9 +192,11 @@ # of any(). if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24 continue - if 'site-packages' in libpath: - continue for top, dirs, files in os.walk(libpath): + for i, d in reversed(list(enumerate(dirs))): + if (not os.path.exists(os.path.join(top, d, '__init__.py')) + or top == libpath and d in ('hgext', 'mercurial')): + del dirs[i] for name in files: if name == '__init__.py': continue @@ -96,48 +204,255 @@ or name.endswith('.pyd')): continue full_path = os.path.join(top, name) - if 'site-packages' in full_path: - continue rel_path = full_path[len(libpath) + 1:] mod = dotted_name_of_path(rel_path) yield mod 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, node.level) + 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_import_convention(module, source): + """Verify imports match our established coding convention. + + We have 2 conventions: legacy and modern. The modern convention is in + effect when using absolute imports. + + The legacy convention only looks for mixed imports. The modern convention + is much more thorough. + """ + root = ast.parse(source) + absolute = usingabsolute(root) + + if absolute: + return verify_modern_convention(module, root) + else: + return verify_stdlib_on_own_line(root) + +def verify_modern_convention(module, root): + """Verify a file conforms to the modern import convention rules. + + The rules of the modern convention are: + + * Ordering is stdlib followed by local imports. Each group is lexically + sorted. + * Importing multiple modules via "import X, Y" is not allowed: use + separate import statements. + * Importing multiple modules via "from X import ..." is allowed if using + parenthesis and one entry per line. + * Only 1 relative import statement per import level ("from .", "from ..") + is allowed. + * Relative imports from higher levels must occur before lower levels. e.g. + "from .." must be before "from .". + * Imports from peer packages should use relative import (e.g. do not + "import mercurial.foo" from a "mercurial.*" module). + * Symbols can only be imported from specific modules (see + `allowsymbolimports`). For other modules, first import the module then + assign the symbol to a module-level variable. In addition, these imports + must be performed before other relative imports. This rule only + applies to import statements outside of any blocks. + * Relative imports from the standard library are not allowed. + * Certain modules must be aliased to alternate names to avoid aliasing + and readability problems. See `requirealias`. + """ + topmodule = module.split('.')[0] + + # Whether a local/non-stdlib import has been performed. + seenlocal = False + # Whether a relative, non-symbol import has been seen. + seennonsymbolrelative = False + # The last name to be imported (for sorting). + lastname = None + # Relative import levels encountered so far. + seenlevels = set() + + for node in ast.walk(root): + if isinstance(node, ast.Import): + # Disallow "import foo, bar" and require separate imports + # for each module. + if len(node.names) > 1: + yield 'multiple imported names: %s' % ', '.join( + n.name for n in node.names) + + name = node.names[0].name + asname = node.names[0].asname + + # Ignore sorting rules on imports inside blocks. + if node.col_offset == 0: + if lastname and name < lastname: + yield 'imports not lexically sorted: %s < %s' % ( + name, lastname) + + lastname = name -def verify_stdlib_on_own_line(source): + # stdlib imports should be before local imports. + stdlib = name in stdlib_modules + if stdlib and seenlocal and node.col_offset == 0: + yield 'stdlib import follows local import: %s' % name + + if not stdlib: + seenlocal = True + + # Import of sibling modules should use relative imports. + topname = name.split('.')[0] + if topname == topmodule: + yield 'import should be relative: %s' % name + + if name in requirealias and asname != requirealias[name]: + yield '%s module must be "as" aliased to %s' % ( + name, requirealias[name]) + + elif isinstance(node, ast.ImportFrom): + # Resolve the full imported module name. + if node.level > 0: + fullname = '.'.join(module.split('.')[:-node.level]) + if node.module: + fullname += '.%s' % node.module + else: + assert node.module + fullname = node.module + + topname = fullname.split('.')[0] + if topname == topmodule: + yield 'import should be relative: %s' % fullname + + # __future__ is special since it needs to come first and use + # symbol import. + if fullname != '__future__': + if not fullname or fullname in stdlib_modules: + yield 'relative import of stdlib module' + else: + seenlocal = True + + # Direct symbol import is only allowed from certain modules and + # must occur before non-symbol imports. + if node.module and node.col_offset == 0: + if fullname not in allowsymbolimports: + yield 'direct symbol import from %s' % fullname + + if seennonsymbolrelative: + yield ('symbol import follows non-symbol import: %s' % + fullname) + + if not node.module: + assert node.level + seennonsymbolrelative = True + + # Only allow 1 group per level. + if node.level in seenlevels and node.col_offset == 0: + yield 'multiple "from %s import" statements' % ( + '.' * node.level) + + # Higher-level groups come before lower-level groups. + if any(node.level > l for l in seenlevels): + yield 'higher-level import should come first: %s' % ( + fullname) + + seenlevels.add(node.level) + + # Entries in "from .X import ( ... )" lists must be lexically + # sorted. + lastentryname = None + + for n in node.names: + if lastentryname and n.name < lastentryname: + yield 'imports from %s not lexically sorted: %s < %s' % ( + fullname, n.name, lastentryname) + + lastentryname = n.name + + if n.name in requirealias and n.asname != requirealias[n.name]: + yield '%s from %s must be "as" aliased to %s' % ( + n.name, fullname, requirealias[n.name]) + +def verify_stdlib_on_own_line(root): """Given some python source, verify that stdlib imports are done in separate statements from relative local module imports. @@ -145,14 +460,14 @@ annoying lib2to3 bug in relative import rewrites: http://bugs.python.org/issue19510. - >>> list(verify_stdlib_on_own_line('import sys, foo')) + >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo'))) ['mixed imports\\n stdlib: sys\\n relative: foo'] - >>> list(verify_stdlib_on_own_line('import sys, os')) + >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os'))) [] - >>> list(verify_stdlib_on_own_line('import foo, bar')) + >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar'))) [] """ - for node in ast.walk(ast.parse(source)): + for node in ast.walk(root): if isinstance(node, ast.Import): from_stdlib = {False: [], True: []} for n in node.names: @@ -171,8 +486,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 +507,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 @@ -206,7 +521,7 @@ for mod in sorted(imports.iterkeys()): try: checkmod(mod, imports) - except CircularImport, e: + except CircularImport as e: cycle = e.args[0] cycles.add(" -> ".join(rotatecycle(cycle))) return cycles @@ -215,18 +530,24 @@ 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)) - for error in verify_stdlib_on_own_line(src): + imported_modules(src, modname, localmods, ignore_nested=True)) + for error in verify_import_convention(modname, src): any_errors = True print source_path, error f.close() @@ -243,7 +564,7 @@ print 'Import cycle:', c firstmods.add(first) any_errors = True - return not any_errors + return any_errors != 0 if __name__ == '__main__': sys.exit(int(main(sys.argv))) diff -r 501c51d60792 -r 96a38d44ba09 contrib/mercurial.spec --- a/contrib/mercurial.spec Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/mercurial.spec Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/packagelib.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/packagelib.sh Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 contrib/perf.py --- a/contrib/perf.py Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/perf.py Sat Jul 18 17:32:38 2015 -0500 @@ -6,6 +6,8 @@ import time, os, sys import functools +formatteropts = commands.formatteropts + cmdtable = {} command = cmdutil.command(cmdtable) @@ -16,6 +18,7 @@ place instead of duplicating it in all performance command.""" # enforce an idle period before execution to counteract power management + # experimental config: perf.presleep time.sleep(ui.configint("perf", "presleep", 1)) if opts is None: @@ -60,9 +63,9 @@ fm.write('count', ' (best of %d)', count) fm.plain('\n') -@command('perfwalk') -def perfwalk(ui, repo, *pats): - timer, fm = gettimer(ui) +@command('perfwalk', formatteropts) +def perfwalk(ui, repo, *pats, **opts): + timer, fm = gettimer(ui, opts) try: m = scmutil.match(repo[None], pats, {}) timer(lambda: len(list(repo.dirstate.walk(m, [], True, False)))) @@ -74,27 +77,27 @@ timer(lambda: len(list(cmdutil.walk(repo, pats, {})))) fm.end() -@command('perfannotate') -def perfannotate(ui, repo, f): - timer, fm = gettimer(ui) +@command('perfannotate', formatteropts) +def perfannotate(ui, repo, f, **opts): + timer, fm = gettimer(ui, opts) fc = repo['.'][f] timer(lambda: len(fc.annotate(True))) fm.end() @command('perfstatus', [('u', 'unknown', False, - 'ask status to look for unknown files')]) + 'ask status to look for unknown files')] + formatteropts) def perfstatus(ui, repo, **opts): #m = match.always(repo.root, repo.getcwd()) #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, # False)))) - timer, fm = gettimer(ui) - timer(lambda: sum(map(len, repo.status(**opts)))) + timer, fm = gettimer(ui, **opts) + timer(lambda: sum(map(len, repo.status(unknown=opts['unknown'])))) fm.end() -@command('perfaddremove') -def perfaddremove(ui, repo): - timer, fm = gettimer(ui) +@command('perfaddremove', formatteropts) +def perfaddremove(ui, repo, **opts): + timer, fm = gettimer(ui, opts) try: oldquiet = repo.ui.quiet repo.ui.quiet = True @@ -113,9 +116,9 @@ cl._nodecache = {nullid: nullrev} cl._nodepos = None -@command('perfheads') -def perfheads(ui, repo): - timer, fm = gettimer(ui) +@command('perfheads', formatteropts) +def perfheads(ui, repo, **opts): + timer, fm = gettimer(ui, opts) cl = repo.changelog def d(): len(cl.headrevs()) @@ -123,11 +126,11 @@ timer(d) fm.end() -@command('perftags') -def perftags(ui, repo): +@command('perftags', formatteropts) +def perftags(ui, repo, **opts): import mercurial.changelog import mercurial.manifest - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) def t(): repo.changelog = mercurial.changelog.changelog(repo.svfs) repo.manifest = mercurial.manifest.manifest(repo.svfs) @@ -136,9 +139,9 @@ timer(t) fm.end() -@command('perfancestors') -def perfancestors(ui, repo): - timer, fm = gettimer(ui) +@command('perfancestors', formatteropts) +def perfancestors(ui, repo, **opts): + timer, fm = gettimer(ui, opts) heads = repo.changelog.headrevs() def d(): for a in repo.changelog.ancestors(heads): @@ -146,9 +149,9 @@ timer(d) fm.end() -@command('perfancestorset') -def perfancestorset(ui, repo, revset): - timer, fm = gettimer(ui) +@command('perfancestorset', formatteropts) +def perfancestorset(ui, repo, revset, **opts): + timer, fm = gettimer(ui, opts) revs = repo.revs(revset) heads = repo.changelog.headrevs() def d(): @@ -158,9 +161,9 @@ timer(d) fm.end() -@command('perfdirs') -def perfdirs(ui, repo): - timer, fm = gettimer(ui) +@command('perfdirs', formatteropts) +def perfdirs(ui, repo, **opts): + timer, fm = gettimer(ui, opts) dirstate = repo.dirstate 'a' in dirstate def d(): @@ -169,9 +172,9 @@ timer(d) fm.end() -@command('perfdirstate') -def perfdirstate(ui, repo): - timer, fm = gettimer(ui) +@command('perfdirstate', formatteropts) +def perfdirstate(ui, repo, **opts): + timer, fm = gettimer(ui, opts) "a" in repo.dirstate def d(): repo.dirstate.invalidate() @@ -179,9 +182,9 @@ timer(d) fm.end() -@command('perfdirstatedirs') -def perfdirstatedirs(ui, repo): - timer, fm = gettimer(ui) +@command('perfdirstatedirs', formatteropts) +def perfdirstatedirs(ui, repo, **opts): + timer, fm = gettimer(ui, opts) "a" in repo.dirstate def d(): "a" in repo.dirstate._dirs @@ -189,9 +192,9 @@ timer(d) fm.end() -@command('perffilefoldmap') -def perffilefoldmap(ui, repo): - timer, fm = gettimer(ui) +@command('perfdirstatefoldmap', formatteropts) +def perffilefoldmap(ui, repo, **opts): + timer, fm = gettimer(ui, opts) dirstate = repo.dirstate 'a' in dirstate def d(): @@ -200,9 +203,9 @@ timer(d) fm.end() -@command('perfdirfoldmap') -def perfdirfoldmap(ui, repo): - timer, fm = gettimer(ui) +@command('perfdirfoldmap', formatteropts) +def perfdirfoldmap(ui, repo, **opts): + timer, fm = gettimer(ui, opts) dirstate = repo.dirstate 'a' in dirstate def d(): @@ -212,9 +215,9 @@ timer(d) fm.end() -@command('perfdirstatewrite') -def perfdirstatewrite(ui, repo): - timer, fm = gettimer(ui) +@command('perfdirstatewrite', formatteropts) +def perfdirstatewrite(ui, repo, **opts): + timer, fm = gettimer(ui, opts) ds = repo.dirstate "a" in ds def d(): @@ -224,9 +227,9 @@ fm.end() @command('perfmergecalculate', - [('r', 'rev', '.', 'rev to merge against')]) -def perfmergecalculate(ui, repo, rev): - timer, fm = gettimer(ui) + [('r', 'rev', '.', 'rev to merge against')] + formatteropts) +def perfmergecalculate(ui, repo, rev, **opts): + timer, fm = gettimer(ui, opts) wctx = repo[None] rctx = scmutil.revsingle(repo, rev, rev) ancestor = wctx.ancestor(rctx) @@ -242,8 +245,8 @@ fm.end() @command('perfpathcopies', [], "REV REV") -def perfpathcopies(ui, repo, rev1, rev2): - timer, fm = gettimer(ui) +def perfpathcopies(ui, repo, rev1, rev2, **opts): + timer, fm = gettimer(ui, opts) ctx1 = scmutil.revsingle(repo, rev1, rev1) ctx2 = scmutil.revsingle(repo, rev2, rev2) def d(): @@ -252,8 +255,8 @@ fm.end() @command('perfmanifest', [], 'REV') -def perfmanifest(ui, repo, rev): - timer, fm = gettimer(ui) +def perfmanifest(ui, repo, rev, **opts): + timer, fm = gettimer(ui, opts) ctx = scmutil.revsingle(repo, rev, rev) t = ctx.manifestnode() def d(): @@ -263,9 +266,9 @@ timer(d) fm.end() -@command('perfchangeset') -def perfchangeset(ui, repo, rev): - timer, fm = gettimer(ui) +@command('perfchangeset', formatteropts) +def perfchangeset(ui, repo, rev, **opts): + timer, fm = gettimer(ui, opts) n = repo[rev].node() def d(): repo.changelog.read(n) @@ -273,10 +276,10 @@ timer(d) fm.end() -@command('perfindex') -def perfindex(ui, repo): +@command('perfindex', formatteropts) +def perfindex(ui, repo, **opts): import mercurial.revlog - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg n = repo["tip"].node() def d(): @@ -285,18 +288,18 @@ timer(d) fm.end() -@command('perfstartup') -def perfstartup(ui, repo): - timer, fm = gettimer(ui) +@command('perfstartup', formatteropts) +def perfstartup(ui, repo, **opts): + timer, fm = gettimer(ui, opts) cmd = sys.argv[0] def d(): os.system("HGRCPATH= %s version -q > /dev/null" % cmd) timer(d) fm.end() -@command('perfparents') -def perfparents(ui, repo): - timer, fm = gettimer(ui) +@command('perfparents', formatteropts) +def perfparents(ui, repo, **opts): + timer, fm = gettimer(ui, opts) nl = [repo.changelog.node(i) for i in xrange(1000)] def d(): for n in nl: @@ -304,41 +307,45 @@ timer(d) fm.end() -@command('perfctxfiles') -def perfparents(ui, repo, x): +@command('perfctxfiles', formatteropts) +def perfparents(ui, repo, x, **opts): x = int(x) - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) def d(): len(repo[x].files()) timer(d) fm.end() -@command('perfrawfiles') -def perfparents(ui, repo, x): +@command('perfrawfiles', formatteropts) +def perfparents(ui, repo, x, **opts): x = int(x) - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) cl = repo.changelog def d(): len(cl.read(x)[3]) timer(d) fm.end() -@command('perflookup') -def perflookup(ui, repo, rev): - timer, fm = gettimer(ui) +@command('perflookup', formatteropts) +def perflookup(ui, repo, rev, **opts): + timer, fm = gettimer(ui, opts) + +@command('perflookup', formatteropts) +def perflookup(ui, repo, rev, **opts): + timer, fm = gettimer(ui, opts) timer(lambda: len(repo.lookup(rev))) fm.end() -@command('perfrevrange') -def perfrevrange(ui, repo, *specs): - timer, fm = gettimer(ui) +@command('perfrevrange', formatteropts) +def perfrevrange(ui, repo, *specs, **opts): + timer, fm = gettimer(ui, opts) revrange = scmutil.revrange timer(lambda: len(revrange(repo, specs))) fm.end() -@command('perfnodelookup') -def perfnodelookup(ui, repo, rev): - timer, fm = gettimer(ui) +@command('perfnodelookup', formatteropts) +def perfnodelookup(ui, repo, rev, **opts): + timer, fm = gettimer(ui, opts) import mercurial.revlog mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg n = repo[rev].node() @@ -350,22 +357,22 @@ fm.end() @command('perflog', - [('', 'rename', False, 'ask log to follow renames')]) + [('', 'rename', False, 'ask log to follow renames')] + formatteropts) def perflog(ui, repo, **opts): - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) ui.pushbuffer() timer(lambda: commands.log(ui, repo, rev=[], date='', user='', copies=opts.get('rename'))) ui.popbuffer() fm.end() -@command('perfmoonwalk') -def perfmoonwalk(ui, repo): +@command('perfmoonwalk', formatteropts) +def perfmoonwalk(ui, repo, **opts): """benchmark walking the changelog backwards This also loads the changelog data for each revision in the changelog. """ - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) def moonwalk(): for i in xrange(len(repo), -1, -1): ctx = repo[i] @@ -373,9 +380,9 @@ timer(moonwalk) fm.end() -@command('perftemplating') -def perftemplating(ui, repo): - timer, fm = gettimer(ui) +@command('perftemplating', formatteropts) +def perftemplating(ui, repo, **opts): + timer, fm = gettimer(ui, opts) ui.pushbuffer() timer(lambda: commands.log(ui, repo, rev=[], date='', user='', template='{date|shortdate} [{rev}:{node|short}]' @@ -383,24 +390,24 @@ ui.popbuffer() fm.end() -@command('perfcca') -def perfcca(ui, repo): - timer, fm = gettimer(ui) +@command('perfcca', formatteropts) +def perfcca(ui, repo, **opts): + timer, fm = gettimer(ui, opts) timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate)) fm.end() -@command('perffncacheload') -def perffncacheload(ui, repo): - timer, fm = gettimer(ui) +@command('perffncacheload', formatteropts) +def perffncacheload(ui, repo, **opts): + timer, fm = gettimer(ui, opts) s = repo.store def d(): s.fncache._load() timer(d) fm.end() -@command('perffncachewrite') -def perffncachewrite(ui, repo): - timer, fm = gettimer(ui) +@command('perffncachewrite', formatteropts) +def perffncachewrite(ui, repo, **opts): + timer, fm = gettimer(ui, opts) s = repo.store s.fncache._load() def d(): @@ -409,9 +416,9 @@ timer(d) fm.end() -@command('perffncacheencode') -def perffncacheencode(ui, repo): - timer, fm = gettimer(ui) +@command('perffncacheencode', formatteropts) +def perffncacheencode(ui, repo, **opts): + timer, fm = gettimer(ui, opts) s = repo.store s.fncache._load() def d(): @@ -420,10 +427,10 @@ timer(d) fm.end() -@command('perfdiffwd') -def perfdiffwd(ui, repo): +@command('perfdiffwd', formatteropts) +def perfdiffwd(ui, repo, **opts): """Profile diff of working directory changes""" - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) options = { 'w': 'ignore_all_space', 'b': 'ignore_space_change', @@ -441,10 +448,10 @@ fm.end() @command('perfrevlog', - [('d', 'dist', 100, 'distance between the revisions')], + [('d', 'dist', 100, 'distance between the revisions')] + formatteropts, "[INDEXFILE]") def perfrevlog(ui, repo, file_, **opts): - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) from mercurial import revlog dist = opts['dist'] def d(): @@ -456,15 +463,15 @@ fm.end() @command('perfrevset', - [('C', 'clear', False, 'clear volatile cache between each call.')], - "REVSET") -def perfrevset(ui, repo, expr, clear=False): + [('C', 'clear', False, 'clear volatile cache between each call.')] + + formatteropts, "REVSET") +def perfrevset(ui, repo, expr, clear=False, **opts): """benchmark the execution time of a revset Use the --clean option if need to evaluate the impact of build volatile revisions set cache on the revset execution. Volatile cache hold filtered and obsolete related cache.""" - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) def d(): if clear: repo.invalidatevolatilesets() @@ -472,12 +479,12 @@ timer(d) fm.end() -@command('perfvolatilesets') -def perfvolatilesets(ui, repo, *names): +@command('perfvolatilesets', formatteropts) +def perfvolatilesets(ui, repo, *names, **opts): """benchmark the computation of various volatile set Volatile set computes element related to filtering and obsolescence.""" - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) repo = repo.unfiltered() def getobs(name): @@ -510,13 +517,13 @@ @command('perfbranchmap', [('f', 'full', False, 'Includes build time of subset'), - ]) -def perfbranchmap(ui, repo, full=False): + ] + formatteropts) +def perfbranchmap(ui, repo, full=False, **opts): """benchmark the update of a branchmap This benchmarks the full repo.branchmap() call with read and write disabled """ - timer, fm = gettimer(ui) + timer, fm = gettimer(ui, opts) def getbranchmap(filtername): """generate a benchmark function for the filtername""" if filtername is None: diff -r 501c51d60792 -r 96a38d44ba09 contrib/revsetbenchmarks.py --- a/contrib/revsetbenchmarks.py Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/revsetbenchmarks.py Sat Jul 18 17:32:38 2015 -0500 @@ -4,21 +4,22 @@ # defined by parameter. Checkout one by one and run perfrevset with every # revset in the list to benchmark its performance. # -# - First argument is a revset of mercurial own repo to runs against. -# - Second argument is the file from which the revset array will be taken -# If second argument is omitted read it from standard input -# # You should run this from the root of your mercurial repository. # -# This script also does one run of the current version of mercurial installed -# to compare performance. +# call with --help for details import sys import os +import re +import math from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE # cannot use argparse, python 2.7 only from optparse import OptionParser +DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last', + 'reverse', 'reverse+first', 'reverse+last', + 'sort', 'sort+first', 'sort+last'] + def check_output(*args, **kwargs): kwargs.setdefault('stderr', PIPE) kwargs.setdefault('stdout', PIPE) @@ -32,69 +33,225 @@ """update the repo to a revision""" try: check_call(['hg', 'update', '--quiet', '--check', str(rev)]) - except CalledProcessError, exc: + except CalledProcessError as exc: print >> sys.stderr, 'update to revision %s failed, aborting' % rev sys.exit(exc.returncode) + +def hg(cmd, repo=None): + """run a mercurial command + + is the list of command + argument, + is an optional repository path to run this command in.""" + fullcmd = ['./hg'] + if repo is not None: + fullcmd += ['-R', repo] + fullcmd += ['--config', + 'extensions.perf=' + os.path.join(contribdir, 'perf.py')] + fullcmd += cmd + return check_output(fullcmd, stderr=STDOUT) + def perf(revset, target=None): """run benchmark for this very revset""" try: - cmd = ['./hg', - '--config', - 'extensions.perf=' - + os.path.join(contribdir, 'perf.py'), - 'perfrevset', - revset] - if target is not None: - cmd.append('-R') - cmd.append(target) - output = check_output(cmd, stderr=STDOUT) - output = output.lstrip('!') # remove useless ! in this context - return output.strip() - except CalledProcessError, exc: - print >> sys.stderr, 'abort: cannot run revset benchmark' - sys.exit(exc.returncode) + output = hg(['perfrevset', revset], repo=target) + return parseoutput(output) + except CalledProcessError as exc: + print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd + if exc.output is None: + print >> sys.stderr, '(no ouput)' + else: + print >> sys.stderr, exc.output + return None + +outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) ' + 'sys (\d+.\d+) \(best of (\d+)\)') + +def parseoutput(output): + """parse a textual output into a dict + + We cannot just use json because we want to compare with old + versions of Mercurial that may not support json output. + """ + match = outputre.search(output) + if not match: + print >> sys.stderr, 'abort: invalid output:' + print >> sys.stderr, output + sys.exit(1) + return {'comb': float(match.group(2)), + 'count': int(match.group(5)), + 'sys': float(match.group(3)), + 'user': float(match.group(4)), + 'wall': float(match.group(1)), + } def printrevision(rev): """print data about a revision""" - sys.stdout.write("Revision: ") + sys.stdout.write("Revision ") sys.stdout.flush() check_call(['hg', 'log', '--rev', str(rev), '--template', - '{desc|firstline}\n']) + '{if(tags, " ({tags})")} ' + '{rev}:{node|short}: {desc|firstline}\n']) + +def idxwidth(nbidx): + """return the max width of number used for index + + This is similar to log10(nbidx), but we use custom code here + because we start with zero and we'd rather not deal with all the + extra rounding business that log10 would imply. + """ + nbidx -= 1 # starts at 0 + idxwidth = 0 + while nbidx: + idxwidth += 1 + nbidx //= 10 + if not idxwidth: + idxwidth = 1 + return idxwidth + +def getfactor(main, other, field, sensitivity=0.05): + """return the relative factor between values for 'field' in main and other + + Return None if the factor is insignicant (less than + variation).""" + factor = 1 + if main is not None: + factor = other[field] / main[field] + low, high = 1 - sensitivity, 1 + sensitivity + if (low < factor < high): + return None + return factor + +def formatfactor(factor): + """format a factor into a 4 char string + + 22% + 156% + x2.4 + x23 + x789 + x1e4 + x5x7 + + """ + if factor is None: + return ' ' + elif factor < 2: + return '%3i%%' % (factor * 100) + elif factor < 10: + return 'x%3.1f' % factor + elif factor < 1000: + return '%4s' % ('x%i' % factor) + else: + order = int(math.log(factor)) + 1 + while 1 < math.log(factor): + factor //= 0 + return 'x%ix%i' % (factor, order) + +def formattiming(value): + """format a value to strictly 8 char, dropping some precision if needed""" + if value < 10**7: + return ('%.6f' % value)[:8] + else: + # value is HUGE very unlikely to happen (4+ month run) + return '%i' % value + +_marker = object() +def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker): + """print a line of result to stdout""" + mask = '%%0%ii) %%s' % idxwidth(maxidx) + + out = [] + for var in variants: + if data[var] is None: + out.append('error ') + out.append(' ' * 4) + continue + out.append(formattiming(data[var]['wall'])) + if reference is not _marker: + factor = None + if reference is not None: + factor = getfactor(reference[var], data[var], 'wall') + out.append(formatfactor(factor)) + if verbose: + out.append(formattiming(data[var]['comb'])) + out.append(formattiming(data[var]['user'])) + out.append(formattiming(data[var]['sys'])) + out.append('%6d' % data[var]['count']) + print mask % (idx, ' '.join(out)) + +def printheader(variants, maxidx, verbose=False, relative=False): + header = [' ' * (idxwidth(maxidx) + 1)] + for var in variants: + if not var: + var = 'iter' + if 8 < len(var): + var = var[:3] + '..' + var[-3:] + header.append('%-8s' % var) + if relative: + header.append(' ') + if verbose: + header.append('%-8s' % 'comb') + header.append('%-8s' % 'user') + header.append('%-8s' % 'sys') + header.append('%6s' % 'count') + print ' '.join(header) def getrevs(spec): """get the list of rev matched by a revset""" try: out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec]) - except CalledProcessError, exc: + except CalledProcessError as exc: print >> sys.stderr, "abort, can't get revision from %s" % spec sys.exit(exc.returncode) return [r for r in out.split() if r] -parser = OptionParser(usage="usage: %prog [options] ") +def applyvariants(revset, variant): + if variant == 'plain': + return revset + for var in variant.split('+'): + revset = '%s(%s)' % (var, revset) + return revset + +helptext="""This script will run multiple variants of provided revsets using +different revisions in your mercurial repository. After the benchmark are run +summary output is provided. Use itto demonstrate speed improvements or pin +point regressions. Revsets to run are specified in a file (or from stdin), one +revsets per line. Line starting with '#' will be ignored, allowing insertion of +comments.""" +parser = OptionParser(usage="usage: %prog [options] ", + description=helptext) parser.add_option("-f", "--file", help="read revset from FILE (stdin if omitted)", metavar="FILE") parser.add_option("-R", "--repo", help="run benchmark on REPO", metavar="REPO") +parser.add_option("-v", "--verbose", + action='store_true', + help="display all timing data (not just best total time)") + +parser.add_option("", "--variants", + default=','.join(DEFAULTVARIANTS), + help="comma separated list of variant to test " + "(eg: plain,min,sorted) (plain = no modification)") + (options, args) = parser.parse_args() -if len(sys.argv) < 2: +if not args: parser.print_help() sys.exit(255) # the directory where both this script and the perf.py extension live. contribdir = os.path.dirname(__file__) -target_rev = args[0] - revsetsfile = sys.stdin if options.file: revsetsfile = open(options.file) revsets = [l.strip() for l in revsetsfile if not l.startswith('#')] +revsets = [l for l in revsets if l] print "Revsets to benchmark" print "----------------------------" @@ -105,8 +262,11 @@ print "----------------------------" print +revs = [] +for a in args: + revs.extend(getrevs(a)) -revs = getrevs(target_rev) +variants = options.variants.split(',') results = [] for r in revs: @@ -116,10 +276,16 @@ update(r) res = [] results.append(res) + printheader(variants, len(revsets), verbose=options.verbose) for idx, rset in enumerate(revsets): - data = perf(rset, target=options.repo) - res.append(data) - print "%i)" % idx, data + varres = {} + for var in variants: + varrset = applyvariants(rset, var) + data = perf(varrset, target=options.repo) + varres[var] = data + res.append(varres) + printresult(variants, idx, varres, len(revsets), + verbose=options.verbose) sys.stdout.flush() print "----------------------------" @@ -130,7 +296,7 @@ ================ """ -print 'Revision:', revs +print 'Revision:' for idx, rev in enumerate(revs): sys.stdout.write('%i) ' % idx) sys.stdout.flush() @@ -142,6 +308,10 @@ for ridx, rset in enumerate(revsets): print "revset #%i: %s" % (ridx, rset) + printheader(variants, len(results), verbose=options.verbose, relative=True) + ref = None for idx, data in enumerate(results): - print '%i) %s' % (idx, data[ridx]) + printresult(variants, idx, data[ridx], len(results), + verbose=options.verbose, reference=ref) + ref = data[ridx] print diff -r 501c51d60792 -r 96a38d44ba09 contrib/revsetbenchmarks.txt --- a/contrib/revsetbenchmarks.txt Fri Jul 03 18:10:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -all() -draft() -::tip -draft() and ::tip -::tip and draft() -0::tip -roots(0::tip) -author(lmoscovicz) -author(mpm) -author(lmoscovicz) or author(mpm) -author(mpm) or author(lmoscovicz) -tip:0 -max(tip:0) -min(0:tip) -0:: -min(0::) -# those two `roots(...)` inputs are close to what phase movement use. -roots((tip~100::) - (tip~100::tip)) -roots((0::) - (0::tip)) -::p1(p1(tip)):: -public() -:10000 and public() -draft() -:10000 and draft() -max(::(tip~20) - obsolete()) -roots((0:tip)::) -(not public() - obsolete()) -(_intlist('20000\x0020001')) and merge() -parents(20000) -(20000::) - (20000) -# The one below is used by rebase -(children(ancestor(tip~5, tip)) and ::(tip~5)):: diff -r 501c51d60792 -r 96a38d44ba09 contrib/synthrepo.py --- a/contrib/synthrepo.py Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/synthrepo.py Sat Jul 18 17:32:38 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 = {} @@ -249,7 +253,7 @@ ''' try: fp = hg.openpath(ui, descpath) - except Exception, err: + except Exception as err: raise util.Abort('%s: %s' % (descpath, err[0].strerror)) desc = json.load(fp) fp.close() @@ -281,7 +285,7 @@ dictfile = opts.get('dict') or '/usr/share/dict/words' try: fp = open(dictfile, 'rU') - except IOError, err: + except IOError as err: raise util.Abort('%s: %s' % (dictfile, err.strerror)) words = fp.read().splitlines() fp.close() diff -r 501c51d60792 -r 96a38d44ba09 contrib/wix/dist.wxs --- a/contrib/wix/dist.wxs Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/wix/dist.wxs Sat Jul 18 17:32:38 2015 -0500 @@ -7,24 +7,28 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff -r 501c51d60792 -r 96a38d44ba09 contrib/wix/guids.wxi --- a/contrib/wix/guids.wxi Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/wix/guids.wxi Sat Jul 18 17:32:38 2015 -0500 @@ -9,7 +9,8 @@ - + + @@ -28,6 +29,7 @@ + diff -r 501c51d60792 -r 96a38d44ba09 contrib/wix/mercurial.wxs --- a/contrib/wix/mercurial.wxs Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/wix/mercurial.wxs Sat Jul 18 17:32:38 2015 -0500 @@ -118,6 +118,7 @@ Level='1' Absent='disallow' > + diff -r 501c51d60792 -r 96a38d44ba09 contrib/wix/templates.wxs --- a/contrib/wix/templates.wxs Fri Jul 03 18:10:58 2015 +0100 +++ b/contrib/wix/templates.wxs Sat Jul 18 17:32:38 2015 -0500 @@ -12,6 +12,7 @@ + @@ -36,6 +37,13 @@ + + + + + + + diff -r 501c51d60792 -r 96a38d44ba09 hgext/acl.py --- a/hgext/acl.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/acl.py Sat Jul 18 17:32:38 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): @@ -282,6 +286,7 @@ ui.debug('acl: checking access for user "%s"\n' % user) + # deprecated config: acl.config cfg = ui.config('acl', 'config') if cfg: ui.readconfig(cfg, sections=['acl.groups', 'acl.allow.branches', diff -r 501c51d60792 -r 96a38d44ba09 hgext/blackbox.py --- a/hgext/blackbox.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/blackbox.py Sat Jul 18 17:32:38 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 @@ -48,14 +52,14 @@ def rotate(oldpath, newpath): try: os.unlink(newpath) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: self.debug("warning: cannot remove '%s': %s\n" % (newpath, err.strerror)) try: if newpath: os.rename(oldpath, newpath) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: self.debug("warning: cannot rename '%s' to '%s': %s\n" % (newpath, oldpath, err.strerror)) @@ -88,7 +92,7 @@ elif util.safehasattr(self, '_bbopener'): try: self._blackbox = self._openlogfile() - except (IOError, OSError), err: + except (IOError, OSError) as err: self.debug('warning: cannot write to blackbox.log: %s\n' % err.strerror) del self._bbopener @@ -106,7 +110,7 @@ formattedmsg = msg[0] % msg[1:] try: blackbox.write('%s %s> %s' % (date, user, formattedmsg)) - except IOError, err: + except IOError as err: self.debug('warning: cannot write to blackbox.log: %s\n' % err.strerror) lastblackbox = blackbox diff -r 501c51d60792 -r 96a38d44ba09 hgext/bugzilla.py --- a/hgext/bugzilla.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/bugzilla.py Sat Jul 18 17:32:38 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): @@ -353,7 +357,7 @@ try: import MySQLdb as mysql bzmysql._MySQLdb = mysql - except ImportError, err: + except ImportError as err: raise util.Abort(_('python mysql support not available: %s') % err) bzaccess.__init__(self, ui) @@ -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() @@ -908,5 +910,5 @@ for bug in bugs: bz.update(bug, bugs[bug], ctx) bz.notify(bugs, util.email(ctx.user())) - except Exception, e: + except Exception as e: raise util.Abort(_('Bugzilla error: %s') % e) diff -r 501c51d60792 -r 96a38d44ba09 hgext/censor.py --- a/hgext/censor.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/censor.py Sat Jul 18 17:32:38 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', @@ -43,6 +47,12 @@ if not rev: raise util.Abort(_('must specify revision to censor')) + wctx = repo[None] + + m = scmutil.match(wctx, (path,)) + if m.anypats() or len(m.files()) != 1: + raise util.Abort(_('can only specify an explicit filename')) + path = m.files()[0] flog = repo.file(path) if not len(flog): raise util.Abort(_('cannot censor file with no history')) @@ -66,7 +76,6 @@ raise util.Abort(_('cannot censor file in heads (%s)') % headlist, hint=_('clean/delete and commit first')) - wctx = repo[None] wp = wctx.parents() if ctx.node() in [p.node() for p in wp]: raise util.Abort(_('cannot censor working directory'), @@ -143,7 +152,7 @@ # Immediate children of censored node must be re-added as fulltext. try: revdata = flog.revision(srev) - except error.CensoredNodeError, e: + except error.CensoredNodeError as e: revdata = e.tombstone dlen = rewrite(srev, offset, revdata) else: diff -r 501c51d60792 -r 96a38d44ba09 hgext/children.py --- a/hgext/children.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/children.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/churn.py --- a/hgext/churn.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/churn.py Sat Jul 18 17:32:38 2015 -0500 @@ -9,21 +9,24 @@ '''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) - except SyntaxError, inst: + except SyntaxError as inst: raise util.Abort(inst.args[0]) return t diff -r 501c51d60792 -r 96a38d44ba09 hgext/color.py --- a/hgext/color.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/color.py Sat Jul 18 17:32:38 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 @@ -190,7 +194,7 @@ try: curses.setupterm() - except curses.error, e: + except curses.error as e: _terminfo_params = {} return @@ -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', @@ -492,14 +496,14 @@ # etc. don't need to be quoted mapping.update(dict([(k, k) for k in _effects])) - thing = templater._evalifliteral(args[1], context, mapping) + thing = args[1][0](context, mapping, args[1][1]) # apparently, repo could be a string that is the favicon? repo = mapping.get('repo', '') if isinstance(repo, str): return thing - label = templater._evalifliteral(args[0], context, mapping) + label = args[0][0](context, mapping, args[0][1]) thing = templater.stringify(thing) label = templater.stringify(label) @@ -527,6 +531,7 @@ return orig(gitsub, commands, env, stream, cwd) extensions.wrapfunction(dispatch, '_runcommand', colorcmd) extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit) + templatelabel.__doc__ = templater.funcs['label'].__doc__ templater.funcs['label'] = templatelabel def extsetup(ui): diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/__init__.py --- a/hgext/convert/__init__.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/__init__.py Sat Jul 18 17:32:38 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. @@ -25,7 +29,7 @@ _('FILE')), ('s', 'source-type', '', _('source repository type'), _('TYPE')), ('d', 'dest-type', '', _('destination repository type'), _('TYPE')), - ('r', 'rev', '', _('import up to source revision REV'), _('REV')), + ('r', 'rev', [], _('import up to source revision REV'), _('REV')), ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')), ('', 'filemap', '', _('remap file names using contents of file'), _('FILE')), @@ -305,6 +309,10 @@ is very expensive for large projects, and is only effective when ``convert.git.similarity`` is greater than 0. The default is False. + :convert.git.remoteprefix: remote refs are converted as bookmarks with + ``convert.git.remoteprefix`` as a prefix followed by a /. The default + is 'remote'. + Perforce Source ############### @@ -324,6 +332,23 @@ Mercurial Destination ##################### + The Mercurial destination will recognize Mercurial subrepositories in the + destination directory, and update the .hgsubstate file automatically if the + destination subrepositories contain the //.hg/shamap file. + Converting a repository with subrepositories requires converting a single + repository at a time, from the bottom up. + + .. container:: verbose + + An example showing how to convert a repository with subrepositories:: + + # so convert knows the type when it sees a non empty destination + $ hg init converted + + $ hg convert orig/sub1 converted/sub1 + $ hg convert orig/sub2 converted/sub2 + $ hg convert orig converted + The following options are supported: :convert.hg.clonebranches: dispatch source branches in separate @@ -334,6 +359,17 @@ :convert.hg.usebranchnames: preserve branch names. The default is True. + + :convert.hg.sourcename: records the given string as a 'convert_source' extra + value on each commit made in the target repository. The default is None. + + All Destinations + ################ + + All destination types accept the following options: + + :convert.skiptags: does not convert tags from the source repo to the target + repo. The default is False. """ return convcmd.convert(ui, src, dest, revmapfile, **opts) diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/bzr.py --- a/hgext/convert/bzr.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/bzr.py Sat Jul 18 17:32:38 2015 -0500 @@ -33,8 +33,8 @@ class bzr_source(converter_source): """Reads Bazaar repositories by using the Bazaar Python libraries""" - def __init__(self, ui, path, rev=None): - super(bzr_source, self).__init__(ui, path, rev=rev) + def __init__(self, ui, path, revs=None): + super(bzr_source, self).__init__(ui, path, revs=revs) if not os.path.exists(os.path.join(path, '.bzr')): raise NoRepo(_('%s does not look like a Bazaar repository') @@ -95,20 +95,20 @@ return self.sourcerepo.find_branches(using=True) def getheads(self): - if not self.rev: + if not self.revs: # Set using=True to avoid nested repositories (see issue3254) heads = sorted([b.last_revision() for b in self._bzrbranches()]) else: revid = None for branch in self._bzrbranches(): try: - r = RevisionSpec.from_string(self.rev) + r = RevisionSpec.from_string(self.revs[0]) info = r.in_history(branch) except errors.BzrError: pass revid = info.rev_id if revid is None: - raise util.Abort(_('%s is not a valid revision') % self.rev) + raise util.Abort(_('%s is not a valid revision') % self.revs[0]) heads = [revid] # Empty repositories return 'null:', which cannot be retrieved heads = [h for h in heads if h != 'null:'] diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/common.py --- a/hgext/convert/common.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/common.py Sat Jul 18 17:32:38 2015 -0500 @@ -7,7 +7,7 @@ import base64, errno, subprocess, os, datetime, re import cPickle as pickle -from mercurial import util +from mercurial import phases, util from mercurial.i18n import _ propertycache = util.propertycache @@ -44,7 +44,7 @@ class commit(object): def __init__(self, author, date, desc, parents, branch=None, rev=None, - extra={}, sortkey=None): + extra={}, sortkey=None, saverev=True, phase=phases.draft): self.author = author or 'unknown' self.date = date or '0 0' self.desc = desc @@ -53,16 +53,18 @@ self.rev = rev self.extra = extra self.sortkey = sortkey + self.saverev = saverev + self.phase = phase class converter_source(object): """Conversion source interface""" - def __init__(self, ui, path=None, rev=None): + def __init__(self, ui, path=None, revs=None): """Initialize conversion source (or raise NoRepo("message") exception if path is not a valid repository)""" self.ui = ui self.path = path - self.rev = rev + self.revs = revs self.encoding = 'utf-8' @@ -425,7 +427,7 @@ return try: fp = open(self.path, 'r') - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise return @@ -449,7 +451,7 @@ if self.fp is None: try: self.fp = open(self.path, 'a') - except IOError, err: + except IOError as err: raise util.Abort(_('could not open map file %r: %s') % (self.path, err.strerror)) self.fp.write('%s %s\n' % (key, value)) diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/convcmd.py Sat Jul 18 17:32:38 2015 -0500 @@ -29,6 +29,39 @@ else: return s.decode('utf-8').encode(orig_encoding, 'replace') +def mapbranch(branch, branchmap): + ''' + >>> bmap = {'default': 'branch1'} + >>> for i in ['', None]: + ... mapbranch(i, bmap) + 'branch1' + 'branch1' + >>> bmap = {'None': 'branch2'} + >>> for i in ['', None]: + ... mapbranch(i, bmap) + 'branch2' + 'branch2' + >>> bmap = {'None': 'branch3', 'default': 'branch4'} + >>> for i in ['None', '', None, 'default', 'branch5']: + ... mapbranch(i, bmap) + 'branch3' + 'branch4' + 'branch4' + 'branch4' + 'branch5' + ''' + # If branch is None or empty, this commit is coming from the source + # repository's default branch and destined for the default branch in the + # destination repository. For such commits, using a literal "default" + # in branchmap below allows the user to map "default" to an alternate + # default branch in the destination repository. + branch = branchmap.get(branch or 'default', branch) + # At some point we used "None" literal to denote the default branch, + # attempt to use that for backward compatibility. + if (not branch): + branch = branchmap.get(str(None), branch) + return branch + source_converters = [ ('cvs', convert_cvs, 'branchsort'), ('git', convert_git, 'branchsort'), @@ -46,15 +79,15 @@ ('svn', svn_sink), ] -def convertsource(ui, path, type, rev): +def convertsource(ui, path, type, revs): exceptions = [] if type and type not in [s[0] for s in source_converters]: raise util.Abort(_('%s: invalid source repository type') % type) for name, source, sortmode in source_converters: try: if not type or name == type: - return source(ui, path, rev), sortmode - except (NoRepo, MissingTool), inst: + return source(ui, path, revs), sortmode + except (NoRepo, MissingTool) as inst: exceptions.append(inst) if not ui.quiet: for inst in exceptions: @@ -68,9 +101,9 @@ try: if not type or name == type: return sink(ui, path) - except NoRepo, inst: + except NoRepo as inst: ui.note(_("convert: %s\n") % inst) - except MissingTool, inst: + except MissingTool as inst: raise util.Abort('%s\n' % inst) raise util.Abort(_('%s: unknown repository type') % path) @@ -377,12 +410,7 @@ def cachecommit(self, rev): commit = self.source.getcommit(rev) commit.author = self.authors.get(commit.author, commit.author) - # If commit.branch is None, this commit is coming from the source - # repository's default branch and destined for the default branch in the - # destination repository. For such commits, passing a literal "None" - # string to branchmap.get() below allows the user to map "None" to an - # alternate default branch in the destination repository. - commit.branch = self.branchmap.get(str(commit.branch), commit.branch) + commit.branch = mapbranch(commit.branch, self.branchmap) self.commitcache[rev] = commit return commit @@ -460,22 +488,23 @@ self.copy(c) self.ui.progress(_('converting'), None) - tags = self.source.gettags() - ctags = {} - for k in tags: - v = tags[k] - if self.map.get(v, SKIPREV) != SKIPREV: - ctags[k] = self.map[v] + if not self.ui.configbool('convert', 'skiptags'): + tags = self.source.gettags() + ctags = {} + for k in tags: + v = tags[k] + if self.map.get(v, SKIPREV) != SKIPREV: + ctags[k] = self.map[v] - if c and ctags: - nrev, tagsparent = self.dest.puttags(ctags) - if nrev and tagsparent: - # write another hash correspondence to override the previous - # one so we don't end up with extra tag heads - tagsparents = [e for e in self.map.iteritems() - if e[1] == tagsparent] - if tagsparents: - self.map[tagsparents[0][0]] = nrev + if c and ctags: + nrev, tagsparent = self.dest.puttags(ctags) + if nrev and tagsparent: + # write another hash correspondence to override the + # previous one so we don't end up with extra tag heads + tagsparents = [e for e in self.map.iteritems() + if e[1] == tagsparent] + if tagsparents: + self.map[tagsparents[0][0]] = nrev bookmarks = self.source.getbookmarks() cbookmarks = {} diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/cvs.py --- a/hgext/convert/cvs.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/cvs.py Sat Jul 18 17:32:38 2015 -0500 @@ -15,8 +15,8 @@ import cvsps class convert_cvs(converter_source): - def __init__(self, ui, path, rev=None): - super(convert_cvs, self).__init__(ui, path, rev=rev) + def __init__(self, ui, path, revs=None): + super(convert_cvs, self).__init__(ui, path, revs=revs) cvs = os.path.join(path, "CVS") if not os.path.exists(cvs): @@ -41,14 +41,17 @@ self.changeset = {} maxrev = 0 - if self.rev: + if self.revs: + if len(self.revs) > 1: + raise util.Abort(_('cvs source does not support specifying ' + 'multiple revs')) # TODO: handle tags try: # patchset number? - maxrev = int(self.rev) + maxrev = int(self.revs[0]) except ValueError: raise util.Abort(_('revision %s is not a patchset number') - % self.rev) + % self.revs[0]) d = os.getcwd() try: @@ -136,7 +139,7 @@ passw = part2 break pf.close() - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: if not getattr(inst, 'filename', None): inst.filename = cvspass diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/cvsps.py --- a/hgext/convert/cvsps.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/cvsps.py Sat Jul 18 17:32:38 2015 -0500 @@ -179,7 +179,7 @@ break ui.note(_('cache has %d log entries\n') % len(oldlog)) - except Exception, e: + except Exception as e: ui.note(_('error reading cache: %r\n') % e) if oldlog: @@ -824,7 +824,7 @@ log += createlog(ui, d, root=opts["root"], cache=cache) else: log = createlog(ui, root=opts["root"], cache=cache) - except logerror, e: + except logerror as e: ui.write("%r\n"%e) return diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/darcs.py --- a/hgext/convert/darcs.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/darcs.py Sat Jul 18 17:32:38 2015 -0500 @@ -27,8 +27,8 @@ pass class darcs_source(converter_source, commandline): - def __init__(self, ui, path, rev=None): - converter_source.__init__(self, ui, path, rev=rev) + def __init__(self, ui, path, revs=None): + converter_source.__init__(self, ui, path, revs=revs) commandline.__init__(self, ui, 'darcs') # check for _darcs, ElementTree so that we can easily skip @@ -197,11 +197,11 @@ try: data = util.readfile(path) mode = os.lstat(path).st_mode - except IOError, inst: + except IOError as inst: if inst.errno == errno.ENOENT: return None, None raise - mode = (mode & 0111) and 'x' or '' + mode = (mode & 0o111) and 'x' or '' return data, mode def gettags(self): diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/filemap.py --- a/hgext/convert/filemap.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/filemap.py Sat Jul 18 17:32:38 2015 -0500 @@ -156,6 +156,9 @@ self.origparents = {} self.children = {} self.seenchildren = {} + # experimental config: convert.ignoreancestorcheck + self.ignoreancestorcheck = self.ui.configbool('convert', + 'ignoreancestorcheck') def before(self): self.base.before() @@ -306,7 +309,7 @@ def getchanges(self, rev, full): parents = self.commits[rev].parents - if len(parents) > 1: + if len(parents) > 1 and not self.ignoreancestorcheck: self.rebuild() # To decide whether we're interested in rev we: @@ -332,9 +335,11 @@ mp1 = self.parentmap[p1] if mp1 == SKIPREV or mp1 in knownparents: continue - isancestor = util.any(p2 for p2 in parents - if p1 != p2 and mp1 != self.parentmap[p2] - and mp1 in self.wantedancestors[p2]) + + isancestor = (not self.ignoreancestorcheck and + 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: # This could be expensive, avoid unnecessary calls. if self._cachedcommit(p1).branch == branch: diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/git.py --- a/hgext/convert/git.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/git.py Sat Jul 18 17:32:38 2015 -0500 @@ -7,7 +7,7 @@ import os import subprocess -from mercurial import util, config +from mercurial import util, config, error from mercurial.node import hex, nullid from mercurial.i18n import _ @@ -86,8 +86,8 @@ data = fh.read() return data, fh.close() - def __init__(self, ui, path, rev=None): - super(convert_git, self).__init__(ui, path, rev=rev) + def __init__(self, ui, path, revs=None): + super(convert_git, self).__init__(ui, path, revs=revs) if os.path.isdir(path + "/.git"): path += "/.git" @@ -119,14 +119,18 @@ f.close() def getheads(self): - if not self.rev: + if not self.revs: heads, ret = self.gitread('git rev-parse --branches --remotes') heads = heads.splitlines() + if ret: + raise util.Abort(_('cannot retrieve git heads')) else: - heads, ret = self.gitread("git rev-parse --verify %s" % self.rev) - heads = [heads[:-1]] - if ret: - raise util.Abort(_('cannot retrieve git heads')) + heads = [] + for rev in self.revs: + rawhead, ret = self.gitread("git rev-parse --verify %s" % rev) + heads.append(rawhead[:-1]) + if ret: + raise util.Abort(_('cannot retrieve git head "%s"') % rev) return heads def catfile(self, rev, type): @@ -174,8 +178,9 @@ """ self.submodules = [] c = config.config() - # Each item in .gitmodules starts with \t that cant be parsed - c.parse('.gitmodules', content.replace('\t','')) + # Each item in .gitmodules starts with whitespace that cant be parsed + c.parse('.gitmodules', '\n'.join(line.strip() for line in + content.split('\n'))) for sec in c.sections(): s = c[sec] if 'url' in s and 'path' in s: @@ -184,9 +189,19 @@ def retrievegitmodules(self, version): modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules')) if ret: - raise util.Abort(_('cannot read submodules config file in %s') % - version) - self.parsegitmodules(modules) + # This can happen if a file is in the repo that has permissions + # 160000, but there is no .gitmodules file. + self.ui.warn(_("warning: cannot read submodules config file in " + "%s\n") % version) + return + + try: + self.parsegitmodules(modules) + except error.ParseError: + self.ui.warn(_("warning: unable to parse .gitmodules in %s\n") + % version) + return + for m in self.submodules: node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path)) if ret: @@ -361,8 +376,9 @@ prefixlen = len(prefix) # factor two commands - gitcmd = { 'remote/': 'git ls-remote --heads origin', - '': 'git show-ref'} + remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote') + gitcmd = { remoteprefix + '/': 'git ls-remote --heads origin', + '': 'git show-ref'} # Origin heads for reftype in gitcmd: diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/gnuarch.py --- a/hgext/convert/gnuarch.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/gnuarch.py Sat Jul 18 17:32:38 2015 -0500 @@ -27,8 +27,8 @@ self.ren_files = {} self.ren_dirs = {} - def __init__(self, ui, path, rev=None): - super(gnuarch_source, self).__init__(ui, path, rev=rev) + def __init__(self, ui, path, revs=None): + super(gnuarch_source, self).__init__(ui, path, revs=revs) if not os.path.exists(os.path.join(path, '{arch}')): raise NoRepo(_("%s does not look like a GNU Arch repository") @@ -215,7 +215,7 @@ mode = '' else: data = open(os.path.join(self.tmppath, name), 'rb').read() - mode = (mode & 0111) and 'x' or '' + mode = (mode & 0o111) and 'x' or '' return data, mode def _exclude(self, name): diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/hg.py --- a/hgext/convert/hg.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/hg.py Sat Jul 18 17:32:38 2015 -0500 @@ -22,8 +22,9 @@ from mercurial.i18n import _ from mercurial.node import bin, hex, nullid from mercurial import hg, util, context, bookmarks, error, scmutil, exchange +from mercurial import phases -from common import NoRepo, commit, converter_source, converter_sink +from common import NoRepo, commit, converter_source, converter_sink, mapfile import re sha1re = re.compile(r'\b[0-9a-f]{12,40}\b') @@ -41,7 +42,7 @@ if not self.repo.local(): raise NoRepo(_('%s is not a local Mercurial repository') % path) - except error.RepoError, err: + except error.RepoError as err: ui.traceback() raise NoRepo(err.args[0]) else: @@ -59,6 +60,7 @@ self.lock = None self.wlock = None self.filemapmode = False + self.subrevmaps = {} def before(self): self.ui.debug('run hg sink pre-conversion action\n') @@ -135,6 +137,45 @@ fp.write('%s %s\n' % (revid, s[1])) return fp.getvalue() + def _rewritesubstate(self, source, data): + fp = cStringIO.StringIO() + for line in data.splitlines(): + s = line.split(' ', 1) + if len(s) != 2: + continue + + revid = s[0] + subpath = s[1] + if revid != hex(nullid): + revmap = self.subrevmaps.get(subpath) + if revmap is None: + revmap = mapfile(self.ui, + self.repo.wjoin(subpath, '.hg/shamap')) + self.subrevmaps[subpath] = revmap + + # It is reasonable that one or more of the subrepos don't + # need to be converted, in which case they can be cloned + # into place instead of converted. Therefore, only warn + # once. + msg = _('no ".hgsubstate" updates will be made for "%s"\n') + if len(revmap) == 0: + sub = self.repo.wvfs.reljoin(subpath, '.hg') + + if self.repo.wvfs.exists(sub): + self.ui.warn(msg % subpath) + + newid = revmap.get(revid) + if not newid: + if len(revmap) > 0: + self.ui.warn(_("%s is missing from %s/.hg/shamap\n") % + (revid, subpath)) + else: + revid = newid + + fp.write('%s %s\n' % (revid, subpath)) + + return fp.getvalue() + def putcommit(self, files, copies, parents, commit, source, revmap, full, cleanp2): files = dict(files) @@ -152,6 +193,8 @@ return None if f == '.hgtags': data = self._rewritetags(source, revmap, data) + if f == '.hgsubstate': + data = self._rewritesubstate(source, data) return context.memfilectx(self.repo, f, data, 'l' in mode, 'x' in mode, copies.get(f)) @@ -182,7 +225,12 @@ extra = commit.extra.copy() - for label in ('source', 'transplant_source', 'rebase_source'): + sourcename = self.repo.ui.config('convert', 'hg.sourcename') + if sourcename: + extra['convert_source'] = sourcename + + for label in ('source', 'transplant_source', 'rebase_source', + 'intermediate-source'): node = extra.get(label) if node is None: @@ -201,7 +249,7 @@ if self.branchnames and commit.branch: extra['branch'] = commit.branch - if commit.rev: + if commit.rev and commit.saverev: extra['convert_revision'] = commit.rev while parents: @@ -216,9 +264,31 @@ fileset.update(self.repo[p2]) ctx = context.memctx(self.repo, (p1, p2), text, fileset, getfilectx, commit.author, commit.date, extra) - self.repo.commitctx(ctx) + + # We won't know if the conversion changes the node until after the + # commit, so copy the source's phase for now. + self.repo.ui.setconfig('phases', 'new-commit', + phases.phasenames[commit.phase], 'convert') + + tr = self.repo.transaction("convert") + + try: + node = hex(self.repo.commitctx(ctx)) + + # If the node value has changed, but the phase is lower than + # draft, set it back to draft since it hasn't been exposed + # anywhere. + if commit.rev != node: + ctx = self.repo[node] + if ctx.phase() < phases.draft: + phases.retractboundary(self.repo, tr, phases.draft, + [ctx.node()]) + tr.close() + finally: + tr.release() + text = "(octopus merge fixup)\n" - p2 = hex(self.repo.changelog.tip()) + p2 = node if self.filemapmode and nparents == 1: man = self.repo.manifest @@ -278,8 +348,8 @@ ctx = context.memctx(self.repo, (tagparent, None), "update tags", [".hgtags"], getfilectx, "convert-repo", date, extra) - self.repo.commitctx(ctx) - return hex(self.repo.changelog.tip()), hex(tagparent) + node = self.repo.commitctx(ctx) + return hex(node), hex(tagparent) def setfilemapmode(self, active): self.filemapmode = active @@ -306,8 +376,11 @@ return rev in self.repo class mercurial_source(converter_source): - def __init__(self, ui, path, rev=None): - converter_source.__init__(self, ui, path, rev) + def __init__(self, ui, path, revs=None): + converter_source.__init__(self, ui, path, revs) + if revs and len(revs) > 1: + raise util.Abort(_("mercurial source does not support specifying " + "multiple revisions")) self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False) self.ignored = set() self.saverev = ui.configbool('convert', 'hg.saverev', False) @@ -341,12 +414,12 @@ self.keep = children.__contains__ else: self.keep = util.always - if rev: - self._heads = [self.repo[rev].node()] + if revs: + self._heads = [self.repo[revs[0]].node()] else: self._heads = self.repo.heads() else: - if rev or startnode is not None: + if revs or startnode is not None: raise util.Abort(_('hg.revs cannot be combined with ' 'hg.startrev or --rev')) nodes = set() @@ -421,7 +494,7 @@ copies[name] = copysource except TypeError: pass - except error.LookupError, e: + except error.LookupError as e: if not self.ignoreerrors: raise self.ignored.add(name) @@ -431,15 +504,14 @@ def getcommit(self, rev): ctx = self.changectx(rev) parents = [p.hex() for p in self.parents(ctx)] - if self.saverev: - crev = rev - else: - crev = None + crev = rev + return commit(author=ctx.user(), date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'), desc=ctx.description(), rev=crev, parents=parents, branch=ctx.branch(), extra=ctx.extra(), - sortkey=ctx.rev()) + sortkey=ctx.rev(), saverev=self.saverev, + phase=ctx.phase()) def gettags(self): # This will get written to .hgtags, filter non global tags out. diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/monotone.py --- a/hgext/convert/monotone.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/monotone.py Sat Jul 18 17:32:38 2015 -0500 @@ -13,14 +13,17 @@ from mercurial.i18n import _ class monotone_source(converter_source, commandline): - def __init__(self, ui, path=None, rev=None): - converter_source.__init__(self, ui, path, rev) + def __init__(self, ui, path=None, revs=None): + converter_source.__init__(self, ui, path, revs) + if revs and len(revs) > 1: + raise util.Abort(_('monotone source does not support specifying ' + 'multiple revs')) commandline.__init__(self, ui, 'mtn') self.ui = ui self.path = path self.automatestdio = False - self.rev = rev + self.revs = revs norepo = NoRepo(_("%s does not look like a monotone repository") % path) @@ -219,10 +222,10 @@ # implement the converter_source interface: def getheads(self): - if not self.rev: + if not self.revs: return self.mtnrun("leaves").splitlines() else: - return [self.rev] + return self.revs def getchanges(self, rev, full): if full: diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/p4.py --- a/hgext/convert/p4.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/p4.py Sat Jul 18 17:32:38 2015 -0500 @@ -23,9 +23,23 @@ except EOFError: pass +def decodefilename(filename): + """Perforce escapes special characters @, #, *, or % + with %40, %23, %2A, or %25 respectively + + >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid') + 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid' + >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A') + '//Depot/Directory/%25/%23/#@.*' + """ + replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')] + for k, v in replacements: + filename = filename.replace(k, v) + return filename + class p4_source(converter_source): - def __init__(self, ui, path, rev=None): - super(p4_source, self).__init__(ui, path, rev=rev) + def __init__(self, ui, path, revs=None): + super(p4_source, self).__init__(ui, path, revs=revs) if "/" in path and not path.startswith('//'): raise NoRepo(_('%s does not look like a P4 repository') % path) @@ -36,11 +50,13 @@ self.heads = {} self.changeset = {} self.files = {} + self.copies = {} self.tags = {} self.lastbranch = {} self.parent = {} self.encoding = "latin_1" self.depotname = {} # mapping from local name to depot name + self.localname = {} # mapping from depot name to local name self.re_type = re.compile( "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)" "(\+\w+)?$") @@ -49,6 +65,9 @@ r":[^$\n]*\$") self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$") + if revs and len(revs) > 1: + raise util.Abort(_("p4 source does not support specifying " + "multiple revisions")) self._parse(ui, path) def _parse_view(self, path): @@ -99,7 +118,7 @@ startrev = self.ui.config('convert', 'p4.startrev', default=0) self.p4changes = [x for x in self.p4changes if ((not startrev or int(x) >= int(startrev)) and - (not self.rev or int(x) <= int(self.rev)))] + (not self.revs or int(x) <= int(self.revs[0])))] # now read the full changelists to get the list of file revisions ui.status(_('collecting p4 changelists\n')) @@ -121,24 +140,65 @@ date = (int(d["time"]), 0) # timezone not set c = commit(author=self.recode(d["user"]), date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'), - parents=parents, desc=desc, branch='', + parents=parents, desc=desc, branch=None, extra={"p4": change}) files = [] + copies = {} + copiedfiles = [] i = 0 while ("depotFile%d" % i) in d and ("rev%d" % i) in d: oldname = d["depotFile%d" % i] filename = None for v in vieworder: - if oldname.startswith(v): - filename = views[v] + oldname[len(v):] + if oldname.lower().startswith(v.lower()): + filename = decodefilename(views[v] + oldname[len(v):]) break if filename: files.append((filename, d["rev%d" % i])) self.depotname[filename] = oldname + if (d.get("action%d" % i) == "move/add"): + copiedfiles.append(filename) + self.localname[oldname] = filename i += 1 + + # Collect information about copied files + for filename in copiedfiles: + oldname = self.depotname[filename] + + flcmd = 'p4 -G filelog %s' \ + % util.shellquote(oldname) + flstdout = util.popen(flcmd, mode='rb') + + copiedfilename = None + for d in loaditer(flstdout): + copiedoldname = None + + i = 0 + while ("change%d" % i) in d: + if (d["change%d" % i] == change and + d["action%d" % i] == "move/add"): + j = 0 + while ("file%d,%d" % (i, j)) in d: + if d["how%d,%d" % (i, j)] == "moved from": + copiedoldname = d["file%d,%d" % (i, j)] + break + j += 1 + i += 1 + + if copiedoldname and copiedoldname in self.localname: + copiedfilename = self.localname[copiedoldname] + break + + if copiedfilename: + copies[filename] = copiedfilename + else: + ui.warn(_("cannot find source for copied file: %s@%s\n") + % (filename, change)) + self.changeset[change] = c self.files[change] = files + self.copies[change] = copies lastid = change if lastid: @@ -150,38 +210,54 @@ def getfile(self, name, rev): cmd = 'p4 -G print %s' \ % util.shellquote("%s#%s" % (self.depotname[name], rev)) - stdout = util.popen(cmd, mode='rb') - mode = None - contents = "" - keywords = None + lasterror = None + while True: + stdout = util.popen(cmd, mode='rb') + + mode = None + contents = "" + keywords = None - for d in loaditer(stdout): - code = d["code"] - data = d.get("data") + for d in loaditer(stdout): + code = d["code"] + data = d.get("data") - if code == "error": - raise IOError(d["generic"], data) + if code == "error": + # if this is the first time error happened + # re-attempt getting the file + if not lasterror: + lasterror = IOError(d["generic"], data) + # this will exit inner-most for-loop + break + else: + raise lasterror - elif code == "stat": - action = d.get("action") - if action in ["purge", "delete", "move/delete"]: - return None, None - p4type = self.re_type.match(d["type"]) - if p4type: - mode = "" - flags = (p4type.group(1) or "") + (p4type.group(3) or "") - if "x" in flags: - mode = "x" - if p4type.group(2) == "symlink": - mode = "l" - if "ko" in flags: - keywords = self.re_keywords_old - elif "k" in flags: - keywords = self.re_keywords + elif code == "stat": + action = d.get("action") + if action in ["purge", "delete", "move/delete"]: + return None, None + p4type = self.re_type.match(d["type"]) + if p4type: + mode = "" + flags = ((p4type.group(1) or "") + + (p4type.group(3) or "")) + if "x" in flags: + mode = "x" + if p4type.group(2) == "symlink": + mode = "l" + if "ko" in flags: + keywords = self.re_keywords_old + elif "k" in flags: + keywords = self.re_keywords - elif code == "text" or code == "binary": - contents += data + elif code == "text" or code == "binary": + contents += data + + lasterror = None + + if not lasterror: + break if mode is None: return None, None @@ -196,7 +272,7 @@ def getchanges(self, rev, full): if full: raise util.Abort(_("convert from p4 do not support --full")) - return self.files[rev], {}, set() + return self.files[rev], self.copies[rev], set() def getcommit(self, rev): return self.changeset[rev] diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/subversion.py --- a/hgext/convert/subversion.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/subversion.py Sat Jul 18 17:32:38 2015 -0500 @@ -126,7 +126,7 @@ except IOError: # Caller may interrupt the iteration pickle.dump(None, fp, protocol) - except Exception, inst: + except Exception as inst: pickle.dump(str(inst), fp, protocol) else: pickle.dump(None, fp, protocol) @@ -216,7 +216,7 @@ opener = urllib2.build_opener() rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path)) data = rsp.read() - except urllib2.HTTPError, inst: + except urllib2.HTTPError as inst: if inst.code != 404: # Except for 404 we cannot know for sure this is not an svn repo ui.warn(_('svn: cannot probe remote repository, assume it could ' @@ -268,8 +268,8 @@ # the parent module. A revision has at most one parent. # class svn_source(converter_source): - def __init__(self, ui, url, rev=None): - super(svn_source, self).__init__(ui, url, rev=rev) + def __init__(self, ui, url, revs=None): + super(svn_source, self).__init__(ui, url, revs=revs) if not (url.startswith('svn://') or url.startswith('svn+ssh://') or (os.path.exists(url) and @@ -325,11 +325,15 @@ "to libsvn version %s") % (self.url, svnversion)) - if rev: + if revs: + if len(revs) > 1: + raise util.Abort(_('subversion source does not support ' + 'specifying multiple revisions')) try: - latest = int(rev) + latest = int(revs[0]) except ValueError: - raise util.Abort(_('svn: revision %s is not an integer') % rev) + raise util.Abort(_('svn: revision %s is not an integer') % + revs[0]) self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/') @@ -944,7 +948,8 @@ firstcset.parents.append(latest) except SvnPathNotFound: pass - except SubversionException, (inst, num): + except SubversionException as xxx_todo_changeme: + (inst, num) = xxx_todo_changeme.args if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION: raise util.Abort(_('svn: branch has no revision %s') % to_revnum) @@ -970,7 +975,7 @@ info = info[-1] mode = ("svn:executable" in info) and 'x' or '' mode = ("svn:special" in info) and 'l' or mode - except SubversionException, e: + except SubversionException as e: notfound = (svn.core.SVN_ERR_FS_NOT_FOUND, svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) if e.apr_err in notfound: # File not found diff -r 501c51d60792 -r 96a38d44ba09 hgext/convert/transport.py --- a/hgext/convert/transport.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/convert/transport.py Sat Jul 18 17:32:38 2015 -0500 @@ -87,7 +87,8 @@ self.ra = svn.client.open_ra_session( self.svn_url, self.client, self.pool) - except SubversionException, (inst, num): + except SubversionException as xxx_todo_changeme: + (inst, num) = xxx_todo_changeme.args if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL, svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, svn.core.SVN_ERR_BAD_URL): diff -r 501c51d60792 -r 96a38d44ba09 hgext/eol.py --- a/hgext/eol.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/eol.py Sat Jul 18 17:32:38 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. @@ -214,7 +218,7 @@ return eolfile(ui, repo.root, data) except (IOError, LookupError): pass - except error.ParseError, inst: + except error.ParseError as inst: ui.warn(_("warning: ignoring .hgeol file due to parse error " "at %s: %s\n") % (inst.args[1], inst.args[0])) return None diff -r 501c51d60792 -r 96a38d44ba09 hgext/extdiff.py --- a/hgext/extdiff.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/extdiff.py Sat Jul 18 17:32:38 2015 -0500 @@ -63,13 +63,18 @@ from mercurial.i18n import _ from mercurial.node import short, nullid from mercurial import cmdutil, scmutil, util, commands, encoding, filemerge +from mercurial import archival import os, shlex, shutil, tempfile, re 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): +def snapshot(ui, repo, files, node, tmproot, listsubrepos): '''snapshot files as of some revision if not using snapshot, -I/-X does not work and recursive diff in tools like kdiff3 and meld displays too many files.''' @@ -80,33 +85,35 @@ dirname = '%s.%s' % (dirname, short(node)) base = os.path.join(tmproot, dirname) os.mkdir(base) + fns_and_mtime = [] + if node is not None: ui.note(_('making snapshot of %d files from rev %s\n') % (len(files), short(node))) else: ui.note(_('making snapshot of %d files from working directory\n') % (len(files))) - wopener = scmutil.opener(base) - fns_and_mtime = [] - ctx = repo[node] - for fn in sorted(files): - wfn = util.pconvert(fn) - if wfn not in ctx: - # File doesn't exist; could be a bogus modify - continue - ui.note(' %s\n' % wfn) - dest = os.path.join(base, wfn) - fctx = ctx[wfn] - data = repo.wwritedata(wfn, fctx.data()) - if 'l' in fctx.flags(): - wopener.symlink(data, wfn) - else: - wopener.write(wfn, data) - if 'x' in fctx.flags(): - util.setflags(dest, False, True) - if node is None: - fns_and_mtime.append((dest, repo.wjoin(fn), - os.lstat(dest).st_mtime)) + + if files: + repo.ui.setconfig("ui", "archivemeta", False) + + archival.archive(repo, base, node, 'files', + matchfn=scmutil.matchfiles(repo, files), + subrepos=listsubrepos) + + ctx = repo[node] + for fn in sorted(files): + wfn = util.pconvert(fn) + if wfn not in ctx: + # File doesn't exist; could be a bogus modify + continue + ui.note(' %s\n' % wfn) + + if node is None: + dest = os.path.join(base, wfn) + + fns_and_mtime.append((dest, repo.wjoin(fn), + os.lstat(dest).st_mtime)) return dirname, fns_and_mtime def dodiff(ui, repo, cmdline, pats, opts): @@ -140,10 +147,14 @@ if node1b == nullid: do3way = False + subrepos=opts.get('subrepos') + matcher = scmutil.match(repo[node2], pats, opts) - mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) + mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher, + listsubrepos=subrepos)[:3]) if do3way: - mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3]) + mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher, + listsubrepos=subrepos)[:3]) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b @@ -155,11 +166,12 @@ try: # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) - dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0] + dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = '@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) - dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0] + dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, + subrepos)[0] rev1b = '@%d' % repo[node1b].rev() else: dir1b = None @@ -171,14 +183,15 @@ dir2root = '' rev2 = '' if node2: - dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0] + dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = '@%d' % repo[node2].rev() elif len(common) > 1: #we only actually need to get the files to copy back to #the working dir in this case (because the other cases #are: diffing 2 revisions or single file -- in which case #the file is already directly passed to the diff tool). - dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot) + dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot, + subrepos) else: # This lets the diff tool open the changed file directly dir2 = '' @@ -246,7 +259,7 @@ _('pass option to comparison program'), _('OPT')), ('r', 'rev', [], _('revision'), _('REV')), ('c', 'change', '', _('change made by revision'), _('REV')), - ] + commands.walkopts, + ] + commands.walkopts + commands.subrepoopts, _('hg extdiff [OPT]... [FILE]...'), inferrepo=True) def extdiff(ui, repo, *pats, **opts): diff -r 501c51d60792 -r 96a38d44ba09 hgext/factotum.py --- a/hgext/factotum.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/factotum.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/fetch.py --- a/hgext/fetch.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/fetch.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/gpg.py --- a/hgext/gpg.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/gpg.py Sat Jul 18 17:32:38 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): @@ -213,6 +217,9 @@ If no revision is given, the parent of the working directory is used, or tip if no revision is checked out. + The ``gpg.cmd`` config setting can be used to specify the command + to run. A default key can be specified with ``gpg.key``. + See :hg:`help dates` for a list of formats valid for -d/--date. """ @@ -255,7 +262,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")) @@ -279,7 +286,7 @@ editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts) repo.commit(message, opts['user'], opts['date'], match=msigs, editor=editor) - except ValueError, inst: + except ValueError as inst: raise util.Abort(str(inst)) def shortkey(ui, key): diff -r 501c51d60792 -r 96a38d44ba09 hgext/graphlog.py --- a/hgext/graphlog.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/graphlog.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/hgcia.py --- a/hgext/hgcia.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/hgcia.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/hgk.py --- a/hgext/hgk.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/hgk.py Sat Jul 18 17:32:38 2015 -0500 @@ -22,7 +22,7 @@ the path to hgk in your configuration file:: [hgk] - path=/location/of/hgk + path = /location/of/hgk hgk can make use of the extdiff extension to visualize revisions. Assuming you had already configured extdiff vdiff command, just add:: @@ -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 501c51d60792 -r 96a38d44ba09 hgext/highlight/__init__.py --- a/hgext/highlight/__init__.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/highlight/__init__.py Sat Jul 18 17:32:38 2015 -0500 @@ -24,9 +24,13 @@ 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): +def filerevision_highlight(orig, web, req, tmpl, fctx): mt = ''.join(tmpl('mimetype', encoding=encoding.encoding)) # only pygmentize for mimetype containing 'html' so we both match # 'text/html' and possibly 'application/xhtml+xml' in the future @@ -38,7 +42,7 @@ if 'html' in mt: style = web.config('web', 'pygments_style', 'colorful') highlight.pygmentize('fileline', fctx, style, tmpl) - return orig(web, tmpl, fctx) + return orig(web, req, tmpl, fctx) def annotate_highlight(orig, web, req, tmpl): mt = ''.join(tmpl('mimetype', encoding=encoding.encoding)) diff -r 501c51d60792 -r 96a38d44ba09 hgext/histedit.py --- a/hgext/histedit.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/histedit.py Sat Jul 18 17:32:38 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 @@ -218,7 +222,7 @@ """Load histedit state from disk and set fields appropriately.""" try: fp = self.repo.vfs('histedit-state', 'r') - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise raise util.Abort(_('no histedit in progress')) @@ -381,7 +385,7 @@ - Add a 'histedit_source' entry in extra. - Note that fold have its own separated logic because its handling is a bit + Note that fold has its own separated logic because its handling is a bit different and not easily factored out of the fold method. """ phasemin = src.phase() @@ -429,6 +433,10 @@ ctxs = list(repo.set('%d::%d', first, last)) if not ctxs: return None + for c in ctxs: + if not c.mutable(): + raise util.Abort( + _("cannot fold into public change %s") % node.short(c.node())) base = first.parents()[0] # commit a new version of the old changeset, including the update @@ -707,15 +715,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' @@ -732,6 +740,7 @@ else: revs.extend(freeargs) if len(revs) == 0: + # experimental config: histedit.defaultrev histeditdefault = ui.config('histedit', 'defaultrev') if histeditdefault: revs.append(histeditdefault) @@ -742,6 +751,7 @@ replacements = [] state.keep = opts.get('keep', False) + supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt) # rebuild state if goal == 'continue': @@ -788,8 +798,13 @@ break else: pass - cleanupnode(ui, repo, 'created', tmpnodes) - cleanupnode(ui, repo, 'temp', leafs) + if supportsmarkers: + obsolete.createmarkers(repo, + ((repo[t],()) for t in sorted(tmpnodes))) + obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(leafs))) + else: + cleanupnode(ui, repo, 'created', tmpnodes) + cleanupnode(ui, repo, 'temp', leafs) state.clear() return else: @@ -873,7 +888,7 @@ if mapping: movebookmarks(ui, repo, mapping, state.topmost, ntm) # TODO update mq state - if obsolete.isenabled(repo, obsolete.createmarkersopt): + if supportsmarkers: markers = [] # sort by revision number because it sound "right" for prec in sorted(mapping, key=repo.changelog.rev): @@ -884,8 +899,10 @@ obsolete.createmarkers(repo, markers) else: cleanupnode(ui, repo, 'replaced', mapping) - - cleanupnode(ui, repo, 'temp', tmpnodes) + if supportsmarkers: + obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes))) + else: + cleanupnode(ui, repo, 'temp', tmpnodes) state.clear() if os.path.exists(repo.sjoin('undo')): os.unlink(repo.sjoin('undo')) @@ -924,7 +941,8 @@ raise util.Abort(_('cannot edit history that contains merges')) root = ctxs[0] # list is already sorted by repo.set if not root.mutable(): - raise util.Abort(_('cannot edit immutable changeset: %s') % root) + raise util.Abort(_('cannot edit public changeset: %s') % root, + hint=_('see "hg help phases" for details')) return [c.node() for c in ctxs] def makedesc(repo, action, rev): diff -r 501c51d60792 -r 96a38d44ba09 hgext/keyword.py --- a/hgext/keyword.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/keyword.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/largefiles/__init__.py --- a/hgext/largefiles/__init__.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/__init__.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/largefiles/basestore.py --- a/hgext/largefiles/basestore.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/basestore.py Sat Jul 18 17:32:38 2015 -0500 @@ -96,7 +96,7 @@ try: gothash = self._getfile(tmpfile, filename, hash) - except StoreError, err: + except StoreError as err: self.ui.warn(err.longmessage()) gothash = "" tmpfile.close() diff -r 501c51d60792 -r 96a38d44ba09 hgext/largefiles/lfcommands.py --- a/hgext/largefiles/lfcommands.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/lfcommands.py Sat Jul 18 17:32:38 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,60 @@ 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) + + if path is None: + raise util.Abort(_("missing largefile for \'%s\' in %s") + % (realname, realrev)) + fp = open(path, 'rb') - ui.progress(_('converting revisions'), None) + 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")) + + orig = convcmd.converter + convcmd.converter = converter + + try: + convcmd.convert(ui, src, dest) + finally: + convcmd.converter = orig 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 @@ -380,9 +369,7 @@ matches the revision ID). With --all, check every changeset in this repository.''' if all: - # Pass a list to the function rather than an iterator because we know a - # list will work. - revs = range(len(repo)) + revs = repo.revs('all()') else: revs = ['.'] @@ -404,7 +391,7 @@ for lfile in lfiles: try: expectedhash = repo[node][lfutil.standin(lfile)].data().strip() - except IOError, err: + except IOError as err: if err.errno == errno.ENOENT: continue # node must be None and standin wasn't found in wctx raise diff -r 501c51d60792 -r 96a38d44ba09 hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/lfutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -238,24 +238,28 @@ 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] + + # no warnings about missing files or directories + badfn = lambda f, msg: None + + if rmatcher and not rmatcher.always(): + pats = [os.path.join(standindir, pat) for pat in rmatcher.files()] + match = scmutil.match(repo[None], pats, badfn=badfn) + # if pats is empty, it would incorrectly always match, so clear _always + match._always = False else: # no patterns: relative to repo root - pats = [standindir] - # no warnings about missing files or directories - match = scmutil.match(repo[None], pats, opts) - match.bad = lambda f, msg: None + match = scmutil.match(repo[None], [standindir], badfn=badfn) return match def composestandinmatcher(repo, rmatcher): '''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 +368,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 501c51d60792 -r 96a38d44ba09 hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/overrides.py Sat Jul 18 17:32:38 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) @@ -51,8 +51,8 @@ def installnormalfilesmatchfn(manifest): '''installmatchfn with a matchfn that ignores all largefiles''' def overridematch(ctx, pats=[], opts={}, globbed=False, - default='relpath'): - match = oldmatch(ctx, pats, opts, globbed, default) + default='relpath', badfn=None): + match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn) return composenormalfilematcher(match, manifest) oldmatch = installmatchfn(overridematch) @@ -100,10 +100,10 @@ lfmatcher = match_.match(repo.root, '', list(lfpats)) lfnames = [] - m = copy.copy(matcher) - m.bad = lambda x, y: None + m = matcher + wctx = repo[None] - for f in repo.walk(m): + for f in repo.walk(match_.badmatch(m, lambda x, y: None)): exact = m.exact(f) lfile = lfutil.standin(f) in wctx nfile = f in wctx @@ -288,13 +288,14 @@ def overridelog(orig, ui, repo, *pats, **opts): def overridematchandpats(ctx, pats=[], opts={}, globbed=False, - default='relpath'): + default='relpath', badfn=None): """Matcher that merges root directory with .hglf, suitable for log. It is still possible to match .hglf directly. For any listed files run log on the standin too. matchfn tries both the given filename and with .hglf stripped. """ - matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default) + matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default, + badfn=badfn) m, p = copy.copy(matchandpats) if m.always(): @@ -358,7 +359,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): @@ -377,9 +378,9 @@ # (2) to determine what files to print out diffs for. # The magic matchandpats override should be used for case (1) but not for # case (2). - def overridemakelogfilematcher(repo, pats, opts): + def overridemakelogfilematcher(repo, pats, opts, badfn=None): wctx = repo[None] - match, pats = oldmatchandpats(wctx, pats, opts) + match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn) return lambda rev: match oldmatchandpats = installmatchandpatsfn(overridematchandpats) @@ -578,14 +579,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 as e: + if str(e) != _('no files to copy'): + raise e + else: + nonormalfiles = True + result = 0 finally: restorematchfn() @@ -608,86 +608,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', badfn=None): + 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, badfn=badfn) + 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 as e: + if str(e) != _('no files to copy'): + raise e + else: + nolfiles = True finally: restorematchfn() wlock.release() @@ -724,8 +723,8 @@ oldstandins = lfutil.getstandinsstate(repo) def overridematch(mctx, pats=[], opts={}, globbed=False, - default='relpath'): - match = oldmatch(mctx, pats, opts, globbed, default) + default='relpath', badfn=None): + match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn) m = copy.copy(match) # revert supports recursing into subrepos, and though largefiles @@ -744,7 +743,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): @@ -879,11 +878,24 @@ repo._lfstatuswriters.pop() repo._lfcommithooks.pop() +def overridearchivecmd(orig, ui, repo, dest, **opts): + repo.unfiltered().lfstatus = True + + try: + return orig(ui, repo.unfiltered(), dest, **opts) + finally: + repo.unfiltered().lfstatus = False + def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None, prefix='', mtime=None, subrepos=None): + if not repo.lfstatus: + return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime, + subrepos) + # No need to lock because we are only reading history and # largefile caches, neither of which are modified. - lfcommands.cachelfiles(repo.ui, repo, node) + if node is not None: + lfcommands.cachelfiles(repo.ui, repo, node) if kind not in archival.archivers: raise util.Abort(_("unknown archive type '%s'") % kind) @@ -908,18 +920,23 @@ archiver = archival.archivers[kind](dest, mtime or ctx.date()[0]) if repo.ui.configbool("ui", "archivemeta", True): - write('.hg_archival.txt', 0644, False, + write('.hg_archival.txt', 0o644, False, lambda: archival.buildmetadata(ctx)) for f in ctx: ff = ctx.flags(f) getdata = ctx[f].data if lfutil.isstandin(f): - path = lfutil.findfile(repo, getdata().strip()) - if path is None: - raise util.Abort( - _('largefile %s not found in repo store or system cache') - % lfutil.splitstandin(f)) + if node is not None: + path = lfutil.findfile(repo, getdata().strip()) + + if path is None: + raise util.Abort( + _('largefile %s not found in repo store or system cache') + % lfutil.splitstandin(f)) + else: + path = lfutil.splitstandin(f) + f = lfutil.splitstandin(f) def getdatafn(): @@ -932,22 +949,27 @@ fd.close() getdata = getdatafn - write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata) + write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata) if subrepos: for subpath in sorted(ctx.substate): - sub = ctx.sub(subpath) + sub = ctx.workingsub(subpath) submatch = match_.narrowmatcher(subpath, matchfn) + sub._repo.lfstatus = True sub.archive(archiver, prefix, submatch) archiver.done() def hgsubrepoarchive(orig, repo, archiver, prefix, match=None): + if not repo._repo.lfstatus: + return orig(repo, archiver, prefix, match) + repo._get(repo._state + ('hg',)) rev = repo._state[1] ctx = repo._repo[rev] - lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node()) + if ctx.node() is not None: + lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node()) def write(name, mode, islink, getdata): # At this point, the standin has been replaced with the largefile name, @@ -962,11 +984,16 @@ ff = ctx.flags(f) getdata = ctx[f].data if lfutil.isstandin(f): - path = lfutil.findfile(repo._repo, getdata().strip()) - if path is None: - raise util.Abort( - _('largefile %s not found in repo store or system cache') - % lfutil.splitstandin(f)) + if ctx.node() is not None: + path = lfutil.findfile(repo._repo, getdata().strip()) + + if path is None: + raise util.Abort( + _('largefile %s not found in repo store or system cache') + % lfutil.splitstandin(f)) + else: + path = lfutil.splitstandin(f) + f = lfutil.splitstandin(f) def getdatafn(): @@ -980,12 +1007,13 @@ getdata = getdatafn - write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata) + write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata) for subpath in sorted(ctx.substate): - sub = ctx.sub(subpath) + sub = ctx.workingsub(subpath) submatch = match_.narrowmatcher(subpath, match) - sub.archive(archiver, os.path.join(prefix, repo._path) + '/', submatch) + sub._repo.lfstatus = True + 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 501c51d60792 -r 96a38d44ba09 hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/proto.py Sat Jul 18 17:32:38 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 as 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 501c51d60792 -r 96a38d44ba09 hgext/largefiles/remotestore.py --- a/hgext/largefiles/remotestore.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/remotestore.py Sat Jul 18 17:32:38 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 as e: + raise util.Abort( + _('remotestore: could not open file %s: %s') + % (filename, str(e))) finally: if fd: fd.close() @@ -50,17 +49,17 @@ def _getfile(self, tmpfile, filename, hash): try: chunks = self._get(hash) - except urllib2.HTTPError, e: + except urllib2.HTTPError as e: # 401s get converted to util.Aborts; everything else is fine being # turned into a StoreError raise basestore.StoreError(filename, hash, self.url, str(e)) - except urllib2.URLError, e: + except urllib2.URLError as e: # This usually indicates a connection problem, so don't # keep trying with the other files... they will probably # all fail too. raise util.Abort('%s: %s' % (util.hidepassword(self.url), e.reason)) - except IOError, e: + except IOError as e: raise basestore.StoreError(filename, hash, self.url, str(e)) return lfutil.copyandhash(chunks, tmpfile) diff -r 501c51d60792 -r 96a38d44ba09 hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/reposetup.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/largefiles/uisetup.py --- a/hgext/largefiles/uisetup.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/largefiles/uisetup.py Sat Jul 18 17:32:38 2015 -0500 @@ -114,6 +114,8 @@ entry = extensions.wrapfunction(cmdutil, 'revert', overrides.overriderevert) + extensions.wrapcommand(commands.table, 'archive', + overrides.overridearchivecmd) extensions.wrapfunction(archival, 'archive', overrides.overridearchive) extensions.wrapfunction(subrepo.hgsubrepo, 'archive', overrides.hgsubrepoarchive) diff -r 501c51d60792 -r 96a38d44ba09 hgext/mq.py --- a/hgext/mq.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/mq.py Sat Jul 18 17:32:38 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 @@ -423,7 +430,9 @@ else: self.gitmode = 'no' except error.ConfigError: - self.gitmode = ui.config('mq', 'git', 'auto').lower() + # let's have check-config ignore the type mismatch + self.gitmode = ui.config(r'mq', 'git', 'auto').lower() + # deprecated config: mq.plain self.plainmode = ui.configbool('mq', 'plain', False) self.checkapplied = True @@ -441,7 +450,7 @@ try: lines = self.opener.read(self.statuspath).splitlines() return list(parselines(lines)) - except IOError, e: + except IOError as e: if e.errno == errno.ENOENT: return [] raise @@ -450,7 +459,7 @@ def fullseries(self): try: return self.opener.read(self.seriespath).splitlines() - except IOError, e: + except IOError as e: if e.errno == errno.ENOENT: return [] raise @@ -567,7 +576,7 @@ self.activeguards = [] try: guards = self.opener.read(self.guardspath).split() - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise guards = [] @@ -668,7 +677,7 @@ return try: os.unlink(undo) - except OSError, inst: + except OSError as inst: self.ui.warn(_('error removing undo: %s\n') % str(inst)) def backup(self, repo, files, copy=False): @@ -797,7 +806,7 @@ fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1, files=files, eolmode=None) return (True, list(files), fuzz) - except Exception, inst: + except Exception as inst: self.ui.note(str(inst) + '\n') if not self.ui.verbose: self.ui.warn(_("patch failed, unable to continue (try -v)\n")) @@ -807,9 +816,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 +828,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, @@ -950,7 +961,7 @@ for p in patches: try: os.unlink(self.join(p)) - except OSError, inst: + except OSError as inst: if inst.errno != errno.ENOENT: raise @@ -1093,9 +1104,9 @@ if name.startswith(prefix): raise util.Abort(_('patch name cannot begin with "%s"') % prefix) - for c in ('#', ':'): + for c in ('#', ':', '\r', '\n'): if c in name: - raise util.Abort(_('"%s" cannot be used in the name of a patch') + raise util.Abort(_('%r cannot be used in the name of a patch') % c) def checkpatchname(self, name, force=False): @@ -1129,12 +1140,11 @@ if inclsubs: substatestate = repo.dirstate['.hgsubstate'] if opts.get('include') or opts.get('exclude') or pats: - match = scmutil.match(repo[None], pats, opts) # detect missing files in pats def badfn(f, msg): if f != '.hgsubstate': # .hgsubstate is auto-created raise util.Abort('%s: %s' % (f, msg)) - match.bad = badfn + match = scmutil.match(repo[None], pats, opts, badfn=badfn) changes = repo.status(match=match) else: changes = self.checklocalchanges(repo, force=True) @@ -1151,7 +1161,7 @@ try: # if patch file write fails, abort early p = self.opener(patchfn, "w") - except IOError, e: + except IOError as e: raise util.Abort(_('cannot write patch "%s": %s') % (patchfn, e.strerror)) try: @@ -1517,7 +1527,7 @@ "managed by this patch queue")) if not repo[self.applied[-1].node].mutable(): raise util.Abort( - _("popping would remove an immutable revision"), + _("popping would remove a public revision"), hint=_('see "hg help phases" for details')) # we know there are no local changes, so we can make a simplified @@ -1588,7 +1598,7 @@ if repo.changelog.heads(top) != [top]: raise util.Abort(_("cannot refresh a revision with children")) if not repo[top].mutable(): - raise util.Abort(_("cannot refresh immutable revision"), + raise util.Abort(_("cannot refresh public revision"), hint=_('see "hg help phases" for details')) cparents = repo.changelog.parents(top) @@ -1681,8 +1691,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 +1746,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 @@ -1808,7 +1818,7 @@ raise util.Abort(_("patch queue directory already exists")) try: os.mkdir(self.path) - except OSError, inst: + except OSError as inst: if inst.errno != errno.EEXIST or not create: raise if create: diff -r 501c51d60792 -r 96a38d44ba09 hgext/notify.py --- a/hgext/notify.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/notify.py Sat Jul 18 17:32:38 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) @@ -277,7 +276,7 @@ p = email.Parser.Parser() try: msg = p.parsestr(data) - except email.Errors.MessageParseError, inst: + except email.Errors.MessageParseError as inst: raise util.Abort(inst) # store sender and subject diff -r 501c51d60792 -r 96a38d44ba09 hgext/pager.py --- a/hgext/pager.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/pager.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/patchbomb.py --- a/hgext/patchbomb.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/patchbomb.py Sat Jul 18 17:32:38 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=':'): @@ -514,6 +514,7 @@ def genmsgid(id): return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn()) + # deprecated config: patchbomb.from sender = (opts.get('from') or ui.config('email', 'from') or ui.config('patchbomb', 'from') or prompt(ui, 'From', ui.username())) @@ -628,14 +629,14 @@ try: generator.flatten(m, 0) fp.write('\n') - except IOError, inst: + except IOError as inst: if inst.errno != errno.EPIPE: raise if fp is not ui: fp.close() else: if not sendmail: - verifycert = ui.config('smtp', 'verifycert') + verifycert = ui.config('smtp', 'verifycert', 'strict') if opts.get('insecure'): ui.setconfig('smtp', 'verifycert', 'loose', 'patchbomb') try: diff -r 501c51d60792 -r 96a38d44ba09 hgext/progress.py --- a/hgext/progress.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/progress.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,316 +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. -"""show progress bars for some actions - -This extension uses the progress information logged by hg commands -to draw progress bars that are as informative as possible. Some progress -bars only offer indeterminate information, while others have a definite -end point. - -The following settings are available:: - - [progress] - delay = 3 # number of seconds (float) before showing the progress bar - changedelay = 1 # changedelay: minimum delay before showing a new topic. - # If set to less than 3 * refresh, that value will - # be used instead. - refresh = 0.1 # time in seconds between refreshes of the progress bar - format = topic bar number estimate # format of the progress bar - width = # if set, the maximum width of the progress information - # (that is, min(width, term width) will be used) - clear-complete = True # clear the progress bar after it's done - disable = False # if true, don't show a progress bar - assume-tty = False # if true, ALWAYS show a progress bar, unless - # disable is given - -Valid entries for the format field are topic, bar, number, unit, -estimate, speed, and item. item defaults to the last 20 characters of -the item, but this can be changed by adding either ``-`` which -would take the last num characters, or ``+`` for the first num -characters. -""" - -import sys -import time -import threading - -from mercurial.i18n import _ -testedwith = 'internal' - -from mercurial import encoding - -def spacejoin(*args): - return ' '.join(s for s in args if s) - -def shouldprint(ui): - return not ui.plain() and (ui._isatty(sys.stderr) or - ui.configbool('progress', 'assume-tty')) - -def fmtremaining(seconds): - if seconds < 60: - # i18n: format XX seconds as "XXs" - return _("%02ds") % (seconds) - minutes = seconds // 60 - if minutes < 60: - seconds -= minutes * 60 - # i18n: format X minutes and YY seconds as "XmYYs" - return _("%dm%02ds") % (minutes, seconds) - # we're going to ignore seconds in this case - minutes += 1 - hours = minutes // 60 - minutes -= hours * 60 - if hours < 30: - # i18n: format X hours and YY minutes as "XhYYm" - return _("%dh%02dm") % (hours, minutes) - # we're going to ignore minutes in this case - hours += 1 - days = hours // 24 - hours -= days * 24 - if days < 15: - # i18n: format X days and YY hours as "XdYYh" - return _("%dd%02dh") % (days, hours) - # we're going to ignore hours in this case - days += 1 - weeks = days // 7 - days -= weeks * 7 - if weeks < 55: - # i18n: format X weeks and YY days as "XwYYd" - return _("%dw%02dd") % (weeks, days) - # we're going to ignore days and treat a year as 52 weeks - weeks += 1 - years = weeks // 52 - weeks -= years * 52 - # i18n: format X years and YY weeks as "XyYYw" - return _("%dy%02dw") % (years, weeks) - -class progbar(object): - def __init__(self, ui): - self.ui = ui - self._refreshlock = threading.Lock() - self.resetstate() - - def resetstate(self): - self.topics = [] - self.topicstates = {} - self.starttimes = {} - self.startvals = {} - self.printed = False - self.lastprint = time.time() + float(self.ui.config( - 'progress', 'delay', default=3)) - self.curtopic = None - self.lasttopic = None - self.indetcount = 0 - self.refresh = float(self.ui.config( - 'progress', 'refresh', default=0.1)) - self.changedelay = max(3 * self.refresh, - float(self.ui.config( - 'progress', 'changedelay', default=1))) - self.order = self.ui.configlist( - 'progress', 'format', - default=['topic', 'bar', 'number', 'estimate']) +"""show progress bars for some actions (DEPRECATED) - def show(self, now, topic, pos, item, unit, total): - if not shouldprint(self.ui): - return - termwidth = self.width() - self.printed = True - head = '' - needprogress = False - tail = '' - for indicator in self.order: - add = '' - if indicator == 'topic': - add = topic - elif indicator == 'number': - if total: - add = ('% ' + str(len(str(total))) + - 's/%s') % (pos, total) - else: - add = str(pos) - elif indicator.startswith('item') and item: - slice = 'end' - if '-' in indicator: - wid = int(indicator.split('-')[1]) - elif '+' in indicator: - slice = 'beginning' - wid = int(indicator.split('+')[1]) - else: - wid = 20 - if slice == 'end': - add = encoding.trim(item, wid, leftside=True) - else: - add = encoding.trim(item, wid) - add += (wid - encoding.colwidth(add)) * ' ' - elif indicator == 'bar': - add = '' - needprogress = True - elif indicator == 'unit' and unit: - add = unit - elif indicator == 'estimate': - add = self.estimate(topic, pos, total, now) - elif indicator == 'speed': - add = self.speed(topic, pos, unit, now) - if not needprogress: - head = spacejoin(head, add) - else: - tail = spacejoin(tail, add) - if needprogress: - used = 0 - if head: - used += encoding.colwidth(head) + 1 - if tail: - used += encoding.colwidth(tail) + 1 - progwidth = termwidth - used - 3 - if total and pos <= total: - amt = pos * progwidth // total - bar = '=' * (amt - 1) - if amt > 0: - bar += '>' - bar += ' ' * (progwidth - amt) - else: - progwidth -= 3 - self.indetcount += 1 - # mod the count by twice the width so we can make the - # cursor bounce between the right and left sides - amt = self.indetcount % (2 * progwidth) - amt -= progwidth - bar = (' ' * int(progwidth - abs(amt)) + '<=>' + - ' ' * int(abs(amt))) - prog = ''.join(('[', bar , ']')) - out = spacejoin(head, prog, tail) - else: - out = spacejoin(head, tail) - sys.stderr.write('\r' + encoding.trim(out, termwidth)) - self.lasttopic = topic - sys.stderr.flush() - - def clear(self): - if not shouldprint(self.ui): - return - sys.stderr.write('\r%s\r' % (' ' * self.width())) - - def complete(self): - if not shouldprint(self.ui): - return - if self.ui.configbool('progress', 'clear-complete', default=True): - self.clear() - else: - sys.stderr.write('\n') - sys.stderr.flush() - - def width(self): - tw = self.ui.termwidth() - return min(int(self.ui.config('progress', 'width', default=tw)), tw) - - def estimate(self, topic, pos, total, now): - if total is None: - return '' - initialpos = self.startvals[topic] - target = total - initialpos - delta = pos - initialpos - if delta > 0: - elapsed = now - self.starttimes[topic] - if elapsed > float( - self.ui.config('progress', 'estimate', default=2)): - seconds = (elapsed * (target - delta)) // delta + 1 - return fmtremaining(seconds) - return '' - - def speed(self, topic, pos, unit, now): - initialpos = self.startvals[topic] - delta = pos - initialpos - elapsed = now - self.starttimes[topic] - if elapsed > float( - self.ui.config('progress', 'estimate', default=2)): - return _('%d %s/sec') % (delta / elapsed, unit) - return '' - - def _oktoprint(self, now): - '''Check if conditions are met to print - e.g. changedelay elapsed''' - if (self.lasttopic is None # first time we printed - # not a topic change - or self.curtopic == self.lasttopic - # it's been long enough we should print anyway - or now - self.lastprint >= self.changedelay): - return True - else: - return False - - def progress(self, topic, pos, item='', unit='', total=None): - now = time.time() - self._refreshlock.acquire() - try: - if pos is None: - self.starttimes.pop(topic, None) - self.startvals.pop(topic, None) - self.topicstates.pop(topic, None) - # reset the progress bar if this is the outermost topic - if self.topics and self.topics[0] == topic and self.printed: - self.complete() - self.resetstate() - # truncate the list of topics assuming all topics within - # this one are also closed - if topic in self.topics: - self.topics = self.topics[:self.topics.index(topic)] - # reset the last topic to the one we just unwound to, - # so that higher-level topics will be stickier than - # lower-level topics - if self.topics: - self.lasttopic = self.topics[-1] - else: - self.lasttopic = None - else: - if topic not in self.topics: - self.starttimes[topic] = now - self.startvals[topic] = pos - self.topics.append(topic) - self.topicstates[topic] = pos, item, unit, total - self.curtopic = topic - if now - self.lastprint >= self.refresh and self.topics: - if self._oktoprint(now): - self.lastprint = now - self.show(now, topic, *self.topicstates[topic]) - finally: - self._refreshlock.release() - -_singleton = None - -def uisetup(ui): - global _singleton - class progressui(ui.__class__): - _progbar = None - - def _quiet(self): - return self.debugflag or self.quiet - - def progress(self, *args, **opts): - if not self._quiet(): - self._progbar.progress(*args, **opts) - return super(progressui, self).progress(*args, **opts) - - def write(self, *args, **opts): - if not self._quiet() and self._progbar.printed: - self._progbar.clear() - return super(progressui, self).write(*args, **opts) - - def write_err(self, *args, **opts): - if not self._quiet() and self._progbar.printed: - self._progbar.clear() - return super(progressui, self).write_err(*args, **opts) - - # Apps that derive a class from ui.ui() can use - # setconfig('progress', 'disable', 'True') to disable this extension - if ui.configbool('progress', 'disable'): - return - if shouldprint(ui) and not ui.debugflag and not ui.quiet: - ui.__class__ = progressui - # we instantiate one globally shared progress bar to avoid - # competing progress bars when multiple UI objects get created - if not progressui._progbar: - if _singleton is None: - _singleton = progbar(ui) - progressui._progbar = _singleton - -def reposetup(ui, repo): - uisetup(repo.ui) +This extension has been merged into core, you can remove it from your config. +See hg help config.progress for configuration options. +""" diff -r 501c51d60792 -r 96a38d44ba09 hgext/purge.py --- a/hgext/purge.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/purge.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/rebase.py --- a/hgext/rebase.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/rebase.py Sat Jul 18 17:32:38 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)')), @@ -326,7 +330,7 @@ root = min(rebaseset) if not keepf and not repo[root].mutable(): - raise util.Abort(_("can't rebase immutable changeset %s") + raise util.Abort(_("can't rebase public changeset %s") % repo[root], hint=_('see "hg help phases" for details')) @@ -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' @@ -836,7 +838,7 @@ _setrebasesetvisibility(repo, state.keys()) return (originalwd, target, state, skipped, collapse, keep, keepbranches, external, activebookmark) - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise raise util.Abort(_('no rebase in progress')) @@ -867,7 +869,7 @@ immutable = [d for d in dstates if not repo[d].mutable()] cleanup = True if immutable: - repo.ui.warn(_("warning: can't clean up immutable changesets %s\n") + repo.ui.warn(_("warning: can't clean up public changesets %s\n") % ', '.join(str(repo[r]) for r in immutable), hint=_('see "hg help phases" for details')) cleanup = False @@ -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 501c51d60792 -r 96a38d44ba09 hgext/record.py --- a/hgext/record.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/record.py Sat Jul 18 17:32:38 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' @@ -49,8 +53,17 @@ This command is not available when committing a merge.''' + if not ui.interactive(): + raise util.Abort(_('running non-interactively, use %s instead') % + 'commit') + 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']: @@ -66,7 +79,7 @@ mq.refresh(ui, repo, **opts) # backup all changed files - cmdutil.dorecord(ui, repo, committomq, 'qrefresh', True, + cmdutil.dorecord(ui, repo, committomq, None, True, cmdutil.recordfilter, *pats, **opts) # This command registration is replaced during uisetup(). @@ -80,7 +93,9 @@ See :hg:`help qnew` & :hg:`help record` for more information and usage. ''' + return _qrecord('qnew', ui, repo, patch, *pats, **opts) +def _qrecord(cmdsuggest, ui, repo, patch, *pats, **opts): try: mq = extensions.find('mq') except KeyError: @@ -92,12 +107,17 @@ 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, cmdsuggest, False, + cmdutil.recordfilter, *pats, **opts) + finally: + ui.restoreconfig(backup) def qnew(origfn, ui, repo, patch, *args, **opts): if opts['interactive']: - return qrecord(ui, repo, patch, *args, **opts) + return _qrecord(None, ui, repo, patch, *args, **opts) return origfn(ui, repo, patch, *args, **opts) diff -r 501c51d60792 -r 96a38d44ba09 hgext/relink.py --- a/hgext/relink.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/relink.py Sat Jul 18 17:32:38 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]')) @@ -174,7 +178,7 @@ ui.progress(_('relinking'), pos, f, _('files'), total) relinked += 1 savedbytes += sz - except OSError, inst: + except OSError as inst: ui.warn('%s: %s\n' % (tgt, str(inst))) ui.progress(_('relinking'), None) diff -r 501c51d60792 -r 96a38d44ba09 hgext/schemes.py --- a/hgext/schemes.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/schemes.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/share.py --- a/hgext/share.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/share.py Sat Jul 18 17:32:38 2015 -0500 @@ -3,15 +3,51 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -'''share a common history between several working directories''' +'''share a common history between several working directories + +Automatic Pooled Storage for Clones +----------------------------------- + +When this extension is active, :hg:`clone` can be configured to +automatically share/pool storage across multiple clones. This +mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`. +The benefit of using this mode is the automatic management of +store paths and intelligent pooling of related repositories. + +The following ``share.`` config options influence this feature: + +``share.pool`` + Filesystem path where shared repository data will be stored. When + defined, :hg:`clone` will automatically use shared repository + storage instead of creating a store inside each clone. + +``share.poolnaming`` + How directory names in ``share.pool`` are constructed. + + "identity" means the name is derived from the first changeset in the + repository. In this mode, different remotes share storage if their + root/initial changeset is identical. In this mode, the local shared + repository is an aggregate of all encountered remote repositories. + + "remote" means the name is derived from the source repository's + path or URL. In this mode, storage is only shared if the path or URL + requested in the :hg:`clone` command matches exactly to a repository + that was cloned before. + + The default naming mode is "identity." +''' from mercurial.i18n import _ -from mercurial import cmdutil, hg, util, extensions, bookmarks +from mercurial import cmdutil, commands, hg, util, extensions, bookmarks from mercurial.hg import repository, parseurl import errno 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', @@ -68,19 +104,33 @@ destlock and destlock.release() lock and lock.release() - # update store, spath, sopener and sjoin of repo + # update store, spath, svfs and sjoin of repo repo.unfiltered().__init__(repo.baseui, repo.root) +# Wrap clone command to pass auto share options. +def clone(orig, ui, source, *args, **opts): + pool = ui.config('share', 'pool', None) + if pool: + pool = util.expandpath(pool) + + opts['shareopts'] = dict( + pool=pool, + mode=ui.config('share', 'poolnaming', 'identity'), + ) + + return orig(ui, source, *args, **opts) + def extsetup(ui): extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile) extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange) extensions.wrapfunction(bookmarks.bmstore, 'write', write) + extensions.wrapcommand(commands.table, 'clone', clone) def _hassharedbookmarks(repo): """Returns whether this repo has shared bookmarks""" try: shared = repo.vfs.read('shared').splitlines() - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise return False diff -r 501c51d60792 -r 96a38d44ba09 hgext/shelve.py --- a/hgext/shelve.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/shelve.py Sat Jul 18 17:32:38 2015 -0500 @@ -21,6 +21,8 @@ shelve". """ +import collections +import itertools from mercurial.i18n import _ from mercurial.node import nullid, nullrev, bin, hex from mercurial import changegroup, cmdutil, scmutil, phases, commands @@ -32,8 +34,14 @@ 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' +backupdir = 'shelve-backup' + class shelvedfile(object): """Helper for the file storing a single shelve @@ -43,6 +51,7 @@ self.repo = repo self.name = name self.vfs = scmutil.vfs(repo.join('shelved')) + self.backupvfs = scmutil.vfs(repo.join(backupdir)) self.ui = self.repo.ui if filetype: self.fname = name + '.' + filetype @@ -55,8 +64,22 @@ def filename(self): return self.vfs.join(self.fname) - def unlink(self): - util.unlink(self.filename()) + def backupfilename(self): + def gennames(base): + yield base + base, ext = base.rsplit('.', 1) + for i in itertools.count(1): + yield '%s-%d.%s' % (base, i, ext) + + name = self.backupvfs.join(self.fname) + for n in gennames(name): + if not self.backupvfs.exists(n): + return n + + def movetobackup(self): + if not self.backupvfs.isdir(): + self.backupvfs.makedir() + util.rename(self.filename(), self.backupfilename()) def stat(self): return self.vfs.stat(self.fname) @@ -64,7 +87,7 @@ def opener(self, mode='rb'): try: return self.vfs(self.fname, mode) - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise raise util.Abort(_("shelved change '%s' not found") % self.name) @@ -135,6 +158,27 @@ def clear(cls, repo): util.unlinkpath(repo.join(cls._filename), ignoremissing=True) +def cleanupoldbackups(repo): + vfs = scmutil.vfs(repo.join(backupdir)) + maxbackups = repo.ui.configint('shelve', 'maxbackups', 10) + hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')] + hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles]) + if 0 < maxbackups and maxbackups < len(hgfiles): + bordermtime = hgfiles[-maxbackups][0] + else: + bordermtime = None + for mtime, f in hgfiles[:len(hgfiles) - maxbackups]: + if mtime == bordermtime: + # keep it, because timestamp can't decide exact order of backups + continue + base = f[:-3] + for ext in 'hg patch'.split(): + try: + vfs.unlink(base + '.' + ext) + except OSError as err: + if err.errno != errno.ENOENT: + raise + def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" @@ -143,7 +187,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 +207,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('/', '_') @@ -235,7 +279,7 @@ if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: - node = cmdutil.dorecord(ui, repo, interactivecommitfunc, 'commit', + node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) @@ -276,7 +320,8 @@ for (name, _type) in repo.vfs.readdir('shelved'): suffix = name.rsplit('.', 1)[-1] if suffix in ('hg', 'patch'): - shelvedfile(repo, name).unlink() + shelvedfile(repo, name).movetobackup() + cleanupoldbackups(repo) finally: lockmod.release(wlock) @@ -284,17 +329,16 @@ """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).movetobackup() + cleanupoldbackups(repo) + except OSError as err: + if err.errno != errno.ENOENT: + raise + raise util.Abort(_("shelved change '%s' not found") % name) finally: lockmod.release(wlock) @@ -302,7 +346,7 @@ """return all shelves in repo as list of (time, filename)""" try: names = repo.vfs.readdir('shelved') - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise return [] @@ -363,6 +407,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(): @@ -428,7 +483,8 @@ """remove related files after an unshelve""" if not opts['keep']: for filetype in 'hg patch'.split(): - shelvedfile(repo, name, filetype).unlink() + shelvedfile(repo, name, filetype).movetobackup() + cleanupoldbackups(repo) def unshelvecontinue(ui, repo, state, opts): """subcommand to continue an in-progress unshelve""" @@ -491,18 +547,30 @@ restore. If none is given, the most recent shelved change is used. If a shelved change is applied successfully, the bundle that - contains the shelved changes is deleted afterwards. + contains the shelved changes is moved to a backup location + (.hg/shelve-backup). Since you can restore a shelved change on top of an arbitrary commit, it is possible that unshelving will result in a conflict between your changes and the commits you are unshelving onto. If this occurs, you must resolve the conflict, then use ``--continue`` to complete the unshelve operation. (The bundle - will not be deleted until you successfully complete the unshelve.) + will not be moved until you successfully complete the unshelve.) (Alternatively, you can use ``--abort`` to abandon an unshelve that causes a conflict. This reverts the unshelved changes, and - does not delete the bundle.) + leaves the bundle in place.) + + After a successful unshelve, the shelved changes are stored in a + backup directory. Only the N most recent backups are kept. N + defaults to 10 but can be overridden using the ``shelve.maxbackups`` + configuration option. + + .. container:: verbose + + Timestamp in seconds is used to decide order of backups. More + than ``maxbackups`` backups are kept, if same timestamp + prevents from deciding exact order of them, for safety. """ abortf = opts['abort'] continuef = opts['continue'] @@ -518,7 +586,7 @@ try: state = shelvedstate.load(repo) - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise raise util.Abort(_('no unshelve operation underway')) @@ -658,8 +726,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 +760,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 +786,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 501c51d60792 -r 96a38d44ba09 hgext/strip.py --- a/hgext/strip.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/strip.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/transplant.py --- a/hgext/transplant.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/transplant.py Sat Jul 18 17:32:38 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): @@ -268,7 +272,7 @@ files = set() patch.patch(self.ui, repo, patchfile, files=files, eolmode=None) files = list(files) - except Exception, inst: + except Exception as inst: seriespath = os.path.join(self.path, 'series') if os.path.exists(seriespath): os.unlink(seriespath) @@ -604,8 +608,10 @@ checkopts(opts, revs) if not opts.get('log'): + # deprecated config: transplant.log opts['log'] = ui.config('transplant', 'log') if not opts.get('filter'): + # deprecated config: transplant.filter opts['filter'] = ui.config('transplant', 'filter') tp = transplanter(ui, repo, opts) diff -r 501c51d60792 -r 96a38d44ba09 hgext/win32mbcs.py --- a/hgext/win32mbcs.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/win32mbcs.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 hgext/win32text.py --- a/hgext/win32text.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/win32text.py Sat Jul 18 17:32:38 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. @@ -167,6 +171,7 @@ repo.adddatafilter(name, fn) def extsetup(ui): + # deprecated config: win32text.warn if ui.configbool('win32text', 'warn', True): ui.warn(_("win32text is deprecated: " "http://mercurial.selenic.com/wiki/Win32TextExtension\n")) diff -r 501c51d60792 -r 96a38d44ba09 hgext/zeroconf/__init__.py --- a/hgext/zeroconf/__init__.py Fri Jul 03 18:10:58 2015 +0100 +++ b/hgext/zeroconf/__init__.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 i18n/polib.py --- a/i18n/polib.py Fri Jul 03 18:10:58 2015 +0100 +++ b/i18n/polib.py Sat Jul 18 17:32:38 2015 -0500 @@ -1276,7 +1276,7 @@ (action, state) = self.transitions[(symbol, self.current_state)] if action(): self.current_state = state - except Exception, exc: + except Exception as exc: raise IOError('Syntax error in po file (line %s)' % linenum) # state handlers diff -r 501c51d60792 -r 96a38d44ba09 mercurial/ancestor.py --- a/mercurial/ancestor.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/ancestor.py Sat Jul 18 17:32:38 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,13 +314,16 @@ parentrevs = self._parentrevs stoprev = self._stoprev - visit = util.deque(revs) + visit = collections.deque(revs) + + see = seen.add + schedule = visit.append while visit: for parent in parentrevs(visit.popleft()): if parent >= stoprev and parent not in seen: - visit.append(parent) - seen.add(parent) + schedule(parent) + see(parent) yield parent def __contains__(self, target): @@ -337,6 +340,7 @@ stoprev = self._stoprev heappop = heapq.heappop heappush = heapq.heappush + see = seen.add targetseen = False @@ -347,7 +351,7 @@ # We need to make sure we push all parents into the heap so # that we leave it in a consistent state for future calls. heappush(visit, -parent) - seen.add(parent) + see(parent) if parent == target: targetseen = True diff -r 501c51d60792 -r 96a38d44ba09 mercurial/archival.py --- a/mercurial/archival.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/archival.py Sat Jul 18 17:32:38 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 @@ -63,20 +67,25 @@ def buildmetadata(ctx): '''build content of .hg_archival.txt''' repo = ctx.repo() + hex = ctx.hex() + if ctx.rev() is None: + hex = ctx.p1().hex() + if ctx.dirty(): + hex += '+' + base = 'repo: %s\nnode: %s\nbranch: %s\n' % ( - _rootctx(repo).hex(), ctx.hex(), encoding.fromlocal(ctx.branch())) + _rootctx(repo).hex(), hex, encoding.fromlocal(ctx.branch())) tags = ''.join('tag: %s\n' % t for t in ctx.tags() if repo.tagtype(t) == 'global') if not tags: repo.ui.pushbuffer() - opts = {'template': '{latesttag}\n{latesttagdistance}', + opts = {'template': '{latesttag}\n{latesttagdistance}\n' + '{changessincelatesttag}', 'style': '', 'patch': None, 'git': None} cmdutil.show_changeset(repo.ui, repo, opts).show(ctx) - ltags, dist = repo.ui.popbuffer().split('\n') + ltags, dist, changessince = repo.ui.popbuffer().split('\n') ltags = ltags.split(':') - # XXX: ctx.rev() needs to be handled differently with wdir() - changessince = len(repo.revs('only(%d,%s)', ctx.rev(), ltags[0])) tags = ''.join('latesttag: %s\n' % t for t in ltags) tags += 'latesttagdistance: %s\n' % dist tags += 'changessincelatesttag: %s\n' % changessince @@ -148,7 +157,7 @@ i.size = len(data) if islink: i.type = tarfile.SYMTYPE - i.mode = 0777 + i.mode = 0o777 i.linkname = data data = None i.size = 0 @@ -211,7 +220,7 @@ i.create_system = 3 ftype = _UNX_IFREG if islink: - mode = 0777 + mode = 0o777 ftype = _UNX_IFLNK i.external_attr = (mode | ftype) << 16L # add "extended-timestamp" extra block, because zip archives @@ -293,7 +302,7 @@ if repo.ui.configbool("ui", "archivemeta", True): name = '.hg_archival.txt' if not matchfn or matchfn(name): - write(name, 0644, False, lambda: buildmetadata(ctx)) + write(name, 0o644, False, lambda: buildmetadata(ctx)) if matchfn: files = [f for f in ctx.manifest().keys() if matchfn(f)] @@ -305,14 +314,14 @@ repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total) for i, f in enumerate(files): ff = ctx.flags(f) - write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data) + write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, ctx[f].data) repo.ui.progress(_('archiving'), i + 1, item=f, unit=_('files'), total=total) repo.ui.progress(_('archiving'), None) if subrepos: for subpath in sorted(ctx.substate): - sub = ctx.sub(subpath) + sub = ctx.workingsub(subpath) submatch = matchmod.narrowmatcher(subpath, matchfn) total += sub.archive(archiver, prefix, submatch) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/bookmarks.py --- a/mercurial/bookmarks.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/bookmarks.py Sat Jul 18 17:32:38 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. """ @@ -45,7 +45,7 @@ self[refspec] = repo.changelog.lookup(sha) except LookupError: pass - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise @@ -54,7 +54,7 @@ if 'HG_PENDING' in os.environ: try: bkfile = repo.vfs('bookmarks.pending') - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise if bkfile is None: @@ -80,11 +80,12 @@ ''' repo = self._repo self._writerepo(repo) + repo.invalidatevolatilesets() 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: @@ -93,12 +94,6 @@ self._write(file) file.close() - # touch 00changelog.i so hgweb reloads bookmarks (no lock needed) - try: - repo.svfs.utime('00changelog.i', None) - except OSError: - pass - finally: wlock.release() @@ -106,17 +101,16 @@ 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') - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise return None @@ -129,17 +123,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 +143,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 as 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 +195,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: @@ -408,6 +396,11 @@ if scid in repo: # add remote bookmarks for changes we already have changed.append((b, bin(scid), status, _("adding remote bookmark %s\n") % (b))) + elif b in explicit: + explicit.remove(b) + ui.warn(_("remote bookmark %s points to locally missing %s\n") + % (b, scid[:12])) + for b, scid, dcid in advsrc: changed.append((b, bin(scid), status, _("updating bookmark %s\n") % (b))) @@ -434,6 +427,11 @@ explicit.discard(b) changed.append((b, bin(scid), status, _("importing bookmark %s\n") % (b))) + for b, scid, dcid in differ: + if b in explicit: + explicit.remove(b) + ui.warn(_("remote bookmark %s points to locally missing %s\n") + % (b, scid[:12])) if changed: tr = trfunc() diff -r 501c51d60792 -r 96a38d44ba09 mercurial/branchmap.py --- a/mercurial/branchmap.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/branchmap.py Sat Jul 18 17:32:38 2015 -0500 @@ -55,7 +55,7 @@ partial._closednodes.add(node) except KeyboardInterrupt: raise - except Exception, inst: + except Exception as inst: if repo.ui.debugflag: msg = 'invalid branchheads cache' if repo.filtername is not None: @@ -203,7 +203,7 @@ repo.ui.log('branchcache', 'wrote %s branch cache with %d labels and %d nodes\n', repo.filtername, len(self), nodecount) - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: repo.ui.debug("couldn't write branch cache: %s\n" % inst) # Abort may be raise by read only opener pass @@ -315,7 +315,7 @@ bndata = repo.vfs.read(_rbcnames) self._rbcsnameslen = len(bndata) # for verification before writing self._names = [encoding.tolocal(bn) for bn in bndata.split('\0')] - except (IOError, OSError), inst: + except (IOError, OSError) as inst: if readonly: # don't try to use cache - fall back to the slow path self.branchinfo = self._branchinfo @@ -324,7 +324,7 @@ try: data = repo.vfs.read(_rbcrevs) self._rbcrevs.fromstring(data) - except (IOError, OSError), inst: + except (IOError, OSError) as inst: repo.ui.debug("couldn't read revision branch cache: %s\n" % inst) # remember number of good records on disk @@ -418,7 +418,7 @@ for b in self._names[self._rbcnamescount:])) self._rbcsnameslen = f.tell() f.close() - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: repo.ui.debug("couldn't write revision branch cache names: " "%s\n" % inst) return @@ -436,7 +436,7 @@ end = revs * _rbcrecsize f.write(self._rbcrevs[start:end]) f.close() - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: repo.ui.debug("couldn't write revision branch cache: %s\n" % inst) return diff -r 501c51d60792 -r 96a38d44ba09 mercurial/bundle2.py --- a/mercurial/bundle2.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/bundle2.py Sat Jul 18 17:32:38 2015 -0500 @@ -66,7 +66,7 @@ :header size: int32 - The total number of Bytes used by the part headers. When the header is empty + The total number of Bytes used by the part header. When the header is empty (size = 0) this is interpreted as the end of stream marker. :header: @@ -156,7 +156,7 @@ import url import re -import changegroup, error +import changegroup, error, tags from i18n import _ _pack = struct.pack @@ -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 as exc: + for nbpart, part in iterparts: # consume the bundle content part.seek(0, 2) # Small hack to let caller code distinguish exceptions from bundle2 @@ -326,10 +347,16 @@ # craziness in a future version. exc.duringunbundle2 = True salvaged = [] + replycaps = None if op.reply is not None: salvaged = op.reply.salvageoutput() + replycaps = op.reply.capabilities + exc._replycaps = replycaps exc._bundle2salvagedoutput = salvaged raise + finally: + repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart) + return op def _processpart(op, part): @@ -337,23 +364,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) - except error.UnsupportedPartError, exc: + status = 'supported' + except error.UnsupportedPartError as 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 +511,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): @@ -532,7 +585,7 @@ if self._seekable: try: return self._fp.tell() - except IOError, e: + except IOError as e: if e.errno == errno.ESPIPE: self._seekable = False else: @@ -544,18 +597,18 @@ if util.safehasattr(self._fp, 'close'): return self._fp.close() -def getunbundler(ui, fp, header=None): - """return a valid unbundler object for a given header""" - if header is None: - header = changegroup.readexactly(fp, 4) - magic, version = header[0:2], header[2:4] +def getunbundler(ui, fp, magicstring=None): + """return a valid unbundler object for a given magicstring""" + if magicstring is None: + magicstring = changegroup.readexactly(fp, 4) + magic, version = magicstring[0:2], magicstring[2:4] if magic != 'HG': raise util.Abort(_('not a Mercurial bundle')) unbundlerclass = formatmap.get(version) 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' % magicstring) return unbundler class unbundle20(unpackermixin): @@ -572,7 +625,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 +658,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 +667,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 +684,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 +771,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 +832,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 as 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 +900,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 +998,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 +1016,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 +1031,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 +1070,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,11 +1106,16 @@ raise util.Abort(_('Seek failed\n')) self._pos = newpos +# These are only the static capabilities. +# Check the 'getrepocaps' function for the rest. capabilities = {'HG20': (), + 'error': ('abort', 'unsupportedcontent', 'pushraced', + 'pushkey'), 'listkeys': (), 'pushkey': (), 'digests': tuple(sorted(util.DIGESTS.keys())), 'remote-changegroup': ('http', 'https'), + 'hgtagsfnodes': (), } def getrepocaps(repo, allowpushback=False): @@ -1050,7 +1146,7 @@ obscaps = caps.get('obsmarkers', ()) return [int(c[1:]) for c in obscaps if c.startswith('V')] -@parthandler('changegroup', ('version',)) +@parthandler('changegroup', ('version', 'nbchanges')) def handlechangegroup(op, inpart): """apply a changegroup part on the repo @@ -1069,7 +1165,11 @@ cg = unpacker(inpart, 'UN') # the source and url passed here are overwritten by the one contained in # the transaction.hookargs argument. So 'bundle2' is a placeholder - ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2') + nbchangesets = None + if 'nbchanges' in inpart.params: + nbchangesets = int(inpart.params.get('nbchanges')) + ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2', + expectedtotal=nbchangesets) op.records.add('changegroup', {'return': ret}) if op.reply is not None: # This is definitely not the final form of this @@ -1148,7 +1248,7 @@ part.addparam('return', '%i' % ret, mandatory=False) try: real_part.validate() - except util.Abort, e: + except util.Abort as e: raise util.Abort(_('bundle at %s is corrupted:\n%s') % (util.hidepassword(raw_url), str(e))) assert not inpart.read() @@ -1195,6 +1295,17 @@ """Used to transmit abort error over the wire""" raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint')) +@parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret', + 'in-reply-to')) +def handleerrorpushkey(op, inpart): + """Used to transmit failure of a mandatory pushkey over the wire""" + kwargs = {} + for name in ('namespace', 'key', 'new', 'old', 'ret'): + value = inpart.params.get(name) + if value is not None: + kwargs[name] = value + raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs) + @parthandler('error:unsupportedcontent', ('parttype', 'params')) def handleerrorunsupportedcontent(op, inpart): """Used to transmit unknown content error over the wire""" @@ -1238,6 +1349,12 @@ rpart = op.reply.newpart('reply:pushkey') rpart.addparam('in-reply-to', str(inpart.id), mandatory=False) rpart.addparam('return', '%i' % ret, mandatory=False) + if inpart.mandatory and not ret: + kwargs = {} + for key in ('namespace', 'key', 'new', 'old', 'ret'): + if key in inpart.params: + kwargs[key] = inpart.params[key] + raise error.PushkeyFailed(partid=str(inpart.id), **kwargs) @parthandler('reply:pushkey', ('return', 'in-reply-to')) def handlepushkeyreply(op, inpart): @@ -1265,8 +1382,29 @@ @parthandler('reply:obsmarkers', ('new', 'in-reply-to')) -def handlepushkeyreply(op, inpart): +def handleobsmarkerreply(op, inpart): """retrieve the result of a pushkey request""" ret = int(inpart.params['new']) partid = int(inpart.params['in-reply-to']) op.records.add('obsmarkers', {'new': ret}, partid) + +@parthandler('hgtagsfnodes') +def handlehgtagsfnodes(op, inpart): + """Applies .hgtags fnodes cache entries to the local repo. + + Payload is pairs of 20 byte changeset nodes and filenodes. + """ + cache = tags.hgtagsfnodescache(op.repo.unfiltered()) + + count = 0 + while True: + node = inpart.read(20) + fnode = inpart.read(20) + if len(node) < 20 or len(fnode) < 20: + op.ui.debug('ignoring incomplete received .hgtags fnodes data\n') + break + cache.setfnode(node, fnode) + count += 1 + + cache.write() + op.ui.debug('applied %i hgtags fnodes cache entries\n' % count) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/bundlerepo.py Sat Jul 18 17:32:38 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) @@ -348,6 +346,7 @@ def instance(ui, path, create): if create: raise util.Abort(_('cannot create new bundle repository')) + # internal config: bundle.mainreporoot parentpath = ui.config("bundle", "mainreporoot", "") if not parentpath: # try to find the correct path to the working directory repo diff -r 501c51d60792 -r 96a38d44ba09 mercurial/byterange.py --- a/mercurial/byterange.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/byterange.py Sat Jul 18 17:32:38 2015 -0500 @@ -264,7 +264,7 @@ try: host = socket.gethostbyname(host) - except socket.error, msg: + except socket.error as msg: raise urllib2.URLError(msg) path, attrs = splitattr(req.get_selector()) dirs = path.split('/') @@ -322,7 +322,7 @@ headers += "Content-Length: %d\n" % retrlen headers = email.message_from_string(headers) return addinfourl(fp, headers, req.get_full_url()) - except ftplib.all_errors, msg: + except ftplib.all_errors as msg: raise IOError('ftp error', msg) def connect_ftp(self, user, passwd, host, port, dirs): @@ -352,7 +352,7 @@ # Use nlst to see if the file exists at all try: self.ftp.nlst(file) - except ftplib.error_perm, reason: + except ftplib.error_perm as reason: raise IOError('ftp error', reason) # Restore the transfer mode! self.ftp.voidcmd(cmd) @@ -360,7 +360,7 @@ try: cmd = 'RETR ' + file conn = self.ftp.ntransfercmd(cmd, rest) - except ftplib.error_perm, reason: + except ftplib.error_perm as reason: if str(reason).startswith('501'): # workaround for REST not supported error fp, retrlen = self.retrfile(file, type) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/changegroup.py --- a/mercurial/changegroup.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/changegroup.py Sat Jul 18 17:32:38 2015 -0500 @@ -283,8 +283,7 @@ if bundlecaps is None: bundlecaps = set() self._bundlecaps = bundlecaps - self._changelog = repo.changelog - self._manifest = repo.manifest + # experimental config: bundle.reorder reorder = repo.ui.config('bundle', 'reorder', 'auto') if reorder == 'auto': reorder = None @@ -304,7 +303,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 +324,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 +346,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 +379,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 +417,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 +443,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 +467,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 @@ -485,7 +492,7 @@ if revlog.iscensored(base) or revlog.iscensored(rev): try: delta = revlog.revision(node) - except error.CensoredNodeError, e: + except error.CensoredNodeError as e: delta = e.tombstone if base == nullrev: prefix = mdiff.trivialdiffheader(len(delta)) @@ -513,11 +520,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) @@ -609,7 +618,7 @@ bundler = cg1packer(repo, bundlecaps) return getsubset(repo, outgoing, bundler, source) -def _computeoutgoing(repo, heads, common): +def computeoutgoing(repo, heads, common): """Computes which revs are outgoing given a set of common and a set of heads. @@ -628,22 +637,6 @@ heads = cl.heads() return discovery.outgoing(cl, common, heads) -def getchangegroupraw(repo, source, heads=None, common=None, bundlecaps=None, - version='01'): - """Like changegroupsubset, but returns the set difference between the - ancestors of heads and the ancestors common. - - If heads is None, use the local heads. If common is None, use [nullid]. - - If version is None, use a version '1' changegroup. - - The nodes in common might not all be known locally due to the way the - current discovery protocol works. Returns a raw changegroup generator. - """ - outgoing = _computeoutgoing(repo, heads, common) - return getlocalchangegroupraw(repo, source, outgoing, bundlecaps=bundlecaps, - version=version) - def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None): """Like changegroupsubset, but returns the set difference between the ancestors of heads and the ancestors common. @@ -653,7 +646,7 @@ The nodes in common might not all be known locally due to the way the current discovery protocol works. """ - outgoing = _computeoutgoing(repo, heads, common) + outgoing = computeoutgoing(repo, heads, common) return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps) def changegroup(repo, basenodes, source): @@ -675,7 +668,7 @@ try: if not fl.addgroup(source, revmap, trp): raise util.Abort(_("received file revlog group is empty")) - except error.CensoredBaseError, e: + except error.CensoredBaseError as e: raise util.Abort(_("received delta base is censored: %s") % e) revisions += len(fl) - o files += 1 @@ -705,7 +698,7 @@ return revisions, files def addchangegroup(repo, source, srctype, url, emptyok=False, - targetphase=phases.draft): + targetphase=phases.draft, expectedtotal=None): """Add the changegroup returned by source.read() to this repo. srctype is a string like 'push', 'pull', or 'unbundle'. url is the URL of the repo where this changegroup is coming from. @@ -728,7 +721,6 @@ return 0 changesets = files = revisions = 0 - efiles = set() tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) # The transaction could have been created before and already carries source @@ -751,33 +743,35 @@ repo.ui.status(_("adding changesets\n")) clstart = len(cl) class prog(object): - step = _('changesets') - count = 1 - ui = repo.ui - total = None - def __call__(repo): - repo.ui.progress(repo.step, repo.count, unit=_('chunks'), - total=repo.total) - repo.count += 1 - pr = prog() - source.callback = pr + def __init__(self, step, total): + self._step = step + self._total = total + self._count = 1 + def __call__(self): + repo.ui.progress(self._step, self._count, unit=_('chunks'), + total=self._total) + self._count += 1 + source.callback = prog(_('changesets'), expectedtotal) + + efiles = set() + def onchangelog(cl, node): + efiles.update(cl.read(node)[3]) source.changelogheader() - srccontent = cl.addgroup(source, csmap, trp) + srccontent = cl.addgroup(source, csmap, trp, + addrevisioncb=onchangelog) + efiles = len(efiles) + if not (srccontent or emptyok): raise util.Abort(_("received changelog group is empty")) clend = len(cl) changesets = clend - clstart - for c in xrange(clstart, clend): - efiles.update(repo[c].files()) - efiles = len(efiles) repo.ui.progress(_('changesets'), None) # pull off the manifest group repo.ui.status(_("adding manifests\n")) - pr.step = _('manifests') - pr.count = 1 - pr.total = changesets # manifests <= changesets + # manifests <= changesets + source.callback = prog(_('manifests'), changesets) # no need to check for empty manifest group here: # if the result of the merge of 1 and 2 is the same in 3 and 4, # no new manifest will be created and the manifest group will @@ -790,19 +784,16 @@ 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) # process the files repo.ui.status(_("adding file changes\n")) - pr.step = _('files') - pr.count = 1 - pr.total = efiles source.callback = None - + pr = prog(_('files'), efiles) newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr, needfiles) revisions += newrevs @@ -835,7 +826,7 @@ repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs) added = [cl.node(r) for r in xrange(clstart, clend)] - publishing = repo.ui.configbool('phases', 'publish', True) + publishing = repo.publishing() if srctype in ('push', 'serve'): # Old servers can not push the boundary themselves. # New servers won't push the boundary if changeset already diff -r 501c51d60792 -r 96a38d44ba09 mercurial/changelog.py --- a/mercurial/changelog.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/changelog.py Sat Jul 18 17:32:38 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: @@ -267,6 +259,18 @@ self.checkinlinesize(tr) def readpending(self, file): + """read index data from a "pending" file + + During a transaction, the actual changeset data is already stored in the + main file, but not yet finalized in the on-disk index. Instead, a + "pending" index is written by the transaction logic. If this function + is running, we are likely in a subprocess invoked in a hook. The + subprocess is informed that it is within a transaction and needs to + access its content. + + This function will read all the index data out of the pending file and + overwrite the main index.""" + if not self.opener.exists(file): return # no pending data for changelog r = revlog.revlog(self.opener, file) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/cmdutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -from node import hex, nullid, nullrev, short +from node import hex, bin, nullid, nullrev, short from i18n import _ import os, sys, errno, re, tempfile, cStringIO, shutil import util, scmutil, templater, patch, error, templatekw, revlog, copies @@ -14,9 +14,22 @@ import changelog import bookmarks import encoding +import formatter 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 +46,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 +54,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,12 +79,13 @@ 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') % - cmdsuggest) + if cmdsuggest: + msg = _('running non-interactively, use %s instead') % cmdsuggest + else: + msg = _('running non-interactively') + raise util.Abort(msg) # make sure username is set before going interactive if not opts.get('user'): @@ -101,17 +122,13 @@ # 1. filter patch, so we have intending-to apply subset of it try: chunks = filterfn(ui, originalchunks) - except patch.PatchError, err: + except patch.PatchError as err: raise util.Abort(_('error parsing patch: %s') % err) # 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: @@ -139,7 +156,7 @@ backupdir = repo.join('record-backups') try: os.mkdir(backupdir) - except OSError, err: + except OSError as err: if err.errno != errno.EEXIST: raise try: @@ -175,7 +192,7 @@ ui.debug('applying patch\n') ui.debug(fp.getvalue()) patch.internalpatch(ui, repo, fp, 1, eolmode=None) - except patch.PatchError, err: + except patch.PatchError as err: raise util.Abort(str(err)) del fp @@ -189,8 +206,16 @@ finally: # 5. finally restore backed-up files try: + dirstate = repo.dirstate for realname, tmpname in backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) + + if dirstate[realname] == 'n': + # without normallookup, restoring timestamp + # may cause partially committed files + # to be treated as unmodified + dirstate.normallookup(realname) + util.copyfile(tmpname, repo.wjoin(realname)) # Our calls to copystat() here and above are a # hack to trick any editors that have f open that @@ -206,7 +231,14 @@ except OSError: pass - return commit(ui, repo, recordfunc, pats, opts) + def recordinwlock(ui, repo, message, match, opts): + wlock = repo.wlock() + try: + return recordfunc(ui, repo, message, match, opts) + finally: + wlock.release() + + return commit(ui, repo, recordinwlock, pats, opts) def findpossible(cmd, table, strict=False): """ @@ -295,7 +327,7 @@ message = ui.fin.read() else: message = '\n'.join(util.readfile(logfile).splitlines()) - except IOError, inst: + except IOError as inst: raise util.Abort(_("can't read commit message '%s': %s") % (logfile, inst.strerror)) return message @@ -404,7 +436,7 @@ newname.append(c) i += 1 return ''.join(newname) - except KeyError, inst: + except KeyError as inst: raise util.Abort(_("invalid format spec '%%%s' in output filename") % inst.args[0]) @@ -450,14 +482,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 +501,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_: @@ -581,7 +623,7 @@ else: util.copyfile(src, target) srcexists = True - except IOError, inst: + except IOError as inst: if inst.errno == errno.ENOENT: ui.warn(_('%s: deleted in working directory\n') % relsrc) srcexists = False @@ -749,7 +791,7 @@ finally: try: os.unlink(lockpath) - except OSError, e: + except OSError as e: if e.errno != errno.ENOENT: raise if parentfn: @@ -818,6 +860,7 @@ msg = _('applied to working directory') rejects = False + dsguard = None try: cmdline_message = logmessage(ui, opts) @@ -859,7 +902,7 @@ n = None if update: - repo.dirstate.beginparentchange() + dsguard = dirstateguard(repo, 'tryimportone') if p1 != parents[0]: updatefunc(repo, p1.node()) if p2 != parents[1]: @@ -873,7 +916,7 @@ try: patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix, files=files, eolmode=None, similarity=sim / 100.0) - except patch.PatchError, e: + except patch.PatchError as e: if not partial: raise util.Abort(str(e)) if partial: @@ -896,10 +939,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' @@ -911,7 +960,7 @@ try: patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix, files, eolmode=None) - except patch.PatchError, e: + except patch.PatchError as e: raise util.Abort(str(e)) if opts.get('exact'): editor = None @@ -937,6 +986,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, @@ -1072,7 +1122,8 @@ self.lastheader = None self.footer = None - def flush(self, rev): + def flush(self, ctx): + rev = ctx.rev() if rev in self.header: h = self.header[rev] if h != self.lastheader: @@ -1105,11 +1156,9 @@ hexfunc = hex else: hexfunc = short - if rev is None: - pctx = ctx.p1() - revnode = (pctx.rev(), hexfunc(pctx.node()) + '+') - else: - revnode = (rev, hexfunc(changenode)) + # as of now, wctx.node() and wctx.rev() return None, but we want to + # show the same values as {node} and {rev} templatekw + revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex()))) if self.ui.quiet: self.ui.write("%d:%s\n" % revnode, label='log.node') @@ -1240,7 +1289,7 @@ return parents if self.ui.debugflag: return [parents[0], self.repo['null']] - if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1: + if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1: return [] return parents @@ -1427,10 +1476,10 @@ self.footer = templater.stringify(self.t(types['footer'], **props)) - except KeyError, inst: + except KeyError as inst: msg = _("%s: no key named '%s'") raise util.Abort(msg % (self.t.mapfile, inst.args[0])) - except SyntaxError, inst: + except SyntaxError as inst: raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0])) def gettemplate(ui, tmpl, style): @@ -1443,9 +1492,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', '')) @@ -1462,40 +1511,7 @@ if not tmpl: return None, None - # looks like a literal template? - if '{' in tmpl: - return tmpl, None - - # perhaps a stock style? - if not os.path.split(tmpl)[0]: - mapname = (templater.templatepath('map-cmdline.' + tmpl) - or templater.templatepath(tmpl)) - if mapname and os.path.isfile(mapname): - return None, mapname - - # perhaps it's a reference to [templates] - t = ui.config('templates', tmpl) - if t: - try: - tmpl = templater.parsestring(t) - except SyntaxError: - tmpl = templater.parsestring(t, quoted=False) - return tmpl, None - - if tmpl == 'list': - ui.write(_("available styles: %s\n") % templater.stylelist()) - raise util.Abort(_("specify a template")) - - # perhaps it's a path to a map or a template - if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl): - # is it a mapfile for a style? - if os.path.basename(tmpl).startswith("map-"): - return None, os.path.realpath(tmpl) - tmpl = open(tmpl).read() - return tmpl, None - - # constant string? - return tmpl, None + return formatter.lookuptemplate(ui, 'changeset', tmpl) def show_changeset(ui, repo, opts, buffered=False): """show one changeset using template or regular display. @@ -1524,7 +1540,7 @@ try: t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered) - except SyntaxError, inst: + except SyntaxError as inst: raise util.Abort(inst.args[0]) return t @@ -1731,7 +1747,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 +1760,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 +1828,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 +1841,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 +1931,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 +1943,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 +2126,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 @@ -2151,7 +2160,7 @@ lines = displayer.hunk.pop(rev).split('\n') if not lines[-1]: del lines[-1] - displayer.flush(rev) + displayer.flush(ctx) edges = edgefn(type, char, lines, seen, rev, parents) for type, char, lines, coldata in edges: graphmod.ascii(ui, state, type, char, lines, coldata) @@ -2189,15 +2198,16 @@ def add(ui, repo, match, prefix, explicitonly, **opts): join = lambda f: os.path.join(prefix, f) bad = [] - oldbad = match.bad - match.bad = lambda x, y: bad.append(x) or oldbad(x, y) + + badfn = lambda x, y: bad.append(x) or match.bad(x, y) names = [] wctx = repo[None] cca = None abort, warn = scmutil.checkportabilityalert(ui) if abort or warn: cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate) - for f in wctx.walk(match): + + for f in wctx.walk(matchmod.badmatch(match, badfn)): exact = match.exact(f) if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): if cca: @@ -2226,11 +2236,11 @@ def forget(ui, repo, match, prefix, explicitonly): join = lambda f: os.path.join(prefix, f) bad = [] - oldbad = match.bad - match.bad = lambda x, y: bad.append(x) or oldbad(x, y) + badfn = lambda x, y: bad.append(x) or match.bad(x, y) wctx = repo[None] forgot = [] - s = repo.status(match=match, clean=True) + + s = repo.status(match=matchmod.badmatch(match, badfn), clean=True) forget = sorted(s[0] + s[1] + s[3] + s[6]) if explicitonly: forget = [f for f in forget if match.exact(f)] @@ -2287,12 +2297,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 +2350,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 @@ -2408,22 +2422,16 @@ return 0 # Don't warn about "missing" files that are really in subrepos - bad = matcher.bad - def badfn(path, msg): for subpath in ctx.substate: if path.startswith(subpath): return - bad(path, msg) - - matcher.bad = badfn - - for abs in ctx.walk(matcher): + matcher.bad(path, msg) + + for abs in ctx.walk(matchmod.badmatch(matcher, badfn)): write(abs) err = 0 - matcher.bad = bad - for subpath in sorted(ctx.substate): sub = ctx.sub(subpath) try: @@ -2463,10 +2471,12 @@ ui.note(_('amending changeset %s\n') % old) base = old.p1() - - wlock = lock = newid = None + createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt) + + wlock = dsguard = lock = newid = None try: wlock = repo.wlock() + dsguard = dirstateguard(repo, 'amend') lock = repo.lock() tr = repo.transaction('amend') try: @@ -2480,13 +2490,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] @@ -2622,21 +2632,23 @@ if bms: marks = repo._bookmarks for bm in bms: + ui.debug('moving bookmarks %r from %s to %s\n' % + (marks, old.hex(), hex(newid))) marks[bm] = newid - marks.write() - #commit the whole amend process - createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt) - if createmarkers and newid != old.node(): - # mark the new changeset as successor of the rewritten one - new = repo[newid] - obs = [(old, (new,))] - if node: - obs.append((ctx, ())) - - obsolete.createmarkers(repo, obs) + marks.recordchange(tr) + #commit the whole amend process + if createmarkers: + # mark the new changeset as successor of the rewritten one + new = repo[newid] + obs = [(old, (new,))] + if node: + obs.append((ctx, ())) + + obsolete.createmarkers(repo, obs) 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 +2657,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=''): @@ -2691,7 +2701,7 @@ try: t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False) - except SyntaxError, inst: + except SyntaxError as inst: raise util.Abort(inst.args[0]) for k, v in repo.ui.configitems('committemplate'): @@ -2721,8 +2731,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]) @@ -2815,8 +2825,7 @@ targetsubs = sorted(s for s in wctx.substate if m(s)) if not m.always(): - m.bad = lambda x, y: False - for abs in repo.walk(m): + for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)): names[abs] = m.rel(abs), m.exact(abs) # walk target manifest to fill `names` @@ -2832,8 +2841,7 @@ return ui.warn("%s: %s\n" % (m.rel(path), msg)) - m.bad = badfn - for abs in ctx.walk(m): + for abs in ctx.walk(matchmod.badmatch(m, badfn)): if abs not in names: names[abs] = m.rel(abs), m.exact(abs) @@ -3077,7 +3085,7 @@ node = ctx.node() def checkout(f): fc = ctx[f] - return repo.wwrite(f, fc.data(), fc.flags()) + repo.wwrite(f, fc.data(), fc.flags()) audit_path = pathutil.pathauditor(repo.root) for f in actions['forget'][0]: @@ -3103,17 +3111,33 @@ 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 + reversehunks = repo.ui.configbool('experimental', + 'revertalternateinteractivemode', + True) + if reversehunks: + diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) + else: + diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts) originalchunks = patch.parsepatch(diff) + try: + chunks = recordfilter(repo.ui, originalchunks) - except patch.PatchError, err: + if reversehunks: + chunks = patch.reversehunks(chunks) + + except patch.PatchError as err: raise util.Abort(_('error parsing patch: %s') % err) + newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks) # Apply changes fp = cStringIO.StringIO() for c in chunks: @@ -3123,22 +3147,20 @@ if dopatch: try: patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None) - except patch.PatchError, err: + except patch.PatchError as err: raise util.Abort(str(err)) del fp else: for f in actions['revert'][0]: - wsize = checkout(f) + checkout(f) if normal: normal(f) - elif wsize == repo.dirstate._map[f][2]: - # changes may be overlooked without normallookup, - # if size isn't changed at reverting - 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 +3281,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 501c51d60792 -r 96a38d44ba09 mercurial/commands.py --- a/mercurial/commands.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/commands.py Sat Jul 18 17:32:38 2015 -0500 @@ -21,7 +21,7 @@ import dagparser, context, simplemerge, graphmod, copies import random import setdiscovery, treediscovery, dagutil, pvec, localrepo -import phases, obsolete, exchange, bundle2 +import phases, obsolete, exchange, bundle2, repair, lock as lockmod import ui as uimod table = {} @@ -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 = [ @@ -344,8 +350,8 @@ def bad(x, y): raise util.Abort("%s: %s" % (x, y)) - m = scmutil.match(ctx, pats, opts) - m.bad = bad + m = scmutil.match(ctx, pats, opts, badfn=bad) + follow = not opts.get('no_follow') diffopts = patch.difffeatureopts(ui, opts, section='annotate', whitespace=True) @@ -970,21 +976,24 @@ raise util.Abort(_("bookmark name required")) if delete or rename or names or inactive: - wlock = repo.wlock() + wlock = lock = tr = None try: + wlock = repo.wlock() + lock = repo.lock() cur = repo.changectx('.').node() marks = repo._bookmarks if delete: + tr = repo.transaction('bookmark') for mark in names: 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() elif rename: + tr = repo.transaction('bookmark') if not names: raise util.Abort(_("new bookmark name required")) elif len(names) > 1: @@ -994,19 +1003,18 @@ 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() - elif names: + tr = repo.transaction('bookmark') newact = None for mark in names: 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,20 +1022,21 @@ 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) - marks.write() - + bookmarks.activate(repo, newact) + elif cur != tgt and newact == repo._activebookmark: + bookmarks.deactivate(repo) 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) + if tr is not None: + marks.recordchange(tr) + tr.close() finally: - wlock.release() + lockmod.release(tr, lock, wlock) else: # show bookmarks fm = ui.formatter('bookmarks', opts) hexfn = fm.hexfunc @@ -1035,9 +1044,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 +1057,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 +1089,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 +1118,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() @@ -1405,7 +1421,8 @@ stream=opts.get('uncompressed'), rev=opts.get('rev'), update=opts.get('updaterev') or not opts.get('noupdate'), - branch=opts.get('branch')) + branch=opts.get('branch'), + shareopts=opts.get('shareopts')) return r is None @@ -1413,7 +1430,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 +1456,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`, @@ -1459,7 +1480,7 @@ """ if opts.get('interactive'): opts.pop('interactive') - cmdutil.dorecord(ui, repo, commit, 'commit', False, + cmdutil.dorecord(ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts) return @@ -1506,21 +1527,10 @@ match, extra=extra) - current = repo._bookmarkcurrent - marks = old.bookmarks() node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts) if node == old.node(): ui.status(_("nothing changed\n")) return 1 - elif marks: - ui.debug('moving bookmarks %r from %s to %s\n' % - (marks, old.hex(), hex(node))) - newmarks = repo._bookmarks - for bm in marks: - newmarks[bm] = node - if bm == current: - bookmarks.setcurrent(repo, bm) - newmarks.write() else: def commitfunc(ui, repo, message, match, opts): backup = ui.backupconfig('phases', 'new-commit') @@ -2056,7 +2066,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 +2174,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 +2238,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) @@ -2321,7 +2333,7 @@ ui.status(_("checking encoding (%s)...\n") % encoding.encoding) try: encoding.fromlocal("test") - except util.Abort, inst: + except util.Abort as inst: ui.write(" %s\n" % inst) ui.write(_(" (check that your locale is properly set)\n")) problems += 1 @@ -2339,7 +2351,7 @@ try: import bdiff, mpatch, base85, osutil dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes - except Exception, inst: + except Exception as inst: ui.write(" %s\n" % inst) ui.write(_(" One or more extensions could not be found")) ui.write(_(" (check that you compiled the extensions)\n")) @@ -2355,7 +2367,7 @@ # template found, check if it is working try: templater.templater(m) - except Exception, inst: + except Exception as inst: ui.write(" %s\n" % inst) p = None else: @@ -2387,7 +2399,7 @@ ui.status(_("checking username...\n")) try: ui.username() - except util.Abort, e: + except util.Abort as e: ui.write(" %s\n" % e) ui.write(_(" (specify a username in your configuration file)\n")) problems += 1 @@ -2498,7 +2510,7 @@ % (user, pid, host) ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age)) return 1 - except OSError, e: + except OSError as e: if e.errno != errno.ENOENT: raise @@ -2545,26 +2557,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 as exc: + raise util.Abort(_('bad obsmarker input: %s') % exc) finally: tr.release() finally: @@ -2710,6 +2721,11 @@ finally: wlock.release() +@command('debugrebuildfncache', [], '') +def debugrebuildfncache(ui, repo): + """rebuild the fncache file""" + repair.rebuildfncache(ui, repo) + @command('debugrename', [('r', 'rev', '', _('revision to debug'), _('REV'))], _('[-r REV] FILE')) @@ -2730,6 +2746,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 +2933,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: @@ -2977,10 +2994,10 @@ else: timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])) - if ent[1] & 020000: + if ent[1] & 0o20000: mode = 'lnk' else: - mode = '%3o' % (ent[1] & 0777 & ~util.umask) + mode = '%3o' % (ent[1] & 0o777 & ~util.umask) ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_)) for f in repo.dirstate.copies(): ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f)) @@ -3448,7 +3465,7 @@ try: nodes = repo.vfs.read('graftstate').splitlines() revs = [repo[node].rev() for node in nodes] - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise raise util.Abort(_("no graft state found, can't continue")) @@ -3642,7 +3659,7 @@ reflags |= re.I try: regexp = util.re.compile(pattern, reflags) - except re.error, inst: + except re.error as inst: ui.warn(_("grep: invalid match pattern: %s\n") % inst) return 1 sep, eol = ':', '\n' @@ -4042,13 +4059,19 @@ if bm: output.append(bm) else: - if not rev: + ctx = scmutil.revsingle(repo, rev, None) + + if ctx.rev() is None: ctx = repo[None] parents = ctx.parents() + taglist = [] + for p in parents: + taglist.extend(p.tags()) + 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" % @@ -4057,11 +4080,11 @@ output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]), changed)) else: - ctx = scmutil.revsingle(repo, rev) if default or id: output = [hexfunc(ctx.node())] if num: output.append(str(ctx.rev())) + taglist = ctx.tags() if default and not ui.quiet: b = ctx.branch() @@ -4069,7 +4092,7 @@ output.append("(%s)" % b) # multiple tags for a single parent separated by '/' - t = '/'.join(ctx.tags()) + t = '/'.join(taglist) if t: output.append(t) @@ -4082,7 +4105,7 @@ output.append(ctx.branch()) if tags: - output.extend(ctx.tags()) + output.extend(taglist) if bookmarks: output.extend(ctx.bookmarks()) @@ -4156,6 +4179,12 @@ cleanly, :hg:`import --partial` will create an empty changeset, importing only the patch metadata. + It is possible to use external patch programs to perform the patch + by setting the ``ui.patch`` configuration option. For the default + internal tool, the fuzz can also be configured via ``patch.fuzz``. + See :hg:`help config` for more information about configuration + files and how to use these options. + To read a patch from standard input, use "-" as the patch name. If a URL is specified, the patch will be downloaded from it. See :hg:`help dates` for a list of formats valid for -d/--date. @@ -4181,6 +4210,15 @@ hg import --exact proposed-fix.patch + - use an external tool to apply a patch which is too fuzzy for + the default internal tool. + + hg import --config ui.patch="patch --merge" fuzzy.patch + + - change the default fuzzing from 2 to a less strict 7 + + hg import --config ui.fuzz=7 fuzz.patch + Returns 0 on success, 1 on partial success (see --partial). """ @@ -4215,7 +4253,7 @@ cmdutil.bailifchanged(repo) base = opts["base"] - wlock = lock = tr = None + wlock = dsguard = lock = tr = None msgs = [] ret = 0 @@ -4223,7 +4261,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') @@ -4264,18 +4302,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, @@ -4427,8 +4463,8 @@ ret = 1 ctx = repo[rev] - m = scmutil.match(ctx, pats, opts, default='relglob') - m.bad = lambda x, y: False + m = scmutil.match(ctx, pats, opts, default='relglob', + badfn=lambda x, y: False) for abs in ctx.matches(m): if opts.get('fullpath'): @@ -4591,7 +4627,7 @@ else: revmatchfn = None displayer.show(ctx, copies=copies, matchfn=revmatchfn) - if displayer.flush(rev): + if displayer.flush(ctx): count += 1 displayer.close() @@ -4704,9 +4740,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] @@ -4721,7 +4757,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()] @@ -4954,11 +4990,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. @@ -4970,6 +5006,8 @@ Returns 0 on success, 1 if no phases were changed or some could not be changed. + + (For more information about the phases concept, see :hg:`help phases`.) """ # search for a unique phase argument targetphase = None @@ -4983,7 +5021,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) @@ -5044,14 +5084,14 @@ checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout) try: ret = hg.update(repo, checkout) - except util.Abort, inst: + except util.Abort as inst: ui.warn(_("not updating: %s\n") % str(inst)) if inst.hint: ui.warn(_("(%s)\n") % inst.hint) 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()) @@ -5102,11 +5142,17 @@ revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev')) - remotebookmarks = other.listkeys('bookmarks') - + + pullopargs = {} if opts.get('bookmark'): if not revs: revs = [] + # The list of bookmark used here is not the one used to actually + # update the bookmark name. This can result in the revision pulled + # not ending up with the name of the bookmark because of a race + # condition on the server. (See issue 4689 for details) + remotebookmarks = other.listkeys('bookmarks') + pullopargs['remotebookmarks'] = remotebookmarks for b in opts['bookmark']: if b not in remotebookmarks: raise util.Abort(_('remote bookmark %s not found!') % b) @@ -5114,6 +5160,9 @@ if revs: try: + # When 'rev' is a bookmark name, we cannot guarantee that it + # will be updated with that name because of a race condition + # server side. (See issue 4689 for details) oldrevs = revs revs = [] # actually, nodes for r in oldrevs: @@ -5128,7 +5177,8 @@ modheads = exchange.pull(repo, other, heads=revs, force=opts.get('force'), - bookmarks=opts.get('bookmark', ())).cgresult + bookmarks=opts.get('bookmark', ()), + opargs=pullopargs).cgresult if checkout: checkout = str(repo.changelog.rev(checkout)) repo._subtoppath = source @@ -5528,7 +5578,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: @@ -5880,7 +5930,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. @@ -5922,15 +5972,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') @@ -5986,6 +6036,14 @@ elif pnode not in bheads: t += _(' (new branch head)') + if parents: + pendingphase = max(p.phase() for p in parents) + else: + pendingphase = phases.public + + if pendingphase > phases.newcommitphase(ui): + t += ' (%s)' % phases.phasenames[pendingphase] + if cleanworkdir: # i18n: column positioning for "hg summary" ui.status(_('commit: %s\n') % t.strip()) @@ -6008,6 +6066,17 @@ 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 draft or secret: + ui.status(_('phases: %s\n') % ', '.join(t)) + cmdutil.summaryhooks(ui, repo) if opts.get('remote'): @@ -6331,7 +6400,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 @@ -6384,7 +6453,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 @@ -6413,15 +6482,20 @@ 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) + else: + # this can happen with a non-linear update + ui.status(_("(leaving bookmark %s)\n") % + repo._activebookmark) + bookmarks.deactivate(repo) 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 501c51d60792 -r 96a38d44ba09 mercurial/commandserver.py --- a/mercurial/commandserver.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/commandserver.py Sat Jul 18 17:32:38 2015 -0500 @@ -132,6 +132,7 @@ def __init__(self, ui, repo, fin, fout): self.cwd = os.getcwd() + # developer config: cmdserver.log logpath = ui.config("cmdserver", "log", None) if logpath: global logfile @@ -300,9 +301,9 @@ sv.serve() # handle exceptions that may be raised by command server. most of # known exceptions are caught by dispatch. - except util.Abort, inst: + except util.Abort as inst: ui.warn(_('abort: %s\n') % inst) - except IOError, inst: + except IOError as inst: if inst.errno != errno.EPIPE: raise except KeyboardInterrupt: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/config.py --- a/mercurial/config.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/config.py Sat Jul 18 17:32:38 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,14 +111,18 @@ 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) - except IOError, inst: + break + except IOError as inst: if inst.errno != errno.ENOENT: raise error.ParseError(_("cannot include %s (%s)") % (inc, inst.strerror), diff -r 501c51d60792 -r 96a38d44ba09 mercurial/context.py --- a/mercurial/context.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/context.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,11 +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. -from node import nullid, nullrev, short, hex, bin +from node import nullid, nullrev, wdirid, short, hex, bin from i18n import _ import mdiff, error, util, scmutil, subrepo, patch, encoding, phases import match as matchmod -import copy, os, errno, stat +import os, errno, stat import obsolete as obsmod import repoview import fileset @@ -249,13 +249,25 @@ return '' def sub(self, path): + '''return a subrepo for the stored revision of path, never wdir()''' return subrepo.subrepo(self, path) - def match(self, pats=[], include=None, exclude=None, default='glob'): + def nullsub(self, path, pctx): + return subrepo.nullsubrepo(self, path, pctx) + + def workingsub(self, path): + '''return a subrepo for the stored revision, or wdir if this is a wdir + context. + ''' + return subrepo.subrepo(self, path, allowwdir=True) + + def match(self, pats=[], include=None, exclude=None, default='glob', + listsubrepos=False, badfn=None): 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, badfn=badfn) def diff(self, ctx2=None, match=None, **opts): """Returns a diff generator for the given contexts and matcher""" @@ -338,7 +350,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 +358,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 +472,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) @@ -563,7 +576,8 @@ elif len(cahs) == 1: anc = cahs[0] else: - for r in self._repo.ui.configlist('merge', 'preferancestor'): + # experimental config: merge.preferancestor + for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']): try: ctx = changectx(self._repo, r) except error.RepoLookupError: @@ -589,19 +603,17 @@ def walk(self, match): '''Generates matching file names.''' - # Override match.bad method to have message with nodeid - match = copy.copy(match) - oldbad = match.bad + # Wrap match.bad method to have message with nodeid 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 + match.bad(fn, _('no such file in rev %s') % self) - return self._manifest.walk(match) + m = matchmod.badmatch(match, bad) + return self._manifest.walk(m) def matches(self, match): return self.walk(match) @@ -1236,10 +1248,7 @@ return self._extra def tags(self): - t = [] - for p in self.parents(): - t.extend(p.tags()) - return t + return [] def bookmarks(self): b = [] @@ -1308,6 +1317,11 @@ self._repo.dirstate.setparents(node) self._repo.dirstate.endparentchange() + # write changes out explicitly, because nesting wlock at + # runtime may prevent 'wlock.release()' in 'repo.commit()' + # from immediately doing so for subsequent changing files + self._repo.dirstate.write() + class workingctx(committablectx): """A workingctx object makes access to data related to the current working directory convenient. @@ -1330,6 +1344,9 @@ def __contains__(self, key): return self._repo.dirstate[key] not in "?r" + def hex(self): + return hex(wdirid) + @propertycache def _parents(self): p = self._repo.dirstate.parents() @@ -1424,7 +1441,7 @@ def copy(self, source, dest): try: st = self._repo.wvfs.lstat(dest) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise self._repo.ui.warn(_("%s does not exist!\n") % dest) @@ -1443,17 +1460,21 @@ 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, badfn=None): 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, + badfn=badfn) return matchmod.match(r.root, r.getcwd(), pats, include, exclude, default, - auditor=r.auditor, ctx=self) + auditor=r.auditor, ctx=self, + listsubrepos=listsubrepos, badfn=badfn) def _filtersuspectsymlink(self, files): if not files or self._repo.dirstate._checklink: @@ -1502,6 +1523,10 @@ try: for f in fixup: normal(f) + # write changes out explicitly, because nesting + # wlock at runtime may prevent 'wlock.release()' + # below from doing so for subsequent changing files + self._repo.dirstate.write() finally: wlock.release() except error.LockError: @@ -1666,7 +1691,7 @@ t, tz = self._changectx.date() try: return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise return (t, tz) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/copies.py --- a/mercurial/copies.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/copies.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/crecord.py --- a/mercurial/crecord.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/crecord.py Sat Jul 18 17:32:38 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. @@ -479,7 +482,12 @@ """ ui.write(_('starting interactive selection\n')) chunkselector = curseschunkselector(headerlist, ui) + f = signal.getsignal(signal.SIGTSTP) curses.wrapper(chunkselector.main) + if chunkselector.initerr is not None: + raise util.Abort(chunkselector.initerr) + # ncurses does not restore signal handler for SIGTSTP + signal.signal(signal.SIGTSTP, f) def testdecorator(testfn, f): def u(*args, **kwargs): @@ -508,6 +516,7 @@ self.ui = ui + self.errorstr = None # list of all chunks self.chunklist = [] for h in headerlist: @@ -973,6 +982,12 @@ # print out the status lines at the top try: + if self.errorstr is not None: + printstring(self.statuswin, self.errorstr, pairname='legend') + printstring(self.statuswin, 'Press any key to continue', + pairname='legend') + self.statuswin.refresh() + return printstring(self.statuswin, "SELECT CHUNKS: (j/k/up/dn/pgup/pgdn) move cursor; " "(space/A) toggle hunk/all; (e)dit hunk;", @@ -1416,6 +1431,13 @@ """ edit the currently chelected chunk """ + def updateui(self): + self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1 + self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize) + self.updatescroll() + self.stdscr.refresh() + self.statuswin.refresh() + self.stdscr.keypad(1) def editpatchwitheditor(self, chunk): if chunk is None: @@ -1451,9 +1473,11 @@ f.close() # start the editor and wait for it to complete editor = self.ui.geteditor() - self.ui.system("%s \"%s\"" % (editor, patchfn), - environ={'hguser': self.ui.username()}, - onerr=util.Abort, errprefix=_("edit failed")) + ret = self.ui.system("%s \"%s\"" % (editor, patchfn), + environ={'hguser': self.ui.username()}) + if ret != 0: + self.errorstr = "Editor exited with status %d" % ret + return None # remove comment lines patchfp = open(patchfn) ncpatchfp = cStringIO.StringIO() @@ -1478,6 +1502,10 @@ beforeadded, beforeremoved = item.added, item.removed newpatches = editpatchwitheditor(self, item) + if newpatches is None: + if not test: + updateui(self) + return header = item.header editedhunkindex = header.hunks.index(item) hunksbefore = header.hunks[:editedhunkindex] @@ -1498,12 +1526,7 @@ self.currentselecteditem = header if not test: - self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1 - self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize) - self.updatescroll() - self.stdscr.refresh() - self.statuswin.refresh() - self.stdscr.keypad(1) + updateui(self) def emptypatch(self): item = self.headerlist @@ -1551,6 +1574,8 @@ self.togglefolded(foldparent=True) elif keypressed in ["?"]: self.helpwindow() + self.stdscr.clear() + self.stdscr.refresh() def main(self, stdscr): """ @@ -1559,6 +1584,9 @@ """ signal.signal(signal.SIGWINCH, self.sigwinchhandler) self.stdscr = stdscr + # error during initialization, cannot be printed in the curses + # interface, it should be printed by the calling code + self.initerr = None self.yscreensize, self.xscreensize = self.stdscr.getmaxyx() curses.start_color() @@ -1584,8 +1612,12 @@ # add 1 so to account for last line text reaching end of line self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1 - self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize) + try: + self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize) + except curses.error: + self.initerr = _('this diff is too large to be displayed') + return # initialize selecteitemendline (initial start-line is 0) self.selecteditemendline = self.getnumlinesdisplayed( self.currentselecteditem, recursechildren=False) @@ -1594,6 +1626,9 @@ self.updatescreen() try: keypressed = self.statuswin.getkey() + if self.errorstr is not None: + self.errorstr = None + continue except curses.error: keypressed = "foobar" if self.handlekeypressed(keypressed): diff -r 501c51d60792 -r 96a38d44ba09 mercurial/dagparser.py --- a/mercurial/dagparser.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/dagparser.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/demandimport.py --- a/mercurial/demandimport.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/demandimport.py Sat Jul 18 17:32:38 2015 -0500 @@ -24,7 +24,15 @@ b = __import__(a) ''' -import __builtin__, os, sys +import os, sys +from contextlib import contextmanager + +# __builtin__ in Python 2, builtins in Python 3. +try: + import __builtin__ as builtins +except ImportError: + import builtins + _origimport = __import__ nothing = object() @@ -34,7 +42,7 @@ level = -1 if sys.version_info[0] >= 3: level = 0 - _origimport(__builtin__.__name__, {}, {}, None, level) + _origimport(builtins.__name__, {}, {}, None, level) except TypeError: # no level argument def _import(name, globals, locals, fromlist, level): "call _origimport with no level argument" @@ -169,13 +177,26 @@ ] def isenabled(): - return __builtin__.__import__ == _demandimport + return builtins.__import__ == _demandimport def enable(): "enable global demand-loading of modules" if os.environ.get('HGDEMANDIMPORT') != 'disable': - __builtin__.__import__ = _demandimport + builtins.__import__ = _demandimport def disable(): "disable global demand-loading of modules" - __builtin__.__import__ = _origimport + builtins.__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 501c51d60792 -r 96a38d44ba09 mercurial/dirs.c --- a/mercurial/dirs.c Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/dirs.c Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/dirstate.py --- a/mercurial/dirstate.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/dirstate.py Sat Jul 18 17:32:38 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 @@ -113,7 +115,7 @@ def _branch(self): try: return self._opener.read("branch").strip() or "default" - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise return "default" @@ -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) @@ -129,7 +131,7 @@ return st[:20], st[20:40] elif l > 0 and l < 40: raise util.Abort(_('working directory state appears damaged!')) - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise return [nullid, nullid] @@ -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,14 +326,31 @@ self._map = {} self._copymap = {} try: - st = self._opener.read("dirstate") - except IOError, err: + fp = self._opener.open(self._filename) + try: + st = fp.read() + finally: + fp.close() + except IOError as err: if err.errno != errno.ENOENT: raise return if not st: return + if util.safehasattr(parsers, 'dict_new_presized'): + # Make an estimate of the number of files in the dirstate based on + # its size. From a linear regression on a set of real-world repos, + # all over 10,000 files, the size of a dirstate entry is 85 + # bytes. The cost of resizing is significantly higher than the cost + # of filling in a larger presized dict, so subtract 20% from the + # size. + # + # This heuristic is imperfect in many ways, so in a future dirstate + # format update it makes sense to just record the number of entries + # on write. + self._map = parsers.dict_new_presized(len(st) / 71) + # Python's garbage collector triggers a GC each time a certain number # of container objects (the number being defined by # gc.get_threshold()) are allocated. parse_dirstate creates a tuple @@ -559,7 +585,8 @@ self._dirty = True def rebuild(self, parent, allfiles, changedfiles=None): - changedfiles = changedfiles or allfiles + if changedfiles is None: + changedfiles = allfiles oldmap = self._map self.clear() for f in allfiles: @@ -567,9 +594,9 @@ self._map[f] = oldmap[f] else: if 'x' in allfiles.flags(f): - self._map[f] = dirstatetuple('n', 0777, -1, 0) + self._map[f] = dirstatetuple('n', 0o777, -1, 0) else: - self._map[f] = dirstatetuple('n', 0666, -1, 0) + self._map[f] = dirstatetuple('n', 0o666, -1, 0) self._pl = (parent, nullid) self._dirty = True @@ -584,7 +611,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 @@ -690,7 +717,7 @@ badfn(ff, badtype(kind)) if nf in dmap: results[nf] = None - except OSError, inst: # nf not found on disk - it is dirstate only + except OSError as inst: # nf not found on disk - it is dirstate only if nf in dmap: # does it exactly match a missing file? results[nf] = None else: # does it match a missing directory? @@ -746,7 +773,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: @@ -775,7 +802,7 @@ skip = '.hg' try: entries = listdir(join(nd), stat=True, skip=skip) - except OSError, inst: + except OSError as inst: if inst.errno in (errno.EACCES, errno.ENOENT): match.bad(self.pathto(nd), inst.strerror) continue @@ -936,7 +963,7 @@ mtime = int(st.st_mtime) if (size >= 0 and ((size != st.st_size and size != st.st_size & _rangemask) - or ((mode ^ st.st_mode) & 0100 and checkexec)) + or ((mode ^ st.st_mode) & 0o100 and checkexec)) or size == -2 # other parent or fn in copymap): madd(fn) @@ -972,7 +999,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 501c51d60792 -r 96a38d44ba09 mercurial/dispatch.py --- a/mercurial/dispatch.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/dispatch.py Sat Jul 18 17:32:38 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, @@ -75,12 +76,12 @@ req.ui.fout = req.fout if req.ferr: req.ui.ferr = req.ferr - except util.Abort, inst: + except util.Abort as inst: ferr.write(_("abort: %s\n") % inst) if inst.hint: ferr.write(_("(%s)\n") % inst.hint) return -1 - except error.ParseError, inst: + except error.ParseError as inst: _formatparse(ferr.write, inst) return -1 @@ -128,19 +129,21 @@ for sec, name, val in cfgs: req.repo.ui.setconfig(sec, name, val, source='--config') - # if we are in HGPLAIN mode, then disable custom debugging + # developer config: ui.debugger debugger = ui.config("ui", "debugger") debugmod = pdb if not debugger or ui.plain(): + # if we are in HGPLAIN mode, then disable custom debugging debugger = 'pdb' elif '--debugger' in req.args: # 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 @@ -170,36 +173,43 @@ # Global exception handling, alphabetically # Mercurial-specific first, followed by built-in and library exceptions - except error.AmbiguousCommand, inst: + except error.AmbiguousCommand as inst: ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") % (inst.args[0], " ".join(inst.args[1]))) - except error.ParseError, inst: + except error.ParseError as inst: _formatparse(ui.warn, inst) return -1 - except error.LockHeld, inst: + except error.LockHeld as inst: if inst.errno == errno.ETIMEDOUT: reason = _('timed out waiting for lock held by %s') % inst.locker else: reason = _('lock held by %s') % inst.locker ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason)) - except error.LockUnavailable, inst: + except error.LockUnavailable as inst: ui.warn(_("abort: could not lock %s: %s\n") % (inst.desc or inst.filename, inst.strerror)) - except error.CommandError, inst: + except error.CommandError as inst: if inst.args[0]: ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1])) commands.help_(ui, inst.args[0], full=False, command=True) else: 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)) - except error.RepoError, inst: + except error.OutOfBandError as inst: + 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 as inst: ui.warn(_("abort: %s!\n") % inst) if inst.hint: ui.warn(_("(%s)\n") % inst.hint) - except error.ResponseError, inst: + except error.ResponseError as inst: ui.warn(_("abort: %s") % inst.args[0]) if not isinstance(inst.args[1], basestring): ui.warn(" %r\n" % (inst.args[1],)) @@ -207,13 +217,13 @@ ui.warn(_(" empty string\n")) else: ui.warn("\n%r\n" % util.ellipsis(inst.args[1])) - except error.CensoredNodeError, inst: + except error.CensoredNodeError as inst: ui.warn(_("abort: file censored %s!\n") % inst) - except error.RevlogError, inst: + except error.RevlogError as inst: ui.warn(_("abort: %s!\n") % inst) except error.SignalInterrupt: ui.warn(_("killed!\n")) - except error.UnknownCommand, inst: + except error.UnknownCommand as inst: ui.warn(_("hg: unknown command '%s'\n") % inst.args[0]) try: # check if the command is in a disabled extension @@ -229,21 +239,21 @@ suggested = True if not suggested: commands.help_(ui, 'shortlist') - except error.InterventionRequired, inst: + except error.InterventionRequired as inst: ui.warn("%s\n" % inst) return 1 - except util.Abort, inst: + except util.Abort as inst: ui.warn(_("abort: %s\n") % inst) if inst.hint: ui.warn(_("(%s)\n") % inst.hint) - except ImportError, inst: + except ImportError as inst: ui.warn(_("abort: %s!\n") % inst) m = str(inst).split()[-1] if m in "mpatch bdiff".split(): ui.warn(_("(did you forget to compile extensions?)\n")) elif m in "zlib".split(): ui.warn(_("(is your Python install correct?)\n")) - except IOError, inst: + except IOError as inst: if util.safehasattr(inst, "code"): ui.warn(_("abort: %s\n") % inst) elif util.safehasattr(inst, "reason"): @@ -267,7 +277,7 @@ ui.warn(_("abort: %s\n") % inst.strerror) else: raise - except OSError, inst: + except OSError as inst: if getattr(inst, "filename", None) is not None: ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename)) else: @@ -275,7 +285,7 @@ except KeyboardInterrupt: try: ui.warn(_("interrupted!\n")) - except IOError, inst: + except IOError as inst: if inst.errno == errno.EPIPE: if ui.debugflag: ui.warn(_("\nbroken pipe\n")) @@ -283,11 +293,11 @@ raise except MemoryError: ui.warn(_("abort: out of memory\n")) - except SystemExit, inst: + except SystemExit as inst: # Commands shouldn't sys.exit directly, but give a return code. # Just in case catch this and and pass exit code to caller. return inst.code - except socket.error, inst: + except socket.error as inst: ui.warn(_("abort: %s\n") % inst.args[-1]) except: # re-raises myver = util.version() @@ -443,7 +453,7 @@ try: args = shlex.split(self.definition) - except ValueError, inst: + except ValueError as inst: self.badalias = (_("error in definition for alias '%s': %s") % (self.name, inst)) return @@ -534,7 +544,7 @@ try: args = fancyopts.fancyopts(args, commands.globalopts, options) - except fancyopts.getopt.GetoptError, inst: + except fancyopts.getopt.GetoptError as inst: raise error.CommandError(None, inst) if args: @@ -557,7 +567,7 @@ try: args = fancyopts.fancyopts(args, c, cmdoptions, True) - except fancyopts.getopt.GetoptError, inst: + except fancyopts.getopt.GetoptError as inst: raise error.CommandError(cmd, inst) # separate global options back out @@ -656,7 +666,7 @@ """ try: wd = os.getcwd() - except OSError, e: + except OSError as e: raise util.Abort(_("error getting current working directory: %s") % e.strerror) path = cmdutil.findrepo(wd) or "" @@ -891,7 +901,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 +931,31 @@ 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')) + # developer config: profiling.freq + 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 +987,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 +1002,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 501c51d60792 -r 96a38d44ba09 mercurial/encoding.py --- a/mercurial/encoding.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/encoding.py Sat Jul 18 17:32:38 2015 -0500 @@ -138,7 +138,7 @@ except UnicodeDecodeError: u = s.decode("utf-8", "replace") # last ditch return u.encode(encoding, "replace") # can't round-trip - except LookupError, k: + except LookupError as k: raise error.Abort(k, hint="please check your locale settings") def fromlocal(s): @@ -158,10 +158,10 @@ try: return s.decode(encoding, encodingmode).encode("utf-8") - except UnicodeDecodeError, inst: + except UnicodeDecodeError as inst: sub = s[max(0, inst.start - 10):inst.start + 10] raise error.Abort("decoding near '%s': %s!" % (sub, inst)) - except LookupError, k: + except LookupError as k: raise error.Abort(k, hint="please check your locale settings") # How to treat ambiguous-width characters. Set to 'wide' to treat as wide. @@ -330,7 +330,7 @@ return lu.encode(encoding) except UnicodeError: return s.lower() # we don't know how to fold this except in ASCII - except LookupError, k: + except LookupError as k: raise error.Abort(k, hint="please check your locale settings") def upper(s): @@ -353,7 +353,7 @@ return uu.encode(encoding) except UnicodeError: return s.upper() # we don't know how to fold this except in ASCII - except LookupError, k: + except LookupError as k: raise error.Abort(k, hint="please check your locale settings") class normcasespecs(object): diff -r 501c51d60792 -r 96a38d44ba09 mercurial/error.py --- a/mercurial/error.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/error.py Sat Jul 18 17:32:38 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 @@ -146,6 +151,21 @@ """error raised when code tries to alter a part being generated""" pass +class PushkeyFailed(Abort): + """error raised when a pushkey part failed to update a value""" + + def __init__(self, partid, namespace=None, key=None, new=None, old=None, + ret=None): + self.partid = partid + self.namespace = namespace + self.key = key + self.new = new + self.old = old + self.ret = ret + # no i18n expected to be processed into a better message + Abort.__init__(self, 'failed to update value for "%s/%s"' + % (namespace, key)) + class CensoredNodeError(RevlogError): """error raised when content verification fails on a censored node diff -r 501c51d60792 -r 96a38d44ba09 mercurial/exchange.py --- a/mercurial/exchange.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/exchange.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,12 +5,14 @@ # 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 +import tags def readbundle(ui, fh, fname, vfs=None): header = changegroup.readexactly(fh, 4) @@ -34,7 +36,7 @@ alg = changegroup.readexactly(fh, 2) return changegroup.cg1unpacker(fh, alg) elif version.startswith('2'): - return bundle2.getunbundler(ui, fh, header=magic + version) + return bundle2.getunbundler(ui, fh, magicstring=magic + version) else: raise util.Abort(_('%s: unknown bundle version %s') % (fname, version)) @@ -57,7 +59,7 @@ """return true if a pull/push can use bundle2 Feel free to nuke this function when we drop the experimental option""" - return (op.repo.ui.configbool('experimental', 'bundle2-exp', False) + return (op.repo.ui.configbool('experimental', 'bundle2-exp', True) and op.remote.capable('bundle2')) @@ -115,6 +117,9 @@ self.outbookmarks = [] # transaction manager self.trmanager = None + # map { pushkey partid -> callback handling failure} + # used to handle exception from mandatory pushkey part failure + self.pkfailcb = {} @util.propertycache def futureheads(self): @@ -210,7 +215,7 @@ localwlock = pushop.repo.wlock() locallock = pushop.repo.lock() pushop.locallocked = True - except IOError, err: + except IOError as err: pushop.locallocked = False if err.errno != errno.EACCES: raise @@ -304,6 +309,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) @@ -419,6 +438,8 @@ raise util.Abort(mso % ctx) elif ctx.troubled(): raise util.Abort(mst[ctx.troubles()[0]] % ctx) + + # internal config: bookmarks.pushing newbm = pushop.ui.configlist('bookmarks', 'pushing') discovery.checkheads(unfi, pushop.remote, outgoing, pushop.remoteheads, @@ -505,6 +526,13 @@ return pushop.stepsdone.add('phases') part2node = [] + + def handlefailure(pushop, exc): + targetid = int(exc.partid) + for partid, node in part2node: + if partid == targetid: + raise error.Abort(_('updating %s to public failed') % node) + enc = pushkey.encode for newremotehead in pushop.outdatedphases: part = bundler.newpart('pushkey') @@ -513,6 +541,8 @@ part.addparam('old', enc(str(phases.draft))) part.addparam('new', enc(str(phases.public))) part2node.append((part.id, newremotehead)) + pushop.pkfailcb[part.id] = handlefailure + def handlereply(op): for partid, node in part2node: partrep = op.records.getreplies(partid) @@ -536,7 +566,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): @@ -549,6 +580,15 @@ pushop.stepsdone.add('bookmarks') part2book = [] enc = pushkey.encode + + def handlefailure(pushop, exc): + targetid = int(exc.partid) + for partid, book, action in part2book: + if partid == targetid: + raise error.Abort(bookmsgmap[action][1].rstrip() % book) + # we should not be called for part we did not generated + assert False + for book, old, new in pushop.outbookmarks: part = bundler.newpart('pushkey') part.addparam('namespace', enc('bookmarks')) @@ -561,7 +601,7 @@ elif not new: action = 'delete' part2book.append((part.id, book, action)) - + pushop.pkfailcb[part.id] = handlefailure def handlereply(op): ui = pushop.ui @@ -606,16 +646,22 @@ return stream = util.chunkbuffer(bundler.getchunks()) try: - reply = pushop.remote.unbundle(stream, ['force'], 'push') - except error.BundleValueError, exc: - raise util.Abort('missing support for %s' % exc) - try: - trgetter = None - if pushback: - trgetter = pushop.trmanager.transaction - op = bundle2.processbundle(pushop.repo, reply, trgetter) - except error.BundleValueError, exc: - raise util.Abort('missing support for %s' % exc) + try: + reply = pushop.remote.unbundle(stream, ['force'], 'push') + except error.BundleValueError as exc: + raise util.Abort('missing support for %s' % exc) + try: + trgetter = None + if pushback: + trgetter = pushop.trmanager.transaction + op = bundle2.processbundle(pushop.repo, reply, trgetter) + except error.BundleValueError as exc: + raise util.Abort('missing support for %s' % exc) + except error.PushkeyFailed as exc: + partid = int(exc.partid) + if partid not in pushop.pkfailcb: + raise + pushop.pkfailcb[partid](pushop, exc) for rephand in replyhandlers: rephand(op) @@ -745,13 +791,13 @@ """utility function to push obsolete markers to a remote""" if 'obsmarkers' in pushop.stepsdone: return - pushop.ui.debug('try to push obsolete markers to remote\n') repo = pushop.repo remote = pushop.remote pushop.stepsdone.add('obsmarkers') if pushop.outobsmarkers: + pushop.ui.debug('try to push obsolete markers to remote\n') 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] @@ -791,7 +837,8 @@ afterward. """ - def __init__(self, repo, remote, heads=None, force=False, bookmarks=()): + def __init__(self, repo, remote, heads=None, force=False, bookmarks=(), + remotebookmarks=None): # repo we pull into self.repo = repo # repo we pull from @@ -811,7 +858,7 @@ # list of missing changeset to fetch remotely self.fetch = None # remote bookmarks data - self.remotebookmarks = None + self.remotebookmarks = remotebookmarks # result of changegroup pulling (used as return code by pull) self.cgresult = None # list of step already done @@ -869,8 +916,11 @@ if self._tr is not None: self._tr.release() -def pull(repo, remote, heads=None, force=False, bookmarks=()): - pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks) +def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None): + if opargs is None: + opargs = {} + pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks, + **opargs) if pullop.remote.local(): missing = set(pullop.remote.requirements) - pullop.repo.supported if missing: @@ -879,7 +929,6 @@ " %s") % (', '.join(sorted(missing))) raise util.Abort(msg) - pullop.remotebookmarks = remote.listkeys('bookmarks') lock = pullop.repo.lock() try: pullop.trmanager = transactionmanager(repo, 'pull', remote.url()) @@ -927,6 +976,22 @@ step = pulldiscoverymapping[stepname] step(pullop) +@pulldiscovery('b1:bookmarks') +def _pullbookmarkbundle1(pullop): + """fetch bookmark data in bundle1 case + + If not using bundle2, we have to fetch bookmarks before changeset + discovery to reduce the chance and impact of race conditions.""" + if pullop.remotebookmarks is not None: + return + if (_canusebundle2(pullop) + and 'listkeys' in bundle2.bundle2caps(pullop.remote)): + # all known bundle2 servers now support listkeys, but lets be nice with + # new implementation. + return + pullop.remotebookmarks = pullop.remote.listkeys('bookmarks') + + @pulldiscovery('changegroup') def _pulldiscoverychangegroup(pullop): """discovery phase for the pull @@ -978,7 +1043,11 @@ kwargs['heads'] = pullop.heads or pullop.rheads kwargs['cg'] = pullop.fetch if 'listkeys' in remotecaps: - kwargs['listkeys'] = ['phase', 'bookmarks'] + kwargs['listkeys'] = ['phase'] + if pullop.remotebookmarks is None: + # make sure to always includes bookmark data when migrating + # `hg incoming --bundle` to using this function. + kwargs['listkeys'].append('bookmarks') if not pullop.fetch: pullop.repo.ui.status(_("no changes found\n")) pullop.cgresult = 0 @@ -994,7 +1063,7 @@ bundle = pullop.remote.getbundle('pull', **kwargs) try: op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction) - except error.BundleValueError, exc: + except error.BundleValueError as exc: raise util.Abort('missing support for %s' % exc) if pullop.fetch: @@ -1010,7 +1079,10 @@ for namespace, value in op.records['listkeys']: if namespace == 'bookmarks': pullop.remotebookmarks = value - _pullbookmarks(pullop) + + # bookmark data were either already there or pulled in the bundle + if pullop.remotebookmarks is not None: + _pullbookmarks(pullop) def _pullbundle2extraprepare(pullop, kwargs): """hook function so that extensions can extend the getbundle call""" @@ -1180,7 +1252,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')) @@ -1218,24 +1290,22 @@ # build changegroup bundle here. version = None cgversions = b2caps.get('changegroup') - if not cgversions: # 3.1 and 3.2 ship with an empty value - cg = changegroup.getchangegroupraw(repo, source, heads=heads, - common=common, - bundlecaps=bundlecaps) - else: + getcgkwargs = {} + if cgversions: # 3.1 and 3.2 ship with an empty value cgversions = [v for v in cgversions if v in changegroup.packermap] if not cgversions: raise ValueError(_('no common changegroup version')) - version = max(cgversions) - cg = changegroup.getchangegroupraw(repo, source, heads=heads, - common=common, - bundlecaps=bundlecaps, - version=version) + version = getcgkwargs['version'] = max(cgversions) + outgoing = changegroup.computeoutgoing(repo, heads, common) + cg = changegroup.getlocalchangegroupraw(repo, source, outgoing, + bundlecaps=bundlecaps, + **getcgkwargs) if cg: part = bundler.newpart('changegroup', data=cg) if version is not None: part.addparam('version', version) + part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False) @getbundle2partsgenerator('listkeys') def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None, @@ -1257,8 +1327,52 @@ heads = repo.heads() subset = [c.node() for c in repo.set('::%ln', heads)] markers = repo.obsstore.relevantmarkers(subset) + markers = sorted(markers) buildobsmarkerspart(bundler, markers) +@getbundle2partsgenerator('hgtagsfnodes') +def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None, + b2caps=None, heads=None, common=None, + **kwargs): + """Transfer the .hgtags filenodes mapping. + + Only values for heads in this bundle will be transferred. + + The part data consists of pairs of 20 byte changeset node and .hgtags + filenodes raw values. + """ + # Don't send unless: + # - changeset are being exchanged, + # - the client supports it. + if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps): + return + + outgoing = changegroup.computeoutgoing(repo, heads, common) + + if not outgoing.missingheads: + return + + cache = tags.hgtagsfnodescache(repo.unfiltered()) + chunks = [] + + # .hgtags fnodes are only relevant for head changesets. While we could + # transfer values for all known nodes, there will likely be little to + # no benefit. + # + # We don't bother using a generator to produce output data because + # a) we only have 40 bytes per head and even esoteric numbers of heads + # consume little memory (1M heads is 40MB) b) we don't want to send the + # part if we don't have entries and knowing if we have entries requires + # cache lookups. + for node in outgoing.missingheads: + # Don't compute missing, as this may slow down serving. + fnode = cache.getfnode(node, computemissing=False) + if fnode is not None: + chunks.extend([node, fnode]) + + if chunks: + bundler.newpart('hgtagsfnodes', data=''.join(chunks)) + def check_heads(repo, their_heads, context): """check if the heads of a repo have been modified @@ -1288,7 +1402,7 @@ # quick fix for output mismatch with bundle2 in 3.4 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture', False) - if url.startswith('remote:'): + if url.startswith('remote:http:') or url.startswith('remote:https:'): captureoutput = True try: check_heads(repo, heads, 'uploading changes') @@ -1313,7 +1427,7 @@ def recordout(output): r.newpart('output', data=output, mandatory=False) tr.close() - except Exception, exc: + except BaseException as exc: exc.duringunbundle2 = True if captureoutput and r is not None: parts = exc._bundle2salvagedoutput = r.salvageoutput() @@ -1330,3 +1444,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) + + svfs = repo.svfs + oldaudit = svfs.mustaudit + debugflag = repo.ui.debugflag + svfs.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 = svfs(name) + try: + data = fp.read(size) + finally: + fp.close() + yield data + else: + for chunk in util.filechunkiter(svfs(name), limit=size): + yield chunk + finally: + svfs.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 501c51d60792 -r 96a38d44ba09 mercurial/extensions.py --- a/mercurial/extensions.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/extensions.py Sat Jul 18 17:32:38 2015 -0500 @@ -53,7 +53,7 @@ else: try: return imp.load_source(module_name, path) - except IOError, exc: + except IOError as exc: if not exc.filename: exc.filename = path # python does not fill this raise @@ -82,9 +82,11 @@ return mod try: mod = importh("hgext.%s" % name) - except ImportError, err: + except ImportError as err: ui.debug('could not import hgext.%s (%s): trying %s\n' % (name, err, name)) + if ui.debugflag: + ui.traceback() mod = importh(name) _extensions[shortname] = mod _order.append(shortname) @@ -103,13 +105,14 @@ load(ui, name, path) except KeyboardInterrupt: raise - except Exception, inst: + except Exception as inst: if path: ui.warn(_("*** failed to import extension %s from %s: %s\n") % (name, path, inst)) else: ui.warn(_("*** failed to import extension %s: %s\n") % (name, inst)) + ui.traceback() for name in _order[newindex:]: uisetup = getattr(_extensions[name], 'uisetup', None) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/fancyopts.py --- a/mercurial/fancyopts.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/fancyopts.py Sat Jul 18 17:32:38 2015 -0500 @@ -103,8 +103,9 @@ # transfer result to state for opt, val in opts: name = argmap[opt] - t = type(defmap[name]) - if t is type(fancyopts): + obj = defmap[name] + t = type(obj) + if callable(obj): state[name] = defmap[name](val) elif t is type(1): try: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/filemerge.py --- a/mercurial/filemerge.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/filemerge.py Sat Jul 18 17:32:38 2015 -0500 @@ -75,6 +75,7 @@ return True return False + # internal config: ui.forcemerge # forcemerge comes from command line arguments, highest priority force = ui.config('ui', 'forcemerge') if force: @@ -354,7 +355,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 501c51d60792 -r 96a38d44ba09 mercurial/fileset.py --- a/mercurial/fileset.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/fileset.py Sat Jul 18 17:32:38 2015 -0500 @@ -10,20 +10,21 @@ from i18n import _ elements = { - "(": (20, ("group", 1, ")"), ("func", 1, ")")), - "-": (5, ("negate", 19), ("minus", 5)), - "not": (10, ("not", 10)), - "!": (10, ("not", 10)), - "and": (5, None, ("and", 5)), - "&": (5, None, ("and", 5)), - "or": (4, None, ("or", 4)), - "|": (4, None, ("or", 4)), - "+": (4, None, ("or", 4)), - ",": (2, None, ("list", 2)), - ")": (0, None, None), - "symbol": (0, ("symbol",), None), - "string": (0, ("string",), None), - "end": (0, None, None), + # token-type: binding-strength, primary, prefix, infix, suffix + "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), + "-": (5, None, ("negate", 19), ("minus", 5), None), + "not": (10, None, ("not", 10), None, None), + "!": (10, None, ("not", 10), None, None), + "and": (5, None, None, ("and", 5), None), + "&": (5, None, None, ("and", 5), None), + "or": (4, None, None, ("or", 4), None), + "|": (4, None, None, ("or", 4), None), + "+": (4, None, None, ("or", 4), None), + ",": (2, None, None, ("list", 2), None), + ")": (0, None, None, None, None), + "symbol": (0, "symbol", None, None, None), + "string": (0, "string", None, None, None), + "end": (0, None, None, None, None), } keywords = set(['and', 'or', 'not']) @@ -80,8 +81,11 @@ yield ('end', None, pos) def parse(expr): - p = parser.parser(tokenize, elements) - return p.parse(expr) + p = parser.parser(elements) + tree, pos = p.parse(tokenize(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'): @@ -186,7 +190,11 @@ def func(mctx, a, b): if a[0] == 'symbol' and a[1] in symbols: return symbols[a[1]](mctx, b) - raise error.UnknownIdentifier(a[1], symbols.keys()) + + keep = lambda fn: getattr(fn, '__doc__', None) is not None + + syms = [s for (s, fn) in symbols.items() if keep(fn)] + raise error.UnknownIdentifier(a[1], syms) def getlist(x): if not x: @@ -273,7 +281,7 @@ try: # i18n: "grep" is a keyword r = re.compile(getstring(x, _("grep requires a pattern"))) - except re.error, e: + except re.error as e: raise error.ParseError(_('invalid match pattern: %s') % e) return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())] @@ -491,9 +499,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 +522,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 501c51d60792 -r 96a38d44ba09 mercurial/formatter.py --- a/mercurial/formatter.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/formatter.py Sat Jul 18 17:32:38 2015 -0500 @@ -9,6 +9,8 @@ from node import hex, short from i18n import _ import encoding, util +import templater +import os class baseformatter(object): def __init__(self, ui, topic, opts): @@ -133,6 +135,58 @@ baseformatter.end(self) self._ui.write("\n]\n") +class templateformatter(baseformatter): + def __init__(self, ui, topic, opts): + baseformatter.__init__(self, ui, topic, opts) + self._topic = topic + self._t = gettemplater(ui, topic, opts.get('template', '')) + def _showitem(self): + g = self._t(self._topic, **self._item) + self._ui.write(templater.stringify(g)) + +def lookuptemplate(ui, topic, tmpl): + # looks like a literal template? + if '{' in tmpl: + return tmpl, None + + # perhaps a stock style? + if not os.path.split(tmpl)[0]: + mapname = (templater.templatepath('map-cmdline.' + tmpl) + or templater.templatepath(tmpl)) + if mapname and os.path.isfile(mapname): + return None, mapname + + # perhaps it's a reference to [templates] + t = ui.config('templates', tmpl) + if t: + try: + tmpl = templater.unquotestring(t) + except SyntaxError: + tmpl = t + return tmpl, None + + if tmpl == 'list': + ui.write(_("available styles: %s\n") % templater.stylelist()) + raise util.Abort(_("specify a template")) + + # perhaps it's a path to a map or a template + if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl): + # is it a mapfile for a style? + if os.path.basename(tmpl).startswith("map-"): + return None, os.path.realpath(tmpl) + tmpl = open(tmpl).read() + return tmpl, None + + # constant string? + return tmpl, None + +def gettemplater(ui, topic, spec): + tmpl, mapfile = lookuptemplate(ui, topic, spec) + t = templater.templater(mapfile, {}) + if tmpl: + t.cache[topic] = tmpl + return t + def formatter(ui, topic, opts): template = opts.get("template", "") if template == "json": @@ -142,9 +196,11 @@ elif template == "debug": return debugformatter(ui, topic, opts) elif template != "": - raise util.Abort(_("custom templates not yet supported")) + return templateformatter(ui, topic, opts) + # developer config: ui.formatdebug elif ui.configbool('ui', 'formatdebug'): return debugformatter(ui, topic, opts) + # deprecated config: ui.formatjson elif ui.configbool('ui', 'formatjson'): return jsonformatter(ui, topic, opts) return plainformatter(ui, topic, opts) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hbisect.py --- a/mercurial/hbisect.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hbisect.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/help.py --- a/mercurial/help.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/help.py Sat Jul 18 17:32:38 2015 -0500 @@ -228,7 +228,7 @@ try: aliases, entry = cmdutil.findcmd(name, commands.table, strict=unknowncmd) - except error.AmbiguousCommand, inst: + except error.AmbiguousCommand as inst: # py3k fix: except vars can't be used outside the scope of the # except block, nor can be used inside a lambda. python issue4617 prefix = inst.args[0] diff -r 501c51d60792 -r 96a38d44ba09 mercurial/help/config.txt --- a/mercurial/help/config.txt Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/help/config.txt Sat Jul 18 17:32:38 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 % @@ -641,8 +641,8 @@ Example for ``~/.hgrc``:: [extensions] - # (the progress extension will get loaded from Mercurial's path) - progress = + # (the color extension will get loaded from Mercurial's path) + color = # (this extension will get loaded from the file specified) myfeature = ~/.hgext/myfeature.py @@ -1093,6 +1093,11 @@ of line, patch line endings are preserved. Default: strict. +``fuzz`` + The number of lines of 'fuzz' to allow when applying patches. This + controls how much context the patcher is allowed to ignore when + trying to apply a patch. + Default: 2 ``paths`` --------- @@ -1214,6 +1219,47 @@ Specific to the ``ls`` instrumenting profiler. Default: 5. +``progress`` +------------ + +Mercurial commands can draw progress bars that are as informative as +possible. Some progress bars only offer indeterminate information, while others +have a definite end point. + +``delay`` + Number of seconds (float) before showing the progress bar. (default: 3) + +``changedelay`` + Minimum delay before showing a new topic. When set to less than 3 * refresh, + that value will be used instead. (default: 1) + +``refresh`` + Time in seconds between refreshes of the progress bar. (default: 0.1) + +``format`` + Format of the progress bar. + + Valid entries for the format field are ``topic``, ``bar``, ``number``, + ``unit``, ``estimate``, speed, and item. item defaults to the last 20 + characters of the item, but this can be changed by adding either ``-`` + which would take the last num characters, or ``+`` for the first num + characters. + + (default: Topic bar number estimate) + +``width`` + If set, the maximum width of the progress information (that is, min(width, + term width) will be used) + +``clear-complete`` + clear the progress bar after it's done (default to True) + +``disable`` + If true, don't show a progress bar + +``assume-tty`` + If true, ALWAYS show a progress bar, unless disable is given + ``revsetalias`` --------------- @@ -1245,6 +1291,10 @@ checking that all new file revisions specified in manifests are present. Default is False. +``maxhttpheaderlen`` + Instruct HTTP clients not to send request headers longer than this + many bytes. Default is 1024. + ``smtp`` -------- @@ -1405,6 +1455,19 @@ markers is different from the encoding of the merged files, serious problems may occur. +``patch`` + An optional external tool that ``hg import`` and some extensions + will use for applying patches. By default Mercurial uses an + internal patch utility. The external tool must work as the common + Unix ``patch`` program. In particular, it must accept a ``-p`` + argument to strip patch headers, a ``-d`` argument to specify the + current directory, a file name to patch, and a patch file to take + from stdin. + + It is possible to specify a patch tool together with extra + arguments. For example, setting this option to ``patch --merge`` + will use the ``patch`` program with its 2-way merge option. + ``portablefilenames`` Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``. Default is ``warn``. @@ -1423,10 +1486,6 @@ ``remotecmd`` remote command to use for clone/push/pull operations. Default is ``hg``. -``reportoldssl`` - Warn if an SSL certificate is unable to be used due to using Python - 2.5 or earlier. True or False. Default is True. - ``report_untrusted`` Warn if a ``.hg/hgrc`` file is ignored due to not being owned by a trusted user or group. True or False. Default is True. @@ -1585,6 +1644,9 @@ ``cache`` Whether to support caching in hgweb. Defaults to True. +``certificate`` + Certificate to use when running :hg:`serve`. + ``collapse`` With ``descend`` enabled, repositories in subdirectories are shown at a single level alongside repositories in the current path. With diff -r 501c51d60792 -r 96a38d44ba09 mercurial/help/hgignore.txt --- a/mercurial/help/hgignore.txt Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/help/hgignore.txt Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/help/patterns.txt --- a/mercurial/help/patterns.txt Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/help/patterns.txt Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/help/subrepos.txt --- a/mercurial/help/subrepos.txt Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/help/subrepos.txt Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/help/templates.txt --- a/mercurial/help/templates.txt Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/help/templates.txt Sat Jul 18 17:32:38 2015 -0500 @@ -47,6 +47,10 @@ - expr % "{template}" +As seen in the above example, "{template}" is interpreted as a template. +To prevent it from being interpreted, you can use an escape character "\{" +or a raw string prefix, "r'...'". + Some sample command line templates: - Format lists, e.g. files:: @@ -67,7 +71,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 +94,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 '@':: @@ -100,8 +104,8 @@ - Show only commit descriptions that start with "template":: - $ hg log --template "{startswith(\"template\", firstline(desc))}\n" + $ hg log --template "{startswith('template', firstline(desc))}\n" - 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 501c51d60792 -r 96a38d44ba09 mercurial/hg.py --- a/mercurial/hg.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hg.py Sat Jul 18 17:32:38 2015 -0500 @@ -92,6 +92,10 @@ try: return thing(path) except TypeError: + # we can't test callable(thing) because 'thing' can be an unloaded + # module that implements __call__ + if not util.safehasattr(thing, 'instance'): + raise return thing def islocal(repo): @@ -198,7 +202,7 @@ requirements = '' try: requirements = srcrepo.vfs.read('requires') - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise @@ -249,7 +253,7 @@ closetopic[0] = topic else: ui.progress(topic, pos + num) - srcpublishing = srcrepo.ui.configbool('phases', 'publish', True) + srcpublishing = srcrepo.publishing() srcvfs = scmutil.vfs(srcrepo.sharedpath) dstvfs = scmutil.vfs(destpath) for f in srcrepo.store.copylist(): @@ -280,8 +284,50 @@ release(destlock) raise +def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False, + rev=None, update=True, stream=False): + """Perform a clone using a shared repo. + + The store for the repository will be located at /.hg. The + specified revisions will be cloned or pulled from "source". A shared repo + will be created at "dest" and a working copy will be created if "update" is + True. + """ + revs = None + if rev: + if not srcpeer.capable('lookup'): + raise util.Abort(_("src repository does not support " + "revision lookup and so doesn't " + "support clone by revision")) + revs = [srcpeer.lookup(r) for r in rev] + + basename = os.path.basename(sharepath) + + if os.path.exists(sharepath): + ui.status(_('(sharing from existing pooled repository %s)\n') % + basename) + else: + ui.status(_('(sharing from new pooled repository %s)\n') % basename) + # Always use pull mode because hardlinks in share mode don't work well. + # Never update because working copies aren't necessary in share mode. + clone(ui, peeropts, source, dest=sharepath, pull=True, + rev=rev, update=False, stream=stream) + + sharerepo = repository(ui, path=sharepath) + share(ui, sharerepo, dest=dest, update=update, bookmarks=False) + + # We need to perform a pull against the dest repo to fetch bookmarks + # and other non-store data that isn't shared by default. In the case of + # non-existing shared repo, this means we pull from the remote twice. This + # is a bit weird. But at the time it was implemented, there wasn't an easy + # way to pull just non-changegroup data. + destrepo = repository(ui, path=dest) + exchange.pull(destrepo, srcpeer, heads=revs) + + return srcpeer, peer(ui, peeropts, dest) + def clone(ui, peeropts, source, dest=None, pull=False, rev=None, - update=True, stream=False, branch=None): + update=True, stream=False, branch=None, shareopts=None): """Make a copy of an existing repository. Create a copy of an existing repository in a new directory. The @@ -316,6 +362,13 @@ anything else is treated as a revision) branch: branches to clone + + shareopts: dict of options to control auto sharing behavior. The "pool" key + activates auto sharing mode and defines the directory for stores. The + "mode" key determines how to construct the directory name of the shared + repository. "identity" means the name is derived from the node of the first + changeset in the repository. "remote" means the name is derived from the + remote's path/URL. Defaults to "identity." """ if isinstance(source, str): @@ -348,6 +401,36 @@ elif destvfs.listdir(): raise util.Abort(_("destination '%s' is not empty") % dest) + shareopts = shareopts or {} + sharepool = shareopts.get('pool') + sharenamemode = shareopts.get('mode') + if sharepool: + sharepath = None + if sharenamemode == 'identity': + # Resolve the name from the initial changeset in the remote + # repository. This returns nullid when the remote is empty. It + # raises RepoLookupError if revision 0 is filtered or otherwise + # not available. If we fail to resolve, sharing is not enabled. + try: + rootnode = srcpeer.lookup('0') + if rootnode != node.nullid: + sharepath = os.path.join(sharepool, node.hex(rootnode)) + else: + ui.status(_('(not using pooled storage: ' + 'remote appears to be empty)\n')) + except error.RepoLookupError: + ui.status(_('(not using pooled storage: ' + 'unable to resolve identity of remote)\n')) + elif sharenamemode == 'remote': + sharepath = os.path.join(sharepool, util.sha1(source).hexdigest()) + else: + raise util.Abort('unknown share naming mode: %s' % sharenamemode) + + if sharepath: + return clonewithshare(ui, peeropts, sharepath, source, srcpeer, + dest, pull=pull, rev=rev, update=update, + stream=stream) + srclock = destlock = cleandir = None srcrepo = srcpeer.local() try: @@ -384,7 +467,7 @@ try: destpath = hgdir util.makedir(destpath, notindexed=True) - except OSError, inst: + except OSError as inst: if inst.errno == errno.EEXIST: cleandir = None raise util.Abort(_("destination '%s' already exists") @@ -424,7 +507,7 @@ try: destpeer = peer(srcrepo or ui, peeropts, dest, create=True) # only pass ui when no srcrepo - except OSError, inst: + except OSError as inst: if inst.errno == errno.EEXIST: cleandir = None raise util.Abort(_("destination '%s' already exists") @@ -497,7 +580,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: @@ -660,7 +743,28 @@ def verify(repo): """verify the consistency of a repository""" - return verifymod.verify(repo) + ret = verifymod.verify(repo) + + # Broken subrepo references in hidden csets don't seem worth worrying about, + # since they can't be pushed/pulled, and --hidden can be used if they are a + # concern. + + # pathto() is needed for -R case + revs = repo.revs("filelog(%s)", + util.pathto(repo.root, repo.getcwd(), '.hgsubstate')) + + if revs: + repo.ui.status(_('checking subrepo links\n')) + for rev in revs: + ctx = repo[rev] + try: + for subpath in ctx.substate: + ret = ctx.sub(subpath).verify() or ret + except Exception: + repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') % + node.short(ctx.node())) + + return ret def remoteui(src, opts): 'build a remote ui from ui or repo and opts' diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/common.py Sat Jul 18 17:32:38 2015 -0500 @@ -112,8 +112,8 @@ def statusmessage(code, message=None): return '%d %s' % (code, message or _statusmessage(code)) -def get_stat(spath, fn="00changelog.i"): - """stat fn (00changelog.i by default) if it exists, spath otherwise""" +def get_stat(spath, fn): + """stat fn if it exists, spath otherwise""" cl_path = os.path.join(spath, fn) if os.path.exists(cl_path): return os.stat(cl_path) @@ -121,7 +121,7 @@ return os.stat(spath) def get_mtime(spath): - return get_stat(spath).st_mtime + return get_stat(spath, "00changelog.i").st_mtime def staticfile(directory, fname, req): """return a file inside directory with guessed Content-Type header @@ -153,7 +153,7 @@ req.respond(HTTP_OK, ct, body=data) except TypeError: raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename') - except OSError, err: + except OSError as err: if err.errno == errno.ENOENT: raise ErrorResponse(HTTP_NOT_FOUND) else: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/hgweb_mod.py Sat Jul 18 17:32:38 2015 -0500 @@ -26,6 +26,15 @@ 'pushkey': 'push', } +## Files of interest +# Used to check if the repository has changed looking at mtime and size of +# theses files. This should probably be relocated a bit higher in core. +foi = [('spath', '00changelog.i'), + ('spath', 'phaseroots'), # ! phase can change content at the same size + ('spath', 'obsstore'), + ('path', 'bookmarks'), # ! bookmark can change content at the same size + ] + def makebreadcrumb(url, prefix=''): '''Return a 'URL breadcrumb' list @@ -69,6 +78,10 @@ r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb') r.ui.setconfig('ui', 'nontty', 'true', 'hgweb') r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb') + # displaying bundling progress bar while serving feel wrong and may + # break some wsgi implementation. + r.ui.setconfig('progress', 'disable', 'true', 'hgweb') + r.baseui.setconfig('progress', 'disable', 'true', 'hgweb') self.repo = r hook.redirect(True) self.repostate = ((-1, -1), (-1, -1)) @@ -96,6 +109,16 @@ untrusted=untrusted) def _getview(self, repo): + """The 'web.view' config controls changeset filter to hgweb. Possible + values are ``served``, ``visible`` and ``all``. Default is ``served``. + The ``served`` filter only shows changesets that can be pulled from the + hgweb instance. The``visible`` filter includes secret changesets but + still excludes "hidden" one. + + See the repoview module for details. + + The option has been around undocumented since Mercurial 2.5, but no + user ever asked about it. So we better keep it undocumented for now.""" viewconfig = repo.ui.config('web', 'view', 'served', untrusted=True) if viewconfig == 'all': @@ -106,10 +129,13 @@ return repo.filtered('served') def refresh(self, request=None): - st = get_stat(self.repo.spath) - pst = get_stat(self.repo.spath, 'phaseroots') - # changelog mtime and size, phaseroots mtime and size - repostate = ((st.st_mtime, st.st_size), (pst.st_mtime, pst.st_size)) + repostate = [] + # file of interrests mtime and size + for meth, fname in foi: + prefix = getattr(self.repo, meth) + st = get_stat(prefix, fname) + repostate.append((st.st_mtime, st.st_size)) + repostate = tuple(repostate) # we need to compare file size in addition to mtime to catch # changes made less than a second ago if repostate != self.repostate: @@ -176,7 +202,7 @@ if cmd in perms: self.check_perm(req, perms[cmd]) return protocol.call(self.repo, req, cmd) - except ErrorResponse, inst: + except ErrorResponse as inst: # A client that sends unbundle without 100-continue will # break if we respond early. if (cmd == 'unbundle' and @@ -209,7 +235,7 @@ req.form['file'] = ['/'.join(args)] else: if args and args[0]: - node = args.pop(0) + node = args.pop(0).replace('%2F', '/') req.form['node'] = [node] if args: req.form['file'] = args @@ -255,17 +281,17 @@ return content - except (error.LookupError, error.RepoLookupError), err: + except (error.LookupError, error.RepoLookupError) as err: req.respond(HTTP_NOT_FOUND, ctype) msg = str(err) if (util.safehasattr(err, 'name') and not isinstance(err, error.ManifestLookupError)): msg = 'revision not found: %s' % err.name return tmpl('error', error=msg) - except (error.RepoError, error.RevlogError), inst: + except (error.RepoError, error.RevlogError) as inst: req.respond(HTTP_SERVER_ERROR, ctype) return tmpl('error', error=str(inst)) - except ErrorResponse, inst: + except ErrorResponse as inst: req.respond(inst, ctype) if inst.code == HTTP_NOT_MODIFIED: # Not allowed to return a body on a 304 diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/hgwebdir_mod.py Sat Jul 18 17:32:38 2015 -0500 @@ -98,6 +98,9 @@ u = ui.ui() u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir') u.setconfig('ui', 'nontty', 'true', 'hgwebdir') + # displaying bundling progress bar while serving feels wrong and may + # break some wsgi implementations. + u.setconfig('progress', 'disable', 'true', 'hgweb') if not isinstance(self.conf, (dict, list, tuple)): map = {'paths': 'hgweb-paths'} @@ -176,71 +179,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 as inst: + msg = inst.strerror + raise ErrorResponse(HTTP_SERVER_ERROR, msg) + except error.RepoError as 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 as err: + req.respond(err, ctype) + return tmpl('error', error=err.message or '') finally: tmpl = None @@ -334,7 +336,7 @@ u = self.ui.copy() try: u.readconfig(os.path.join(path, '.hg', 'hgrc')) - except Exception, e: + except Exception as e: u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e)) continue def get(section, name, default=None): diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/request.py Sat Jul 18 17:32:38 2015 -0500 @@ -111,7 +111,7 @@ if thing: try: self.server_write(thing) - except socket.error, inst: + except socket.error as inst: if inst[0] != errno.ECONNRESET: raise diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/server.py Sat Jul 18 17:32:38 2015 -0500 @@ -71,7 +71,7 @@ def do_write(self): try: self.do_hgweb() - except socket.error, inst: + except socket.error as inst: if inst[0] != errno.EPIPE: raise @@ -226,7 +226,7 @@ import OpenSSL try: _httprequesthandler.do_write(self) - except OpenSSL.SSL.SysCallError, inst: + except OpenSSL.SSL.SysCallError as inst: if inst.args[0] != errno.EPIPE: raise @@ -344,6 +344,6 @@ port = util.getport(ui.config('web', 'port', 8000)) try: return cls(ui, app, (address, port), handler) - except socket.error, inst: + except socket.error as inst: raise util.Abort(_("cannot start server at '%s:%d': %s") % (address, port, inst.args[1])) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/webcommands.py Sat Jul 18 17:32:38 2015 -0500 @@ -76,7 +76,7 @@ try: fctx = webutil.filectx(web.repo, req) - except error.LookupError, inst: + except error.LookupError as inst: try: content = manifest(web, req, tmpl) req.respond(HTTP_OK, web.ctype) @@ -100,7 +100,7 @@ req.respond(HTTP_OK, mt, path, body=text) return [] -def _filerevision(web, tmpl, fctx): +def _filerevision(web, req, tmpl, fctx): f = fctx.path() text = fctx.data() parity = paritygen(web.stripecount) @@ -121,6 +121,7 @@ path=webutil.up(f), text=lines(), rev=fctx.rev(), + symrev=webutil.symrevorshortnode(req, fctx), node=fctx.hex(), author=fctx.user(), date=fctx.date(), @@ -130,6 +131,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') @@ -156,8 +159,8 @@ if not path: return manifest(web, req, tmpl) try: - return _filerevision(web, tmpl, webutil.filectx(web.repo, req)) - except error.LookupError, inst: + return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req)) + except error.LookupError as inst: try: return manifest(web, req, tmpl) except ErrorResponse: @@ -221,7 +224,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 +233,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 @@ -314,7 +317,7 @@ tip = web.repo['tip'] parity = paritygen(web.stripecount) - return tmpl('search', query=query, node=tip.hex(), + return tmpl('search', query=query, node=tip.hex(), symrev='tip', entries=changelist, archives=web.archivelist("tip"), morevars=morevars, lessvars=lessvars, modedesc=searchfunc[1], @@ -349,10 +352,12 @@ query = '' if 'node' in req.form: ctx = webutil.changectx(web.repo, req) + symrev = webutil.symrevorshortnode(req, ctx) elif 'rev' in req.form: return _search(web, req, tmpl) else: ctx = web.repo['tip'] + symrev = 'tip' def changelist(): revs = [] @@ -401,7 +406,7 @@ nextentry = [] return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav, - node=ctx.hex(), rev=pos, changesets=count, + node=ctx.hex(), rev=pos, symrev=symrev, changesets=count, entries=entries, latestentry=latestentry, nextentry=nextentry, archives=web.archivelist("tip"), revcount=revcount, @@ -468,7 +473,12 @@ The ``manifest`` template will be rendered for this handler. """ - ctx = webutil.changectx(web.repo, req) + if 'node' in req.form: + ctx = webutil.changectx(web.repo, req) + symrev = webutil.symrevorshortnode(req, ctx) + else: + ctx = web.repo['tip'] + symrev = 'tip' path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) mf = ctx.manifest() node = ctx.node() @@ -537,6 +547,7 @@ return tmpl("manifest", rev=ctx.rev(), + symrev=symrev, node=hex(node), path=abspath, up=webutil.up(abspath), @@ -546,6 +557,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)) @@ -752,6 +764,7 @@ branches=branches, shortlog=changelist, node=tip.hex(), + symrev='tip', archives=web.archivelist("tip")) @webcommand('filediff') @@ -800,6 +813,7 @@ file=path, node=hex(n), rev=ctx.rev(), + symrev=webutil.symrevorshortnode(req, ctx), date=ctx.date(), desc=ctx.description(), extra=ctx.extra(), @@ -808,6 +822,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) @@ -872,6 +888,7 @@ file=path, node=hex(ctx.node()), rev=ctx.rev(), + symrev=webutil.symrevorshortnode(req, ctx), date=ctx.date(), desc=ctx.description(), extra=ctx.extra(), @@ -880,6 +897,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, @@ -937,6 +956,7 @@ annotate=annotate, path=webutil.up(f), rev=fctx.rev(), + symrev=webutil.symrevorshortnode(req, fctx), node=fctx.hex(), author=fctx.user(), date=fctx.date(), @@ -946,6 +966,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') @@ -1034,6 +1056,7 @@ revnav = webutil.filerevnav(web.repo, fctx.path()) nav = revnav.gen(end - 1, revcount, count) return tmpl("filelog", file=f, node=fctx.hex(), nav=nav, + symrev=webutil.symrevorshortnode(req, fctx), entries=entries, latestentry=latestentry, revcount=revcount, morevars=morevars, lessvars=lessvars) @@ -1140,7 +1163,12 @@ This handler will render the ``graph`` template. """ - ctx = webutil.changectx(web.repo, req) + if 'node' in req.form: + ctx = webutil.changectx(web.repo, req) + symrev = webutil.symrevorshortnode(req, ctx) + else: + ctx = web.repo['tip'] + symrev = 'tip' rev = ctx.rev() bg_height = 39 @@ -1243,7 +1271,8 @@ rows = len(tree) canvasheight = (rows + 1) * bg_height - 27 - return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev, + return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount, + uprev=uprev, lessvars=lessvars, morevars=morevars, downrev=downrev, cols=cols, rows=rows, canvaswidth=(cols + 1) * bg_height, diff -r 501c51d60792 -r 96a38d44ba09 mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hgweb/webutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -9,7 +9,8 @@ import os, copy from mercurial import match, patch, error, ui, util, pathutil, context from mercurial.i18n import _ -from mercurial.node import hex, nullid +from mercurial.node import hex, nullid, short +from mercurial.templatefilters import revescape from common import ErrorResponse, paritygen from common import HTTP_NOT_FOUND import difflib @@ -279,6 +280,12 @@ "branches": nodebranchdict(repo, ctx) } +def symrevorshortnode(req, ctx): + if 'node' in req.form: + return revescape(req.form['node'][0]) + else: + return short(ctx.node()) + def changesetentry(web, req, tmpl, ctx): '''Obtain a dictionary to be used to render the "changeset" template.''' @@ -314,6 +321,7 @@ diff=diff, rev=ctx.rev(), node=ctx.hex(), + symrev=symrevorshortnode(req, ctx), parent=tuple(parents(ctx)), child=children(ctx), basenode=basectx.hex(), @@ -331,7 +339,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 501c51d60792 -r 96a38d44ba09 mercurial/hook.py --- a/mercurial/hook.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/hook.py Sat Jul 18 17:32:38 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 as 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 501c51d60792 -r 96a38d44ba09 mercurial/httpclient/__init__.py --- a/mercurial/httpclient/__init__.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/httpclient/__init__.py Sat Jul 18 17:32:38 2015 -0500 @@ -166,7 +166,7 @@ raise HTTPTimeoutException('timeout reading data') try: data = self.sock.recv(INCOMING_BUFFER_SIZE) - except socket.sslerror, e: + except socket.sslerror as e: if e.args[0] != socket.SSL_ERROR_WANT_READ: raise logger.debug('SSL_ERROR_WANT_READ in _select, should retry later') @@ -555,7 +555,7 @@ try: try: data = r[0].recv(INCOMING_BUFFER_SIZE) - except socket.sslerror, e: + except socket.sslerror as e: if e.args[0] != socket.SSL_ERROR_WANT_READ: raise logger.debug('SSL_ERROR_WANT_READ while sending ' @@ -610,7 +610,7 @@ # Jump to the next select() call so we load more # data if the server is still sending us content. continue - except socket.error, e: + except socket.error as e: if e[0] != errno.EPIPE and not was_first: raise @@ -633,7 +633,7 @@ else: out = data amt = w[0].send(out) - except socket.error, e: + except socket.error as e: if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl: # This means that SSL hasn't flushed its buffer into # the socket yet. diff -r 501c51d60792 -r 96a38d44ba09 mercurial/httpclient/socketutil.py --- a/mercurial/httpclient/socketutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/httpclient/socketutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -64,7 +64,7 @@ sock = socket.socket(af, socktype, proto) logger.info("connect: (%s, %s)", host, port) sock.connect(sa) - except socket.error, msg: + except socket.error as msg: logger.info('connect fail: %s %s', host, port) if sock: sock.close() @@ -100,7 +100,7 @@ while True: try: return self._ssl.read(buflen) - except socket.sslerror, x: + except socket.sslerror as x: if x.args[0] == socket.SSL_ERROR_WANT_READ: continue else: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/httpconnection.py --- a/mercurial/httpconnection.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/httpconnection.py Sat Jul 18 17:32:38 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 @@ -130,6 +126,7 @@ self.ui = ui self.pwmgr = pwmgr self._connections = {} + # developer config: ui.http2debuglevel loglevel = ui.config('ui', 'http2debuglevel', default=None) if loglevel and not _configuredlogging: _configuredlogging = True @@ -215,7 +212,7 @@ path = '/' + path h.request(req.get_method(), path, req.data, headers) r = h.getresponse() - except socket.error, err: # XXX what error? + except socket.error as err: # XXX what error? raise urllib2.URLError(err) # Pick apart the HTTPResponse object to get the addinfourl @@ -281,7 +278,7 @@ kwargs.update(sslutil.sslkwargs(self.ui, host)) con = HTTPConnection(host, port, use_ssl=True, - ssl_wrap_socket=sslutil.ssl_wrap_socket, + ssl_wrap_socket=sslutil.wrapsocket, ssl_validator=sslutil.validator(self.ui, host), **kwargs) return con diff -r 501c51d60792 -r 96a38d44ba09 mercurial/httppeer.py --- a/mercurial/httppeer.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/httppeer.py Sat Jul 18 17:32:38 2015 -0500 @@ -30,6 +30,7 @@ self.caps = None self.handler = None self.urlopener = None + self.requestbuilder = None u = util.url(path) if u.query or u.fragment: raise util.Abort(_('unsupported URL component: "%s"') % @@ -42,6 +43,7 @@ self.ui.debug('using %s\n' % self._url) self.urlopener = url.opener(ui, authinfo) + self.requestbuilder = urllib2.Request def __del__(self): if self.urlopener: @@ -111,17 +113,17 @@ q += sorted(args.items()) qs = '?%s' % urllib.urlencode(q) cu = "%s%s" % (self._url, qs) - req = urllib2.Request(cu, data, headers) + req = self.requestbuilder(cu, data, headers) if data is not None: self.ui.debug("sending %s bytes\n" % size) req.add_unredirected_header('Content-Length', '%d' % size) try: resp = self.urlopener.open(req) - except urllib2.HTTPError, inst: + except urllib2.HTTPError as inst: if inst.code == 401: raise util.Abort(_('authorization failed')) raise - except httplib.HTTPException, inst: + except httplib.HTTPException as inst: self.ui.debug('http error while sending %s command\n' % cmd) self.ui.traceback() raise IOError(None, inst) @@ -198,16 +200,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 as 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) @@ -266,7 +267,7 @@ # No luck, try older compatibility check. inst.between([(nullid, nullid)]) return inst - except error.RepoError, httpexception: + except error.RepoError as httpexception: try: r = statichttprepo.instance(ui, "static-" + path, create) ui.note('(falling back to static-http)\n') diff -r 501c51d60792 -r 96a38d44ba09 mercurial/ignore.py --- a/mercurial/ignore.py Fri Jul 03 18:10:58 2015 +0100 +++ /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 501c51d60792 -r 96a38d44ba09 mercurial/keepalive.py --- a/mercurial/keepalive.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/keepalive.py Sat Jul 18 17:32:38 2015 -0500 @@ -251,7 +251,7 @@ self._cm.add(host, h, 0) self._start_transaction(h, req) r = h.getresponse() - except (socket.error, httplib.HTTPException), err: + except (socket.error, httplib.HTTPException) as err: raise urllib2.URLError(err) # if not a persistent connection, don't try to reuse it @@ -343,7 +343,7 @@ h.putheader('Content-length', '%d' % len(data)) else: h.putrequest('GET', req.get_selector(), **skipheaders) - except (socket.error), err: + except (socket.error) as err: raise urllib2.URLError(err) for k, v in headers.items(): h.putheader(k, v) @@ -550,7 +550,7 @@ data = read(blocksize) else: self.sock.sendall(str) - except socket.error, v: + except socket.error as v: reraise = True if v[0] == errno.EPIPE: # Broken pipe if self._HTTPConnection__state == httplib._CS_REQ_SENT: @@ -605,7 +605,7 @@ status, reason = fo.status, fo.reason except AttributeError: status, reason = None, None - except IOError, e: + except IOError as e: print " EXCEPTION: %s" % e raise else: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/localrepo.py --- a/mercurial/localrepo.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/localrepo.py Sat Jul 18 17:32:38 2015 -0500 @@ -4,7 +4,7 @@ # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -from node import hex, nullid, short +from node import hex, nullid, wdirrev, short from i18n import _ import urllib import peer, changegroup, subrepo, pushkey, obsolete, repoview @@ -135,7 +135,7 @@ stream = util.chunkbuffer(ret.getchunks()) ret = bundle2.getunbundler(self.ui, stream) return ret - except Exception, exc: + except Exception as exc: # If the exception contains output salvaged from a bundle2 # reply, we need to make sure it is printed before continuing # to fail. So we build a bundle2 with such output and consume @@ -152,7 +152,7 @@ b = bundle2.getunbundler(self.ui, stream) bundle2.processbundle(self._repo, b) raise - except error.PushRaced, exc: + except error.PushRaced as exc: raise error.ResponseError(_('push failed:'), str(exc)) def lock(self): @@ -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,36 +244,38 @@ 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", '\0\0\0\2' # represents revlogv2 ' dummy changelog to prevent using the old repo layout' ) + # experimental config: format.generaldelta 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) - except IOError, inst: + self.requirements = scmutil.readrequires( + self.vfs, self.supported) + except IOError as inst: if inst.errno != errno.ENOENT: raise - requirements = set() self.sharedpath = self.path try: @@ -283,17 +286,17 @@ raise error.RepoError( _('.hg/sharedpath points to nonexistent directory %s') % s) self.sharedpath = s - except IOError, inst: + except IOError as inst: 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,28 +339,24 @@ 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) + # experimental config: format.chunkcachesize chunkcachesize = self.ui.configint('format', 'chunkcachesize') if chunkcachesize is not None: self.svfs.options['chunkcachesize'] = chunkcachesize + # experimental config: format.maxchainlen maxchainlen = self.ui.configint('format', 'maxchainlen') if maxchainlen is not None: self.svfs.options['maxchainlen'] = maxchainlen + # experimental config: format.manifestcachesize 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 +418,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] @@ -437,6 +436,7 @@ @storecache('obsstore') def obsstore(self): # read default format for new obsstore. + # developer config: format.obsstore-version defaultformat = self.ui.configint('format', 'obsstore-version', None) # rely on obsstore class default when possible. kwargs = {} @@ -464,6 +464,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] @@ -481,7 +484,7 @@ return dirstate.dirstate(self.vfs, self.ui, self.root, validate) def __getitem__(self, changeid): - if changeid is None: + if changeid is None or changeid == wdirrev: return context.workingctx(self) if isinstance(changeid, slice): return [context.changectx(self, i) @@ -579,7 +582,7 @@ try: fp = self.wfile('.hgtags', 'rb+') - except IOError, e: + except IOError as e: if e.errno != errno.ENOENT: raise fp = self.wfile('.hgtags', 'ab') @@ -628,7 +631,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')) @@ -798,11 +801,16 @@ def local(self): return self + def publishing(self): + # it's safe (and desirable) to trust the publish flag unconditionally + # so that we don't finalize changes shared between users via ssh or nfs + return self.ui.configbool('phases', 'publish', True, untrusted=True) + def cancopy(self): # so statichttprepo's override of local() works if not self.local(): return False - if not self.ui.configbool('phases', 'publish', True): + if not self.publishing(): return True # if publishing we can't copy if there is filtered content return not self.filtered('visible').changelog.filteredrevs @@ -945,11 +953,11 @@ 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: - scmutil.develwarn(self.ui, 'transaction with no lock') + self.ui.develwarn('transaction with no lock') tr = self.currenttransaction() if tr is not None: return tr.nest() @@ -979,7 +987,7 @@ reporef().hook('pretxnclose', throw=True, pending=pending, txnname=desc, **tr.hookargs) - tr = transaction.transaction(rp, self.sopener, vfsmap, + tr = transaction.transaction(rp, self.svfs, vfsmap, "journal", "undo", aftertrans(renames), @@ -1185,7 +1193,7 @@ def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc): try: l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc) - except error.LockHeld, inst: + except error.LockHeld as inst: if not wait: raise self.ui.warn(_("waiting for lock on %s held by %r\n") % @@ -1250,11 +1258,11 @@ # 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: - scmutil.develwarn(self.ui, '"wlock" acquired after "lock"') + self.ui.develwarn('"wlock" acquired after "lock"') def unlock(): if self.dirstate.pendingparentchange(): @@ -1382,7 +1390,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 +1453,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 +1475,11 @@ 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()): + # internal config: ui.allowemptycommit + 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 +1532,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) @@ -1565,10 +1575,10 @@ m[f] = self._filecommit(fctx, m1, m2, linkrev, trp, changed) m.setflag(f, fctx.flags()) - except OSError, inst: + except OSError as inst: self.ui.warn(_("trouble committing %s!\n") % f) raise - except IOError, inst: + except IOError as inst: errcode = getattr(inst, 'errno', errno.ENOENT) if error or errcode and errcode != errno.ENOENT: self.ui.warn(_("trouble committing %s!\n") % f) @@ -1755,89 +1765,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 +1824,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 +1837,6 @@ cache.write(rview) break self.invalidate() - return len(self.heads()) + 1 finally: lock.release() @@ -1897,6 +1872,7 @@ if not streamreqs - self.supportedformats: self.stream_in(remote, streamreqs) + # internal config: ui.quietbookmarkmove quiet = self.ui.backupconfig('ui', 'quietbookmarkmove') try: self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone') @@ -1918,7 +1894,7 @@ hookargs['old'] = old hookargs['new'] = new self.hook('prepushkey', throw=True, **hookargs) - except error.HookAbort, exc: + except error.HookAbort as exc: self.ui.write_err(_("pushkey-abort: %s\n") % exc) if exc.hint: self.ui.write_err(_("(%s)\n") % exc.hint) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/lock.py --- a/mercurial/lock.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/lock.py Sat Jul 18 17:32:38 2015 -0500 @@ -58,7 +58,7 @@ try: self.trylock() return self.timeout - timeout - except error.LockHeld, inst: + except error.LockHeld as inst: if timeout != 0: time.sleep(1) if timeout > 0: @@ -78,7 +78,7 @@ try: self.vfs.makelock(lockname, self.f) self.held = 1 - except (OSError, IOError), why: + except (OSError, IOError) as why: if why.errno == errno.EEXIST: locker = self.testlock() if locker is not None: @@ -102,7 +102,7 @@ """ try: locker = self.vfs.readlock(self.f) - except (OSError, IOError), why: + except (OSError, IOError) as why: if why.errno == errno.ENOENT: return None raise diff -r 501c51d60792 -r 96a38d44ba09 mercurial/mail.py --- a/mercurial/mail.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/mail.py Sat Jul 18 17:32:38 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): @@ -49,8 +45,8 @@ raise smtplib.SMTPException(msg) (resp, reply) = self.docmd("STARTTLS") if resp == 220: - self.sock = sslutil.ssl_wrap_socket(self.sock, keyfile, certfile, - **self._sslkwargs) + self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile, + **self._sslkwargs) if not util.safehasattr(self.sock, "read"): # using httplib.FakeSocket with Python 2.5.x or earlier self.sock.read = self.sock.recv @@ -78,9 +74,9 @@ if self.debuglevel > 0: print >> sys.stderr, 'connect:', (host, port) new_socket = socket.create_connection((host, port), timeout) - new_socket = sslutil.ssl_wrap_socket(new_socket, - self.keyfile, self.certfile, - **self._sslkwargs) + new_socket = sslutil.wrapsocket(new_socket, + self.keyfile, self.certfile, + **self._sslkwargs) self.file = smtplib.SSLFakeFile(new_socket) return new_socket else: @@ -108,7 +104,8 @@ if (starttls or smtps) and verifycert: sslkwargs = sslutil.sslkwargs(ui, mailhost) else: - sslkwargs = {} + # 'ui' is required by sslutil.wrapsocket() and set by sslkwargs() + sslkwargs = {'ui': ui} if smtps: ui.note(_('(using smtps)\n')) s = SMTPS(sslkwargs, local_hostname=local_hostname) @@ -141,23 +138,23 @@ (username)) try: s.login(username, password) - except smtplib.SMTPException, inst: + except smtplib.SMTPException as inst: raise util.Abort(inst) def send(sender, recipients, msg): try: return s.sendmail(sender, recipients, msg) - except smtplib.SMTPRecipientsRefused, inst: + except smtplib.SMTPRecipientsRefused as inst: recipients = [r[1] for r in inst.recipients.values()] raise util.Abort('\n' + '\n'.join(recipients)) - except smtplib.SMTPException, inst: + except smtplib.SMTPException as inst: raise util.Abort(inst) return send def _sendmail(ui, sender, recipients, msg): '''send mail using sendmail.''' - program = ui.config('email', 'method') + program = ui.config('email', 'method', 'smtp') cmdline = '%s -f %s %s' % (program, util.email(sender), ' '.join(map(util.email, recipients))) ui.note(_('sending mail: %s\n') % cmdline) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/manifest.c --- a/mercurial/manifest.c Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/manifest.c Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/manifest.py --- a/mercurial/manifest.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/manifest.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/match.py --- a/mercurial/match.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/match.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import re +import copy, re import util, pathutil from i18n import _ @@ -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, badfn=None): """build an object to match a set of file patterns arguments: @@ -58,6 +89,8 @@ 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 + badfn - optional bad() callback for this matcher instead of the default a pattern is one of: 'glob:' - a glob relative to cwd @@ -67,6 +100,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 +112,28 @@ 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() + + if badfn is not None: + self.bad = badfn 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._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) + if not _anypats(kindpats): + self._excluderoots.update(_roots(kindpats)) matchfns.append(lambda f: not em(f)) if exact: if isinstance(patterns, list): @@ -97,7 +146,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 +163,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 +211,35 @@ @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 + if (self._includeroots and + '.' not in self._includeroots and + dir not in self._includeroots and + dir not in self._includedirs and + not any(parent in self._includeroots + for parent in util.finddirs(dir))): + 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 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 +250,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,18 +283,41 @@ 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 as inst: + raise util.Abort('%s: %s' % (pat, inst[0])) + except IOError as 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): - return match(root, cwd, files, exact=True) +def exact(root, cwd, files, badfn=None): + return match(root, cwd, files, exact=True, badfn=badfn) def always(root, cwd): return match(root, cwd, []) +def badmatch(match, badfn): + """Make a copy of the given matcher, replacing its bad method with the given + one. + """ + m = copy.copy(match) + m.bad = badfn + return m + class narrowmatcher(match): """Adapt a matcher to work on a subdirectory only. @@ -264,11 +362,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 +383,26 @@ """ def __init__(self, root, cwd, patterns, include, exclude, default, auditor, - ctx): + ctx, listsubrepos=False, badfn=None): 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, badfn=badfn) # 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 +415,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 @@ -420,24 +518,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) @@ -452,25 +566,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('/'): @@ -485,6 +603,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 501c51d60792 -r 96a38d44ba09 mercurial/merge.py --- a/mercurial/merge.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/merge.py Sat Jul 18 17:32:38 2015 -0500 @@ -145,7 +145,7 @@ else: records.append(('F', l[:-1])) f.close() - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise return records @@ -170,7 +170,7 @@ off += length records.append((rtype, record)) f.close() - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise return records @@ -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 @@ -660,7 +660,7 @@ audit(f) try: unlink(wjoin(f), ignoremissing=True) - except OSError, inst: + except OSError as inst: repo.ui.warn(_("update failed to remove %s: %s!\n") % (f, inst.strerror)) if i == 100: @@ -741,15 +741,7 @@ numupdates = sum(len(l) for m, l in actions.items() if m != 'k') - def dirtysubstate(): - # mark '.hgsubstate' as possibly dirty forcibly, because - # modified '.hgsubstate' is misunderstood as clean, - # when both st_size/st_mtime of '.hgsubstate' aren't changed, - # even if "submerge" fails and '.hgsubstate' is inconsistent - repo.dirstate.normallookup('.hgsubstate') - if [a for a in actions['r'] if a[0] == '.hgsubstate']: - dirtysubstate() subrepo.submerge(repo, wctx, mctx, wctx, overwrite) # remove in parallel (must come first) @@ -768,7 +760,6 @@ updated = len(actions['g']) if [a for a in actions['g'] if a[0] == '.hgsubstate']: - dirtysubstate() subrepo.submerge(repo, wctx, mctx, wctx, overwrite) # forget (manifest only, just log it) (must come first) @@ -794,7 +785,6 @@ z += 1 progress(_updating, z, item=f, total=numupdates, unit=_files) if f == '.hgsubstate': # subrepo states need updating - dirtysubstate() subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite) continue @@ -1021,7 +1011,7 @@ p2 = repo[node] if pas[0] is None: - if repo.ui.config('merge', 'preferancestor', '*') == '*': + if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']: cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node()) pas = [repo[anc] for anc in (sorted(cahs) or [nullid])] else: @@ -1080,6 +1070,7 @@ # Allow jumping branches if clean and specific rev given pas = [p1] + # deprecated config: merge.followcopies followcopies = False if overwrite: pas = [wc] diff -r 501c51d60792 -r 96a38d44ba09 mercurial/minirst.py --- a/mercurial/minirst.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/minirst.py Sat Jul 18 17:32:38 2015 -0500 @@ -682,7 +682,10 @@ secs = [] def getname(b): - x = b['lines'][0] + if b['type'] == 'field': + x = b['key'] + else: + x = b['lines'][0] x = x.lower().strip('"') if '(' in x: x = x.split('(')[0] @@ -696,7 +699,7 @@ level = nest.index(i) + 1 nest = nest[:level] secs.append((getname(b), level, [b])) - elif b['type'] == 'definition': + elif b['type'] in ('definition', 'field'): i = ' ' if i not in nest: nest += i diff -r 501c51d60792 -r 96a38d44ba09 mercurial/node.py --- a/mercurial/node.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/node.py Sat Jul 18 17:32:38 2015 -0500 @@ -10,6 +10,11 @@ nullrev = -1 nullid = "\0" * 20 +# pseudo identifiers for working directory +# (they are experimental, so don't add too many dependencies on them) +wdirrev = 0x7fffffff +wdirid = "\xff" * 20 + # This ugly style has a noticeable effect in manifest parsing hex = binascii.hexlify bin = binascii.unhexlify diff -r 501c51d60792 -r 96a38d44ba09 mercurial/obsolete.py --- a/mercurial/obsolete.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/obsolete.py Sat Jul 18 17:32:38 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 @@ -517,12 +517,12 @@ # parents: (tuple of nodeid) or None, parents of precursors # None is used when no data has been recorded - def __init__(self, sopener, defaultformat=_fm1version, readonly=False): + def __init__(self, svfs, defaultformat=_fm1version, readonly=False): # caches for various obsolescence related cache self.caches = {} self._all = [] - self.sopener = sopener - data = sopener.tryread('obsstore') + self.svfs = svfs + data = svfs.tryread('obsstore') self._version = defaultformat self._readonly = readonly if data: @@ -588,7 +588,7 @@ known.add(m) new.append(m) if new: - f = self.sopener('obsstore', 'ab') + f = self.svfs('obsstore', 'ab') try: offset = f.tell() transaction.add('obsstore', offset) @@ -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') @@ -1211,8 +1214,9 @@ localmetadata.update(rel[2]) if not prec.mutable(): - raise util.Abort("cannot obsolete immutable changeset: %s" - % prec) + raise util.Abort("cannot obsolete public changeset: %s" + % prec, + hint='see "hg help phases" for details') nprec = prec.node() nsucs = tuple(s.node() for s in sucs) npare = None diff -r 501c51d60792 -r 96a38d44ba09 mercurial/parser.py --- a/mercurial/parser.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/parser.py Sat Jul 18 17:32:38 2015 -0500 @@ -10,8 +10,9 @@ # for background # takes a tokenizer and elements -# tokenizer is an iterator that returns type, value pairs -# elements is a mapping of types to binding strength, prefix and infix actions +# tokenizer is an iterator that returns (type, value, pos) tuples +# elements is a mapping of types to binding strength, primary, prefix, infix +# and suffix actions # an action is a tree node name, a tree label, and an optional match # __call__(program) parses program into a labeled tree @@ -19,68 +20,58 @@ from i18n import _ class parser(object): - def __init__(self, tokenizer, elements, methods=None): - self._tokenizer = tokenizer + def __init__(self, elements, methods=None): self._elements = elements self._methods = methods self.current = None 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): + def _hasnewterm(self): + 'True if next token may start new term' + return any(self._elements[self.current[0]][1:3]) + def _match(self, m): 'make sure the tokenizer matches an end condition' if self.current[0] != m: raise error.ParseError(_("unexpected token: %s") % self.current[0], self.current[2]) self._advance() + def _parseoperand(self, bind, m=None): + 'gather right-hand-side operand until an end condition or binding met' + if m and self.current[0] == m: + expr = None + else: + expr = self._parse(bind) + if m: + self._match(m) + return expr def _parse(self, bind=0): token, value, pos = self._advance() - # handle prefix rules on current token - prefix = self._elements[token][1] - if not prefix: - raise error.ParseError(_("not a prefix: %s") % token, pos) - if len(prefix) == 1: - expr = (prefix[0], value) + # handle prefix rules on current token, take as primary if unambiguous + primary, prefix = self._elements[token][1:3] + if primary and not (prefix and self._hasnewterm()): + expr = (primary, value) + elif prefix: + expr = (prefix[0], self._parseoperand(*prefix[1:])) else: - if len(prefix) > 2 and prefix[2] == self.current[0]: - self._match(prefix[2], pos) - expr = (prefix[0], None) - else: - expr = (prefix[0], self._parse(prefix[1])) - if len(prefix) > 2: - self._match(prefix[2], pos) + raise error.ParseError(_("not a prefix: %s") % token, pos) # gather tokens until we meet a lower binding strength while bind < self._elements[self.current[0]][0]: token, value, pos = self._advance() - e = self._elements[token] - # check for suffix - next token isn't a valid prefix - if len(e) == 4 and not self._elements[self.current[0]][1]: - suffix = e[3] + # handle infix rules, take as suffix if unambiguous + infix, suffix = self._elements[token][3:] + if suffix and not (infix and self._hasnewterm()): expr = (suffix[0], expr) + elif infix: + expr = (infix[0], expr, self._parseoperand(*infix[1:])) else: - # handle infix rules - if len(e) < 3 or not e[2]: - raise error.ParseError(_("not an infix: %s") % token, pos) - infix = e[2] - if len(infix) == 3 and infix[2] == self.current[0]: - self._match(infix[2], pos) - expr = (infix[0], expr, (None)) - else: - expr = (infix[0], expr, self._parse(infix[1])) - if len(infix) == 3: - self._match(infix[2], pos) + raise error.ParseError(_("not an infix: %s") % token, pos) return expr - def parse(self, message, lookup=None): - 'generate a parse tree from a message' - if lookup: - self._iter = self._tokenizer(message, lookup) - else: - self._iter = self._tokenizer(message) + def parse(self, tokeniter): + 'generate a parse tree from tokens' + self._iter = tokeniter self._advance() res = self._parse() token, value, pos = self.current @@ -90,9 +81,133 @@ if not isinstance(tree, tuple): return tree return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]]) - def __call__(self, message): - 'parse a message into a parse tree and evaluate if methods given' - t = self.parse(message) + def __call__(self, tokeniter): + 'parse tokens into a parse tree and evaluate if methods given' + t = self.parse(tokeniter) if self._methods: return self.eval(t) return t + +def buildargsdict(trees, funcname, keys, keyvaluenode, keynode): + """Build dict from list containing positional and keyword arguments + + Invalid keywords or too many positional arguments are rejected, but + missing arguments are just omitted. + """ + if len(trees) > len(keys): + raise error.ParseError(_("%(func)s takes at most %(nargs)d arguments") + % {'func': funcname, 'nargs': len(keys)}) + args = {} + # consume positional arguments + for k, x in zip(keys, trees): + if x[0] == keyvaluenode: + break + args[k] = x + # remainder should be keyword arguments + for x in trees[len(args):]: + if x[0] != keyvaluenode or x[1][0] != keynode: + raise error.ParseError(_("%(func)s got an invalid argument") + % {'func': funcname}) + k = x[1][1] + if k not in keys: + raise error.ParseError(_("%(func)s got an unexpected keyword " + "argument '%(key)s'") + % {'func': funcname, 'key': k}) + if k in args: + raise error.ParseError(_("%(func)s got multiple values for keyword " + "argument '%(key)s'") + % {'func': funcname, 'key': k}) + args[k] = x[2] + return args + +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 501c51d60792 -r 96a38d44ba09 mercurial/parsers.c --- a/mercurial/parsers.c Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/parsers.c Sat Jul 18 17:32:38 2015 -0500 @@ -173,6 +173,28 @@ return _asciitransform(str_obj, uppertable, NULL); } +static inline PyObject *_dict_new_presized(Py_ssize_t expected_size) +{ + /* _PyDict_NewPresized expects a minused parameter, but it actually + creates a dictionary that's the nearest power of two bigger than the + parameter. For example, with the initial minused = 1000, the + dictionary created has size 1024. Of course in a lot of cases that + can be greater than the maximum load factor Python's dict object + expects (= 2/3), so as soon as we cross the threshold we'll resize + anyway. So create a dictionary that's at least 3/2 the size. */ + return _PyDict_NewPresized(((1 + expected_size) / 2) * 3); +} + +static PyObject *dict_new_presized(PyObject *self, PyObject *args) +{ + Py_ssize_t expected_size; + + if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) + return NULL; + + return _dict_new_presized(expected_size); +} + static PyObject *make_file_foldmap(PyObject *self, PyObject *args) { PyObject *dmap, *spec_obj, *normcase_fallback; @@ -205,20 +227,9 @@ goto quit; } -#if PY_VERSION_HEX >= 0x02060000 - /* _PyDict_NewPresized expects a minused parameter, but it actually - creates a dictionary that's the nearest power of two bigger than the - parameter. For example, with the initial minused = 1000, the - dictionary created has size 1024. Of course in a lot of cases that - can be greater than the maximum load factor Python's dict object - expects (= 2/3), so as soon as we cross the threshold we'll resize - anyway. So create a dictionary that's 3/2 the size. Also add some - more to deal with additions outside this function. */ - file_foldmap = _PyDict_NewPresized((PyDict_Size(dmap) / 5) * 8); -#else - file_foldmap = PyDict_New(); -#endif - + /* Add some more entries to deal with additions outside this + function. */ + file_foldmap = _dict_new_presized((PyDict_Size(dmap) / 10) * 11); if (file_foldmap == NULL) goto quit; @@ -741,6 +752,29 @@ return PyString_AS_STRING(self->data) + pos * v1_hdrsize; } +static inline int index_get_parents(indexObject *self, Py_ssize_t rev, + int *ps, int maxrev) +{ + 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); + } + /* If index file is corrupted, ps[] may point to invalid revisions. So + * there is a risk of buffer overflow to trust them unconditionally. */ + if (ps[0] > maxrev || ps[1] > maxrev) { + PyErr_SetString(PyExc_ValueError, "parent out of range"); + return -1; + } + return 0; +} + + /* * RevlogNG format (all in big endian, data may be inlined): * 6 bytes: offset @@ -1071,23 +1105,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 +1132,72 @@ /* 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++) { + if (index_get_parents(self, i, parents, len - 1) < 0) + goto release_phasesetlist; + 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 +1240,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 +1256,12 @@ 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"); + if (index_get_parents(self, i, parents, len - 1) < 0) goto bail; - } - - isfiltered = check_filter(filter, i); - if (isfiltered == -1) { - PyErr_SetString(PyExc_TypeError, - "unable to check filter"); - goto bail; + for (j = 0; j < 2; j++) { + if (parents[j] >= 0) + nothead[parents[j]] = 1; } - - 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++) { @@ -1647,20 +1664,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; /* @@ -1722,7 +1725,8 @@ } } } - index_get_parents(self, v, parents); + if (index_get_parents(self, v, parents, maxrev) < 0) + goto bail; for (i = 0; i < 2; i++) { int p = parents[i]; @@ -1819,7 +1823,8 @@ continue; sv = seen[v]; - index_get_parents(self, v, parents); + if (index_get_parents(self, v, parents, maxrev) < 0) + goto bail; for (i = 0; i < 2; i++) { int p = parents[i]; @@ -2271,8 +2276,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, @@ -2540,6 +2545,8 @@ {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"}, {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"}, {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"}, + {"dict_new_presized", dict_new_presized, METH_VARARGS, + "construct a dict with an expected size\n"}, {"make_file_foldmap", make_file_foldmap, METH_VARARGS, "make file foldmap\n"}, {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"}, diff -r 501c51d60792 -r 96a38d44ba09 mercurial/patch.py --- a/mercurial/patch.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/patch.py Sat Jul 18 17:32:38 2015 -0500 @@ -6,16 +6,12 @@ # 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 -# load. This was not a problem on Python 2.7. -import email.Generator -import email.Parser from i18n import _ from node import hex, short -import cStringIO import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error import pathutil @@ -292,8 +288,8 @@ self.binary = False def setmode(self, mode): - islink = mode & 020000 - isexec = mode & 0100 + islink = mode & 0o20000 + isexec = mode & 0o100 self.mode = (islink, isexec) def copy(self): @@ -434,13 +430,13 @@ isexec = False try: - isexec = self.opener.lstat(fname).st_mode & 0100 != 0 - except OSError, e: + isexec = self.opener.lstat(fname).st_mode & 0o100 != 0 + except OSError as e: if e.errno != errno.ENOENT: raise try: return (self.opener.read(fname), (False, isexec)) - except IOError, e: + except IOError as e: if e.errno != errno.ENOENT: raise return None, None @@ -773,7 +769,7 @@ for x, s in enumerate(self.lines): self.hash.setdefault(s, []).append(x) - for fuzzlen in xrange(3): + for fuzzlen in xrange(self.ui.configint("patch", "fuzz", 2) + 1): for toponly in [True, False]: old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly) oldstart = oldstart + self.offset + self.skew @@ -830,7 +826,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 +849,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 +868,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 +881,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 +944,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 @@ -1021,9 +1019,11 @@ f.close() # Start the editor and wait for it to complete editor = ui.geteditor() - ui.system("%s \"%s\"" % (editor, patchfn), - environ={'HGUSER': ui.username()}, - onerr=util.Abort, errprefix=_("edit failed")) + ret = ui.system("%s \"%s\"" % (editor, patchfn), + environ={'HGUSER': ui.username()}) + if ret != 0: + ui.warn(_("editor exited with exit code %d\n") % ret) + continue # Remove comment lines patchfp = open(patchfn) ncpatchfp = cStringIO.StringIO() @@ -1363,7 +1363,7 @@ l = ord(l) - ord('a') + 27 try: dec.append(base85.b85decode(line[1:])[:l]) - except ValueError, e: + except ValueError as e: raise PatchError(_('could not decode "%s" binary patch: %s') % (self._fname, str(e))) line = getline(lr, self.hunk) @@ -1383,6 +1383,77 @@ return s return s[:i] +def reversehunks(hunks): + '''reverse the signs in the hunks given as argument + + This function operates on hunks coming out of patch.filterpatch, that is + a list of the form: [header1, hunk1, hunk2, header2...]. Example usage: + + >>> rawpatch = """diff --git a/folder1/g b/folder1/g + ... --- a/folder1/g + ... +++ b/folder1/g + ... @@ -1,7 +1,7 @@ + ... +firstline + ... c + ... 1 + ... 2 + ... + 3 + ... -4 + ... 5 + ... d + ... +lastline""" + >>> hunks = parsepatch(rawpatch) + >>> hunkscomingfromfilterpatch = [] + >>> for h in hunks: + ... hunkscomingfromfilterpatch.append(h) + ... hunkscomingfromfilterpatch.extend(h.hunks) + + >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch) + >>> fp = cStringIO.StringIO() + >>> for c in reversedhunks: + ... c.write(fp) + >>> fp.seek(0) + >>> reversedpatch = fp.read() + >>> print reversedpatch + diff --git a/folder1/g b/folder1/g + --- a/folder1/g + +++ b/folder1/g + @@ -1,4 +1,3 @@ + -firstline + c + 1 + 2 + @@ -1,6 +2,6 @@ + c + 1 + 2 + - 3 + +4 + 5 + d + @@ -5,3 +6,2 @@ + 5 + d + -lastline + + ''' + + import crecord as crecordmod + newhunks = [] + for c in hunks: + if isinstance(c, crecordmod.uihunk): + # curses hunks encapsulate the record hunk in _hunk + c = c._hunk + if isinstance(c, recordhunk): + for j, line in enumerate(c.hunk): + if line.startswith("-"): + c.hunk[j] = "+" + c.hunk[j][1:] + elif line.startswith("+"): + c.hunk[j] = "-" + c.hunk[j][1:] + c.added, c.removed = c.removed, c.added + newhunks.append(c) + return newhunks + def parsepatch(originalchunks): """patch -> [] of headers -> [] of hunks """ class parser(object): @@ -1867,7 +1938,7 @@ try: current_file = patcher(ui, gp, backend, store, eolmode=eolmode) - except PatchError, inst: + except PatchError as inst: ui.warn(str(inst) + '\n') current_file = None rejects += 1 @@ -2102,7 +2173,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 501c51d60792 -r 96a38d44ba09 mercurial/pathutil.py --- a/mercurial/pathutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/pathutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -1,4 +1,4 @@ -import os, errno, stat +import os, errno, stat, posixpath import encoding import util @@ -76,7 +76,7 @@ curpath = os.path.join(self.root, prefix) try: st = os.lstat(curpath) - except OSError, err: + except OSError as err: # EINVAL can be raised as invalid path syntax under win32. # They must be ignored for patterns can be checked too. if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL): @@ -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 501c51d60792 -r 96a38d44ba09 mercurial/phases.py --- a/mercurial/phases.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/phases.py Sat Jul 18 17:32:38 2015 -0500 @@ -129,7 +129,7 @@ if 'HG_PENDING' in os.environ: try: f = repo.svfs('phaseroots.pending') - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise if f is None: @@ -140,7 +140,7 @@ roots[int(phase)].add(bin(nh)) finally: f.close() - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise if phasedefaults: @@ -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 @@ -166,10 +167,12 @@ ph.dirty = self.dirty ph.opener = self.opener ph._phaserevs = self._phaserevs + ph._phasesets = self._phasesets return ph def replace(self, phcache): - for a in 'phaseroots dirty opener _phaserevs'.split(): + """replace all values in 'self' with content of phcache""" + for a in ('phaseroots', 'dirty', 'opener', '_phaserevs', '_phasesets'): setattr(self, a, getattr(phcache, a)) def _getphaserevsnative(self, repo): @@ -192,20 +195,22 @@ for rev in repo.changelog.descendants(roots): revs[rev] = phase - def getphaserevs(self, repo): + def loadphaserevs(self, repo): + """ensure phase information is loaded in the object""" if self._phaserevs is None: try: if repo.ui.configbool('experimental', '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 def invalidate(self): self._phaserevs = None + self._phasesets = None def _populatephaseroots(self, repo): """Fills the _phaserevs cache with phases for the roots. @@ -229,7 +234,7 @@ raise ValueError(_('cannot lookup negative revision')) if self._phaserevs is None or rev >= len(self._phaserevs): self.invalidate() - self._phaserevs = self.getphaserevs(repo) + self.loadphaserevs(repo) return self._phaserevs[rev] def write(self): @@ -355,7 +360,7 @@ for root in repo._phasecache.phaseroots[draft]: keys[hex(root)] = value - if repo.ui.configbool('phases', 'publish', True): + if repo.publishing(): # Add an extra data to let remote know we are a publishing # repo. Publishing repo can't just pretend they are old repo. # When pushing to a publishing repo, the client still need to diff -r 501c51d60792 -r 96a38d44ba09 mercurial/posix.py --- a/mercurial/posix.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/posix.py Sat Jul 18 17:32:38 2015 -0500 @@ -8,6 +8,7 @@ from i18n import _ import encoding import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata +import select import fcntl, re posixfile = open @@ -70,7 +71,7 @@ def isexec(f): """check whether a file is executable""" - return (os.lstat(f).st_mode & 0100 != 0) + return (os.lstat(f).st_mode & 0o100 != 0) def setflags(f, l, x): s = os.lstat(f).st_mode @@ -97,30 +98,30 @@ fp = open(f, "w") fp.write(data) fp.close() - s = 0666 & ~umask # avoid restatting for chmod + s = 0o666 & ~umask # avoid restatting for chmod - sx = s & 0100 + sx = s & 0o100 if x and not sx: # Turn on +x for every +r bit when making a file executable # and obey umask. - os.chmod(f, s | (s & 0444) >> 2 & ~umask) + os.chmod(f, s | (s & 0o444) >> 2 & ~umask) elif not x and sx: # Turn off all +x bits - os.chmod(f, s & 0666) + os.chmod(f, s & 0o666) def copymode(src, dst, mode=None): '''Copy the file mode from the file at path src to dst. If src doesn't exist, we're using mode instead. If mode is None, we're using umask.''' try: - st_mode = os.lstat(src).st_mode & 0777 - except OSError, inst: + st_mode = os.lstat(src).st_mode & 0o777 + except OSError as inst: if inst.errno != errno.ENOENT: raise st_mode = mode if st_mode is None: st_mode = ~umask - st_mode &= 0666 + st_mode &= 0o666 os.chmod(dst, st_mode) def checkexec(path): @@ -139,10 +140,10 @@ fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-') try: os.close(fh) - m = os.stat(fn).st_mode & 0777 + m = os.stat(fn).st_mode & 0o777 new_file_has_exec = m & EXECFLAGS os.chmod(fn, m ^ EXECFLAGS) - exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m) + exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m) finally: os.unlink(fn) except (IOError, OSError): @@ -165,7 +166,7 @@ fd.close() except AttributeError: return False - except OSError, inst: + except OSError as inst: # sshfs might report failure while successfully creating the link if inst[0] == errno.EIO and os.path.exists(name): os.unlink(name) @@ -354,7 +355,7 @@ try: os.kill(pid, 0) return True - except OSError, inst: + except OSError as inst: return inst.errno != errno.ESRCH def explainexit(code): @@ -409,7 +410,7 @@ st = lstat(nf) if getkind(st.st_mode) not in _wantedkinds: st = None - except OSError, err: + except OSError as err: if err.errno not in (errno.ENOENT, errno.ENOTDIR): raise st = None @@ -476,7 +477,7 @@ pass except ValueError: pass - except IOError, e: + except IOError as e: if e[0] == errno.EINVAL: pass else: @@ -492,7 +493,7 @@ """unlink and remove the directory if it is empty""" try: os.unlink(f) - except OSError, e: + except OSError as e: if not (ignoremissing and e.errno == errno.ENOENT): raise # try removing directories that might now be empty @@ -559,7 +560,7 @@ os.unlink(self.path) try: self.bind(self.realpath) - except socket.error, err: + except socket.error as err: if err.args[0] == 'AF_UNIX path too long': tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem) self.realpath = os.path.join(tmpdir, sockname) @@ -577,7 +578,7 @@ def okayifmissing(f, path): try: f(path) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise @@ -592,7 +593,20 @@ def statisexec(st): '''check whether a stat result is an executable file''' - return st and (st.st_mode & 0100 != 0) + return st and (st.st_mode & 0o100 != 0) + +def poll(fds): + """block until something happens on any file descriptor + + This is a generic helper that will check for any activity + (read, write. exception) and return the list of touched files. + + In unsupported cases, it will raise a NotImplementedError""" + try: + res = select.select(fds, fds, fds) + except ValueError: # out of range file descriptor + raise NotImplementedError() + return sorted(list(set(sum(res, [])))) def readpipe(pipe): """Read all available data from a pipe.""" diff -r 501c51d60792 -r 96a38d44ba09 mercurial/progress.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/progress.py Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,252 @@ +# progress.py progress bars related code +# +# Copyright (C) 2010 Augie Fackler +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +import sys +import time +import threading +from mercurial import encoding + +from mercurial.i18n import _ + + +def spacejoin(*args): + return ' '.join(s for s in args if s) + +def shouldprint(ui): + return not (ui.quiet or ui.plain()) and ( + ui._isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')) + +def fmtremaining(seconds): + """format a number of remaining seconds in humain readable way + + This will properly display seconds, minutes, hours, days if needed""" + if seconds < 60: + # i18n: format XX seconds as "XXs" + return _("%02ds") % (seconds) + minutes = seconds // 60 + if minutes < 60: + seconds -= minutes * 60 + # i18n: format X minutes and YY seconds as "XmYYs" + return _("%dm%02ds") % (minutes, seconds) + # we're going to ignore seconds in this case + minutes += 1 + hours = minutes // 60 + minutes -= hours * 60 + if hours < 30: + # i18n: format X hours and YY minutes as "XhYYm" + return _("%dh%02dm") % (hours, minutes) + # we're going to ignore minutes in this case + hours += 1 + days = hours // 24 + hours -= days * 24 + if days < 15: + # i18n: format X days and YY hours as "XdYYh" + return _("%dd%02dh") % (days, hours) + # we're going to ignore hours in this case + days += 1 + weeks = days // 7 + days -= weeks * 7 + if weeks < 55: + # i18n: format X weeks and YY days as "XwYYd" + return _("%dw%02dd") % (weeks, days) + # we're going to ignore days and treat a year as 52 weeks + weeks += 1 + years = weeks // 52 + weeks -= years * 52 + # i18n: format X years and YY weeks as "XyYYw" + return _("%dy%02dw") % (years, weeks) + +class progbar(object): + def __init__(self, ui): + self.ui = ui + self._refreshlock = threading.Lock() + self.resetstate() + + def resetstate(self): + self.topics = [] + self.topicstates = {} + self.starttimes = {} + self.startvals = {} + self.printed = False + self.lastprint = time.time() + float(self.ui.config( + 'progress', 'delay', default=3)) + self.curtopic = None + self.lasttopic = None + self.indetcount = 0 + self.refresh = float(self.ui.config( + 'progress', 'refresh', default=0.1)) + self.changedelay = max(3 * self.refresh, + float(self.ui.config( + 'progress', 'changedelay', default=1))) + self.order = self.ui.configlist( + 'progress', 'format', + default=['topic', 'bar', 'number', 'estimate']) + + def show(self, now, topic, pos, item, unit, total): + if not shouldprint(self.ui): + return + termwidth = self.width() + self.printed = True + head = '' + needprogress = False + tail = '' + for indicator in self.order: + add = '' + if indicator == 'topic': + add = topic + elif indicator == 'number': + if total: + add = ('% ' + str(len(str(total))) + + 's/%s') % (pos, total) + else: + add = str(pos) + elif indicator.startswith('item') and item: + slice = 'end' + if '-' in indicator: + wid = int(indicator.split('-')[1]) + elif '+' in indicator: + slice = 'beginning' + wid = int(indicator.split('+')[1]) + else: + wid = 20 + if slice == 'end': + add = encoding.trim(item, wid, leftside=True) + else: + add = encoding.trim(item, wid) + add += (wid - encoding.colwidth(add)) * ' ' + elif indicator == 'bar': + add = '' + needprogress = True + elif indicator == 'unit' and unit: + add = unit + elif indicator == 'estimate': + add = self.estimate(topic, pos, total, now) + elif indicator == 'speed': + add = self.speed(topic, pos, unit, now) + if not needprogress: + head = spacejoin(head, add) + else: + tail = spacejoin(tail, add) + if needprogress: + used = 0 + if head: + used += encoding.colwidth(head) + 1 + if tail: + used += encoding.colwidth(tail) + 1 + progwidth = termwidth - used - 3 + if total and pos <= total: + amt = pos * progwidth // total + bar = '=' * (amt - 1) + if amt > 0: + bar += '>' + bar += ' ' * (progwidth - amt) + else: + progwidth -= 3 + self.indetcount += 1 + # mod the count by twice the width so we can make the + # cursor bounce between the right and left sides + amt = self.indetcount % (2 * progwidth) + amt -= progwidth + bar = (' ' * int(progwidth - abs(amt)) + '<=>' + + ' ' * int(abs(amt))) + prog = ''.join(('[', bar , ']')) + out = spacejoin(head, prog, tail) + else: + out = spacejoin(head, tail) + sys.stderr.write('\r' + encoding.trim(out, termwidth)) + self.lasttopic = topic + sys.stderr.flush() + + def clear(self): + if not shouldprint(self.ui): + return + sys.stderr.write('\r%s\r' % (' ' * self.width())) + + def complete(self): + if not shouldprint(self.ui): + return + if self.ui.configbool('progress', 'clear-complete', default=True): + self.clear() + else: + sys.stderr.write('\n') + sys.stderr.flush() + + def width(self): + tw = self.ui.termwidth() + return min(int(self.ui.config('progress', 'width', default=tw)), tw) + + def estimate(self, topic, pos, total, now): + if total is None: + return '' + initialpos = self.startvals[topic] + target = total - initialpos + delta = pos - initialpos + if delta > 0: + elapsed = now - self.starttimes[topic] + # experimental config: progress.estimate + if elapsed > float( + self.ui.config('progress', 'estimate', default=2)): + seconds = (elapsed * (target - delta)) // delta + 1 + return fmtremaining(seconds) + return '' + + def speed(self, topic, pos, unit, now): + initialpos = self.startvals[topic] + delta = pos - initialpos + elapsed = now - self.starttimes[topic] + if elapsed > float( + self.ui.config('progress', 'estimate', default=2)): + return _('%d %s/sec') % (delta / elapsed, unit) + return '' + + def _oktoprint(self, now): + '''Check if conditions are met to print - e.g. changedelay elapsed''' + if (self.lasttopic is None # first time we printed + # not a topic change + or self.curtopic == self.lasttopic + # it's been long enough we should print anyway + or now - self.lastprint >= self.changedelay): + return True + else: + return False + + def progress(self, topic, pos, item='', unit='', total=None): + now = time.time() + self._refreshlock.acquire() + try: + if pos is None: + self.starttimes.pop(topic, None) + self.startvals.pop(topic, None) + self.topicstates.pop(topic, None) + # reset the progress bar if this is the outermost topic + if self.topics and self.topics[0] == topic and self.printed: + self.complete() + self.resetstate() + # truncate the list of topics assuming all topics within + # this one are also closed + if topic in self.topics: + self.topics = self.topics[:self.topics.index(topic)] + # reset the last topic to the one we just unwound to, + # so that higher-level topics will be stickier than + # lower-level topics + if self.topics: + self.lasttopic = self.topics[-1] + else: + self.lasttopic = None + else: + if topic not in self.topics: + self.starttimes[topic] = now + self.startvals[topic] = pos + self.topics.append(topic) + self.topicstates[topic] = pos, item, unit, total + self.curtopic = topic + if now - self.lastprint >= self.refresh and self.topics: + if self._oktoprint(now): + self.lastprint = now + self.show(now, topic, *self.topicstates[topic]) + finally: + self._refreshlock.release() diff -r 501c51d60792 -r 96a38d44ba09 mercurial/pure/osutil.py --- a/mercurial/pure/osutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/pure/osutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -59,7 +59,6 @@ posixfile = open else: import ctypes, msvcrt - from errno import ESRCH, ENOENT _kernel32 = ctypes.windll.kernel32 @@ -99,14 +98,7 @@ def _raiseioerror(name): err = ctypes.WinError() - # For python 2.4, treat ESRCH as ENOENT like WindowsError does - # in python 2.5 or later. - # py24: WindowsError(3, '').errno => 3 - # py25 or later: WindowsError(3, '').errno => 2 - errno = err.errno - if errno == ESRCH: - errno = ENOENT - raise IOError(errno, '%s: %s' % (name, err.strerror)) + raise IOError(err.errno, '%s: %s' % (name, err.strerror)) class posixfile(object): '''a file object aiming for POSIX-like semantics diff -r 501c51d60792 -r 96a38d44ba09 mercurial/repair.py --- a/mercurial/repair.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/repair.py Sat Jul 18 17:32:38 2015 -0500 @@ -7,13 +7,13 @@ # 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 def _bundle(repo, bases, heads, node, suffix, compress=True): """create a bundle with the specified revisions as a backup""" - usebundle2 = (repo.ui.config('experimental', 'bundle2-exp') and + usebundle2 = (repo.ui.configbool('experimental', 'bundle2-exp', True) and repo.ui.config('experimental', 'strip-bundle2-version')) if usebundle2: cgversion = repo.ui.config('experimental', 'strip-bundle2-version') @@ -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) @@ -201,7 +205,7 @@ for undovfs, undofile in repo.undofiles(): try: undovfs.unlink(undofile) - except OSError, e: + except OSError as e: if e.errno != errno.ENOENT: ui.warn(_('error removing %s: %s\n') % (undovfs.join(undofile), str(e))) @@ -223,3 +227,72 @@ vfs.unlink(chgrpfile) repo.destroyed() + +def rebuildfncache(ui, repo): + """Rebuilds the fncache file from repo history. + + Missing entries will be added. Extra entries will be removed. + """ + repo = repo.unfiltered() + + if 'fncache' not in repo.requirements: + ui.warn(_('(not rebuilding fncache because repository does not ' + 'support fncache\n')) + return + + lock = repo.lock() + try: + fnc = repo.store.fncache + # Trigger load of fncache. + if 'irrelevant' in fnc: + pass + + oldentries = set(fnc.entries) + newentries = set() + seenfiles = set() + + repolen = len(repo) + for rev in repo: + ui.progress(_('changeset'), rev, total=repolen) + + ctx = repo[rev] + for f in ctx.files(): + # This is to minimize I/O. + if f in seenfiles: + continue + seenfiles.add(f) + + i = 'data/%s.i' % f + d = 'data/%s.d' % f + + if repo.store._exists(i): + newentries.add(i) + if repo.store._exists(d): + newentries.add(d) + + ui.progress(_('changeset'), None) + + addcount = len(newentries - oldentries) + removecount = len(oldentries - newentries) + for p in sorted(oldentries - newentries): + ui.write(_('removing %s\n') % p) + for p in sorted(newentries - oldentries): + ui.write(_('adding %s\n') % p) + + if addcount or removecount: + ui.write(_('%d items added, %d removed from fncache\n') % + (addcount, removecount)) + fnc.entries = newentries + fnc._dirty = True + + tr = repo.transaction('fncache') + try: + fnc.write(tr) + tr.close() + finally: + tr.release() + else: + ui.write(_('fncache already up to date\n')) + finally: + lock.release() + diff -r 501c51d60792 -r 96a38d44ba09 mercurial/repoview.py --- a/mercurial/repoview.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/repoview.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/revlog.py --- a/mercurial/revlog.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/revlog.py Sat Jul 18 17:32:38 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 @@ -88,7 +89,7 @@ if t == 'x': try: return _decompress(bin) - except zlib.error, e: + except zlib.error as e: raise RevlogError(_("revlog decompress error: %s") % str(e)) if t == 'u': return bin[1:] @@ -152,6 +153,10 @@ ngshaoffset = 32 versionformat = ">I" +# corresponds to uncompressed length of indexformatng (2 gigs, 4-byte +# signed integer) +_maxentrysize = 0x7fffffff + class revlogio(object): def __init__(self): self.size = struct.calcsize(indexformatng) @@ -241,7 +246,7 @@ if len(i) > 0: v = struct.unpack(versionformat, i[:4])[0] self._initempty = False - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise @@ -485,7 +490,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: @@ -725,7 +730,7 @@ return self._headrevs() def computephases(self, roots): - return self.index.computephases(roots) + return self.index.computephasesmapsets(roots) def _headrevs(self): count = len(self) @@ -1179,6 +1184,12 @@ if link == nullrev: raise RevlogError(_("attempted to add linkrev -1 to %s") % self.indexfile) + + if len(text) > _maxentrysize: + raise RevlogError( + _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB") + % (self.indexfile, len(text))) + node = node or self.hash(text, p1, p2) if node in self.nodemap: return node @@ -1368,13 +1379,16 @@ ifh.write(data[1]) self.checkinlinesize(transaction, ifh) - def addgroup(self, bundle, linkmapper, transaction): + def addgroup(self, bundle, linkmapper, transaction, addrevisioncb=None): """ add a delta group given a set of deltas, add them to the revision log. the first delta is against its parent, which should be in our log, the rest are against the previous delta. + + If ``addrevisioncb`` is defined, it will be called with arguments of + this revlog and the node that was added. """ # track the base of the current delta log @@ -1448,6 +1462,14 @@ chain = self._addrevision(node, None, transaction, link, p1, p2, flags, (baserev, delta), ifh, dfh) + + if addrevisioncb: + # Data for added revision can't be read unless flushed + # because _loadchunk always opensa new file handle and + # there is no guarantee data was actually written yet. + flush() + addrevisioncb(self, chain) + if not dfh and not self._inline: # addrevision switched from inline to conventional # reopen the index @@ -1560,7 +1582,7 @@ actual = f.tell() f.close() dd = actual - expected - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise dd = 0 @@ -1579,7 +1601,7 @@ databytes += max(0, self.length(r)) dd = 0 di = actual - len(self) * s - databytes - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise di = 0 diff -r 501c51d60792 -r 96a38d44ba09 mercurial/revset.py --- a/mercurial/revset.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/revset.py Sat Jul 18 17:32:38 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]: @@ -59,6 +58,8 @@ def iterate(): cl = repo.changelog + # XXX this should be 'parentset.min()' assuming 'parentset' is a + # smartset (and if it is not, it should.) first = min(revs) nullrev = node.nullrev if first == nullrev: @@ -86,51 +87,59 @@ visit = list(heads) reachable = set() seen = {} + # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset + # (and if it is not, it should.) minroot = min(roots) roots = set(roots) + # prefetch all the things! (because python is slow) + reached = reachable.add + dovisit = visit.append + nextvisit = visit.pop # open-code the post-order traversal due to the tiny size of # sys.getrecursionlimit() while visit: - rev = visit.pop() + rev = nextvisit() if rev in roots: - reachable.add(rev) + reached(rev) parents = parentrevs(rev) seen[rev] = parents for parent in parents: if parent >= minroot and parent not in seen: - visit.append(parent) + dovisit(parent) if not reachable: return baseset() for rev in sorted(seen): for parent in seen[rev]: if parent in reachable: - reachable.add(rev) + reached(rev) return baseset(sorted(reachable)) elements = { - "(": (21, ("group", 1, ")"), ("func", 1, ")")), - "##": (20, None, ("_concat", 20)), - "~": (18, None, ("ancestor", 18)), - "^": (18, None, ("parent", 18), ("parentpost", 18)), - "-": (5, ("negate", 19), ("minus", 5)), - "::": (17, ("dagrangepre", 17), ("dagrange", 17), + # token-type: binding-strength, primary, prefix, infix, suffix + "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None), + "##": (20, None, None, ("_concat", 20), None), + "~": (18, None, None, ("ancestor", 18), None), + "^": (18, None, None, ("parent", 18), ("parentpost", 18)), + "-": (5, None, ("negate", 19), ("minus", 5), None), + "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), ("dagrangepost", 17)), - "..": (17, ("dagrangepre", 17), ("dagrange", 17), + "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), ("dagrangepost", 17)), - ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)), - "not": (10, ("not", 10)), - "!": (10, ("not", 10)), - "and": (5, None, ("and", 5)), - "&": (5, None, ("and", 5)), - "%": (5, None, ("only", 5), ("onlypost", 5)), - "or": (4, None, ("or", 4)), - "|": (4, None, ("or", 4)), - "+": (4, None, ("or", 4)), - ",": (2, None, ("list", 2)), - ")": (0, None, None), - "symbol": (0, ("symbol",), None), - "string": (0, ("string",), None), - "end": (0, None, None), + ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)), + "not": (10, None, ("not", 10), None, None), + "!": (10, None, ("not", 10), None, None), + "and": (5, None, None, ("and", 5), None), + "&": (5, None, None, ("and", 5), None), + "%": (5, None, None, ("only", 5), ("onlypost", 5)), + "or": (4, None, None, ("or", 4), None), + "|": (4, None, None, ("or", 4), None), + "+": (4, None, None, ("or", 4), None), + "=": (3, None, None, ("keyvalue", 3), None), + ",": (2, None, None, ("list", 2), None), + ")": (0, None, None, None, None), + "symbol": (0, "symbol", None, None, None), + "string": (0, "string", None, None, None), + "end": (0, None, None, None, None), } keywords = set(['and', 'or', 'not']) @@ -183,7 +192,7 @@ elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully yield ('##', None, pos) pos += 1 # skip ahead - elif c in "():,-|&+!~^%": # handle simple operators + elif c in "():=,-|&+!~^%": # handle simple operators yield (c, None, pos) elif (c in '"\'' or c == 'r' and program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings @@ -274,6 +283,10 @@ raise error.ParseError(err) return l +def getargsdict(x, funcname, keys): + return parser.buildargsdict(getlist(x), funcname, keys.split(), + keyvaluenode='keyvalue', keynode='symbol') + def isvalidsymbol(tree): """Examine whether specified ``tree`` is valid ``symbol`` or not """ @@ -314,6 +327,13 @@ s = methods[x[0]](repo, subset, *x[1:]) if util.safehasattr(s, 'isascending'): return s + if (repo.ui.configbool('devel', 'all-warnings') + or repo.ui.configbool('devel', 'old-revset')): + # else case should not happen, because all non-func are internal, + # ignoring for now. + if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols: + repo.ui.develwarn('revset "%s" use list instead of smartset, ' + '(upgrade your code)' % x[1][1]) return baseset(s) def _getrevsource(repo, r): @@ -335,11 +355,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) @@ -348,24 +363,36 @@ return baseset() m, n = m.first(), n.last() - if m < n: + if m == n: + r = baseset([m]) + elif n == node.wdirrev: + r = spanset(repo, m, len(repo)) + baseset([n]) + elif m == node.wdirrev: + r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1) + elif m < n: r = spanset(repo, m, n + 1) else: r = spanset(repo, m, n - 1) + # XXX We should combine with subset first: 'subset & baseset(...)'. This is + # necessary to ensure we preserve the order in subset. + # + # This has performance implication, carrying the sorting over when possible + # would be more efficient. return r & subset def dagrange(repo, subset, x, y): r = fullreposet(repo) xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y)) + # XXX We should combine with subset first: 'subset & baseset(...)'. This is + # necessary to ensure we preserve the order in subset. return xs & subset 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) @@ -373,10 +400,17 @@ def listset(repo, subset, a, b): raise error.ParseError(_("can't use a list in this context")) +def keyvaluepair(repo, subset, k, v): + raise error.ParseError(_("can't use a key-value pair in this context")) + def func(repo, subset, a, b): if a[0] == 'symbol' and a[1] in symbols: return symbols[a[1]](repo, subset, b) - raise error.UnknownIdentifier(a[1], symbols.keys()) + + keep = lambda fn: getattr(fn, '__doc__', None) is not None + + syms = [s for (s, fn) in symbols.items() if keep(fn)] + raise error.UnknownIdentifier(a[1], syms) # functions @@ -610,17 +644,19 @@ return subset.filter(matches) def _children(repo, narrow, parentset): + if not parentset: + return baseset() cs = set() - if not parentset: - return baseset(cs) pr = repo.changelog.parentrevs - minrev = min(parentset) + minrev = parentset.min() for r in narrow: if r <= minrev: continue for p in pr(r): if p in parentset: cs.add(r) + # XXX using a set to feed the baseset is wrong. Sets are not ordered. + # This does not break because of other fullreposet misbehavior. return baseset(cs) def children(repo, subset, x): @@ -793,16 +829,6 @@ divergent = obsmod.getrevs(repo, 'divergent') return subset & divergent -def draft(repo, subset, x): - """``draft()`` - Changeset in draft phase.""" - # i18n: "draft" is a keyword - getargs(x, 0, 0, _("draft takes no arguments")) - phase = repo._phasecache.phase - target = phases.draft - condition = lambda r: phase(repo, r) == target - return subset.filter(condition, cache=False) - def extinct(repo, subset, x): """``extinct()`` Obsolete changesets with obsolete descendants only. @@ -821,16 +847,19 @@ a regular expression. To match a value that actually starts with `re:`, use the prefix `literal:`. """ - - # i18n: "extra" is a keyword - l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments')) + args = getargsdict(x, 'extra', 'label value') + if 'label' not in args: + # i18n: "extra" is a keyword + raise error.ParseError(_('extra takes at least 1 argument')) # i18n: "extra" is a keyword - label = getstring(l[0], _('first argument to extra must be a string')) + label = getstring(args['label'], _('first argument to extra must be ' + 'a string')) value = None - if len(l) > 1: + if 'value' in args: # i18n: "extra" is a keyword - value = getstring(l[1], _('second argument to extra must be a string')) + value = getstring(args['value'], _('second argument to extra must be ' + 'a string')) kind, value, matcher = _stringmatcher(value) def _matchvalue(r): @@ -1008,7 +1037,7 @@ try: # i18n: "grep" is a keyword gr = re.compile(getstring(x, _("grep requires a string"))) - except re.error, e: + except re.error as e: raise error.ParseError(_('invalid match pattern: %s') % e) def matches(x): @@ -1097,9 +1126,14 @@ # i18n: "head" is a keyword getargs(x, 0, 0, _("head takes no arguments")) hs = set() + cl = repo.changelog for b, ls in repo.branchmap().iteritems(): - hs.update(repo[h].rev() for h in ls) - return baseset(hs).filter(subset.__contains__) + hs.update(cl.rev(h) for h in ls) + # XXX using a set to feed the baseset is wrong. Sets are not ordered. + # This does not break because of other fullreposet misbehavior. + # XXX We should combine with subset first: 'subset & baseset(...)'. This is + # necessary to ensure we preserve the order in subset. + return baseset(hs) & subset def heads(repo, subset, x): """``heads(set)`` @@ -1128,8 +1162,8 @@ def matches(r): c = repo[r] - return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(), - c.description()]) + return any(kw in encoding.lower(t) + for t in c.files() + [c.user(), c.description()]) return subset.filter(matches) @@ -1152,12 +1186,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 +1213,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): @@ -1217,6 +1249,8 @@ cl = repo.changelog if not subset: return baseset() + # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset + # (and if it is not, it should.) baserev = min(subset) parentscount = [0]*(len(repo) - baserev) for r in cl.revs(start=baserev + 1): @@ -1340,6 +1374,8 @@ exclude = getset(repo, fullreposet(repo), args[1]) results = set(cl.findmissingrevs(common=exclude, heads=include)) + # XXX we should turn this into a baseset instead of a set, smartset may do + # some optimisations from the fact this is a baseset. return subset & results def origin(repo, subset, x): @@ -1369,6 +1405,8 @@ o = set([_firstsrc(r) for r in dests]) o -= set([None]) + # XXX we should turn this into a baseset instead of a set, smartset may do + # some optimisations from the fact this is a baseset. return subset & o def outgoing(repo, subset, x): @@ -1411,6 +1449,8 @@ for r in getset(repo, fullreposet(repo), x): ps.add(cl.parentrevs(r)[0]) ps -= set([node.nullrev]) + # XXX we should turn this into a baseset instead of a set, smartset may do + # some optimisations from the fact this is a baseset. return subset & ps def p2(repo, subset, x): @@ -1432,6 +1472,8 @@ for r in getset(repo, fullreposet(repo), x): ps.add(cl.parentrevs(r)[1]) ps -= set([node.nullrev]) + # XXX we should turn this into a baseset instead of a set, smartset may do + # some optimisations from the fact this is a baseset. return subset & ps def parents(repo, subset, x): @@ -1443,11 +1485,45 @@ else: ps = set() cl = repo.changelog + up = ps.update + parentrevs = cl.parentrevs for r in getset(repo, fullreposet(repo), x): - ps.update(cl.parentrevs(r)) + if r == node.wdirrev: + up(p.rev() for p in repo[r].parents()) + else: + up(parentrevs(r)) ps -= set([node.nullrev]) return subset & ps +def _phase(repo, subset, target): + """helper to select all rev in phase """ + repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded + if repo._phasecache._phasesets: + s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs + s = baseset(s) + s.sort() # set are non ordered, so we enforce ascending + return subset & s + else: + phase = repo._phasecache.phase + condition = lambda r: phase(repo, r) == target + return subset.filter(condition, cache=False) + +def draft(repo, subset, x): + """``draft()`` + Changeset in draft phase.""" + # i18n: "draft" is a keyword + getargs(x, 0, 0, _("draft takes no arguments")) + target = phases.draft + return _phase(repo, subset, target) + +def secret(repo, subset, x): + """``secret()`` + Changeset in secret phase.""" + # i18n: "secret" is a keyword + getargs(x, 0, 0, _("secret takes no arguments")) + target = phases.secret + return _phase(repo, subset, target) + def parentspec(repo, subset, x, n): """``set^0`` The set. @@ -1487,6 +1563,23 @@ except error.RepoLookupError: return baseset() +# for internal use +def _notpublic(repo, subset, x): + getargs(x, 0, 0, "_notpublic takes no arguments") + repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded + if repo._phasecache._phasesets: + s = set() + for u in repo._phasecache._phasesets[1:]: + s.update(u) + s = baseset(s - repo.changelog.filteredrevs) + s.sort() + 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,19 +1778,13 @@ 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]) - cs = _children(repo, subset, s) - return subset - cs - -def secret(repo, subset, x): - """``secret()`` - Changeset in secret phase.""" - # i18n: "secret" is a keyword - getargs(x, 0, 0, _("secret takes no arguments")) - phase = repo._phasecache.phase - target = phases.secret - condition = lambda r: phase(repo, r) == target - return subset.filter(condition, cache=False) + parents = repo.changelog.parentrevs + def filter(r): + for p in parents(r): + if 0 <= p and p in s: + return False + return True + return subset & s.filter(filter) def sort(repo, subset, x): """``sort(set[, [-]key...])`` @@ -1788,7 +1875,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 +1886,7 @@ return True if s.removed: - return util.any(submatches(c.p1().substate.keys())) + return any(submatches(c.p1().substate.keys())) return False @@ -1836,7 +1923,7 @@ pattern = pattern[3:] try: regex = re.compile(pattern) - except re.error, e: + except re.error as e: raise error.ParseError(_('invalid regular expression: %s') % e) return 're', pattern, regex.search @@ -1906,8 +1993,8 @@ def wdir(repo, subset, x): # i18n: "wdir" is a keyword getargs(x, 0, 0, _("wdir takes no arguments")) - if None in subset or isinstance(subset, fullreposet): - return baseset([None]) + if node.wdirrev in subset or isinstance(subset, fullreposet): + return baseset([node.wdirrev]) return baseset() # for internal use @@ -1915,9 +2002,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 +2097,7 @@ "parents": parents, "present": present, "public": public, + "_notpublic": _notpublic, "remote": remote, "removes": removes, "rev": rev, @@ -2067,6 +2172,7 @@ "parents", "present", "public", + "_notpublic", "remote", "removes", "rev", @@ -2089,16 +2195,16 @@ "range": rangeset, "dagrange": dagrange, "string": stringset, - "symbol": symbolset, + "symbol": stringset, "and": andset, "or": orset, "not": notset, "list": listset, + "keyvalue": keyvaluepair, "func": func, "ancestor": ancestorspec, "parent": parentspec, "parentpost": p1, - "only": only, } def optimize(x, small): @@ -2121,6 +2227,8 @@ return optimize(('func', ('symbol', 'ancestors'), x[1]), small) elif op == 'dagrangepost': return optimize(('func', ('symbol', 'descendants'), x[1]), small) + elif op == 'rangeall': + return optimize(('range', ('string', '0'), ('string', 'tip')), small) elif op == 'rangepre': return optimize(('range', ('string', '0'), x[1]), small) elif op == 'rangepost': @@ -2153,14 +2261,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]) @@ -2274,9 +2413,9 @@ >>> _parsealiasdecl('foo($1, $2, $1)') ('foo', None, None, 'argument names collide with each other') """ - p = parser.parser(_tokenizealias, elements) + p = parser.parser(elements) try: - tree, pos = p.parse(decl) + tree, pos = p.parse(_tokenizealias(decl)) if (pos != len(decl)): raise error.ParseError(_('invalid token'), pos) @@ -2303,7 +2442,7 @@ return (name, ('func', ('symbol', name)), args, None) return (decl, None, None, _("invalid format")) - except error.ParseError, inst: + except error.ParseError as inst: return (decl, None, None, parseerrordetail(inst)) def _parsealiasdefn(defn, args): @@ -2365,11 +2504,11 @@ pos) yield (t, value, pos) - p = parser.parser(tokenizedefn, elements) - tree, pos = p.parse(defn) + p = parser.parser(elements) + tree, pos = p.parse(tokenizedefn(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. @@ -2392,7 +2531,7 @@ self.replacement = _parsealiasdefn(value, self.args) # Check for placeholder injection _checkaliasarg(self.replacement, self.args) - except error.ParseError, inst: + except error.ParseError as inst: self.error = _('failed to parse the definition of revset alias' ' "%s": %s') % (self.name, parseerrordetail(inst)) @@ -2496,8 +2635,11 @@ return tuple(foldconcat(t) for t in tree) def parse(spec, lookup=None): - p = parser.parser(tokenize, elements) - return p.parse(spec, lookup=lookup) + p = parser.parser(elements) + tree, pos = p.parse(tokenize(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 +2651,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 +2762,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): @@ -2926,20 +3054,73 @@ def last(self): it = None - if self._subset.isascending: - it = self.fastdesc - elif self._subset.isdescending: + if self.isascending(): it = self.fastdesc - if it is None: - # slowly consume everything. This needs improvement - it = lambda: reversed(list(self)) - for x in it(): + elif self.isdescending(): + it = self.fastasc + if it is not None: + for x in it(): + return x + return None #empty case + else: + x = None + for x in self: + pass return x - return None 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 +3130,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 +3206,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 +3220,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 +3270,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 +3281,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 +3348,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 +3477,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 +3489,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] @@ -3424,6 +3629,17 @@ # object. other = baseset(other - self._hiddenrevs) + # XXX As fullreposet is also used as bootstrap, this is wrong. + # + # With a giveme312() revset returning [3,1,2], this makes + # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong) + # We cannot just drop it because other usage still need to sort it: + # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right) + # + # There is also some faulty revset implementations that rely on it + # (eg: children as of its state in e8075329c5fb) + # + # When we fix the two points above we can move this into the if clause other.sort(reverse=self.isdescending()) return other diff -r 501c51d60792 -r 96a38d44ba09 mercurial/scmutil.py --- a/mercurial/scmutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/scmutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -6,11 +6,11 @@ # GNU General Public License version 2 or any later version. from i18n import _ -from mercurial.node import nullrev +from mercurial.node import nullrev, wdirrev import util, error, osutil, revset, similar, encoding, phases import pathutil import match as matchmod -import os, errno, re, glob, tempfile, shutil, stat, inspect +import os, errno, re, glob, tempfile, shutil, stat if os.name == 'nt': import scmwindows as scmplatform @@ -80,9 +80,24 @@ # has been modified (in ctx2) but not yet committed (in ctx1). subpaths = dict.fromkeys(ctx2.substate, ctx2) subpaths.update(dict.fromkeys(ctx1.substate, ctx1)) + + missing = set() + + for subpath in ctx2.substate: + if subpath not in ctx1.substate: + del subpaths[subpath] + missing.add(subpath) + for subpath, ctx in sorted(subpaths.iteritems()): yield subpath, ctx.sub(subpath) + # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way, + # status and diff will have an accurate result when it does + # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared + # against itself. + for subpath in missing: + yield subpath, ctx2.nullsub(subpath, ctx1) + def nochangesfound(ui, repo, excluded=None): '''Report no changes for push/pull, excluded is None or a list of nodes excluded from the push/pull. @@ -172,16 +187,6 @@ self._loweredfiles.add(fl) self._newfiles.add(f) -def develwarn(tui, msg): - """issue a developer warning message""" - msg = 'devel-warn: ' + msg - if tui.tracebackflag: - util.debugstacktrace(msg, 2) - else: - curframe = inspect.currentframe() - calframe = inspect.getouterframes(curframe, 2) - tui.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4])) - def filteredhash(repo, maxrev): """build hash of filtered revisions in the current repoview. @@ -217,7 +222,7 @@ '''gracefully return an empty string for missing files''' try: return self.read(path) - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise return "" @@ -226,7 +231,7 @@ '''gracefully return an empty array for missing files''' try: return self.readlines(path, mode=mode) - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise return [] @@ -277,9 +282,21 @@ finally: fp.close() + def basename(self, path): + """return base element of a path (as os.path.basename would do) + + This exists to allow handling of strange encoding if needed.""" + return os.path.basename(path) + def chmod(self, path, mode): return os.chmod(self.join(path), mode) + def dirname(self, path): + """return dirname element of a path (as os.path.dirname would do) + + This exists to allow handling of strange encoding if needed.""" + return os.path.dirname(path) + def exists(self, path=None): return os.path.exists(self.join(path)) @@ -445,7 +462,7 @@ def _fixfilemode(self, name): if self.createmode is None or not self._chmod: return - os.chmod(name, self.createmode & 0666) + os.chmod(name, self.createmode & 0o666) def __call__(self, path, mode="r", text=False, atomictemp=False, notindexed=False): @@ -486,7 +503,7 @@ if nlink < 1: nlink = 2 # force mktempcopy (issue1922) fd.close() - except (OSError, IOError), e: + except (OSError, IOError) as e: if e.errno != errno.ENOENT: raise nlink = 0 @@ -514,7 +531,7 @@ if self._cansymlink: try: os.symlink(src, linkname) - except OSError, err: + except OSError as err: raise OSError(err.errno, _('could not symlink to %r: %s') % (src, err.strerror), linkname) else: @@ -657,11 +674,11 @@ _rcpath = osrcpath() return _rcpath -def intrev(repo, rev): +def intrev(rev): """Return integer for a given revision that can be used in comparison or arithmetic operation""" if rev is None: - return len(repo) + return wdirrev return rev def revsingle(repo, revspec, default='.'): @@ -709,14 +726,12 @@ return defval return repo[val].rev() - seen, l = set(), revset.baseset([]) + subsets = [] revsetaliases = [alias for (alias, _) in repo.ui.configitems("revsetalias")] for spec in revs: - if l and not seen: - seen = set(l) # attempt to parse old-style ranges first to deal with # things like old-tag which contain query metacharacters try: @@ -727,8 +742,7 @@ raise error.RepoLookupError if isinstance(spec, int): - seen.add(spec) - l = l + revset.baseset([spec]) + subsets.append(revset.baseset([spec])) continue if _revrangesep in spec: @@ -740,40 +754,24 @@ end = revfix(repo, end, len(repo) - 1) if end == nullrev and start < 0: start = nullrev - rangeiter = repo.changelog.revs(start, end) - if not seen and not l: - # by far the most common case: revs = ["-1:0"] - l = revset.baseset(rangeiter) - # defer syncing seen until next iteration - continue - newrevs = set(rangeiter) - if seen: - newrevs.difference_update(seen) - seen.update(newrevs) + if start < end: + l = revset.spanset(repo, start, end + 1) else: - seen = newrevs - l = l + revset.baseset(sorted(newrevs, reverse=start > end)) + l = revset.spanset(repo, start, end - 1) + subsets.append(l) continue elif spec and spec in repo: # single unquoted rev rev = revfix(repo, spec, None) - if rev in seen: - continue - seen.add(rev) - l = l + revset.baseset([rev]) + subsets.append(revset.baseset([rev])) continue except error.RepoLookupError: pass # fall through to new-style queries if old-style fails m = revset.match(repo.ui, spec, repo) - if seen or l: - dl = [r for r in m(repo) if r not in seen] - l = l + revset.baseset(dl) - seen.update(dl) - else: - l = m(repo) + subsets.append(m(repo)) - return l + return revset._combinesets(subsets) def expandpats(pats): '''Expand bare globs when running on windows. @@ -794,34 +792,40 @@ ret.append(kindpat) return ret -def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'): +def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath', + badfn=None): '''Return a matcher and the patterns that were used. - The matcher will warn about bad matches.''' + The matcher will warn about bad matches, unless an alternate badfn callback + is provided.''' if pats == ("",): pats = [] if not globbed and default == 'relpath': pats = expandpats(pats or []) + def bad(f, msg): + ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg)) + + if badfn is None: + badfn = bad + m = ctx.match(pats, opts.get('include'), opts.get('exclude'), - default) - def badfn(f, msg): - ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg)) - m.bad = badfn + default, listsubrepos=opts.get('subrepos'), badfn=badfn) + if m.always(): pats = [] return m, pats -def match(ctx, pats=[], opts={}, globbed=False, default='relpath'): +def match(ctx, pats=[], opts={}, globbed=False, default='relpath', badfn=None): '''Return a matcher that will warn about bad matches.''' - return matchandpats(ctx, pats, opts, globbed, default)[0] + return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0] def matchall(repo): '''Return a matcher that will efficiently match everything.''' return matchmod.always(repo.root, repo.getcwd()) -def matchfiles(repo, files): +def matchfiles(repo, files, badfn=None): '''Return a matcher that will efficiently match exactly these files.''' - return matchmod.exact(repo.root, repo.getcwd(), files) + return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn) def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None): m = matcher @@ -854,15 +858,14 @@ % join(subpath)) rejected = [] - origbad = m.bad def badfn(f, msg): if f in m.files(): - origbad(f, msg) + m.bad(f, msg) rejected.append(f) - m.bad = badfn - added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m) - m.bad = origbad + badmatch = matchmod.badmatch(m, badfn) + added, unknown, deleted, removed, forgotten = _interestingfiles(repo, + badmatch) unknownset = set(unknown + forgotten) toprint = unknownset.copy() @@ -889,9 +892,8 @@ def marktouched(repo, files, similarity=0.0): '''Assert that files have somehow been operated upon. files are relative to the repo root.''' - m = matchfiles(repo, files) + m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x)) rejected = [] - m.bad = lambda x, y: rejected.append(x) added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m) @@ -1011,6 +1013,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 @@ -1062,7 +1070,7 @@ def stat(path): try: return util.cachestat(path) - except OSError, e: + except OSError as e: if e.errno != errno.ENOENT: raise diff -r 501c51d60792 -r 96a38d44ba09 mercurial/setdiscovery.py --- a/mercurial/setdiscovery.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/setdiscovery.py Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/sshpeer.py --- a/mercurial/sshpeer.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/sshpeer.py Sat Jul 18 17:32:38 2015 -0500 @@ -27,6 +27,87 @@ 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 doublepipe(object): + """Operate a side-channel pipe in addition of a main one + + The side-channel pipe contains server output to be forwarded to the user + input. The double pipe will behave as the "main" pipe, but will ensure the + content of the "side" pipe is properly processed while we wait for blocking + call on the "main" pipe. + + If large amounts of data are read from "main", the forward will cease after + the first bytes start to appear. This simplifies the implementation + without affecting actual output of sshpeer too much as we rarely issue + large read for data not yet emitted by the server. + + The main pipe is expected to be a 'bufferedinputpipe' from the util module + that handle all the os specific bites. This class lives in this module + because it focus on behavior specifig to the ssh protocol.""" + + def __init__(self, ui, main, side): + self._ui = ui + self._main = main + self._side = side + + def _wait(self): + """wait until some data are available on main or side + + return a pair of boolean (ismainready, issideready) + + (This will only wait for data if the setup is supported by `util.poll`) + """ + if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe + return (True, True) # main has data, assume side is worth poking at. + fds = [self._main.fileno(), self._side.fileno()] + try: + act = util.poll(fds) + except NotImplementedError: + # non supported yet case, assume all have data. + act = fds + return (self._main.fileno() in act, self._side.fileno() in act) + + def write(self, data): + return self._call('write', data) + + def read(self, size): + return self._call('read', size) + + def readline(self): + return self._call('readline') + + def _call(self, methname, data=None): + """call on "main", forward output of "side" while blocking + """ + # data can be '' or 0 + if (data is not None and not data) or self._main.closed: + _forwardoutput(self._ui, self._side) + return '' + while True: + mainready, sideready = self._wait() + if sideready: + _forwardoutput(self._ui, self._side) + if mainready: + meth = getattr(self._main, methname) + if data is None: + return meth() + else: + return meth(data) + + def close(self): + return self._main.close() + + def flush(self): + return self._main.flush() + class sshpeer(wireproto.wirepeer): def __init__(self, ui, path, create=False): self._url = path @@ -78,7 +159,16 @@ # while self.subprocess isn't used, having it allows the subprocess to # to clean up correctly later - self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd) + # + # no buffer allow the use of 'select' + # feel free to remove buffering and select usage when we ultimately + # move to threading. + sub = util.popen4(cmd, bufsize=0) + self.pipeo, self.pipei, self.pipee, self.subprocess = sub + + self.pipei = util.bufferedinputpipe(self.pipei) + self.pipei = doublepipe(self.ui, self.pipei, self.pipee) + self.pipeo = doublepipe(self.ui, self.pipeo, self.pipee) # skip any noise generated by remote shell self._callstream("hello") @@ -108,10 +198,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 +282,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 501c51d60792 -r 96a38d44ba09 mercurial/sshserver.py --- a/mercurial/sshserver.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/sshserver.py Sat Jul 18 17:32:38 2015 -0500 @@ -6,7 +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 util, hook, wireproto, changegroup +import util, hook, wireproto import os, sys class sshserver(wireproto.abstractserverproto): @@ -120,33 +120,6 @@ else: self.sendresponse("") return cmd != '' - def do_lock(self): - '''DEPRECATED - allowing remote client to lock repo is not safe''' - - self.lock = self.repo.lock() - return "" - - def do_unlock(self): - '''DEPRECATED''' - - if self.lock: - self.lock.release() - self.lock = None - return "" - - def do_addchangegroup(self): - '''DEPRECATED''' - - if not self.lock: - self.sendresponse("not locked") - return - - self.sendresponse("") - cg = changegroup.cg1unpacker(self.fin, "UN") - r = changegroup.addchangegroup(self.repo, cg, 'serve', self._client()) - self.lock.release() - return str(r) - def _client(self): client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client diff -r 501c51d60792 -r 96a38d44ba09 mercurial/sslutil.py --- a/mercurial/sslutil.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/sslutil.py Sat Jul 18 17:32:38 2015 -0500 @@ -6,77 +6,59 @@ # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import os, sys +import os, sys, ssl from mercurial import util from mercurial.i18n import _ _canloaddefaultcerts = False try: - # avoid using deprecated/broken FakeSocket in python 2.6 - import ssl - CERT_REQUIRED = ssl.CERT_REQUIRED - try: - ssl_context = ssl.SSLContext - _canloaddefaultcerts = util.safehasattr(ssl_context, - 'load_default_certs') - - def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE, - ca_certs=None, serverhostname=None): - # Allow any version of SSL starting with TLSv1 and - # up. Note that specifying TLSv1 here prohibits use of - # newer standards (like TLSv1_2), so this is the right way - # to do this. Note that in the future it'd be better to - # support using ssl.create_default_context(), which sets - # up a bunch of things in smart ways (strong ciphers, - # protocol versions, etc) and is upgraded by Python - # maintainers for us, but that breaks too many things to - # do it in a hurry. - sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3 - if certfile is not None: - sslcontext.load_cert_chain(certfile, keyfile) - sslcontext.verify_mode = cert_reqs - if ca_certs is not None: - sslcontext.load_verify_locations(cafile=ca_certs) - elif _canloaddefaultcerts: - sslcontext.load_default_certs() + ssl_context = ssl.SSLContext + _canloaddefaultcerts = util.safehasattr(ssl_context, 'load_default_certs') - sslsocket = sslcontext.wrap_socket(sock, - server_hostname=serverhostname) - # check if wrap_socket failed silently because socket had been - # closed - # - see http://bugs.python.org/issue13721 - if not sslsocket.cipher(): - raise util.Abort(_('ssl connection failed')) - return sslsocket - except AttributeError: - def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE, - ca_certs=None, serverhostname=None): - sslsocket = ssl.wrap_socket(sock, keyfile, certfile, - cert_reqs=cert_reqs, ca_certs=ca_certs, - ssl_version=ssl.PROTOCOL_TLSv1) - # check if wrap_socket failed silently because socket had been - # closed - # - see http://bugs.python.org/issue13721 - if not sslsocket.cipher(): - raise util.Abort(_('ssl connection failed')) - return sslsocket -except ImportError: - CERT_REQUIRED = 2 + def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE, + ca_certs=None, serverhostname=None): + # Allow any version of SSL starting with TLSv1 and + # up. Note that specifying TLSv1 here prohibits use of + # newer standards (like TLSv1_2), so this is the right way + # to do this. Note that in the future it'd be better to + # support using ssl.create_default_context(), which sets + # up a bunch of things in smart ways (strong ciphers, + # protocol versions, etc) and is upgraded by Python + # maintainers for us, but that breaks too many things to + # do it in a hurry. + sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3 + if certfile is not None: + def password(): + f = keyfile or certfile + return ui.getpass(_('passphrase for %s: ') % f, '') + sslcontext.load_cert_chain(certfile, keyfile, password) + sslcontext.verify_mode = cert_reqs + if ca_certs is not None: + sslcontext.load_verify_locations(cafile=ca_certs) + elif _canloaddefaultcerts: + sslcontext.load_default_certs() - import socket, httplib - - def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED, - ca_certs=None, serverhostname=None): - if not util.safehasattr(socket, 'ssl'): - raise util.Abort(_('Python SSL support not found')) - if ca_certs: - raise util.Abort(_( - 'certificate checking requires Python 2.6')) - - ssl = socket.ssl(sock, keyfile, certfile) - return httplib.FakeSocket(sock, ssl) + sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname) + # check if wrap_socket failed silently because socket had been + # closed + # - see http://bugs.python.org/issue13721 + if not sslsocket.cipher(): + raise util.Abort(_('ssl connection failed')) + return sslsocket +except AttributeError: + def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE, + ca_certs=None, serverhostname=None): + sslsocket = ssl.wrap_socket(sock, keyfile, certfile, + cert_reqs=cert_reqs, ca_certs=ca_certs, + ssl_version=ssl.PROTOCOL_TLSv1) + # check if wrap_socket failed silently because socket had been + # closed + # - see http://bugs.python.org/issue13721 + if not sslsocket.cipher(): + raise util.Abort(_('ssl connection failed')) + return sslsocket def _verifycert(cert, hostname): '''Verify that cert (in socket.getpeercert() format) matches hostname. @@ -117,9 +99,6 @@ # CERT_REQUIRED means fetch the cert from the server all the time AND # validate it against the CA store provided in web.cacerts. -# -# We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally -# busted on those versions. def _plainapplepython(): """return true if this seems to be a pure Apple Python that @@ -146,7 +125,7 @@ return '!' def sslkwargs(ui, host): - kws = {} + kws = {'ui': ui} hostfingerprint = ui.config('hostfingerprints', host) if hostfingerprint: return kws @@ -164,7 +143,7 @@ ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts') if cacerts != '!': kws.update({'ca_certs': cacerts, - 'cert_reqs': CERT_REQUIRED, + 'cert_reqs': ssl.CERT_REQUIRED, }) return kws @@ -177,17 +156,6 @@ host = self.host cacerts = self.ui.config('web', 'cacerts') hostfingerprint = self.ui.config('hostfingerprints', host) - if not getattr(sock, 'getpeercert', False): # python 2.5 ? - if hostfingerprint: - raise util.Abort(_("host fingerprint for %s can't be " - "verified (Python too old)") % host) - if strict: - raise util.Abort(_("certificate for %s can't be verified " - "(Python too old)") % host) - if self.ui.configbool('ui', 'reportoldssl', True): - self.ui.warn(_("warning: certificate for %s can't be verified " - "(Python too old)\n") % host) - return if not sock.cipher(): # work around http://bugs.python.org/issue13721 raise util.Abort(_('%s ssl connection error') % host) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/statichttprepo.py Sat Jul 18 17:32:38 2015 -0500 @@ -32,15 +32,11 @@ 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))() - except urllib2.HTTPError, inst: + code = f.code + except urllib2.HTTPError as inst: num = inst.code == 404 and errno.ENOENT or None raise IOError(num, inst) - except urllib2.URLError, inst: + except urllib2.URLError as inst: raise IOError(None, inst.reason[1]) if code == 200: @@ -110,7 +106,7 @@ try: requirements = scmutil.readrequires(self.vfs, self.supported) - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise requirements = set() @@ -120,7 +116,7 @@ fp = self.vfs("00changelog.i") fp.read(1) fp.close() - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise # we do not care about empty old-style repositories here @@ -131,7 +127,6 @@ self.store = store.store(requirements, self.path, opener) self.spath = self.store.path self.svfs = self.store.opener - self.sopener = self.svfs self.sjoin = self.store.join self._filecache = {} self.requirements = requirements diff -r 501c51d60792 -r 96a38d44ba09 mercurial/store.py --- a/mercurial/store.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/store.py Sat Jul 18 17:32:38 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) @@ -274,7 +274,7 @@ # files in .hg/ will be created using this mode mode = vfs.stat().st_mode # avoid some useless chmods - if (0777 & ~util.umask) == (0777 & mode): + if (0o777 & ~util.umask) == (0o777 & mode): mode = None except OSError: mode = None @@ -489,7 +489,7 @@ ef = self.encode(f) try: yield f, ef, self.getsize(ef) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise @@ -513,7 +513,7 @@ try: self.getsize(ef) return True - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise # nonexistent entry diff -r 501c51d60792 -r 96a38d44ba09 mercurial/subrepo.py --- a/mercurial/subrepo.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/subrepo.py Sat Jul 18 17:32:38 2015 -0500 @@ -44,10 +44,10 @@ def decoratedmethod(self, *args, **kargs): try: res = func(self, *args, **kargs) - except SubrepoAbort, ex: + except SubrepoAbort as ex: # This exception has already been handled raise ex - except error.Abort, ex: + except error.Abort as ex: subrepo = subrelpath(self) errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo # avoid handling this exception by raising a SubrepoAbort exception @@ -62,23 +62,22 @@ (key in types dict)) """ p = config.config() + repo = ctx.repo() def read(f, sections=None, remap=None): if f in ctx: try: data = ctx[f].data() - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise # handle missing subrepo spec files as removed ui.warn(_("warning: subrepo spec file \'%s\' not found\n") % - util.pathto(ctx.repo().root, ctx.repo().getcwd(), f)) + repo.pathto(f)) return p.parse(f, data, sections, remap, read) else: - repo = ctx.repo() raise util.Abort(_("subrepo spec file \'%s\' not found") % - util.pathto(repo.root, repo.getcwd(), f)) - + repo.pathto(f)) if '.hgsub' in ctx: read('.hgsub') @@ -95,13 +94,11 @@ try: revision, path = l.split(" ", 1) except ValueError: - repo = ctx.repo() raise util.Abort(_("invalid subrepository revision " "specifier in \'%s\' line %d") - % (util.pathto(repo.root, repo.getcwd(), - '.hgsubstate'), (i + 1))) + % (repo.pathto('.hgsubstate'), (i + 1))) rev[path] = revision - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise @@ -116,7 +113,7 @@ repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl) try: src = re.sub(pattern, repl, src, 1) - except re.error, e: + except re.error as e: raise util.Abort(_("bad subrepository pattern in %s: %s") % (p.source('subpaths', pattern), e)) return src @@ -132,7 +129,7 @@ src = src.lstrip() # strip any extra whitespace after ']' if not util.url(src).isabs(): - parent = _abssource(ctx.repo(), abort=False) + parent = _abssource(repo, abort=False) if parent: parent = util.url(parent) parent.path = posixpath.join(parent.path or '', src) @@ -316,7 +313,7 @@ if d.lower() == ignore: del dirs[i] break - if os.path.basename(dirname).lower() != '.hg': + if vfs.basename(dirname).lower() != '.hg': continue for f in names: if f.lower() == 'hgrc': @@ -324,7 +321,7 @@ "in '%s'\n") % vfs.join(dirname)) vfs.unlink(vfs.reljoin(dirname, f)) -def subrepo(ctx, path): +def subrepo(ctx, path, allowwdir=False): """return instance of the right subrepo class for subrepo in path""" # subrepo inherently violates our import layering rules # because it wants to make repo objects from deep inside the stack @@ -338,8 +335,29 @@ state = ctx.substate[path] if state[2] not in types: raise util.Abort(_('unknown subrepo type %s') % state[2]) + if allowwdir: + state = (state[0], ctx.subrev(path), state[2]) return types[state[2]](ctx, path, state[:2]) +def nullsubrepo(ctx, path, pctx): + """return an empty subrepo in pctx for the extant subrepo in ctx""" + # subrepo inherently violates our import layering rules + # because it wants to make repo objects from deep inside the stack + # so we manually delay the circular imports to not break + # scripts that don't use our demand-loading + global hg + import hg as h + hg = h + + pathutil.pathauditor(ctx.repo().root)(path) + state = ctx.substate[path] + if state[2] not in types: + raise util.Abort(_('unknown subrepo type %s') % state[2]) + subrev = '' + if state[2] == 'hg': + subrev = "0" * 40 + return types[state[2]](pctx, path, (state[0], subrev)) + def newcommitphase(ui, ctx): commitphase = phases.newcommitphase(ui) substate = getattr(ctx, "substate", None) @@ -500,7 +518,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 @@ -515,7 +537,7 @@ unit=_('files'), total=total) for i, name in enumerate(files): flags = self.fileflags(name) - mode = 'x' in flags and 0755 or 0644 + mode = 'x' in flags and 0o755 or 0o644 symlink = 'l' in flags archiver.addfile(prefix + self._path + '/' + name, mode, symlink, self.filedata(name)) @@ -549,6 +571,12 @@ def shortid(self, revid): return revid + def verify(self): + '''verify the integrity of the repository. Return 0 on success or + warning, 1 on any error. + ''' + return 0 + @propertycache def wvfs(self): """return vfs to access the working directory of this subrepository @@ -579,6 +607,7 @@ v = r.ui.config(s, k) if v: self.ui.setconfig(s, k, v, 'subrepo') + # internal config: ui._usedassubrepo self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo') self._initrepo(r, state[0], create) @@ -592,21 +621,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): @@ -646,6 +668,15 @@ finally: lock.release() + def _getctx(self): + '''fetch the context for this subrepo revision, possibly a workingctx + ''' + if self._ctx.rev() is None: + return self._repo[None] # workingctx if parent is workingctx + else: + rev = self._state[1] + return self._repo[rev] + @annotatesubrepoerror def _initrepo(self, parentrepo, source, create): self._repo._subparent = parentrepo @@ -701,7 +732,7 @@ ctx1 = self._repo[rev1] ctx2 = self._repo[rev2] return self._repo.status(ctx1, ctx2, **opts) - except error.RepoLookupError, inst: + except error.RepoLookupError as inst: self.ui.warn(_('warning: error "%s" in subrepository "%s"\n') % (inst, subrelpath(self))) return scmutil.status([], [], [], [], [], [], []) @@ -718,7 +749,7 @@ node1, node2, match, prefix=posixpath.join(prefix, self._path), listsubrepos=True, **opts) - except error.RepoLookupError, inst: + except error.RepoLookupError as inst: self.ui.warn(_('warning: error "%s" in subrepository "%s"\n') % (inst, subrelpath(self))) @@ -729,7 +760,7 @@ rev = self._state[1] ctx = self._repo[rev] for subpath in ctx.substate: - s = subrepo(ctx, subpath) + s = subrepo(ctx, subpath, True) submatch = matchmod.narrowmatcher(subpath, match) total += s.archive(archiver, prefix + self._path + '/', submatch) return total @@ -907,7 +938,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 +946,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] @@ -966,6 +1017,24 @@ def shortid(self, revid): return revid[:12] + def verify(self): + try: + rev = self._state[1] + ctx = self._repo.unfiltered()[rev] + if ctx.hidden(): + # Since hidden revisions aren't pushed/pulled, it seems worth an + # explicit warning. + ui = self._repo.ui + ui.warn(_("subrepo '%s' is hidden in revision %s\n") % + (self._relpath, node.short(self._ctx.node()))) + return 0 + except error.RepoLookupError: + # A missing subrepo revision may be a case of needing to pull it, so + # don't treat this as an error. + self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") % + (self._relpath, node.short(self._ctx.node()))) + return 0 + @propertycache def wvfs(self): """return own wvfs for efficiency and consitency @@ -1138,7 +1207,8 @@ self.wvfs.rmtree(forcibly=True) try: - self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path)) + pwvfs = self._ctx.repo().wvfs + pwvfs.removedirs(pwvfs.dirname(self._path)) except OSError: pass @@ -1209,7 +1279,7 @@ try: self._gitexecutable = 'git' out, err = self._gitnodir(['--version']) - except OSError, e: + except OSError as e: if e.errno != 2 or os.name != 'nt': raise self._gitexecutable = 'git.cmd' @@ -1711,7 +1781,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 501c51d60792 -r 96a38d44ba09 mercurial/tags.py --- a/mercurial/tags.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/tags.py Sat Jul 18 17:32:38 2015 -0500 @@ -120,7 +120,7 @@ '''Read local tags in repo. Update alltags and tagtypes.''' try: data = repo.vfs.read("localtags") - except IOError, inst: + except IOError as inst: if inst.errno != errno.ENOENT: raise return @@ -442,11 +442,13 @@ self._raw.pop() self._dirtyoffset = len(self._raw) - def getfnode(self, node): + def getfnode(self, node, computemissing=True): """Obtain the filenode of the .hgtags file at a specified revision. If the value is in the cache, the entry will be validated and returned. - Otherwise, the filenode will be computed and returned. + Otherwise, the filenode will be computed and returned unless + "computemissing" is False, in which case None will be returned without + any potentially expensive computation being performed. If an .hgtags does not exist at the specified revision, nullid is returned. @@ -470,21 +472,39 @@ # Fall through. - # If we get here, the entry is either missing or invalid. Populate it. + # If we get here, the entry is either missing or invalid. + + if not computemissing: + return None + + # Populate missing entry. try: fnode = ctx.filenode('.hgtags') except error.LookupError: # No .hgtags file on this revision. fnode = nullid + self._writeentry(offset, properprefix, fnode) + return fnode + + def setfnode(self, node, fnode): + """Set the .hgtags filenode for a given changeset.""" + assert len(fnode) == 20 + ctx = self._repo[node] + + # Do a lookup first to avoid writing if nothing has changed. + if self.getfnode(ctx.node(), computemissing=False) == fnode: + return + + self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode) + + def _writeentry(self, offset, prefix, fnode): # Slices on array instances only accept other array. - entry = array('c', properprefix + fnode) + entry = array('c', prefix + fnode) self._raw[offset:offset + _fnodesrecsize] = entry # self._dirtyoffset could be None. self._dirtyoffset = min(self._dirtyoffset, offset) or 0 - return fnode - def write(self): """Perform all necessary writes to cache file. @@ -509,26 +529,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) as inst: + repo.ui.log('tagscache', + "couldn't write %s: %s\n" % ( + _fnodescachefile, inst)) finally: lock.release() diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templatefilters.py --- a/mercurial/templatefilters.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templatefilters.py Sat Jul 18 17:32:38 2015 -0500 @@ -283,6 +283,13 @@ f = author.find('@') return author[:f].replace('.', ' ') +def revescape(text): + """:revescape: Any text. Escapes all "special" characters, except @. + Forward slashes are escaped twice to prevent web servers from prematurely + unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz". + """ + return urllib.quote(text, safe='/@').replace('/', '%252F') + def rfc3339date(text): """:rfc3339date: Date. Returns a date using the Internet date format specified in RFC 3339: "2009-08-18T13:00:13+02:00". @@ -326,6 +333,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): @@ -400,6 +409,7 @@ "obfuscate": obfuscate, "permissions": permissions, "person": person, + "revescape": revescape, "rfc3339date": rfc3339date, "rfc822date": rfc822date, "short": short, diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templatekw.py --- a/mercurial/templatekw.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templatekw.py Sat Jul 18 17:32:38 2015 -0500 @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. from node import hex -import patch, util, error +import patch, scmutil, util, error import hbisect # This helper class allows us to handle both: @@ -40,24 +40,25 @@ raise AttributeError(name) return getattr(self.values, name) -def showlist(name, values, plural=None, element=None, **args): +def showlist(name, values, plural=None, element=None, separator=' ', **args): if not element: element = name - f = _showlist(name, values, plural, **args) + f = _showlist(name, values, plural, separator, **args) return _hybrid(f, values, lambda x: {element: x}) -def _showlist(name, values, plural=None, **args): +def _showlist(name, values, plural=None, separator=' ', **args): '''expand set of values. name is name of key in template map. values is list of strings or dicts. plural is plural of name, if not simply name + 's'. + separator is used to join values as a string expansion works like this, given name 'foo'. if values is empty, expand 'no_foos'. if 'foo' not in template map, return values as a string, - joined by space. + joined by 'separator'. expand 'start_foos'. @@ -77,7 +78,7 @@ return if name not in templ: if isinstance(values[0], str): - yield ' '.join(values) + yield separator.join(values) else: for v in values: yield dict(v, **args) @@ -120,7 +121,7 @@ if 'latesttags' not in cache: # Cache mapping from rev to a tuple with tag date, tag # distance and tag name - cache['latesttags'] = {-1: (0, 0, 'null')} + cache['latesttags'] = {-1: (0, 0, ['null'])} latesttags = cache['latesttags'] rev = ctx.rev() @@ -133,7 +134,7 @@ tags = [t for t in ctx.tags() if (repo.tagtype(t) and repo.tagtype(t) != 'local')] if tags: - latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags)) + latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)] continue try: # The tuples are laid out so the right one can be found by @@ -206,12 +207,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 +222,18 @@ 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): + """:activebookmark: 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 + active = args['repo']._activebookmark + if active and active in args['ctx'].bookmarks(): + return active return '' def showdate(repo, ctx, templ, **args): @@ -321,19 +325,39 @@ """ return showlist('file', args['ctx'].files(), **args) -def showlatesttag(repo, ctx, templ, cache, **args): - """:latesttag: String. Most recent global tag in the ancestors of this - changeset. +def showlatesttag(**args): + """:latesttag: List of strings. The global tags on the most recent globally + tagged ancestor of this changeset. """ - return getlatesttags(repo, ctx, cache)[2] + repo, ctx = args['repo'], args['ctx'] + cache = args['cache'] + latesttags = getlatesttags(repo, ctx, cache)[2] + + return showlist('latesttag', latesttags, separator=':', **args) def showlatesttagdistance(repo, ctx, templ, cache, **args): """:latesttagdistance: Integer. Longest path to the latest tag.""" return getlatesttags(repo, ctx, cache)[1] +def showchangessincelatesttag(repo, ctx, templ, cache, **args): + """:changessincelatesttag: Integer. All ancestors not in the latest tag.""" + latesttag = getlatesttags(repo, ctx, cache)[2][0] + offset = 0 + revs = [ctx.rev()] + + # The only() revset doesn't currently support wdir() + if ctx.rev() is None: + offset = 1 + revs = [p.rev() for p in ctx.parents()] + + return len(repo.revs('only(%ld, %s)', revs, latesttag)) + offset + def showmanifest(**args): repo, ctx, templ = args['repo'], args['ctx'], args['templ'] mnode = ctx.manifestnode() + if mnode is None: + # just avoid crash, we might want to use the 'ff...' hash in future + return args = args.copy() args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)}) return templ('manifest', **args) @@ -376,7 +400,7 @@ def showrev(repo, ctx, templ, **args): """:rev: Integer. The repository-local changeset revision number.""" - return ctx.rev() + return scmutil.intrev(ctx.rev()) def showsubrepos(**args): """:subrepos: List of strings. Updated subrepositories in the changeset.""" @@ -418,12 +442,15 @@ # 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, + 'changessincelatesttag': showchangessincelatesttag, 'children': showchildren, + # currentbookmark is deprecated 'currentbookmark': showcurrentbookmark, 'date': showdate, 'desc': showdescription, diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templater.py --- a/mercurial/templater.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templater.py Sat Jul 18 17:32:38 2015 -0500 @@ -15,19 +15,20 @@ # template parsing elements = { - "(": (20, ("group", 1, ")"), ("func", 1, ")")), - ",": (2, None, ("list", 2)), - "|": (5, None, ("|", 5)), - "%": (6, None, ("%", 6)), - ")": (0, None, None), - "symbol": (0, ("symbol",), None), - "string": (0, ("string",), None), - "rawstring": (0, ("rawstring",), None), - "end": (0, None, None), + # token-type: binding-strength, primary, prefix, infix, suffix + "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), + ",": (2, None, None, ("list", 2), None), + "|": (5, None, None, ("|", 5), None), + "%": (6, None, None, ("%", 6), None), + ")": (0, None, None, None, None), + "integer": (0, "integer", None, None, None), + "symbol": (0, "symbol", None, None, None), + "string": (0, "string", None, None, None), + "template": (0, "template", None, None, None), + "end": (0, None, None, None, None), } -def tokenizer(data): - program, start, end = data +def tokenize(program, start, end): pos = start while pos < end: c = program[pos] @@ -35,30 +36,40 @@ pass elif c in "(,)%|": # handle simple operators yield (c, None, pos) - elif (c in '"\'' or c == 'r' and - program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings - if c == 'r': - pos += 1 - c = program[pos] - decode = False - else: - decode = True - pos += 1 - s = pos + elif c in '"\'': # handle quoted templates + s = pos + 1 + data, pos = _parsetemplate(program, s, end, c) + yield ('template', data, s) + pos -= 1 + elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'): + # handle quoted strings + c = program[pos + 1] + s = pos = pos + 2 while pos < end: # find closing quote d = program[pos] if d == '\\': # skip over escaped characters pos += 2 continue if d == c: - if not decode: - yield ('rawstring', program[s:pos], s) - break yield ('string', program[s:pos], s) break 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 == '\\' and program[pos:pos + 2] in (r"\'", r'\"') or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')): # handle escaped quoted strings for compatibility with 2.9.2-3.4, @@ -73,9 +84,9 @@ # escaped quoted string if c == 'r': pos += 1 - token = 'rawstring' + token = 'string' else: - token = 'string' + token = 'template' quote = program[pos:pos + 2] s = pos = pos + 2 while pos < end: # find closing escaped quote @@ -88,6 +99,8 @@ data = program[s:pos].decode('string-escape') except ValueError: # unbalanced escapes raise error.ParseError(_("syntax error"), s) + if token == 'template': + data = _parsetemplate(data, 0, len(data))[0] yield (token, data, s) pos += 1 break @@ -106,41 +119,65 @@ yield ('symbol', sym, s) pos -= 1 elif c == '}': - pos += 1 - break + yield ('end', None, pos + 1) + return else: raise error.ParseError(_("syntax error"), pos) pos += 1 - yield ('end', None, pos) + raise error.ParseError(_("unterminated template expansion"), start) -def compiletemplate(tmpl, context, strtoken="string"): +def _parsetemplate(tmpl, start, stop, quote=''): + r""" + >>> _parsetemplate('foo{bar}"baz', 0, 12) + ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12) + >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"') + ([('string', 'foo'), ('symbol', 'bar')], 9) + >>> _parsetemplate('foo"{bar}', 0, 9, quote='"') + ([('string', 'foo')], 4) + >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"') + ([('string', 'foo"'), ('string', 'bar')], 9) + >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"') + ([('string', 'foo\\')], 6) + """ parsed = [] - pos, stop = 0, len(tmpl) - p = parser.parser(tokenizer, elements) + sepchars = '{' + quote + pos = start + p = parser.parser(elements) while pos < stop: - n = tmpl.find('{', pos) + n = min((tmpl.find(c, pos, stop) for c in sepchars), + key=lambda n: (n < 0, n)) if n < 0: - parsed.append((strtoken, tmpl[pos:])) + parsed.append(('string', tmpl[pos:stop].decode('string-escape'))) + pos = stop break + c = tmpl[n] bs = (n - pos) - len(tmpl[pos:n].rstrip('\\')) - if strtoken == 'string' and bs % 2 == 1: - # escaped (e.g. '\{', '\\\{', but not '\\{' nor r'\{') - parsed.append((strtoken, (tmpl[pos:n - 1] + "{"))) + if bs % 2 == 1: + # escaped (e.g. '\{', '\\\{', but not '\\{') + parsed.append(('string', + tmpl[pos:n - 1].decode('string-escape') + c)) pos = n + 1 continue if n > pos: - parsed.append((strtoken, tmpl[pos:n])) + parsed.append(('string', tmpl[pos:n].decode('string-escape'))) + if c == quote: + return parsed, n + 1 - pd = [tmpl, n + 1, stop] - parseres, pos = p.parse(pd) + parseres, pos = p.parse(tokenize(tmpl, n + 1, stop)) parsed.append(parseres) - return [compileexp(e, context) for e in parsed] + if quote: + raise error.ParseError(_("unterminated string"), start) + return parsed, pos -def compileexp(exp, context): +def compiletemplate(tmpl, context): + parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) + return [compileexp(e, context, methods) for e in parsed] + +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 @@ -164,16 +201,19 @@ return context._filters[f] def gettemplate(exp, context): - if exp[0] == 'string' or exp[0] == 'rawstring': - return compiletemplate(exp[1], context, strtoken=exp[0]) + if exp[0] == 'template': + return [compileexp(e, context, methods) for e in exp[1]] if exp[0] == 'symbol': + # unlike runsymbol(), here 'symbol' is always taken as template name + # even if it exists in mapping. this allows us to override mapping + # by web templates, e.g. 'changelogtag' is redefined in map file. 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") - -def runrawstring(context, mapping, data): return data def runsymbol(context, mapping, key): @@ -191,8 +231,18 @@ v = list(v) return v +def buildtemplate(exp, context): + ctmpl = [compileexp(e, context, methods) for e in exp[1]] + if len(ctmpl) == 1: + return ctmpl[0] # fast path for string with no template fragment + return (runtemplate, ctmpl) + +def runtemplate(context, mapping, template): + for func, data in template: + yield func(context, mapping, data) + 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)) @@ -214,14 +264,10 @@ "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)) -def runtemplate(context, mapping, template): - for func, data in template: - yield func(context, mapping, data) - def runmap(context, mapping, data): func, data, ctmpl = data d = func(context, mapping, data) @@ -243,7 +289,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) @@ -311,8 +357,8 @@ # i18n: "fill" is a keyword raise error.ParseError(_("fill expects an integer width")) try: - initindent = stringify(_evalifliteral(args[2], context, mapping)) - hangindent = stringify(_evalifliteral(args[3], context, mapping)) + initindent = stringify(args[2][0](context, mapping, args[2][1])) + hangindent = stringify(args[3][0](context, mapping, args[3][1])) except IndexError: pass @@ -328,9 +374,6 @@ width = int(args[1][1]) text = stringify(args[0][0](context, mapping, args[0][1])) - if args[0][0] == runstring: - text = stringify(runtemplate(context, mapping, - compiletemplate(text, context))) right = False fillchar = ' ' @@ -344,6 +387,26 @@ else: return text.ljust(width, fillchar) +def indent(context, mapping, args): + """:indent(text, indentchars[, firstline]): Indents all non-empty lines + with the characters given in the indentchars string. An optional + third parameter will override the indent for the first line only + if present.""" + if not (2 <= len(args) <= 3): + # i18n: "indent" is a keyword + raise error.ParseError(_("indent() expects two or three arguments")) + + text = stringify(args[0][0](context, mapping, args[0][1])) + indent = stringify(args[1][0](context, mapping, args[1][1])) + + if len(args) == 3: + firstline = stringify(args[2][0](context, mapping, args[2][1])) + else: + firstline = indent + + # the indent function doesn't indent the first line, so we do it here + return templatefilters.indent(firstline + text, indent) + def get(context, mapping, args): """:get(dict, key): Get an attribute/key from an object. Some keywords are complex types. This function allows you to obtain the value of an @@ -360,15 +423,6 @@ key = args[1][0](context, mapping, args[1][1]) yield dictarg.get(key) -def _evalifliteral(arg, context, mapping): - # get back to token tag to reinterpret string as template - strtoken = {runstring: 'string', runrawstring: 'rawstring'}.get(arg[0]) - if strtoken: - yield runtemplate(context, mapping, - compiletemplate(arg[1], context, strtoken)) - else: - yield stringify(arg[0](context, mapping, arg[1])) - def if_(context, mapping, args): """:if(expr, then[, else]): Conditionally execute based on the result of an expression.""" @@ -378,9 +432,9 @@ test = stringify(args[0][0](context, mapping, args[0][1])) if test: - yield _evalifliteral(args[1], context, mapping) + yield args[1][0](context, mapping, args[1][1]) elif len(args) == 3: - yield _evalifliteral(args[2], context, mapping) + yield args[2][0](context, mapping, args[2][1]) def ifcontains(context, mapping, args): """:ifcontains(search, thing, then[, else]): Conditionally execute based @@ -393,9 +447,9 @@ items = args[1][0](context, mapping, args[1][1]) if item in items: - yield _evalifliteral(args[2], context, mapping) + yield args[2][0](context, mapping, args[2][1]) elif len(args) == 4: - yield _evalifliteral(args[3], context, mapping) + yield args[3][0](context, mapping, args[3][1]) def ifeq(context, mapping, args): """:ifeq(expr1, expr2, then[, else]): Conditionally execute based on @@ -407,9 +461,9 @@ test = stringify(args[0][0](context, mapping, args[0][1])) match = stringify(args[1][0](context, mapping, args[1][1])) if test == match: - yield _evalifliteral(args[2], context, mapping) + yield args[2][0](context, mapping, args[2][1]) elif len(args) == 4: - yield _evalifliteral(args[3], context, mapping) + yield args[3][0](context, mapping, args[3][1]) def join(context, mapping, args): """:join(list, sep): Join items in a list with a delimiter.""" @@ -443,7 +497,7 @@ raise error.ParseError(_("label expects two arguments")) # ignore args[0] (the label string) since this is supposed to be a a no-op - yield _evalifliteral(args[1], context, mapping) + yield args[1][0](context, mapping, args[1][1]) def revset(context, mapping, args): """:revset(query[, formatargs...]): Execute a revision set query. See @@ -559,7 +613,7 @@ pat = stringify(args[0][0](context, mapping, args[0][1])) rpl = stringify(args[1][0](context, mapping, args[1][1])) - src = stringify(_evalifliteral(args[2], context, mapping)) + src = stringify(args[2][0](context, mapping, args[2][1])) yield re.sub(pat, rpl, src) def startswith(context, mapping, args): @@ -587,8 +641,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])) @@ -601,17 +654,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), + "template": buildtemplate, + "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, @@ -620,6 +679,7 @@ "if": if_, "ifcontains": ifcontains, "ifeq": ifeq, + "indent": indent, "join": join, "label": label, "pad": pad, @@ -654,14 +714,11 @@ 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')) + return s[1:-1] class engine(object): '''template expansion engine. @@ -745,7 +802,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(): @@ -753,8 +810,8 @@ raise SyntaxError(_('%s: missing value') % conf.source('', key)) if val[0] in "'\"": try: - self.cache[key] = parsestring(val) - except SyntaxError, inst: + self.cache[key] = unquotestring(val) + except SyntaxError as inst: raise SyntaxError('%s: %s' % (conf.source('', key), inst.args[0])) else: @@ -771,10 +828,10 @@ if t not in self.cache: try: self.cache[t] = util.readfile(self.map[t][1]) - except KeyError, inst: + except KeyError as inst: raise TemplateNotFound(_('"%s" not in template map') % inst.args[0]) - except IOError, inst: + except IOError as inst: raise IOError(inst.args[0], _('template file %s: %s') % (self.map[t][1], inst.args[1])) return self.cache[t] diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/coal/map --- a/mercurial/templates/coal/map Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/coal/map Sat Jul 18 17:32:38 2015 -0500 @@ -43,12 +43,12 @@ filenav = '{before%filenaventry}{after%filenaventry}' direntry = ' - + - + dir. {basename|escape}/ - + {emptydirs|escape} @@ -57,9 +57,9 @@ ' fileentry = ' - + - + file {basename|escape} @@ -73,11 +73,11 @@ filecomparison = ../paper/filecomparison.tmpl filelog = ../paper/filelog.tmpl fileline = ' -
{linenumber} {line|escape}
' +
{linenumber} {line|escape}
' filelogentry = ../paper/filelogentry.tmpl annotateline = ' - + {author|user}@{rev} @@ -85,7 +85,7 @@ {linenumber} {line|escape} ' -diffblock = '
{lines}
' +diffblock = '
{lines}
' difflineplus = '{linenumber} {line|escape}' difflineminus = '{linenumber} {line|escape}' difflineat = '{linenumber} {line|escape}' @@ -156,38 +156,44 @@ ' tags = ../paper/tags.tmpl tagentry = ' - + - + {tag|escape} - {node|short} + + {node|short} + ' bookmarks = ../paper/bookmarks.tmpl bookmarkentry = ' - + - + {bookmark|escape} - {node|short} + + {node|short} + ' branches = ../paper/branches.tmpl branchentry = ' - + - + {branch|escape} - {node|short} + + {node|short} + ' changelogtag = '{name|escape} ' @@ -219,7 +225,7 @@ ' indexentry = ' - + {name|escape} {description} {contact|obfuscate} @@ -230,7 +236,7 @@ index = ../paper/index.tmpl archiveentry = '
  • - {type|escape} + {type|escape}
  • ' notfound = ../paper/notfound.tmpl error = ../paper/error.tmpl diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/bookmarks.tmpl --- a/mercurial/templates/gitweb/bookmarks.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/bookmarks.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,7 +20,7 @@ tags | bookmarks | branches | -files | +files | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/branches.tmpl --- a/mercurial/templates/gitweb/branches.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/branches.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,7 +20,7 @@ tags | bookmarks | branches | -files | +files | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/changelog.tmpl --- a/mercurial/templates/gitweb/changelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/changelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,13 +21,13 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/error.tmpl --- a/mercurial/templates/gitweb/error.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/error.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -19,7 +19,7 @@ tags | bookmarks | branches | -files | +files | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/fileannotate.tmpl --- a/mercurial/templates/gitweb/fileannotate.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/fileannotate.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,15 +20,15 @@ tags | bookmarks | branches | -files | -changeset | -file | +files | +changeset | +file | latest | -revisions | +revisions | annotate | -diff | -comparison | -raw | +diff | +comparison | +raw | help
    @@ -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 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/filecomparison.tmpl --- a/mercurial/templates/gitweb/filecomparison.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/filecomparison.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,15 +20,15 @@ tags | bookmarks | branches | -files | -changeset | -file | +files | +changeset | +file | latest | -revisions | -annotate | -diff | +revisions | +annotate | +diff | comparison | -raw | +raw | help
    @@ -39,7 +39,8 @@ {branch%filerevbranch} changeset {rev} - {node|short} + {node|short} + {parent%filecompparent} {child%filecompchild} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/filediff.tmpl --- a/mercurial/templates/gitweb/filediff.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/filediff.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,15 +20,15 @@ tags | bookmarks | branches | -files | -changeset | -file | +files | +changeset | +file | latest | -revisions | -annotate | +revisions | +annotate | diff | -comparison | -raw | +comparison | +raw | help
    @@ -39,7 +39,8 @@ {branch%filerevbranch} changeset {rev} - {node|short} + {node|short} + {parent%filediffparent} {child%filediffchild} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/filelog.tmpl --- a/mercurial/templates/gitweb/filelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/filelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,11 +20,11 @@ tags | bookmarks | branches | -file | +file | revisions | -annotate | -diff | -comparison | +annotate | +diff | +comparison | rss | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/filerevision.tmpl --- a/mercurial/templates/gitweb/filerevision.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/filerevision.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,15 +20,15 @@ tags | bookmarks | branches | -files | -changeset | +files | +changeset | file | latest | -revisions | -annotate | -diff | -comparison | -raw | +revisions | +annotate | +diff | +comparison | +raw | help
    @@ -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 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/graph.tmpl --- a/mercurial/templates/gitweb/graph.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/graph.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -22,16 +22,16 @@ @@ -103,8 +103,8 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/help.tmpl --- a/mercurial/templates/gitweb/help.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/help.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,5 @@ {header} -{repo|escape}: Branches +Help: {topic} tags | bookmarks | branches | -files | +files | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/helptopics.tmpl --- a/mercurial/templates/gitweb/helptopics.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/helptopics.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,5 @@ {header} -{repo|escape}: Branches +Help: {title} tags | bookmarks | branches | -files | +files | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/manifest.tmpl --- a/mercurial/templates/gitweb/manifest.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/manifest.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,7 +21,7 @@ bookmarks | branches | files | -changeset {archives%archiveentry} | +changeset {archives%archiveentry} | help
    @@ -32,7 +32,7 @@ drwxr-xr-x -[up] +[up]   {dentries%direntry} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/map --- a/mercurial/templates/gitweb/map Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/map Sat Jul 18 17:32:38 2015 -0500 @@ -66,11 +66,11 @@ - {basename|escape} - {emptydirs|escape} + {basename|escape} + {emptydirs|escape} - files + files ' fileentry = ' @@ -79,12 +79,12 @@ {date|isodate} {size} - {basename|escape} + {basename|escape} - file | - revisions | - annotate + file | + revisions | + annotate ' filerevision = filerevision.tmpl @@ -293,12 +293,17 @@ {desc|strip|firstline|escape|nonempty} + {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag} - file | diff | annotate {rename%filelogrename} - ' -archiveentry = ' | {type|escape} ' + file | + diff | + annotate + {rename%filelogrename} + + ' +archiveentry = ' | {type|escape} ' indexentry = ' diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/search.tmpl --- a/mercurial/templates/gitweb/search.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/search.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -27,7 +27,7 @@ tags | bookmarks | branches | -files{archives%archiveentry} +files{archives%archiveentry} | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/shortlog.tmpl --- a/mercurial/templates/gitweb/shortlog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/shortlog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,12 +21,12 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/summary.tmpl --- a/mercurial/templates/gitweb/summary.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/summary.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -26,7 +26,7 @@ tags | bookmarks | branches | -files{archives%archiveentry} | +files{archives%archiveentry} | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/gitweb/tags.tmpl --- a/mercurial/templates/gitweb/tags.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/gitweb/tags.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -20,7 +20,7 @@ tags | bookmarks | branches | -files | +files | help
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/map-cmdline.bisect --- a/mercurial/templates/map-cmdline.bisect Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/map-cmdline.bisect Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/templates/map-cmdline.default --- a/mercurial/templates/map-cmdline.default Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/map-cmdline.default Sat Jul 18 17:32:38 2015 -0500 @@ -71,3 +71,5 @@ 'description:')} {label('ui.note log.description', '{desc|strip}')}\n\n")}' + +status = '{status} {path}\n{if(copy, " {copy}\n")}' diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/map-cmdline.phases --- a/mercurial/templates/map-cmdline.phases Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/map-cmdline.phases Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/templates/map-cmdline.status --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templates/map-cmdline.status Sat Jul 18 17:32:38 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 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/bookmarks.tmpl --- a/mercurial/templates/monoblue/bookmarks.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/bookmarks.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/branches.tmpl --- a/mercurial/templates/monoblue/branches.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/branches.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/changelog.tmpl --- a/mercurial/templates/monoblue/changelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/changelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • {archives%archiveentry}
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/changeset.tmpl --- a/mercurial/templates/monoblue/changeset.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/changeset.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,18 +21,18 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • @@ -48,7 +48,7 @@
    {date|rfc822date}
    {branch%changesetbranch}
    changeset {rev}
    -
    {node|short}
    +
    {node|short}
    {ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)} {child%changesetchild} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/error.tmpl --- a/mercurial/templates/monoblue/error.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/error.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -18,14 +18,14 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/fileannotate.tmpl --- a/mercurial/templates/monoblue/fileannotate.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/fileannotate.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,22 +21,22 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/filecomparison.tmpl --- a/mercurial/templates/monoblue/filecomparison.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/filecomparison.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,22 +21,22 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/filediff.tmpl --- a/mercurial/templates/monoblue/filediff.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/filediff.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,22 +21,22 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/filelog.tmpl --- a/mercurial/templates/monoblue/filelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/filelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,21 +21,21 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/filerevision.tmpl --- a/mercurial/templates/monoblue/filerevision.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/filerevision.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,22 +21,22 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/graph.tmpl --- a/mercurial/templates/monoblue/graph.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/graph.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -26,7 +26,7 @@
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • @@ -99,8 +99,8 @@
    - less - more + less + more | {changenav%navgraph}
    diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/help.tmpl --- a/mercurial/templates/monoblue/help.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/help.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,5 @@ {header} - {repo|escape}: Branches + Help: {topic} @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/helptopics.tmpl --- a/mercurial/templates/monoblue/helptopics.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/helptopics.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,5 @@ {header} - {repo|escape}: Branches + Help: {title} @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/index.tmpl --- a/mercurial/templates/monoblue/index.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/index.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,5 @@ {header} - {repo|escape}: Mercurial repositories index + Mercurial repositories index diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/manifest.tmpl --- a/mercurial/templates/monoblue/manifest.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/manifest.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,7 +21,7 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • @@ -31,7 +31,7 @@ @@ -43,7 +43,7 @@ drwxr-xr-x - [up] + [up]   {dentries%direntry} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/map --- a/mercurial/templates/monoblue/map Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/map Sat Jul 18 17:32:38 2015 -0500 @@ -65,19 +65,22 @@ drwxr-xr-x - {basename|escape} - files + + {basename|escape} + {emptydirs|escape} + + files ' fileentry = ' {permissions|permissions} {date|isodate} {size} - {basename|escape} + {basename|escape} - file | - revisions | - annotate + file | + revisions | + annotate ' filerevision = filerevision.tmpl @@ -244,13 +247,18 @@ 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} ' -archiveentry = '
  • {type|escape}
  • ' +archiveentry = '
  • {type|escape}
  • ' indexentry = ' {name|escape} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/notfound.tmpl --- a/mercurial/templates/monoblue/notfound.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/notfound.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,5 @@ {header} - {repo|escape}: Mercurial repository not found + Mercurial repository not found @@ -18,14 +18,14 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/search.tmpl --- a/mercurial/templates/monoblue/search.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/search.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • {archives%archiveentry}
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/shortlog.tmpl --- a/mercurial/templates/monoblue/shortlog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/shortlog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • {archives%archiveentry}
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/summary.tmpl --- a/mercurial/templates/monoblue/summary.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/summary.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/monoblue/tags.tmpl --- a/mercurial/templates/monoblue/tags.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/monoblue/tags.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -21,11 +21,11 @@
  • summary
  • shortlog
  • changelog
  • -
  • graph
  • +
  • graph
  • tags
  • bookmarks
  • branches
  • -
  • files
  • +
  • files
  • help
  • diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/changeset.tmpl --- a/mercurial/templates/paper/changeset.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/changeset.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -9,16 +9,16 @@ mercurial
      {archives%archiveentry} @@ -31,7 +31,10 @@
      -

      changeset {rev}:{node|short} {changesetbranch%changelogbranchname} {changesettag} {changesetbookmark}

      +

      + changeset {rev}:{node|short} + {changesetbranch%changelogbranchname}{changesettag}{changesetbookmark} +

      • help
      • @@ -37,7 +37,10 @@
        -

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

        +

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

        {sessionvars%hiddenformentry} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/filecomparison.tmpl --- a/mercurial/templates/paper/filecomparison.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/filecomparison.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -10,24 +10,24 @@ mercurial
        • help
        • @@ -36,7 +36,10 @@
          -

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

          +

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

          {sessionvars%hiddenformentry}

          diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/filediff.tmpl --- a/mercurial/templates/paper/filediff.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/filediff.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -10,24 +10,24 @@ mercurial
          • help
          • @@ -36,7 +36,10 @@
            -

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

            +

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

            {sessionvars%hiddenformentry}

            diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/filelog.tmpl --- a/mercurial/templates/paper/filelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/filelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -14,23 +14,23 @@ mercurial
            • help
            • @@ -53,8 +53,8 @@ @@ -71,8 +71,8 @@
              diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/filelogentry.tmpl --- a/mercurial/templates/paper/filelogentry.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/filelogentry.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,8 @@ {date|rfc822date} {author|person} - {desc|strip|firstline|escape|nonempty}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{rename%filelogrename} + + {desc|strip|firstline|escape|nonempty} + {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}{rename%filelogrename} + diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/filerevision.tmpl --- a/mercurial/templates/paper/filerevision.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/filerevision.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -10,24 +10,24 @@ mercurial
              • help
              • @@ -36,7 +36,10 @@
                -

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

                +

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

                • help
                • @@ -46,8 +46,8 @@ @@ -116,8 +116,8 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/manifest.tmpl --- a/mercurial/templates/paper/manifest.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/manifest.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -10,14 +10,14 @@ mercurial
                    @@ -30,7 +30,10 @@
                    -

                    directory {path|escape} @ {rev}:{node|short} {tags%changelogtag}

                    +

                    + directory {path|escape} @ {rev}:{node|short} + {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag} +

                  + diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/shortlog.tmpl --- a/mercurial/templates/paper/shortlog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/shortlog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -15,14 +15,14 @@
                    {archives%archiveentry} @@ -48,8 +48,8 @@ @@ -67,8 +67,8 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/paper/shortlogentry.tmpl --- a/mercurial/templates/paper/shortlogentry.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/paper/shortlogentry.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,8 @@ {date|rfc822date} {author|person} - {desc|strip|firstline|escape|nonempty}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag} + + {desc|strip|firstline|escape|nonempty} + {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag} + diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/branches.tmpl --- a/mercurial/templates/spartan/branches.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/branches.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -12,7 +12,7 @@ shortlog graph tags -files +files help rss atom diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/changelog.tmpl --- a/mercurial/templates/spartan/changelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/changelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -8,11 +8,11 @@
                    -shortlog -graph +shortlog +graph tags branches -files +files {archives%archiveentry} help rss diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/changeset.tmpl --- a/mercurial/templates/spartan/changeset.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/changeset.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -4,13 +4,13 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/fileannotate.tmpl --- a/mercurial/templates/spartan/fileannotate.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/fileannotate.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -4,16 +4,16 @@ @@ -22,12 +22,14 @@ - + + {parent%fileannotateparent} {child%fileannotatechild} - + + diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/filediff.tmpl --- a/mercurial/templates/spartan/filediff.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/filediff.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -4,16 +4,16 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/filelog.tmpl --- a/mercurial/templates/spartan/filelog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/filelog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -13,8 +13,8 @@ graphtagsbranches -file -annotate +file +annotatehelprssatom diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/filerevision.tmpl --- a/mercurial/templates/spartan/filerevision.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/filerevision.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -4,16 +4,16 @@ @@ -22,18 +22,22 @@
                    changeset {rev}:{node|short}
                    {node|short}
                    author:{author|obfuscate}
                    {author|obfuscate}
                    date: {date|rfc822date}
                    - + + {parent%filerevparent} {child%filerevchild} - + + - + + - + + diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/graph.tmpl --- a/mercurial/templates/spartan/graph.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/graph.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -9,11 +9,11 @@ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/manifest.tmpl --- a/mercurial/templates/spartan/manifest.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/manifest.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -4,12 +4,12 @@ @@ -21,7 +21,7 @@ {dentries%direntry} {fentries%fileentry} diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/map --- a/mercurial/templates/spartan/map Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/map Sat Jul 18 17:32:38 2015 -0500 @@ -31,8 +31,8 @@ ' index = index.tmpl -archiveentry = '{type|escape} ' +archiveentry = '{type|escape} ' notfound = notfound.tmpl error = error.tmpl urlparameter = '{separator}{name}={value|urlescape}' diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/search.tmpl --- a/mercurial/templates/spartan/search.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/search.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -9,7 +9,7 @@ graphtagsbranches -files +files {archives%archiveentry} help diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/shortlog.tmpl --- a/mercurial/templates/spartan/shortlog.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/shortlog.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -8,11 +8,11 @@
                    -changelog -graph +changelog +graph tags branches -files +files {archives%archiveentry} help rss diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/spartan/tags.tmpl --- a/mercurial/templates/spartan/tags.tmpl Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/spartan/tags.tmpl Sat Jul 18 17:32:38 2015 -0500 @@ -12,7 +12,7 @@ shortlog graph branches -files +files help rss atom diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/static/style-coal.css --- a/mercurial/templates/static/style-coal.css Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/static/style-coal.css Sat Jul 18 17:32:38 2015 -0500 @@ -97,8 +97,12 @@ .age { white-space:nowrap; } .date { white-space:nowrap; } .indexlinks { white-space:nowrap; } -.parity0 { background-color: #f0f0f0; } -.parity1 { background-color: white; } +.parity0, +.stripes4 > :nth-child(4n+1), +.stripes2 > :nth-child(2n+1) { background-color: #f0f0f0; } +.parity1, +.stripes4 > :nth-child(4n+3), +.stripes2 > :nth-child(2n+2) { background-color: white; } .plusline { color: green; } .minusline { color: #dc143c; } /* crimson */ .atline { color: purple; } diff -r 501c51d60792 -r 96a38d44ba09 mercurial/templates/static/style-monoblue.css --- a/mercurial/templates/static/style-monoblue.css Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/templates/static/style-monoblue.css Sat Jul 18 17:32:38 2015 -0500 @@ -53,10 +53,8 @@ } div.page-header form { - position: absolute; - margin-bottom: 2px; - bottom: 0; - right: 20px; + float: right; + margin-top: -2px; } div.page-header form label { color: #DDD; @@ -83,7 +81,6 @@ margin: 10px 0 0 0; list-style-type: none; overflow: hidden; - width: 900px; } ul.page-nav li { margin: 0 2px 0 0; @@ -156,7 +153,7 @@ } div.page-footer p { position: relative; - left: 20px; + padding-left: 20px; bottom: 5px; font-size: 1.2em; } diff -r 501c51d60792 -r 96a38d44ba09 mercurial/transaction.py --- a/mercurial/transaction.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/transaction.py Sat Jul 18 17:32:38 2015 -0500 @@ -39,7 +39,7 @@ else: try: opener.unlink(f) - except (IOError, OSError), inst: + except (IOError, OSError) as inst: if inst.errno != errno.ENOENT: raise @@ -62,10 +62,10 @@ target = f or b try: vfs.unlink(target) - except (IOError, OSError), inst: + except (IOError, OSError) as inst: if inst.errno != errno.ENOENT: raise - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: if not c: raise @@ -77,7 +77,7 @@ for f in backupfiles: if opener.exists(f): opener.unlink(f) - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: # only pure backup file remains, it is sage to ignore any error pass @@ -130,8 +130,8 @@ self._backupsfile.write('%d\n' % version) if createmode is not None: - opener.chmod(self.journal, createmode & 0666) - opener.chmod(self._backupjournal, createmode & 0666) + opener.chmod(self.journal, createmode & 0o666) + opener.chmod(self._backupjournal, createmode & 0o666) # hold file generations to be performed on commit self._filegenerators = {} @@ -405,7 +405,7 @@ if not f and b and vfs.exists(b): try: vfs.unlink(b) - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: if not c: raise # Abort may be raise by read only opener @@ -428,7 +428,7 @@ if b and vfs.exists(b): try: vfs.unlink(b) - except (IOError, OSError, util.Abort), inst: + except (IOError, OSError, util.Abort) as inst: if not c: raise # Abort may be raise by read only opener @@ -496,7 +496,7 @@ _playback(self.journal, self.report, self.opener, self._vfsmap, self.entries, self._backupentries, False) self.report(_("rollback completed\n")) - except Exception: + except BaseException: self.report(_("rollback failed - please run hg recover\n")) finally: self.journal = None diff -r 501c51d60792 -r 96a38d44ba09 mercurial/treediscovery.py --- a/mercurial/treediscovery.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/treediscovery.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,6 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import collections from node import nullid, short from i18n import _ import util, error @@ -56,7 +57,7 @@ # a 'branch' here is a linear segment of history, with four parts: # head, root, first parent, second parent # (a branch always has two parents (or none) by definition) - unknown = util.deque(remote.branches(unknown)) + unknown = collections.deque(remote.branches(unknown)) while unknown: r = [] while unknown: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/ui.py --- a/mercurial/ui.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/ui.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,9 +5,10 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import inspect from i18n import _ import errno, getpass, os, socket, sys, tempfile, traceback -import config, scmutil, util, error, formatter +import config, scmutil, util, error, formatter, progress from node import hex samplehgrcs = { @@ -152,7 +153,7 @@ try: cfg.read(filename, fp, sections=sections, remap=remap) fp.close() - except error.ConfigError, inst: + except error.ConfigError as inst: if trusted: raise self.warn(_("ignored: %s\n") % str(inst)) @@ -584,6 +585,7 @@ "cmdname.type" is recommended. For example, status issues a label of "status.modified" for modified files. ''' + self._progclear() if self._buffers: self._buffers[-1].extend([str(a) for a in args]) else: @@ -591,6 +593,7 @@ self.fout.write(str(a)) def write_err(self, *args, **opts): + self._progclear() try: if self._bufferstates and self._bufferstates[-1][0]: return self.write(*args, **opts) @@ -602,7 +605,7 @@ # including stdout. if not getattr(self.ferr, 'closed', False): self.ferr.flush() - except IOError, inst: + except IOError as inst: if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): raise @@ -842,7 +845,7 @@ output will be redirected if fout is not stdout. ''' out = self.fout - if util.any(s[1] for s in self._bufferstates): + if any(s[1] for s in self._bufferstates): out = self return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr, errprefix=errprefix, out=out) @@ -867,8 +870,8 @@ ''.join(causetb), ''.join(exconly)) else: - traceback.print_exception(exc[0], exc[1], exc[2], - file=self.ferr) + output = traceback.format_exception(exc[0], exc[1], exc[2]) + self.write_err(''.join(output)) return self.tracebackflag or force def geteditor(self): @@ -885,6 +888,22 @@ os.environ.get("VISUAL") or os.environ.get("EDITOR", editor)) + @util.propertycache + def _progbar(self): + """setup the progbar singleton to the ui object""" + if (self.quiet or self.debugflag + or self.configbool('progress', 'disable', False) + or not progress.shouldprint(self)): + return None + return getprogbar(self) + + def _progclear(self): + """clear progress bar output if any. use it before any output""" + if '_progbar' not in vars(self): # nothing loadef yet + return + if self._progbar is not None and self._progbar.printed: + self._progbar.clear() + def progress(self, topic, pos, item="", unit="", total=None): '''show a progress message @@ -901,8 +920,10 @@ All topics should be marked closed by setting pos to None at termination. ''' - - if pos is None or not self.debugflag: + if self._progbar is not None: + self._progbar.progress(topic, pos, item=item, unit=unit, + total=total) + if pos is None or not self.configbool('progress', 'debug'): return if unit: @@ -938,6 +959,16 @@ ''' return msg + def develwarn(self, msg): + """issue a developer warning message""" + msg = 'devel-warn: ' + msg + if self.tracebackflag: + util.debugstacktrace(msg, 2) + else: + curframe = inspect.currentframe() + calframe = inspect.getouterframes(curframe, 2) + self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4])) + class paths(dict): """Represents a collection of paths and their configs. @@ -982,3 +1013,15 @@ self.name = name # We'll do more intelligent things with rawloc in the future. self.loc = rawloc + +# we instantiate one globally shared progress bar to avoid +# competing progress bars when multiple UI objects get created +_progresssingleton = None + +def getprogbar(ui): + global _progresssingleton + if _progresssingleton is None: + # passing 'ui' object to the singleton is fishy, + # this is how the extension used to work but feel free to rework it. + _progresssingleton = progress.progbar(ui) + return _progresssingleton diff -r 501c51d60792 -r 96a38d44ba09 mercurial/url.py --- a/mercurial/url.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/url.py Sat Jul 18 17:32:38 2015 -0500 @@ -120,16 +120,6 @@ if e.startswith('.') and host.endswith(e[1:]): return None - # work around a bug in Python < 2.4.2 - # (it leaves a "\n" at the end of Proxy-authorization headers) - baseclass = req.__class__ - class _request(baseclass): - def add_header(self, key, val): - if key.lower() == 'proxy-authorization': - val = val.strip() - return baseclass.add_header(self, key, val) - req.__class__ = _request - return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) def _gen_sendfile(orgsend): @@ -169,7 +159,7 @@ sock.connect(sa) return sock - except socket.error, msg: + except socket.error as msg: if sock is not None: sock.close() @@ -185,8 +175,8 @@ self.sock.connect((self.host, self.port)) if _generic_proxytunnel(self): # we do not support client X.509 certificates - self.sock = sslutil.ssl_wrap_socket(self.sock, None, None, - serverhostname=self.host) + self.sock = sslutil.wrapsocket(self.sock, None, None, None, + serverhostname=self.host) else: keepalive.HTTPConnection.connect(self) @@ -328,11 +318,18 @@ return keepalive.HTTPHandler._start_transaction(self, h, req) if has_https: - class httpsconnection(httplib.HTTPSConnection): + class httpsconnection(httplib.HTTPConnection): response_class = keepalive.HTTPResponse + default_port = httplib.HTTPS_PORT # must be able to send big bundle as stream. send = _gen_sendfile(keepalive.safesend) - getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection) + getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection) + + def __init__(self, host, port=None, key_file=None, cert_file=None, + *args, **kwargs): + httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs) + self.key_file = key_file + self.cert_file = cert_file def connect(self): self.sock = _create_connection((self.host, self.port)) @@ -341,7 +338,7 @@ if self.realhostport: # use CONNECT proxy _generic_proxytunnel(self) host = self.realhostport.rsplit(':', 1)[0] - self.sock = sslutil.ssl_wrap_socket( + self.sock = sslutil.wrapsocket( self.sock, self.key_file, self.cert_file, serverhostname=host, **sslutil.sslkwargs(self.ui, host)) sslutil.validator(self.ui, host)(self.sock) @@ -414,7 +411,7 @@ try: return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed( self, auth_header, host, req, headers) - except ValueError, inst: + except ValueError as inst: arg = inst.args[0] if arg.startswith("AbstractDigestAuthHandler doesn't know "): return @@ -472,6 +469,7 @@ construct an opener suitable for urllib2 authinfo will be added to the password manager ''' + # experimental config: ui.usehttp2 if ui.configbool('ui', 'usehttp2', False): handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))] else: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/util.h --- a/mercurial/util.h Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/util.h Sat Jul 18 17:32:38 2015 -0500 @@ -57,66 +57,6 @@ #endif /* PY_MAJOR_VERSION */ -/* Backports from 2.6 */ -#if PY_VERSION_HEX < 0x02060000 - -#define Py_TYPE(ob) (ob)->ob_type -#define Py_SIZE(ob) (ob)->ob_size -#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, - -/* Shamelessly stolen from bytesobject.h */ -#define PyBytesObject PyStringObject -#define PyBytes_Type PyString_Type - -#define PyBytes_Check PyString_Check -#define PyBytes_CheckExact PyString_CheckExact -#define PyBytes_CHECK_INTERNED PyString_CHECK_INTERNED -#define PyBytes_AS_STRING PyString_AS_STRING -#define PyBytes_GET_SIZE PyString_GET_SIZE -#define Py_TPFLAGS_BYTES_SUBCLASS Py_TPFLAGS_STRING_SUBCLASS - -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_FromString PyString_FromString -#define PyBytes_FromFormatV PyString_FromFormatV -#define PyBytes_FromFormat PyString_FromFormat -#define PyBytes_Size PyString_Size -#define PyBytes_AsString PyString_AsString -#define PyBytes_Repr PyString_Repr -#define PyBytes_Concat PyString_Concat -#define PyBytes_ConcatAndDel PyString_ConcatAndDel -#define _PyBytes_Resize _PyString_Resize -#define _PyBytes_Eq _PyString_Eq -#define PyBytes_Format PyString_Format -#define _PyBytes_FormatLong _PyString_FormatLong -#define PyBytes_DecodeEscape PyString_DecodeEscape -#define _PyBytes_Join _PyString_Join -#define PyBytes_Decode PyString_Decode -#define PyBytes_Encode PyString_Encode -#define PyBytes_AsEncodedObject PyString_AsEncodedObject -#define PyBytes_AsEncodedString PyString_AsEncodedString -#define PyBytes_AsDecodedObject PyString_AsDecodedObject -#define PyBytes_AsDecodedString PyString_AsDecodedString -#define PyBytes_AsStringAndSize PyString_AsStringAndSize -#define _PyBytes_InsertThousandsGrouping _PyString_InsertThousandsGrouping - -#endif /* PY_VERSION_HEX */ - -#if (PY_VERSION_HEX < 0x02050000) -/* Definitions to get compatibility with python 2.4 and earlier which - does not have Py_ssize_t. See also PEP 353. - Note: msvc (8 or earlier) does not have ssize_t, so we use Py_ssize_t. -*/ -typedef int Py_ssize_t; -typedef Py_ssize_t (*lenfunc)(PyObject *); -typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); -#define PyInt_FromSsize_t PyInt_FromLong - -#if !defined(PY_SSIZE_T_MIN) -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif -#endif - #ifdef _WIN32 #ifdef _MSC_VER /* msvc 6.0 has problems */ diff -r 501c51d60792 -r 96a38d44ba09 mercurial/util.py --- a/mercurial/util.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/util.py Sat Jul 18 17:32:38 2015 -0500 @@ -19,7 +19,7 @@ import errno, shutil, sys, tempfile, traceback import re as remod import os, time, datetime, calendar, textwrap, signal, collections -import imp, socket, urllib, struct +import imp, socket, urllib import gc if os.name == 'nt': @@ -54,6 +54,7 @@ oslink = platform.oslink parsepatchoutput = platform.parsepatchoutput pconvert = platform.pconvert +poll = platform.poll popen = platform.popen posixfile = platform.posixfile quotecommand = platform.quotecommand @@ -232,14 +233,101 @@ import subprocess closefds = os.name == 'posix' -def unpacker(fmt): - """create a struct unpacker for the specified format""" - try: - # 2.5+ - return struct.Struct(fmt).unpack - except AttributeError: - # 2.4 - return lambda buf: struct.unpack(fmt, buf) +_chunksize = 4096 + +class bufferedinputpipe(object): + """a manually buffered input pipe + + Python will not let us use buffered IO and lazy reading with 'polling' at + the same time. We cannot probe the buffer state and select will not detect + that data are ready to read if they are already buffered. + + This class let us work around that by implementing its own buffering + (allowing efficient readline) while offering a way to know if the buffer is + empty from the output (allowing collaboration of the buffer with polling). + + This class lives in the 'util' module because it makes use of the 'os' + module from the python stdlib. + """ + + def __init__(self, input): + self._input = input + self._buffer = [] + self._eof = False + self._lenbuf = 0 + + @property + def hasbuffer(self): + """True is any data is currently buffered + + This will be used externally a pre-step for polling IO. If there is + already data then no polling should be set in place.""" + return bool(self._buffer) + + @property + def closed(self): + return self._input.closed + + def fileno(self): + return self._input.fileno() + + def close(self): + return self._input.close() + + def read(self, size): + while (not self._eof) and (self._lenbuf < size): + self._fillbuffer() + return self._frombuffer(size) + + def readline(self, *args, **kwargs): + if 1 < len(self._buffer): + # this should not happen because both read and readline end with a + # _frombuffer call that collapse it. + self._buffer = [''.join(self._buffer)] + self._lenbuf = len(self._buffer[0]) + lfi = -1 + if self._buffer: + lfi = self._buffer[-1].find('\n') + while (not self._eof) and lfi < 0: + self._fillbuffer() + if self._buffer: + lfi = self._buffer[-1].find('\n') + size = lfi + 1 + if lfi < 0: # end of file + size = self._lenbuf + elif 1 < len(self._buffer): + # we need to take previous chunks into account + size += self._lenbuf - len(self._buffer[-1]) + return self._frombuffer(size) + + def _frombuffer(self, size): + """return at most 'size' data from the buffer + + The data are removed from the buffer.""" + if size == 0 or not self._buffer: + return '' + buf = self._buffer[0] + if 1 < len(self._buffer): + buf = ''.join(self._buffer) + + data = buf[:size] + buf = buf[len(data):] + if buf: + self._buffer = [buf] + self._lenbuf = len(buf) + else: + self._buffer = [] + self._lenbuf = 0 + return data + + def _fillbuffer(self): + """read data to the buffer""" + data = os.read(self._input.fileno(), _chunksize) + if not data: + self._eof = True + else: + self._lenbuf += len(data) + self._buffer.append(data) def popen2(cmd, env=None, newlines=False): # Setting bufsize to -1 lets the system decide the buffer size. @@ -256,8 +344,8 @@ stdin, stdout, stderr, p = popen4(cmd, env, newlines) return stdin, stdout, stderr -def popen4(cmd, env=None, newlines=False): - p = subprocess.Popen(cmd, shell=True, bufsize=-1, +def popen4(cmd, env=None, newlines=False, bufsize=-1): + p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, close_fds=closefds, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -334,18 +422,6 @@ return f -try: - collections.deque.remove - deque = collections.deque -except AttributeError: - # python 2.4 lacks deque.remove - class deque(collections.deque): - def remove(self, val): - for i, v in enumerate(self): - if v == val: - del self[i] - break - class sortdict(dict): '''a simple sorted dictionary''' def __init__(self, data=None): @@ -396,7 +472,7 @@ def __init__(self, maxsize): self._cache = {} self._maxsize = maxsize - self._order = deque() + self._order = collections.deque() def __getitem__(self, key): value = self._cache[key] @@ -418,12 +494,12 @@ def clear(self): self._cache.clear() - self._order = deque() + self._order = collections.deque() def lrucachefunc(func): '''cache most recent results of function calls''' cache = {} - order = deque() + order = collections.deque() if func.func_code.co_argcount == 1: def f(arg): if arg not in cache: @@ -739,7 +815,7 @@ try: shutil.copyfile(src, dest) shutil.copymode(src, dest) - except shutil.Error, inst: + except shutil.Error as inst: raise Abort(str(inst)) def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None): @@ -838,7 +914,7 @@ def makelock(info, pathname): try: return os.symlink(info, pathname) - except OSError, why: + except OSError as why: if why.errno == errno.EEXIST: raise except AttributeError: # no symlink in os @@ -851,7 +927,7 @@ def readlock(pathname): try: return os.readlink(pathname) - except OSError, why: + except OSError as why: if why.errno not in (errno.EINVAL, errno.ENOSYS): raise except AttributeError: # no symlink in os @@ -1003,15 +1079,13 @@ f2 = testfile + ".hgtmp2" fd = None try: - try: - oslink(f1, f2) - except OSError: - return False - + oslink(f1, f2) # nlinks() may behave differently for files on Windows shares if # the file is open. fd = posixfile(f2) return nlinks(f2) > 1 + except OSError: + return False finally: if fd is not None: fd.close() @@ -1070,7 +1144,7 @@ try: try: ifp = posixfile(name, "rb") - except IOError, inst: + except IOError as inst: if inst.errno == errno.ENOENT: return temp if not getattr(inst, 'filename', None): @@ -1129,7 +1203,7 @@ """recursive directory creation with parent mode inheritance""" try: makedir(name, notindexed) - except OSError, err: + except OSError as err: if err.errno == errno.EEXIST: return if err.errno != errno.ENOENT or not name: @@ -1156,7 +1230,7 @@ ensuredirs(parent, mode, notindexed) try: makedir(name, notindexed) - except OSError, err: + except OSError as err: if err.errno == errno.EEXIST and os.path.isdir(name): # someone else seems to have won a directory creation race return @@ -1203,7 +1277,7 @@ else: yield chunk self.iter = splitbig(in_iter) - self._queue = deque() + self._queue = collections.deque() def read(self, l=None): """Read L bytes of data from the iterator of chunks of data. @@ -1573,13 +1647,6 @@ This requires use decision to determine width of such characters. """ - def __init__(self, **kwargs): - textwrap.TextWrapper.__init__(self, **kwargs) - - # for compatibility between 2.4 and 2.6 - if getattr(self, 'drop_whitespace', None) is None: - self.drop_whitespace = kwargs.get('drop_whitespace', True) - def _cutdown(self, ucstr, space_left): l = 0 colwidth = encoding.ucolwidth @@ -1734,21 +1801,6 @@ if prevhandler is not None: signal.signal(signal.SIGCHLD, prevhandler) -try: - any, all = any, all -except NameError: - def any(iterable): - for i in iterable: - if i: - return True - return False - - def all(iterable): - for i in iterable: - if not i: - return False - return True - def interpolate(prefix, mapping, s, fn=None, escape_prefix=False): """Return the result of interpolating items in the mapping into string s. diff -r 501c51d60792 -r 96a38d44ba09 mercurial/verify.py --- a/mercurial/verify.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/verify.py Sat Jul 18 17:32:38 2015 -0500 @@ -108,7 +108,7 @@ if p2 not in seen and p2 != nullid: err(lr, _("unknown parent 2 %s of %s") % (short(p2), short(node)), f) - except Exception, inst: + except Exception as inst: exc(lr, _("checking parents of %s") % short(node), inst, f) if node in seen: @@ -144,7 +144,7 @@ refersmf = True for f in changes[3]: filelinkrevs.setdefault(_normpath(f), []).append(i) - except Exception, inst: + except Exception as inst: refersmf = True exc(i, _("unpacking changeset %s") % short(n), inst) ui.progress(_('checking'), None) @@ -171,7 +171,7 @@ err(lr, _("file without name in manifest")) elif f != "/dev/null": # ignore this in very old repos filenodes.setdefault(_normpath(f), {}).setdefault(fn, lr) - except Exception, inst: + except Exception as inst: exc(lr, _("reading manifest delta %s") % short(n), inst) ui.progress(_('checking'), None) @@ -219,6 +219,7 @@ elif size > 0 or not revlogv1: storefiles.add(_normpath(f)) + fncachewarned = False files = sorted(set(filenodes) | set(filelinkrevs)) total = len(files) for i, f in enumerate(files): @@ -236,7 +237,7 @@ try: fl = repo.file(f) - except error.RevlogError, e: + except error.RevlogError as e: err(lr, _("broken revlog! (%s)") % e, f) continue @@ -244,7 +245,8 @@ try: storefiles.remove(ff) except KeyError: - err(lr, _("missing revlog!"), ff) + warn(_(" warning: revlog '%s' not in fncache!") % ff) + fncachewarned = True checklog(fl, f, lr) seen = {} @@ -268,9 +270,10 @@ err(lr, _("unpacked size is %s, %s expected") % (l, fl.size(i)), f) except error.CensoredNodeError: + # experimental config: censor.policy if ui.config("censor", "policy", "abort") == "abort": err(lr, _("censored file data"), f) - except Exception, inst: + except Exception as inst: exc(lr, _("unpacking %s") % short(n), inst, f) # check renames @@ -296,7 +299,7 @@ % (f, lr, rp[0], short(rp[1]))) else: fl2.rev(rp[1]) - except Exception, inst: + except Exception as inst: exc(lr, _("checking rename of %s") % short(n), inst, f) # cross-check @@ -313,6 +316,9 @@ (len(files), len(cl), revisions)) if warnings[0]: ui.warn(_("%d warnings encountered!\n") % warnings[0]) + if fncachewarned: + ui.warn(_('hint: run "hg debugrebuildfncache" to recover from ' + 'corrupt fncache\n')) if errors[0]: ui.warn(_("%d integrity errors encountered!\n") % errors[0]) if badrevs: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/win32.py --- a/mercurial/win32.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/win32.py Sat Jul 18 17:32:38 2015 -0500 @@ -467,7 +467,7 @@ try: os.rename(f, temp) # raises OSError EEXIST if temp exists break - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise else: diff -r 501c51d60792 -r 96a38d44ba09 mercurial/windows.py --- a/mercurial/windows.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/windows.py Sat Jul 18 17:32:38 2015 -0500 @@ -25,8 +25,7 @@ testpid = win32.testpid unlink = win32.unlink -umask = 0022 -_SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5 +umask = 0o022 def posixfile(name, mode='r', buffering=-1): '''Open a file with even more POSIX-like semantics''' @@ -36,10 +35,10 @@ # The position when opening in append mode is implementation defined, so # make it consistent with other platforms, which position at EOF. if 'a' in mode: - fp.seek(0, _SEEK_END) + fp.seek(0, os.SEEK_END) return fp - except WindowsError, err: + except WindowsError as err: # convert to a friendlier exception raise IOError(err.errno, '%s: %s' % (name, err.strerror)) @@ -70,7 +69,7 @@ end = start + limit self.fp.write(s[start:end]) start = end - except IOError, inst: + except IOError as inst: if inst.errno != 0: raise self.close() @@ -79,7 +78,7 @@ def flush(self): try: return self.fp.flush() - except IOError, inst: + except IOError as inst: if inst.errno != errno.EINVAL: raise self.close() @@ -139,7 +138,7 @@ return pconvert(os.path.normpath(path)) def normcase(path): - return encoding.upper(path) + return encoding.upper(path) # NTFS compares via upper() # see posix.py for definitions normcasespec = encoding.normcasespecs.upper @@ -162,6 +161,18 @@ _quotere = None _needsshellquote = None def shellquote(s): + r""" + >>> shellquote(r'C:\Users\xyz') + '"C:\\Users\\xyz"' + >>> shellquote(r'C:\Users\xyz/mixed') + '"C:\\Users\\xyz/mixed"' + >>> # Would be safe not to quote too, since it is all double backslashes + >>> shellquote(r'C:\\Users\\xyz') + '"C:\\\\Users\\\\xyz"' + >>> # But this must be quoted + >>> shellquote(r'C:\\Users\\xyz/abc') + '"C:\\\\Users\\\\xyz/abc"' + """ global _quotere if _quotere is None: _quotere = re.compile(r'(\\*)("|\\$)') @@ -248,12 +259,10 @@ dmap = dict([(normcase(n), s) for n, k, s in osutil.listdir(dir, True) if getkind(s.st_mode) in _wantedkinds]) - except OSError, err: - # handle directory not found in Python version prior to 2.5 - # Python <= 2.4 returns native Windows code 3 in errno + except OSError as err: # Python >= 2.5 returns ENOENT and adds winerror field # EINVAL is raised if dir is not a directory. - if err.errno not in (3, errno.ENOENT, errno.EINVAL, + if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR): raise dmap = {} @@ -294,7 +303,7 @@ """unlink and remove the directory if it is empty""" try: unlink(f) - except OSError, e: + except OSError as e: if not (ignoremissing and e.errno == errno.ENOENT): raise # try removing directories that might now be empty @@ -307,7 +316,7 @@ '''atomically rename file src to dst, replacing dst if it exists''' try: os.rename(src, dst) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise unlink(dst) @@ -361,6 +370,10 @@ '''check whether a stat result is an executable file''' return False +def poll(fds): + # see posix.py for description + raise NotImplementedError() + def readpipe(pipe): """Read all available data from a pipe.""" chunks = [] diff -r 501c51d60792 -r 96a38d44ba09 mercurial/wireproto.py --- a/mercurial/wireproto.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/wireproto.py Sat Jul 18 17:32:38 2015 -0500 @@ -9,7 +9,7 @@ from i18n import _ from node import bin, hex import changegroup as changegroupmod, bundle2, pushkey as pushkeymod -import peer, error, encoding, util, store, exchange +import peer, error, encoding, util, exchange class abstractserverproto(object): @@ -175,24 +175,23 @@ try: return sep.join(map(hex, l)) except TypeError: - print l raise # batched call argument encoding def escapearg(plain): return (plain - .replace(':', '::') - .replace(',', ':,') - .replace(';', ':;') - .replace('=', ':=')) + .replace(':', ':c') + .replace(',', ':o') + .replace(';', ':s') + .replace('=', ':e')) def unescapearg(escaped): return (escaped - .replace(':=', '=') - .replace(':;', ';') - .replace(':,', ',') - .replace('::', ':')) + .replace(':e', '=') + .replace(':s', ';') + .replace(':o', ',') + .replace(':c', ':')) # mapping of options accepted by getbundle and their types # @@ -203,11 +202,12 @@ # # :nodes: list of binary nodes # :csv: list of comma-separated values +# :scsv: list of comma-separated values return as set # :plain: string with no transformation needed. gboptsmap = {'heads': 'nodes', 'common': 'nodes', 'obsmarkers': 'boolean', - 'bundlecaps': 'csv', + 'bundlecaps': 'scsv', 'listkeys': 'csv', 'cg': 'boolean'} @@ -220,10 +220,11 @@ def _submitbatch(self, req): cmds = [] for op, argsdict in req: - args = ','.join('%s=%s' % p for p in argsdict.iteritems()) + args = ','.join('%s=%s' % (escapearg(k), escapearg(v)) + for k, v in argsdict.iteritems()) cmds.append('%s %s' % (op, args)) rsp = self._call("batch", cmds=';'.join(cmds)) - return rsp.split(';') + return [unescapearg(r) for r in rsp.split(';')] def _submitone(self, op, args): return self._call(op, **args) @@ -324,6 +325,8 @@ self.ui.debug('preparing listkeys for "%s"\n' % namespace) yield {'namespace': encoding.fromlocal(namespace)}, f d = f.value + self.ui.debug('received listkey for "%s": %i bytes\n' + % (namespace, len(d))) yield pushkeymod.decodekeys(d) def stream_out(self): @@ -345,6 +348,11 @@ def getbundle(self, source, **kwargs): self.requirecap('getbundle', _('look up remote changes')) opts = {} + bundlecaps = kwargs.get('bundlecaps') + if bundlecaps is not None: + kwargs['bundlecaps'] = sorted(bundlecaps) + else: + bundlecaps = () # kwargs could have it to None for key, value in kwargs.iteritems(): if value is None: continue @@ -353,7 +361,7 @@ assert False, 'unexpected' elif keytype == 'nodes': value = encodelist(value) - elif keytype == 'csv': + elif keytype in ('csv', 'scsv'): value = ','.join(value) elif keytype == 'boolean': value = '%i' % bool(value) @@ -362,10 +370,7 @@ % keytype) opts[key] = value f = self._callcompressable("getbundle", **opts) - bundlecaps = kwargs.get('bundlecaps') - if bundlecaps is None: - bundlecaps = () # kwargs could have it to None - if util.any((cap.startswith('HG2') for cap in bundlecaps)): + if any((cap.startswith('HG2') for cap in bundlecaps)): return bundle2.getunbundler(self.ui, f) else: return changegroupmod.cg1unpacker(f, 'UN') @@ -619,7 +624,8 @@ capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo)) caps.append('bundle2=' + urllib.quote(capsblob)) caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority)) - caps.append('httpheader=1024') + caps.append( + 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024)) return caps # If you are writing an extension and consider wrapping this function. Wrap @@ -661,6 +667,8 @@ if keytype == 'nodes': opts[k] = decodelist(v) elif keytype == 'csv': + opts[k] = list(v.split(',')) + elif keytype == 'scsv': opts[k] = set(v.split(',')) elif keytype == 'boolean': opts[k] = bool(v) @@ -698,7 +706,7 @@ c = repo[k] r = c.hex() success = 1 - except Exception, inst: + except Exception as inst: r = str(inst) success = 0 return "%s %s\n" % (success, r) @@ -742,76 +750,27 @@ def _allowstream(ui): return ui.configbool('server', 'uncompressed', True, untrusted=True) -def _walkstreamfiles(repo): - # this is it's own function so extensions can override it - return repo.store.walk() - @wireprotocommand('stream_out') def stream(repo, proto): '''If the server supports streaming clone, it advertises the "stream" capability with a value representing the version and flags of the repo it is serving. Client checks to see if it understands the format. - - The format is simple: the server writes out a line with the amount - of files, then the total amount of bytes to be transferred (separated - by a space). Then, for each file, the server first writes the filename - and file size (separated by the null character), then the file contents. ''' - if not _allowstream(repo.ui): return '1\n' - entries = [] - total_bytes = 0 - try: - # 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() - except error.LockError: - return '2\n' # error: 2 - - def streamer(repo, entries, total): - '''stream out all metadata files in repository.''' - yield '0\n' # success - repo.ui.debug('%d files, %d bytes to transfer\n' % - (len(entries), total_bytes)) - yield '%d %d\n' % (len(entries), total_bytes) + def getstream(it): + yield '0\n' + for chunk in it: + yield chunk - 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 - # replace with "finally:" when support for python 2.4 has been dropped - except Exception: - sopener.mustaudit = oldaudit - raise - sopener.mustaudit = oldaudit - - return streamres(streamer(repo, entries, total_bytes)) + try: + # LockError may be raised before the first result is yielded. Don't + # emit output until we're sure we got the lock successfully. + it = exchange.generatestreamclone(repo) + return streamres(getstream(it)) + except error.LockError: + return '2\n' @wireprotocommand('unbundle', 'heads') def unbundle(repo, proto, heads): @@ -842,7 +801,7 @@ fp.close() os.unlink(tempname) - except (error.BundleValueError, util.Abort, error.PushRaced), exc: + except (error.BundleValueError, util.Abort, error.PushRaced) as exc: # handle non-bundle2 case first if not getattr(exc, 'duringunbundle2', False): try: @@ -861,20 +820,40 @@ for out in getattr(exc, '_bundle2salvagedoutput', ()): bundler.addpart(out) try: - raise - except error.BundleValueError, exc: + try: + raise + except error.PushkeyFailed as exc: + # check client caps + remotecaps = getattr(exc, '_replycaps', None) + if (remotecaps is not None + and 'pushkey' not in remotecaps.get('error', ())): + # no support remote side, fallback to Abort handler. + raise + part = bundler.newpart('error:pushkey') + part.addparam('in-reply-to', exc.partid) + if exc.namespace is not None: + part.addparam('namespace', exc.namespace, mandatory=False) + if exc.key is not None: + part.addparam('key', exc.key, mandatory=False) + if exc.new is not None: + part.addparam('new', exc.new, mandatory=False) + if exc.old is not None: + part.addparam('old', exc.old, mandatory=False) + if exc.ret is not None: + part.addparam('ret', exc.ret, mandatory=False) + except error.BundleValueError as exc: errpart = bundler.newpart('error:unsupportedcontent') if exc.parttype is not None: errpart.addparam('parttype', exc.parttype) if exc.params: errpart.addparam('params', '\0'.join(exc.params)) - except util.Abort, exc: + except util.Abort as exc: manargs = [('message', str(exc))] advargs = [] if exc.hint is not None: advargs.append(('hint', exc.hint)) bundler.addpart(bundle2.bundlepart('error:abort', manargs, advargs)) - except error.PushRaced, exc: + except error.PushRaced as exc: bundler.newpart('error:pushraced', [('message', str(exc))]) return streamres(bundler.getchunks()) diff -r 501c51d60792 -r 96a38d44ba09 mercurial/worker.py --- a/mercurial/worker.py Fri Jul 03 18:10:58 2015 +0100 +++ b/mercurial/worker.py Sat Jul 18 17:32:38 2015 -0500 @@ -101,7 +101,7 @@ for p in pids: try: os.kill(p, signal.SIGTERM) - except OSError, err: + except OSError as err: if err.errno != errno.ESRCH: raise def waitforworkers(): diff -r 501c51d60792 -r 96a38d44ba09 setup.py --- a/setup.py Fri Jul 03 18:10:58 2015 +0100 +++ b/setup.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,8 +5,8 @@ # 'python setup.py --help' for more options import sys, platform -if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'): - raise SystemExit("Mercurial requires Python 2.4 or later.") +if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'): + raise SystemExit("Mercurial requires Python 2.6 or later.") if sys.version_info[0] >= 3: def b(s): @@ -106,25 +106,24 @@ tmpdir = tempfile.mkdtemp(prefix='hg-install-') devnull = oldstderr = None try: - try: - fname = os.path.join(tmpdir, 'funcname.c') - f = open(fname, 'w') - f.write('int main(void) {\n') - f.write(' %s();\n' % funcname) - f.write('}\n') - f.close() - # Redirect stderr to /dev/null to hide any error messages - # from the compiler. - # This will have to be changed if we ever have to check - # for a function on Windows. - devnull = open('/dev/null', 'w') - oldstderr = os.dup(sys.stderr.fileno()) - os.dup2(devnull.fileno(), sys.stderr.fileno()) - objects = cc.compile([fname], output_dir=tmpdir) - cc.link_executable(objects, os.path.join(tmpdir, "a.out")) - except Exception: - return False + fname = os.path.join(tmpdir, 'funcname.c') + f = open(fname, 'w') + f.write('int main(void) {\n') + f.write(' %s();\n' % funcname) + f.write('}\n') + f.close() + # Redirect stderr to /dev/null to hide any error messages + # from the compiler. + # This will have to be changed if we ever have to check + # for a function on Windows. + devnull = open('/dev/null', 'w') + oldstderr = os.dup(sys.stderr.fileno()) + os.dup2(devnull.fileno(), sys.stderr.fileno()) + objects = cc.compile([fname], output_dir=tmpdir) + cc.link_executable(objects, os.path.join(tmpdir, "a.out")) return True + except Exception: + return False finally: if oldstderr is not None: os.dup2(oldstderr, sys.stderr.fileno()) @@ -408,11 +407,12 @@ # Persist executable bit (apply it to group and other if user # has it) if st[stat.ST_MODE] & stat.S_IXUSR: - setmode = 0755 + setmode = int('0755', 8) else: - setmode = 0644 - os.chmod(dst, (stat.S_IMODE(st[stat.ST_MODE]) & ~0777) | - setmode) + setmode = int('0644', 8) + m = stat.S_IMODE(st[stat.ST_MODE]) + m = (m & ~int('0777', 8)) | setmode + os.chmod(dst, m) file_util.copy_file = copyfileandsetmode try: install_lib.run(self) @@ -483,6 +483,11 @@ common_depends = ['mercurial/util.h'] +osutil_ldflags = [] + +if sys.platform == 'darwin': + osutil_ldflags += ['-framework', 'ApplicationServices'] + extmodules = [ Extension('mercurial.base85', ['mercurial/base85.c'], depends=common_depends), @@ -497,21 +502,11 @@ 'mercurial/parsers.c', 'mercurial/pathencode.c'], depends=common_depends), + Extension('mercurial.osutil', ['mercurial/osutil.c'], + extra_link_args=osutil_ldflags, + depends=common_depends), ] -osutil_ldflags = [] - -if sys.platform == 'darwin': - osutil_ldflags += ['-framework', 'ApplicationServices'] - -# disable osutil.c under windows + python 2.4 (issue1364) -if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'): - pymodules.append('mercurial.pure.osutil') -else: - extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'], - extra_link_args=osutil_ldflags, - depends=common_depends)) - try: from distutils import cygwinccompiler @@ -562,6 +557,8 @@ 'product_version':version}] # sub command of 'build' because 'py2exe' does not handle sub_commands build.sub_commands.insert(0, ('build_hgextindex', None)) + # put dlls in sub directory so that they won't pollute PATH + extra['zipfile'] = 'lib/library.zip' if os.name == 'nt': # Windows binary file versions for exe/dll files must have the @@ -572,6 +569,8 @@ version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines() if version: version = version[0] + if sys.version_info[0] == 3: + version = version.decode('utf-8') xcode4 = (version.startswith('Xcode') and StrictVersion(version.split()[1]) >= StrictVersion('4.0')) xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None diff -r 501c51d60792 -r 96a38d44ba09 tests/fakedirstatewritetime.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/fakedirstatewritetime.py Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,58 @@ +# extension to emulate invoking 'dirstate.write()' at the time +# specified by '[fakedirstatewritetime] fakenow', only when +# 'dirstate.write()' is invoked via functions below: +# +# - 'workingctx._checklookup()' (= 'repo.status()') +# - 'committablectx.markcommitted()' + +from mercurial import context, extensions, parsers, util + +def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): + # execute what original parsers.pack_dirstate should do actually + # for consistency + actualnow = int(now) + for f, e in dmap.iteritems(): + if e[0] == 'n' and e[3] == actualnow: + e = parsers.dirstatetuple(e[0], e[1], e[2], -1) + dmap[f] = e + + return orig(dmap, copymap, pl, fakenow) + +def fakewrite(ui, func): + # fake "now" of 'pack_dirstate' only if it is invoked while 'func' + + fakenow = ui.config('fakedirstatewritetime', 'fakenow') + if not fakenow: + # Execute original one, if fakenow isn't configured. This is + # useful to prevent subrepos from executing replaced one, + # because replacing 'parsers.pack_dirstate' is also effective + # in subrepos. + return func() + + # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between + # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy + timestamp = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0] + fakenow = float(timestamp) + + orig_pack_dirstate = parsers.pack_dirstate + wrapper = lambda *args: pack_dirstate(fakenow, orig_pack_dirstate, *args) + + parsers.pack_dirstate = wrapper + try: + return func() + finally: + parsers.pack_dirstate = orig_pack_dirstate + +def _checklookup(orig, workingctx, files): + ui = workingctx.repo().ui + return fakewrite(ui, lambda : orig(workingctx, files)) + +def markcommitted(orig, committablectx, node): + ui = committablectx.repo().ui + return fakewrite(ui, lambda : orig(committablectx, node)) + +def extsetup(ui): + extensions.wrapfunction(context.workingctx, '_checklookup', + _checklookup) + extensions.wrapfunction(context.committablectx, 'markcommitted', + markcommitted) diff -r 501c51d60792 -r 96a38d44ba09 tests/fakepatchtime.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/fakepatchtime.py Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,26 @@ +# extension to emulate invoking 'patch.internalpatch()' at the time +# specified by '[fakepatchtime] fakenow' + +from mercurial import extensions, patch as patchmod, util + +def internalpatch(orig, ui, repo, patchobj, strip, + prefix='', files=None, + eolmode='strict', similarity=0): + if files is None: + files = set() + r = orig(ui, repo, patchobj, strip, + prefix=prefix, files=files, + eolmode=eolmode, similarity=similarity) + + fakenow = ui.config('fakepatchtime', 'fakenow') + if fakenow: + # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between + # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy + fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0] + for f in files: + repo.wvfs.utime(f, (fakenow, fakenow)) + + return r + +def extsetup(ui): + extensions.wrapfunction(patchmod, 'internalpatch', internalpatch) diff -r 501c51d60792 -r 96a38d44ba09 tests/get-with-headers.py --- a/tests/get-with-headers.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/get-with-headers.py Sat Jul 18 17:32:38 2015 -0500 @@ -33,8 +33,6 @@ sys.argv.remove('--json') formatjson = True -reasons = {'Not modified': 'Not Modified'} # python 2.4 - tag = None def request(host, path, show): assert not path.startswith('/'), path @@ -46,7 +44,7 @@ conn = httplib.HTTPConnection(host) conn.request("GET", '/' + path, None, headers) response = conn.getresponse() - print response.status, reasons.get(response.reason, response.reason) + print response.status, response.reason if show[:1] == ['-']: show = sorted(h for h, v in response.getheaders() if h.lower() not in show) diff -r 501c51d60792 -r 96a38d44ba09 tests/heredoctest.py --- a/tests/heredoctest.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/heredoctest.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,7 +5,7 @@ while lines: l = lines.pop(0) if l.startswith('SALT'): - print l[:-1] + print(l[:-1]) elif l.startswith('>>> '): snippet = l[4:] while lines and lines[0].startswith('... '): @@ -13,6 +13,6 @@ snippet += l[4:] c = compile(snippet, '', 'single') try: - exec c in globalvars - except Exception, inst: - print repr(inst) + exec(c, globalvars) + except Exception as inst: + print(repr(inst)) diff -r 501c51d60792 -r 96a38d44ba09 tests/hghave --- a/tests/hghave Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/hghave Sat Jul 18 17:32:38 2015 -0500 @@ -4,7 +4,7 @@ prefixed with "no-", the absence of feature is tested. """ import optparse -import sys +import os, sys import hghave checks = hghave.checks @@ -33,8 +33,30 @@ parser.add_option("-q", "--quiet", action="store_true", help="check features silently") +def _loadaddon(quiet): + if 'TESTDIR' in os.environ: + # loading from '.' isn't needed, because `hghave` should be + # running at TESTTMP in this case + path = os.environ['TESTDIR'] + else: + path = '.' + + if not os.path.exists(os.path.join(path, 'hghaveaddon.py')): + return + + sys.path.insert(0, path) + try: + import hghaveaddon + except BaseException, inst: + if not quiet: + sys.stderr.write('failed to import hghaveaddon.py from %r: %s\n' + % (path, inst)) + sys.exit(2) + sys.path.pop(0) + if __name__ == '__main__': options, args = parser.parse_args() + _loadaddon(options.quiet) if options.list_features: list_features() sys.exit(0) diff -r 501c51d60792 -r 96a38d44ba09 tests/hghave.py --- a/tests/hghave.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/hghave.py Sat Jul 18 17:32:38 2015 -0500 @@ -88,10 +88,10 @@ fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) try: os.close(fh) - m = os.stat(fn).st_mode & 0777 + m = os.stat(fn).st_mode & 0o777 new_file_has_exec = m & EXECFLAGS os.chmod(fn, m ^ EXECFLAGS) - exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m) + exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m) finally: os.unlink(fn) except (IOError, OSError): @@ -225,12 +225,11 @@ os.close(fh) name = tempfile.mktemp(dir='.', prefix=tempprefix) try: - try: - util.oslink(fn, name) - os.unlink(name) - return True - except OSError: - return False + util.oslink(fn, name) + os.unlink(name) + return True + except OSError: + return False finally: os.unlink(fn) @@ -247,13 +246,13 @@ d = tempfile.mkdtemp(dir='.', prefix=tempprefix) try: fname = os.path.join(d, 'foo') - for umask in (077, 007, 022): + for umask in (0o77, 0o07, 0o22): os.umask(umask) f = open(fname, 'w') f.close() mode = os.stat(fname).st_mode os.unlink(fname) - if mode & 0777 != ~umask & 0666: + if mode & 0o777 != ~umask & 0o666: return False return True finally: @@ -282,10 +281,6 @@ except ImportError: return False -@check("python243", "python >= 2.4.3") -def has_python243(): - return sys.version_info >= (2, 4, 3) - @check("json", "some json module available") def has_json(): try: @@ -320,6 +315,15 @@ except ImportError: return False +@check("sslcontext", "python >= 2.7.9 ssl") +def has_sslcontext(): + try: + import ssl + ssl.SSLContext + return True + except (ImportError, AttributeError): + return False + @check("defaultcacerts", "can verify SSL certs by system's CA certs store") def has_defaultcacerts(): from mercurial import sslutil diff -r 501c51d60792 -r 96a38d44ba09 tests/killdaemons.py --- a/tests/killdaemons.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/killdaemons.py Sat Jul 18 17:32:38 2015 -0500 @@ -64,7 +64,7 @@ os.kill(pid, 0) logfn('# Daemon process %d is stuck - really killing it' % pid) os.kill(pid, signal.SIGKILL) - except OSError, err: + except OSError as err: if err.errno != errno.ESRCH: raise @@ -87,5 +87,9 @@ pass if __name__ == '__main__': - path, = sys.argv[1:] + if len(sys.argv) > 1: + path, = sys.argv[1:] + else: + path = os.environ["DAEMON_PIDS"] + killdaemons(path) diff -r 501c51d60792 -r 96a38d44ba09 tests/md5sum.py --- a/tests/md5sum.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/md5sum.py Sat Jul 18 17:32:38 2015 -0500 @@ -23,7 +23,7 @@ for filename in sys.argv[1:]: try: fp = open(filename, 'rb') - except IOError, msg: + except IOError as msg: sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg)) sys.exit(1) @@ -34,7 +34,7 @@ if not data: break m.update(data) - except IOError, msg: + except IOError as msg: sys.stderr.write('%s: I/O error: %s\n' % (filename, msg)) sys.exit(1) sys.stdout.write('%s %s\n' % (m.hexdigest(), filename)) diff -r 501c51d60792 -r 96a38d44ba09 tests/printenv.py --- a/tests/printenv.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/printenv.py Sat Jul 18 17:32:38 2015 -0500 @@ -1,3 +1,5 @@ +#!/usr/bin/env python +# # simple script to be used in hooks # # put something like this in the repo .hg/hgrc: diff -r 501c51d60792 -r 96a38d44ba09 tests/readlink.py --- a/tests/readlink.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/readlink.py Sat Jul 18 17:32:38 2015 -0500 @@ -5,7 +5,7 @@ for f in sys.argv[1:]: try: print f, '->', os.readlink(f) - except OSError, err: + except OSError as err: if err.errno != errno.EINVAL: raise print f, 'not a symlink' diff -r 501c51d60792 -r 96a38d44ba09 tests/run-tests.py --- a/tests/run-tests.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/run-tests.py Sat Jul 18 17:32:38 2015 -0500 @@ -41,6 +41,8 @@ # completes fairly quickly, includes both shell and Python scripts, and # includes some scripts that run daemon processes.) +from __future__ import print_function + from distutils import version import difflib import errno @@ -49,6 +51,7 @@ import shutil import subprocess import signal +import socket import sys import tempfile import time @@ -56,10 +59,15 @@ import re import threading import killdaemons as killmod -import Queue as queue +try: + import Queue as queue +except ImportError: + import queue from xml.dom import minidom import unittest +osenvironb = getattr(os, 'environb', os.environ) + try: import json except ImportError: @@ -70,14 +78,46 @@ processlock = threading.Lock() -# subprocess._cleanup can race with any Popen.wait or Popen.poll on py24 -# http://bugs.python.org/issue1731717 for details. We shouldn't be producing -# zombies but it's pretty harmless even if we do. -if sys.version_info < (2, 5): - subprocess._cleanup = lambda: None +if sys.version_info > (3, 5, 0): + PYTHON3 = True + xrange = range # we use xrange in one place, and we'd rather not use range + def _bytespath(p): + return p.encode('utf-8') + + def _strpath(p): + return p.decode('utf-8') + +elif sys.version_info >= (3, 0, 0): + print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' % + (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))) + sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` +else: + PYTHON3 = False + # In python 2.x, path operations are generally done using + # bytestrings by default, so we don't have to do any extra + # fiddling there. We define the wrapper functions anyway just to + # help keep code consistent between platforms. + def _bytespath(p): + return p + + _strpath = _bytespath + +# For Windows support wifexited = getattr(os, "WIFEXITED", lambda x: False) +def checkportisavailable(port): + """return true if a port seems free to bind on localhost""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('localhost', port)) + s.close() + return True + except socket.error as exc: + if not exc.errno == errno.EADDRINUSE: + raise + return False + closefds = os.name == 'posix' def Popen4(cmd, wd, timeout, env=None): processlock.acquire() @@ -104,10 +144,10 @@ return p -PYTHON = sys.executable.replace('\\', '/') -IMPL_PATH = 'PYTHONPATH' +PYTHON = _bytespath(sys.executable.replace('\\', '/')) +IMPL_PATH = b'PYTHONPATH' if 'java' in sys.platform: - IMPL_PATH = 'JYTHONPATH' + IMPL_PATH = b'JYTHONPATH' defaults = { 'jobs': ('HGTEST_JOBS', 1), @@ -122,15 +162,15 @@ try: path = os.path.expanduser(os.path.expandvars(filename)) f = open(path, "rb") - except IOError, err: + except IOError as err: if err.errno != errno.ENOENT: raise if warn: - print "warning: no such %s file: %s" % (listtype, filename) + print("warning: no such %s file: %s" % (listtype, filename)) continue for line in f.readlines(): - line = line.split('#', 1)[0].strip() + line = line.split(b'#', 1)[0].strip() if line: entries[line] = filename @@ -217,6 +257,8 @@ help='set the given config opt in the test hgrc') parser.add_option('--random', action="store_true", help='run tests in random order') + parser.add_option('--profile-runner', action='store_true', + help='run statprof on run-tests') for option, (envvar, default) in defaults.items(): defaults[option] = type(default)(os.environ.get(envvar, default)) @@ -240,8 +282,8 @@ if not os.path.basename(options.with_hg) == 'hg': sys.stderr.write('warning: --with-hg should specify an hg script\n') if options.local: - testdir = os.path.dirname(os.path.realpath(sys.argv[0])) - hgbin = os.path.join(os.path.dirname(testdir), 'hg') + testdir = os.path.dirname(_bytespath(os.path.realpath(sys.argv[0]))) + hgbin = os.path.join(os.path.dirname(testdir), b'hg') if os.name != 'nt' and not os.access(hgbin, os.X_OK): parser.error('--local specified, but %r not found or not executable' % hgbin) @@ -283,8 +325,9 @@ 'warning: --timeout option ignored with --debug\n') options.timeout = 0 if options.py3k_warnings: - if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0): - parser.error('--py3k-warnings can only be used on Python 2.6+') + if PYTHON3: + parser.error( + '--py3k-warnings can only be used on Python 2.6 and 2.7') if options.blacklist: options.blacklist = parselistfiles(options.blacklist, 'blacklist') if options.whitelist: @@ -301,17 +344,22 @@ shutil.copy(src, dst) os.remove(src) +_unified_diff = difflib.unified_diff +if PYTHON3: + import functools + _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff) + def getdiff(expected, output, ref, err): servefail = False lines = [] - for line in difflib.unified_diff(expected, output, ref, err): - if line.startswith('+++') or line.startswith('---'): - line = line.replace('\\', '/') - if line.endswith(' \n'): - line = line[:-2] + '\n' + for line in _unified_diff(expected, output, ref, err): + if line.startswith(b'+++') or line.startswith(b'---'): + line = line.replace(b'\\', b'/') + if line.endswith(b' \n'): + line = line[:-2] + b'\n' lines.append(line) if not servefail and line.startswith( - '+ abort: child process failed to start'): + b'+ abort: child process failed to start'): servefail = True return servefail, lines @@ -326,7 +374,7 @@ # Bytes that break XML even in a CDATA block: control characters 0-31 # sans \t, \n and \r -CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]") +CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]") def cdatasafe(data): """Make a string safe to include in a CDATA block. @@ -336,21 +384,20 @@ replaces illegal bytes with ? and adds a space between the ]] so that it won't break the CDATA block. """ - return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>') + return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>') def log(*msg): """Log something to stdout. Arguments are strings to print. """ - iolock.acquire() - if verbose: - print verbose, - for m in msg: - print m, - print - sys.stdout.flush() - iolock.release() + with iolock: + if verbose: + print(verbose, end=' ') + for m in msg: + print(m, end=' ') + print() + sys.stdout.flush() def terminate(proc): """Terminate subprocess (with fallback for Python versions < 2.6)""" @@ -408,11 +455,11 @@ shell is the shell to execute tests in. """ - self.path = path - self.name = os.path.basename(path) + self.bname = os.path.basename(path) + self.name = _strpath(self.bname) self._testdir = os.path.dirname(path) - self.errpath = os.path.join(self._testdir, '%s.err' % self.name) + self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname) self._threadtmp = tmpdir self._keeptmpdir = keeptmpdir @@ -421,7 +468,7 @@ self._startport = startport self._extraconfigopts = extraconfigopts or [] self._py3kwarnings = py3kwarnings - self._shell = shell + self._shell = _bytespath(shell) self._aborted = False self._daemonpids = [] @@ -442,6 +489,11 @@ else: self._refout = [] + # needed to get base class __repr__ running + @property + def _testMethodName(self): + return self.name + def __str__(self): return self.name @@ -457,7 +509,7 @@ try: os.mkdir(self._threadtmp) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise @@ -469,7 +521,7 @@ if os.path.exists(self.errpath): try: os.remove(self.errpath) - except OSError, e: + except OSError as e: # We might have raced another test to clean up a .err # file, so ignore ENOENT when removing a previous .err # file. @@ -499,20 +551,20 @@ except KeyboardInterrupt: self._aborted = True raise - except SkipTest, e: + except SkipTest as e: result.addSkip(self, str(e)) # The base class will have already counted this as a # test we "ran", but we want to exclude skipped tests # from those we count towards those run. result.testsRun -= 1 - except IgnoreTest, e: + except IgnoreTest as e: result.addIgnore(self, str(e)) # As with skips, ignores also should be excluded from # the number of tests executed. result.testsRun -= 1 - except WarnTest, e: + except WarnTest as e: result.addWarn(self, str(e)) - except self.failureException, e: + except self.failureException as e: # This differs from unittest in that we don't capture # the stack trace. This is for historical reasons and # this decision could be revisited in the future, @@ -620,7 +672,7 @@ f.write(line) f.close() - vlog("# Ret was:", self._ret) + vlog("# Ret was:", self._ret, '(%s)' % self.name) def _run(self, env): # This should be implemented in child classes to run tests. @@ -638,20 +690,20 @@ occur. """ r = [ - (r':%s\b' % self._startport, ':$HGPORT'), - (r':%s\b' % (self._startport + 1), ':$HGPORT1'), - (r':%s\b' % (self._startport + 2), ':$HGPORT2'), - (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$', - r'\1 (glob)'), + (br':%d\b' % self._startport, b':$HGPORT'), + (br':%d\b' % (self._startport + 1), b':$HGPORT1'), + (br':%d\b' % (self._startport + 2), b':$HGPORT2'), + (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$', + br'\1 (glob)'), ] if os.name == 'nt': r.append( - (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or - c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c - for c in self._testtmp), '$TESTTMP')) + (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or + c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c + for c in self._testtmp), b'$TESTTMP')) else: - r.append((re.escape(self._testtmp), '$TESTTMP')) + r.append((re.escape(self._testtmp), b'$TESTTMP')) return r @@ -663,8 +715,8 @@ env["HGPORT"] = str(self._startport) env["HGPORT1"] = str(self._startport + 1) env["HGPORT2"] = str(self._startport + 2) - env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc') - env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids') + env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc') + env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids') env["HGEDITOR"] = ('"' + sys.executable + '"' + ' -c "import sys; sys.exit(0)"') env["HGMERGE"] = "internal:merge" @@ -695,27 +747,27 @@ def _createhgrc(self, path): """Create an hgrc file for this test.""" hgrc = open(path, 'wb') - hgrc.write('[ui]\n') - hgrc.write('slash = True\n') - hgrc.write('interactive = False\n') - hgrc.write('mergemarkers = detailed\n') - hgrc.write('promptecho = True\n') - hgrc.write('[defaults]\n') - hgrc.write('backout = -d "0 0"\n') - hgrc.write('commit = -d "0 0"\n') - hgrc.write('shelve = --date "0 0"\n') - hgrc.write('tag = -d "0 0"\n') - hgrc.write('[devel]\n') - hgrc.write('all = true\n') - hgrc.write('[largefiles]\n') - hgrc.write('usercache = %s\n' % - (os.path.join(self._testtmp, '.cache/largefiles'))) + hgrc.write(b'[ui]\n') + hgrc.write(b'slash = True\n') + hgrc.write(b'interactive = False\n') + hgrc.write(b'mergemarkers = detailed\n') + hgrc.write(b'promptecho = True\n') + hgrc.write(b'[defaults]\n') + hgrc.write(b'backout = -d "0 0"\n') + hgrc.write(b'commit = -d "0 0"\n') + hgrc.write(b'shelve = --date "0 0"\n') + hgrc.write(b'tag = -d "0 0"\n') + hgrc.write(b'[devel]\n') + hgrc.write(b'all-warnings = true\n') + hgrc.write(b'[largefiles]\n') + hgrc.write(b'usercache = %s\n' % + (os.path.join(self._testtmp, b'.cache/largefiles'))) for opt in self._extraconfigopts: section, key = opt.split('.', 1) assert '=' in key, ('extra config opt %s must ' 'have an = for assignment' % opt) - hgrc.write('[%s]\n%s\n' % (section, key)) + hgrc.write(b'[%s]\n%s\n' % (section, key)) hgrc.close() def fail(self, msg): @@ -777,11 +829,11 @@ @property def refpath(self): - return os.path.join(self._testdir, '%s.out' % self.name) + return os.path.join(self._testdir, b'%s.out' % self.bname) def _run(self, env): - py3kswitch = self._py3kwarnings and ' -3' or '' - cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path) + py3kswitch = self._py3kwarnings and b' -3' or b'' + cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path) vlog("# Running", cmd) normalizenewlines = os.name == 'nt' result = self._runcommand(cmd, env, @@ -795,25 +847,29 @@ # Windows, but check-code.py wants a glob on these lines unconditionally. Don't # warn if that is the case for anything matching these lines. checkcodeglobpats = [ - re.compile(r'^pushing to \$TESTTMP/.*[^)]$'), - re.compile(r'^moving \S+/.*[^)]$'), - re.compile(r'^pulling from \$TESTTMP/.*[^)]$') + re.compile(br'^pushing to \$TESTTMP/.*[^)]$'), + re.compile(br'^moving \S+/.*[^)]$'), + re.compile(br'^pulling from \$TESTTMP/.*[^)]$') ] +bchr = chr +if PYTHON3: + bchr = lambda x: bytes([x]) + class TTest(Test): """A "t test" is a test backed by a .t file.""" SKIPPED_PREFIX = 'skipped: ' FAILED_PREFIX = 'hghave check failed: ' - NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search + NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search - ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub - ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)) - ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'}) + ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub + ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256)) + ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) @property def refpath(self): - return os.path.join(self._testdir, self.name) + return os.path.join(self._testdir, self.bname) def _run(self, env): f = open(self.path, 'rb') @@ -823,13 +879,13 @@ salt, script, after, expected = self._parsetest(lines) # Write out the generated script. - fname = '%s.sh' % self._testtmp + fname = b'%s.sh' % self._testtmp f = open(fname, 'wb') for l in script: f.write(l) f.close() - cmd = '%s "%s"' % (self._shell, fname) + cmd = b'%s "%s"' % (self._shell, fname) vlog("# Running", cmd) exitcode, output = self._runcommand(cmd, env) @@ -846,16 +902,17 @@ def _hghave(self, reqs): # TODO do something smarter when all other uses of hghave are gone. - tdir = self._testdir.replace('\\', '/') - proc = Popen4('%s -c "%s/hghave %s"' % - (self._shell, tdir, ' '.join(reqs)), + runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__))) + tdir = runtestdir.replace(b'\\', b'/') + proc = Popen4(b'%s -c "%s/hghave %s"' % + (self._shell, tdir, b' '.join(reqs)), self._testtmp, 0, self._getenv()) stdout, stderr = proc.communicate() ret = proc.wait() if wifexited(ret): ret = os.WEXITSTATUS(ret) if ret == 2: - print stdout + print(stdout) sys.exit(1) return ret == 0 @@ -864,12 +921,12 @@ # We generate a shell script which outputs unique markers to line # up script results with our source. These markers include input # line number and the last return code. - salt = "SALT" + str(time.time()) + salt = b"SALT%d" % time.time() def addsalt(line, inpython): if inpython: - script.append('%s %d 0\n' % (salt, line)) + script.append(b'%s %d 0\n' % (salt, line)) else: - script.append('echo %s %s $?\n' % (salt, line)) + script.append(b'echo %s %d $?\n' % (salt, line)) script = [] @@ -892,42 +949,42 @@ inpython = False if self._debug: - script.append('set -x\n') + script.append(b'set -x\n') if os.getenv('MSYSTEM'): - script.append('alias pwd="pwd -W"\n') + script.append(b'alias pwd="pwd -W"\n') for n, l in enumerate(lines): - if not l.endswith('\n'): - l += '\n' - if l.startswith('#require'): + if not l.endswith(b'\n'): + l += b'\n' + if l.startswith(b'#require'): lsplit = l.split() - if len(lsplit) < 2 or lsplit[0] != '#require': + if len(lsplit) < 2 or lsplit[0] != b'#require': after.setdefault(pos, []).append(' !!! invalid #require\n') if not self._hghave(lsplit[1:]): - script = ["exit 80\n"] + script = [b"exit 80\n"] break after.setdefault(pos, []).append(l) - elif l.startswith('#if'): + elif l.startswith(b'#if'): lsplit = l.split() - if len(lsplit) < 2 or lsplit[0] != '#if': + if len(lsplit) < 2 or lsplit[0] != b'#if': after.setdefault(pos, []).append(' !!! invalid #if\n') if skipping is not None: after.setdefault(pos, []).append(' !!! nested #if\n') skipping = not self._hghave(lsplit[1:]) after.setdefault(pos, []).append(l) - elif l.startswith('#else'): + elif l.startswith(b'#else'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = not skipping after.setdefault(pos, []).append(l) - elif l.startswith('#endif'): + elif l.startswith(b'#endif'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = None after.setdefault(pos, []).append(l) elif skipping: after.setdefault(pos, []).append(l) - elif l.startswith(' >>> '): # python inlines + elif l.startswith(b' >>> '): # python inlines after.setdefault(pos, []).append(l) prepos = pos pos = n @@ -935,39 +992,39 @@ # We've just entered a Python block. Add the header. inpython = True addsalt(prepos, False) # Make sure we report the exit code. - script.append('%s -m heredoctest < '): # continuations + elif l.startswith(b' > '): # continuations after.setdefault(prepos, []).append(l) script.append(l[4:]) - elif l.startswith(' '): # results + elif l.startswith(b' '): # results # Queue up a list of expected results. expected.setdefault(pos, []).append(l[2:]) else: if inpython: - script.append('EOF\n') + script.append(b'EOF\n') inpython = False # Non-command/result. Queue up for merged output. after.setdefault(pos, []).append(l) if inpython: - script.append('EOF\n') + script.append(b'EOF\n') if skipping is not None: after.setdefault(pos, []).append(' !!! missing #endif\n') addsalt(n + 1, False) @@ -987,9 +1044,9 @@ if salt in l: lout, lcmd = l.split(salt, 1) - if lout: - if not lout.endswith('\n'): - lout += ' (no-eol)\n' + while lout: + if not lout.endswith(b'\n'): + lout += b' (no-eol)\n' # Find the expected output at the current position. el = None @@ -1004,26 +1061,38 @@ elif r == '-glob': lout = ''.join(el.rsplit(' (glob)', 1)) r = '' # Warn only this line. + elif r == "retry": + postout.append(b' ' + el) + continue else: log('\ninfo, unknown linematch result: %r\n' % r) r = False if r: - postout.append(' ' + el) + postout.append(b' ' + el) else: if self.NEEDESCAPE(lout): - lout = TTest._stringescape('%s (esc)\n' % - lout.rstrip('\n')) - postout.append(' ' + lout) # Let diff deal with it. + lout = TTest._stringescape(b'%s (esc)\n' % + lout.rstrip(b'\n')) + postout.append(b' ' + lout) # Let diff deal with it. if r != '': # If line failed. warnonly = 3 # for sure not elif warnonly == 1: # Is "not yet" and line is warn only. warnonly = 2 # Yes do warn. + break + + # clean up any optional leftovers + while expected.get(pos, None): + el = expected[pos].pop(0) + if not el.endswith(" (?)\n"): + expected[pos].insert(0, el) + break + postout.append(b' ' + el) if lcmd: # Add on last return code. ret = int(lcmd.split()[1]) if ret != 0: - postout.append(' [%s]\n' % ret) + postout.append(b' [%d]\n' % ret) if pos in after: # Merge in non-active test bits. postout += after.pop(pos) @@ -1042,8 +1111,8 @@ try: # use \Z to ensure that the regex matches to the end of the string if os.name == 'nt': - return re.match(el + r'\r?\n\Z', l) - return re.match(el + r'\n\Z', l) + return re.match(el + br'\r?\n\Z', l) + return re.match(el + br'\n\Z', l) except re.error: # el is an invalid regex return False @@ -1052,51 +1121,59 @@ def globmatch(el, l): # The only supported special characters are * and ? plus / which also # matches \ on windows. Escaping of these characters is supported. - if el + '\n' == l: + if el + b'\n' == l: if os.altsep: # matching on "/" is not needed for this line for pat in checkcodeglobpats: if pat.match(el): return True - return '-glob' + return b'-glob' return True i, n = 0, len(el) - res = '' + res = b'' while i < n: - c = el[i] + c = el[i:i + 1] i += 1 - if c == '\\' and i < n and el[i] in '*?\\/': + if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/': res += el[i - 1:i + 1] i += 1 - elif c == '*': - res += '.*' - elif c == '?': - res += '.' - elif c == '/' and os.altsep: - res += '[/\\\\]' + elif c == b'*': + res += b'.*' + elif c == b'?': + res += b'.' + elif c == b'/' and os.altsep: + res += b'[/\\\\]' else: res += re.escape(c) return TTest.rematch(res, l) @staticmethod def linematch(el, l): + retry = False if el == l: # perfect match (fast) return True if el: - if el.endswith(" (esc)\n"): - el = el[:-7].decode('string-escape') + '\n' - if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l: + if el.endswith(" (?)\n"): + retry = "retry" + el = el[:-5] + "\n" + if el.endswith(b" (esc)\n"): + if PYTHON3: + el = el[:-7].decode('unicode_escape') + '\n' + el = el.encode('utf-8') + else: + el = el[:-7].decode('string-escape') + '\n' + if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: return True - if el.endswith(" (re)\n"): - return TTest.rematch(el[:-6], l) - if el.endswith(" (glob)\n"): + if el.endswith(b" (re)\n"): + return TTest.rematch(el[:-6], l) or retry + if el.endswith(b" (glob)\n"): # ignore '(glob)' added to l by 'replacements' - if l.endswith(" (glob)\n"): - l = l[:-8] + "\n" + if l.endswith(b" (glob)\n"): + l = l[:-8] + b"\n" return TTest.globmatch(el[:-8], l) - if os.altsep and l.replace('\\', '/') == el: - return '+glob' - return False + if os.altsep and l.replace(b'\\', b'/') == el: + return b'+glob' + return retry @staticmethod def parsehghaveoutput(lines): @@ -1160,6 +1237,7 @@ self.warned = [] self.times = [] + self._firststarttime = None # Data stored for the benefit of generating xunit reports. self.successes = [] self.faildata = {} @@ -1170,18 +1248,16 @@ if self._options.first: self.stop() else: - iolock.acquire() - if not self._options.nodiff: - self.stream.write('\nERROR: %s output changed\n' % test) + with iolock: + if not self._options.nodiff: + self.stream.write('\nERROR: %s output changed\n' % test) - self.stream.write('!') - self.stream.flush() - iolock.release() + self.stream.write('!') + self.stream.flush() def addSuccess(self, test): - iolock.acquire() - super(TestResult, self).addSuccess(test) - iolock.release() + with iolock: + super(TestResult, self).addSuccess(test) self.successes.append(test) def addError(self, test, err): @@ -1192,26 +1268,24 @@ # Polyfill. def addSkip(self, test, reason): self.skipped.append((test, reason)) - iolock.acquire() - if self.showAll: - self.stream.writeln('skipped %s' % reason) - else: - self.stream.write('s') - self.stream.flush() - iolock.release() + with iolock: + if self.showAll: + self.stream.writeln('skipped %s' % reason) + else: + self.stream.write('s') + self.stream.flush() def addIgnore(self, test, reason): self.ignored.append((test, reason)) - iolock.acquire() - if self.showAll: - self.stream.writeln('ignored %s' % reason) - else: - if reason != 'not retesting' and reason != "doesn't match keyword": - self.stream.write('i') + with iolock: + if self.showAll: + self.stream.writeln('ignored %s' % reason) else: - self.testsRun += 1 - self.stream.flush() - iolock.release() + if reason not in ('not retesting', "doesn't match keyword"): + self.stream.write('i') + else: + self.testsRun += 1 + self.stream.flush() def addWarn(self, test, reason): self.warned.append((test, reason)) @@ -1219,13 +1293,12 @@ if self._options.first: self.stop() - iolock.acquire() - if self.showAll: - self.stream.writeln('warned %s' % reason) - else: - self.stream.write('~') - self.stream.flush() - iolock.release() + with iolock: + if self.showAll: + self.stream.writeln('warned %s' % reason) + else: + self.stream.write('~') + self.stream.flush() def addOutputMismatch(self, test, ret, got, expected): """Record a mismatch in test output for a particular test.""" @@ -1239,38 +1312,45 @@ failed = False lines = [] - iolock.acquire() - if self._options.nodiff: - pass - elif self._options.view: - os.system("%s %s %s" % - (self._options.view, test.refpath, test.errpath)) - else: - servefail, lines = getdiff(expected, got, - test.refpath, test.errpath) - if servefail: - self.addFailure( - test, - 'server failed to start (HGPORT=%s)' % test._startport) + with iolock: + if self._options.nodiff: + pass + elif self._options.view: + v = self._options.view + if PYTHON3: + v = _bytespath(v) + os.system(b"%s %s %s" % + (v, test.refpath, test.errpath)) else: - self.stream.write('\n') - for line in lines: - self.stream.write(line) - self.stream.flush() + servefail, lines = getdiff(expected, got, + test.refpath, test.errpath) + if servefail: + self.addFailure( + test, + 'server failed to start (HGPORT=%s)' % test._startport) + else: + self.stream.write('\n') + for line in lines: + if PYTHON3: + self.stream.flush() + self.stream.buffer.write(line) + self.stream.buffer.flush() + else: + self.stream.write(line) + self.stream.flush() - # handle interactive prompt without releasing iolock - if self._options.interactive: - self.stream.write('Accept this change? [n] ') - answer = sys.stdin.readline().strip() - if answer.lower() in ('y', 'yes'): - if test.name.endswith('.t'): - rename(test.errpath, test.path) - else: - rename(test.errpath, '%s.out' % test.path) - accepted = True - if not accepted and not failed: - self.faildata[test.name] = ''.join(lines) - iolock.release() + # handle interactive prompt without releasing iolock + if self._options.interactive: + self.stream.write('Accept this change? [n] ') + answer = sys.stdin.readline().strip() + if answer.lower() in ('y', 'yes'): + if test.name.endswith('.t'): + rename(test.errpath, test.path) + else: + rename(test.errpath, '%s.out' % test.path) + accepted = True + if not accepted and not failed: + self.faildata[test.name] = b''.join(lines) return accepted @@ -1282,6 +1362,8 @@ # This module has one limitation. It can only work for Linux user # and not for Windows. test.started = os.times() + if self._firststarttime is None: # thread racy but irrelevant + self._firststarttime = test.started[4] def stopTest(self, test, interrupted=False): super(TestResult, self).stopTest(test) @@ -1290,14 +1372,19 @@ starttime = test.started endtime = test.stopped - self.times.append((test.name, endtime[2] - starttime[2], - endtime[3] - starttime[3], endtime[4] - starttime[4])) + origin = self._firststarttime + self.times.append((test.name, + endtime[2] - starttime[2], # user space CPU time + endtime[3] - starttime[3], # sys space CPU time + endtime[4] - starttime[4], # real time + starttime[4] - origin, # start date in run context + endtime[4] - origin, # end date in run context + )) if interrupted: - iolock.acquire() - self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( - test.name, self.times[-1][3])) - iolock.release() + with iolock: + self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( + test.name, self.times[-1][3])) class TestSuite(unittest.TestSuite): """Custom unittest TestSuite that knows how to execute Mercurial tests.""" @@ -1352,14 +1439,14 @@ def get(): num_tests[0] += 1 if getattr(test, 'should_reload', False): - return self._loadtest(test.name, num_tests[0]) + return self._loadtest(test.bname, num_tests[0]) return test if not os.path.exists(test.path): result.addSkip(test, "Doesn't exist") continue if not (self._whitelist and test.name in self._whitelist): - if self._blacklist and test.name in self._blacklist: + if self._blacklist and test.bname in self._blacklist: result.addSkip(test, 'blacklisted') continue @@ -1369,7 +1456,7 @@ if self._keywords: f = open(test.path, 'rb') - t = f.read().lower() + test.name.lower() + t = f.read().lower() + test.bname.lower() f.close() ignored = False for k in self._keywords.lower().split(): @@ -1460,101 +1547,92 @@ skipped = len(result.skipped) ignored = len(result.ignored) - iolock.acquire() - self.stream.writeln('') - - if not self._runner.options.noskips: - for test, msg in result.skipped: - self.stream.writeln('Skipped %s: %s' % (test.name, msg)) - for test, msg in result.warned: - self.stream.writeln('Warned %s: %s' % (test.name, msg)) - for test, msg in result.failures: - self.stream.writeln('Failed %s: %s' % (test.name, msg)) - for test, msg in result.errors: - self.stream.writeln('Errored %s: %s' % (test.name, msg)) + with iolock: + self.stream.writeln('') - if self._runner.options.xunit: - xuf = open(self._runner.options.xunit, 'wb') - try: - timesd = dict( - (test, real) for test, cuser, csys, real in result.times) - doc = minidom.Document() - s = doc.createElement('testsuite') - s.setAttribute('name', 'run-tests') - s.setAttribute('tests', str(result.testsRun)) - s.setAttribute('errors', "0") # TODO - s.setAttribute('failures', str(failed)) - s.setAttribute('skipped', str(skipped + ignored)) - doc.appendChild(s) - for tc in result.successes: - t = doc.createElement('testcase') - t.setAttribute('name', tc.name) - t.setAttribute('time', '%.3f' % timesd[tc.name]) - s.appendChild(t) - for tc, err in sorted(result.faildata.iteritems()): - t = doc.createElement('testcase') - t.setAttribute('name', tc) - t.setAttribute('time', '%.3f' % timesd[tc]) - # createCDATASection expects a unicode or it will convert - # using default conversion rules, which will fail if - # string isn't ASCII. - err = cdatasafe(err).decode('utf-8', 'replace') - cd = doc.createCDATASection(err) - t.appendChild(cd) - s.appendChild(t) - xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) - finally: - xuf.close() + if not self._runner.options.noskips: + for test, msg in result.skipped: + self.stream.writeln('Skipped %s: %s' % (test.name, msg)) + for test, msg in result.warned: + self.stream.writeln('Warned %s: %s' % (test.name, msg)) + for test, msg in result.failures: + self.stream.writeln('Failed %s: %s' % (test.name, msg)) + for test, msg in result.errors: + self.stream.writeln('Errored %s: %s' % (test.name, msg)) - if self._runner.options.json: - if json is None: - raise ImportError("json module not installed") - jsonpath = os.path.join(self._runner._testdir, 'report.json') - fp = open(jsonpath, 'w') - try: - timesd = {} - for test, cuser, csys, real in result.times: - timesd[test] = (real, cuser, csys) - - outcome = {} - for tc in result.successes: - testresult = {'result': 'success', - 'time': ('%0.3f' % timesd[tc.name][0]), - 'cuser': ('%0.3f' % timesd[tc.name][1]), - 'csys': ('%0.3f' % timesd[tc.name][2])} - outcome[tc.name] = testresult - - for tc, err in sorted(result.faildata.iteritems()): - testresult = {'result': 'failure', - 'time': ('%0.3f' % timesd[tc][0]), - 'cuser': ('%0.3f' % timesd[tc][1]), - 'csys': ('%0.3f' % timesd[tc][2])} - outcome[tc] = testresult + if self._runner.options.xunit: + xuf = open(self._runner.options.xunit, 'wb') + try: + timesd = dict((t[0], t[3]) for t in result.times) + doc = minidom.Document() + s = doc.createElement('testsuite') + s.setAttribute('name', 'run-tests') + s.setAttribute('tests', str(result.testsRun)) + s.setAttribute('errors', "0") # TODO + s.setAttribute('failures', str(failed)) + s.setAttribute('skipped', str(skipped + ignored)) + doc.appendChild(s) + for tc in result.successes: + t = doc.createElement('testcase') + t.setAttribute('name', tc.name) + t.setAttribute('time', '%.3f' % timesd[tc.name]) + s.appendChild(t) + for tc, err in sorted(result.faildata.items()): + t = doc.createElement('testcase') + t.setAttribute('name', tc) + t.setAttribute('time', '%.3f' % timesd[tc]) + # createCDATASection expects a unicode or it will + # convert using default conversion rules, which will + # fail if string isn't ASCII. + err = cdatasafe(err).decode('utf-8', 'replace') + cd = doc.createCDATASection(err) + t.appendChild(cd) + s.appendChild(t) + xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) + finally: + xuf.close() - for tc, reason in result.skipped: - testresult = {'result': 'skip', - 'time': ('%0.3f' % timesd[tc.name][0]), - 'cuser': ('%0.3f' % timesd[tc.name][1]), - 'csys': ('%0.3f' % timesd[tc.name][2])} - outcome[tc.name] = testresult - - jsonout = json.dumps(outcome, sort_keys=True, indent=4) - fp.writelines(("testreport =", jsonout)) - finally: - fp.close() + if self._runner.options.json: + if json is None: + raise ImportError("json module not installed") + jsonpath = os.path.join(self._runner._testdir, 'report.json') + fp = open(jsonpath, 'w') + try: + timesd = {} + for tdata in result.times: + test = tdata[0] + timesd[test] = tdata[1:] - self._runner._checkhglib('Tested') + outcome = {} + groups = [('success', ((tc, None) + for tc in result.successes)), + ('failure', result.failures), + ('skip', result.skipped)] + for res, testcases in groups: + for tc, __ in testcases: + tres = {'result': res, + 'time': ('%0.3f' % timesd[tc.name][2]), + 'cuser': ('%0.3f' % timesd[tc.name][0]), + 'csys': ('%0.3f' % timesd[tc.name][1]), + 'start': ('%0.3f' % timesd[tc.name][3]), + 'end': ('%0.3f' % timesd[tc.name][4])} + outcome[tc.name] = tres + jsonout = json.dumps(outcome, sort_keys=True, indent=4) + fp.writelines(("testreport =", jsonout)) + finally: + fp.close() - self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.' - % (result.testsRun, - skipped + ignored, warned, failed)) - if failed: - self.stream.writeln('python hash seed: %s' % - os.environ['PYTHONHASHSEED']) - if self._runner.options.time: - self.printtimes(result.times) + self._runner._checkhglib('Tested') - iolock.release() + self.stream.writeln( + '# Ran %d tests, %d skipped, %d warned, %d failed.' + % (result.testsRun, + skipped + ignored, warned, failed)) + if failed: + self.stream.writeln('python hash seed: %s' % + os.environ['PYTHONHASHSEED']) + if self._runner.options.time: + self.printtimes(result.times) return result @@ -1562,11 +1640,13 @@ # iolock held by run self.stream.writeln('# Producing time report') times.sort(key=lambda t: (t[3])) - cols = '%7.3f %7.3f %7.3f %s' - self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real', - 'Test')) - for test, cuser, csys, real in times: - self.stream.writeln(cols % (cuser, csys, real, test)) + cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s' + self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' % + ('start', 'end', 'cuser', 'csys', 'real', 'Test')) + for tdata in times: + test = tdata[0] + cuser, csys, real, start, end = tdata[1:6] + self.stream.writeln(cols % (start, end, cuser, csys, real, test)) class TestRunner(object): """Holds context for executing tests. @@ -1576,19 +1656,19 @@ # Programs required to run tests. REQUIREDTOOLS = [ - os.path.basename(sys.executable), - 'diff', - 'grep', - 'unzip', - 'gunzip', - 'bunzip2', - 'sed', + os.path.basename(_bytespath(sys.executable)), + b'diff', + b'grep', + b'unzip', + b'gunzip', + b'bunzip2', + b'sed', ] # Maps file extensions to test class. TESTTYPES = [ - ('.py', PythonTest), - ('.t', TTest), + (b'.py', PythonTest), + (b'.t', TTest), ] def __init__(self): @@ -1603,18 +1683,31 @@ self._coveragefile = None self._createdfiles = [] self._hgpath = None + self._portoffset = 0 + self._ports = {} def run(self, args, parser=None): """Run the test suite.""" - oldmask = os.umask(022) + oldmask = os.umask(0o22) try: parser = parser or getparser() options, args = parseargs(args, parser) + # positional arguments are paths to test files to run, so + # we make sure they're all bytestrings + args = [_bytespath(a) for a in args] self.options = options self._checktools() tests = self.findtests(args) - return self._run(tests) + if options.profile_runner: + import statprof + statprof.start() + result = self._run(tests) + if options.profile_runner: + statprof.stop() + statprof.display() + return result + finally: os.umask(oldmask) @@ -1623,22 +1716,26 @@ random.shuffle(tests) else: # keywords for slow tests - slow = 'svn gendoc check-code-hg'.split() + slow = {b'svn': 10, + b'gendoc': 10, + b'check-code-hg': 100, + } def sortkey(f): # run largest tests first, as they tend to take the longest try: val = -os.stat(f).st_size - except OSError, e: + except OSError as e: if e.errno != errno.ENOENT: raise return -1e9 # file does not exist, tell early - for kw in slow: + for kw, mul in slow.items(): if kw in f: - val *= 10 + val *= mul return val tests.sort(key=sortkey) - self._testdir = os.environ['TESTDIR'] = os.getcwd() + self._testdir = osenvironb[b'TESTDIR'] = getattr( + os, 'getcwdb', os.getcwd)() if 'PYTHONHASHSEED' not in os.environ: # use a random python hash seed all the time @@ -1647,12 +1744,12 @@ if self.options.tmpdir: self.options.keep_tmpdir = True - tmpdir = self.options.tmpdir + tmpdir = _bytespath(self.options.tmpdir) if os.path.exists(tmpdir): # Meaning of tmpdir has changed since 1.3: we used to create # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if # tmpdir already exists. - print "error: temp dir %r already exists" % tmpdir + print("error: temp dir %r already exists" % tmpdir) return 1 # Automatically removing tmpdir sounds convenient, but could @@ -1666,15 +1763,24 @@ if os.name == 'nt': # without this, we get the default temp dir location, but # in all lowercase, which causes troubles with paths (issue3490) - d = os.getenv('TMP') - tmpdir = tempfile.mkdtemp('', 'hgtests.', d) - self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir) + d = osenvironb.get(b'TMP', None) + tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) + + self._hgtmp = osenvironb[b'HGTMP'] = ( + os.path.realpath(tmpdir)) if self.options.with_hg: self._installdir = None - self._bindir = os.path.dirname(os.path.realpath( - self.options.with_hg)) - self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin') + whg = self.options.with_hg + # If --with-hg is not specified, we have bytes already, + # but if it was specified in python3 we get a str, so we + # have to encode it back into a bytes. + if PYTHON3: + if not isinstance(whg, bytes): + whg = _bytespath(whg) + self._bindir = os.path.dirname(os.path.realpath(whg)) + assert isinstance(self._bindir, bytes) + self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') os.makedirs(self._tmpbindir) # This looks redundant with how Python initializes sys.path from @@ -1684,25 +1790,33 @@ # ... which means it's not really redundant at all. self._pythondir = self._bindir else: - self._installdir = os.path.join(self._hgtmp, "install") - self._bindir = os.environ["BINDIR"] = \ - os.path.join(self._installdir, "bin") + self._installdir = os.path.join(self._hgtmp, b"install") + self._bindir = osenvironb[b"BINDIR"] = \ + os.path.join(self._installdir, b"bin") self._tmpbindir = self._bindir - self._pythondir = os.path.join(self._installdir, "lib", "python") + self._pythondir = os.path.join(self._installdir, b"lib", b"python") + + osenvironb[b"BINDIR"] = self._bindir + osenvironb[b"PYTHON"] = PYTHON - os.environ["BINDIR"] = self._bindir - os.environ["PYTHON"] = PYTHON - - runtestdir = os.path.abspath(os.path.dirname(__file__)) - path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep) + fileb = _bytespath(__file__) + runtestdir = os.path.abspath(os.path.dirname(fileb)) + osenvironb[b'RUNTESTDIR'] = runtestdir + if PYTHON3: + sepb = _bytespath(os.pathsep) + else: + sepb = os.pathsep + path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb) if os.path.islink(__file__): # test helper will likely be at the end of the symlink - realfile = os.path.realpath(__file__) + realfile = os.path.realpath(fileb) realdir = os.path.abspath(os.path.dirname(realfile)) path.insert(2, realdir) + if self._testdir != runtestdir: + path = [self._testdir] + path if self._tmpbindir != self._bindir: path = [self._tmpbindir] + path - os.environ["PATH"] = os.pathsep.join(path) + osenvironb[b"PATH"] = sepb.join(path) # Include TESTDIR in PYTHONPATH so that out-of-tree extensions # can run .../tests/run-tests.py test-foo where test-foo @@ -1713,20 +1827,21 @@ # it, in case external libraries are only available via current # PYTHONPATH. (In particular, the Subversion bindings on OS X # are in /opt/subversion.) - oldpypath = os.environ.get(IMPL_PATH) + oldpypath = osenvironb.get(IMPL_PATH) if oldpypath: pypath.append(oldpypath) - os.environ[IMPL_PATH] = os.pathsep.join(pypath) + osenvironb[IMPL_PATH] = sepb.join(pypath) if self.options.pure: os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure" - self._coveragefile = os.path.join(self._testdir, '.coverage') + self._coveragefile = os.path.join(self._testdir, b'.coverage') vlog("# Using TESTDIR", self._testdir) + vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR']) vlog("# Using HGTMP", self._hgtmp) vlog("# Using PATH", os.environ["PATH"]) - vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) + vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH]) try: return self._runtests(tests) or 0 @@ -1745,13 +1860,13 @@ proc = Popen4('hg st --rev "%s" -man0 .' % self.options.changed, None, 0) stdout, stderr = proc.communicate() - args = stdout.strip('\0').split('\0') + args = stdout.strip(b'\0').split(b'\0') else: - args = os.listdir('.') + args = os.listdir(b'.') return [t for t in args - if os.path.basename(t).startswith('test-') - and (t.endswith('.py') or t.endswith('.t'))] + if os.path.basename(t).startswith(b'test-') + and (t.endswith(b'.py') or t.endswith(b'.t'))] def _runtests(self, tests): try: @@ -1768,20 +1883,23 @@ break tests.pop(0) if not tests: - print "running all tests" + print("running all tests") tests = orig tests = [self._gettest(t, i) for i, t in enumerate(tests)] failed = False warned = False + kws = self.options.keywords + if kws is not None and PYTHON3: + kws = kws.encode('utf-8') suite = TestSuite(self._testdir, jobs=self.options.jobs, whitelist=self.options.whitelisted, blacklist=self.options.blacklist, retest=self.options.retest, - keywords=self.options.keywords, + keywords=kws, loop=self.options.loop, runs_per_test=self.options.runs_per_test, tests=tests, loadtest=self._gettest) @@ -1800,13 +1918,31 @@ self._outputcoverage() except KeyboardInterrupt: failed = True - print "\ninterrupted!" + print("\ninterrupted!") if failed: return 1 if warned: return 80 + def _getport(self, count): + port = self._ports.get(count) # do we have a cached entry? + if port is None: + port = self.options.port + self._portoffset + portneeded = 3 + # above 100 tries we just give up and let test reports failure + for tries in xrange(100): + allfree = True + for idx in xrange(portneeded): + if not checkportisavailable(port + idx): + allfree = False + break + self._portoffset += portneeded + if allfree: + break + self._ports[count] = port + return port + def _gettest(self, test, count): """Obtain a Test by looking at its filename. @@ -1822,13 +1958,13 @@ break refpath = os.path.join(self._testdir, test) - tmpdir = os.path.join(self._hgtmp, 'child%d' % count) + tmpdir = os.path.join(self._hgtmp, b'child%d' % count) t = testcls(refpath, tmpdir, keeptmpdir=self.options.keep_tmpdir, debug=self.options.debug, timeout=self.options.timeout, - startport=self.options.port + count * 3, + startport=self._getport(count), extraconfigopts=self.options.extra_config_opt, py3kwarnings=self.options.py3k_warnings, shell=self.options.shell) @@ -1852,7 +1988,7 @@ def _usecorrectpython(self): """Configure the environment to use the appropriate Python in tests.""" # Tests must use the same interpreter as us or bad things will happen. - pyexename = sys.platform == 'win32' and 'python.exe' or 'python' + pyexename = sys.platform == 'win32' and b'python.exe' or b'python' if getattr(os, 'symlink', None): vlog("# Making python executable in test path a symlink to '%s'" % sys.executable) @@ -1861,14 +1997,14 @@ if os.readlink(mypython) == sys.executable: return os.unlink(mypython) - except OSError, err: + except OSError as err: if err.errno != errno.ENOENT: raise if self._findprogram(pyexename) != sys.executable: try: os.symlink(sys.executable, mypython) self._createdfiles.append(mypython) - except OSError, err: + except OSError as err: # child processes may race, which is harmless if err.errno != errno.EEXIST: raise @@ -1881,7 +2017,7 @@ path.remove(exedir) os.environ['PATH'] = os.pathsep.join([exedir] + path) if not self._findprogram(pyexename): - print "WARNING: Cannot find %s in search path" % pyexename + print("WARNING: Cannot find %s in search path" % pyexename) def _installhg(self): """Install hg into the test environment. @@ -1889,47 +2025,51 @@ This will also configure hg with the appropriate testing settings. """ vlog("# Performing temporary installation of HG") - installerrs = os.path.join("tests", "install.err") + installerrs = os.path.join(b"tests", b"install.err") compiler = '' if self.options.compiler: compiler = '--compiler ' + self.options.compiler if self.options.pure: - pure = "--pure" + pure = b"--pure" else: - pure = "" + pure = b"" py3 = '' - if sys.version_info[0] == 3: - py3 = '--c2to3' # Run installer in hg root script = os.path.realpath(sys.argv[0]) + exe = sys.executable + if PYTHON3: + py3 = b'--c2to3' + compiler = _bytespath(compiler) + script = _bytespath(script) + exe = _bytespath(exe) hgroot = os.path.dirname(os.path.dirname(script)) self._hgroot = hgroot os.chdir(hgroot) - nohome = '--home=""' + nohome = b'--home=""' if os.name == 'nt': # The --home="" trick works only on OS where os.sep == '/' # because of a distutils convert_path() fast-path. Avoid it at # least on Windows for now, deal with .pydistutils.cfg bugs # when they happen. - nohome = '' - cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all' - ' build %(compiler)s --build-base="%(base)s"' - ' install --force --prefix="%(prefix)s"' - ' --install-lib="%(libdir)s"' - ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' - % {'exe': sys.executable, 'py3': py3, 'pure': pure, - 'compiler': compiler, - 'base': os.path.join(self._hgtmp, "build"), - 'prefix': self._installdir, 'libdir': self._pythondir, - 'bindir': self._bindir, - 'nohome': nohome, 'logfile': installerrs}) + nohome = b'' + cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all' + b' build %(compiler)s --build-base="%(base)s"' + b' install --force --prefix="%(prefix)s"' + b' --install-lib="%(libdir)s"' + b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' + % {b'exe': exe, b'py3': py3, b'pure': pure, + b'compiler': compiler, + b'base': os.path.join(self._hgtmp, b"build"), + b'prefix': self._installdir, b'libdir': self._pythondir, + b'bindir': self._bindir, + b'nohome': nohome, b'logfile': installerrs}) # setuptools requires install directories to exist. def makedirs(p): try: os.makedirs(p) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise makedirs(self._pythondir) @@ -1942,7 +2082,10 @@ else: f = open(installerrs, 'rb') for line in f: - sys.stdout.write(line) + if PYTHON3: + sys.stdout.buffer.write(line) + else: + sys.stdout.write(line) f.close() sys.exit(1) os.chdir(self._testdir) @@ -1960,21 +2103,21 @@ f.write(line + '\n') f.close() - hgbat = os.path.join(self._bindir, 'hg.bat') + hgbat = os.path.join(self._bindir, b'hg.bat') if os.path.isfile(hgbat): # hg.bat expects to be put in bin/scripts while run-tests.py # installation layout put it in bin/ directly. Fix it f = open(hgbat, 'rb') data = f.read() f.close() - if '"%~dp0..\python" "%~dp0hg" %*' in data: - data = data.replace('"%~dp0..\python" "%~dp0hg" %*', - '"%~dp0python" "%~dp0hg" %*') + if b'"%~dp0..\python" "%~dp0hg" %*' in data: + data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*', + b'"%~dp0python" "%~dp0hg" %*') f = open(hgbat, 'wb') f.write(data) f.close() else: - print 'WARNING: cannot fix hg.bat reference to python.exe' + print('WARNING: cannot fix hg.bat reference to python.exe') if self.options.anycoverage: custom = os.path.join(self._testdir, 'sitecustomize.py') @@ -1987,7 +2130,7 @@ covdir = os.path.join(self._installdir, '..', 'coverage') try: os.mkdir(covdir) - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise @@ -2001,7 +2144,7 @@ # The pythondir has been inferred from --with-hg flag. # We cannot expect anything sensible here. return - expecthg = os.path.join(self._pythondir, 'mercurial') + expecthg = os.path.join(self._pythondir, b'mercurial') actualhg = self._gethgpath() if os.path.abspath(actualhg) != os.path.abspath(expecthg): sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' @@ -2013,10 +2156,13 @@ if self._hgpath is not None: return self._hgpath - cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"' - pipe = os.popen(cmd % PYTHON) + cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"' + cmd = cmd % PYTHON + if PYTHON3: + cmd = _strpath(cmd) + pipe = os.popen(cmd) try: - self._hgpath = pipe.read().strip() + self._hgpath = _bytespath(pipe.read().strip()) finally: pipe.close() @@ -2052,7 +2198,9 @@ def _findprogram(self, program): """Search PATH for a executable program""" - for p in os.environ.get('PATH', os.defpath).split(os.pathsep): + dpb = _bytespath(os.defpath) + sepb = _bytespath(os.pathsep) + for p in osenvironb.get(b'PATH', dpb).split(sepb): name = os.path.join(p, program) if os.name == 'nt' or os.access(name, os.X_OK): return name @@ -2067,7 +2215,7 @@ if found: vlog("# Found prerequisite", p, "at", found) else: - print "WARNING: Did not find prerequisite tool: %s " % p + print("WARNING: Did not find prerequisite tool: %s " % p) if __name__ == '__main__': runner = TestRunner() diff -r 501c51d60792 -r 96a38d44ba09 tests/test-acl.t --- a/tests/test-acl.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-acl.t Sat Jul 18 17:32:38 2015 -0500 @@ -44,6 +44,13 @@ > EOF > } + $ cat << EOF >> $HGRCPATH + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True + > EOF + $ hg init a $ cd a $ mkdir foo foo/Bar quux @@ -91,37 +98,40 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -151,39 +161,42 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: changes have source "push" - skipping + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -214,33 +227,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -254,9 +260,19 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" acl: path access granted: "911600dab2ae" + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -287,33 +303,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -323,6 +332,8 @@ acl: acl.deny not enabled acl: branch access granted: "ef1ea85a6374" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") @@ -357,33 +368,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -397,6 +401,8 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") @@ -432,33 +438,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "barney" @@ -468,6 +467,8 @@ acl: acl.deny enabled, 0 entries for user barney acl: branch access granted: "ef1ea85a6374" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") @@ -504,33 +505,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -544,6 +538,8 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") @@ -581,33 +577,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -619,6 +608,8 @@ acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") @@ -655,33 +646,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "barney" @@ -691,6 +675,8 @@ acl: acl.deny enabled, 0 entries for user barney acl: branch access granted: "ef1ea85a6374" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") @@ -731,33 +717,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "barney" @@ -771,9 +750,19 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" acl: path access granted: "911600dab2ae" + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -811,33 +800,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "wilma" @@ -851,6 +833,8 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae") @@ -894,37 +878,32 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "barney" error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config' + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: No such file or directory: ../acl.config @@ -972,33 +951,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "betty" @@ -1012,6 +984,8 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae") @@ -1061,33 +1035,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "barney" @@ -1101,9 +1068,19 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" acl: path access granted: "911600dab2ae" + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -1144,33 +1121,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -1184,9 +1154,19 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" acl: path access granted: "911600dab2ae" + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -1223,33 +1203,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -1261,6 +1234,8 @@ acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") @@ -1304,33 +1279,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -1345,9 +1313,19 @@ acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" acl: path access granted: "911600dab2ae" + bundle2-input-part: total payload size 1606 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-bundle: 3 parts total updating the branch cache + bundle2-output-bundle: "HG20", 2 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 1 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 0 (undo push) 0:6675d58eff77 @@ -1384,33 +1362,26 @@ ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: foo/Bar/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 2/3 files (66.67%) - bundling: quux/file.py 3/3 files (100.00%) + bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae adding manifests - manifests: 1/3 chunks (33.33%) - manifests: 2/3 chunks (66.67%) - manifests: 3/3 chunks (100.00%) adding file changes adding foo/Bar/file.txt revisions - files: 1/3 chunks (33.33%) adding foo/file.txt revisions - files: 2/3 chunks (66.67%) adding quux/file.py revisions - files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "fred" @@ -1424,6 +1395,8 @@ acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") + bundle2-input-part: total payload size 1606 + bundle2-input-bundle: 3 parts total transaction abort! rollback completed abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") @@ -1504,41 +1477,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "astro" @@ -1554,9 +1515,23 @@ acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" acl: path access granted: "e8fc755d4d82" + bundle2-input-part: total payload size 2101 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" + bundle2-input-bundle: 4 parts total updating the branch cache + bundle2-output-bundle: "HG20", 3 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 2 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 2 (undo push) 2:fb35475503ef @@ -1590,41 +1565,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "astro" @@ -1639,6 +1602,8 @@ acl: branch access granted: "911600dab2ae" on branch "default" acl: path access granted: "911600dab2ae" error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82") + bundle2-input-part: total payload size 2101 + bundle2-input-bundle: 4 parts total transaction abort! rollback completed abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82") @@ -1674,41 +1639,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "astro" @@ -1717,6 +1670,8 @@ acl: acl.allow not enabled acl: acl.deny not enabled error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 2101 + bundle2-input-bundle: 4 parts total transaction abort! rollback completed abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") @@ -1754,41 +1709,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "astro" @@ -1797,6 +1740,8 @@ acl: acl.allow not enabled acl: acl.deny not enabled error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 2101 + bundle2-input-bundle: 4 parts total transaction abort! rollback completed abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") @@ -1828,41 +1773,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "george" @@ -1878,9 +1811,23 @@ acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" acl: path access granted: "e8fc755d4d82" + bundle2-input-part: total payload size 2101 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" + bundle2-input-bundle: 4 parts total updating the branch cache + bundle2-output-bundle: "HG20", 3 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 2 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 2 (undo push) 2:fb35475503ef @@ -1919,41 +1866,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "george" @@ -1969,9 +1904,23 @@ acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" acl: path access granted: "e8fc755d4d82" + bundle2-input-part: total payload size 2101 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" + bundle2-input-bundle: 4 parts total updating the branch cache + bundle2-output-bundle: "HG20", 3 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 2 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 2 (undo push) 2:fb35475503ef @@ -2009,41 +1958,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "george" @@ -2052,6 +1989,8 @@ acl: acl.allow not enabled acl: acl.deny not enabled error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 2101 + bundle2-input-bundle: 4 parts total transaction abort! rollback completed abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374") @@ -2088,41 +2027,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "astro" @@ -2138,9 +2065,23 @@ acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" acl: path access granted: "e8fc755d4d82" + bundle2-input-part: total payload size 2101 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" + bundle2-input-bundle: 4 parts total updating the branch cache + bundle2-output-bundle: "HG20", 3 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 2 parts total listing keys for "phases" - try to push obsolete markers to remote repository tip rolled back to revision 2 (undo push) 2:fb35475503ef @@ -2172,41 +2113,29 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 - bundling: 1/4 changesets (25.00%) - bundling: 2/4 changesets (50.00%) - bundling: 3/4 changesets (75.00%) - bundling: 4/4 changesets (100.00%) - bundling: 1/4 manifests (25.00%) - bundling: 2/4 manifests (50.00%) - bundling: 3/4 manifests (75.00%) - bundling: 4/4 manifests (100.00%) - bundling: abc.txt 1/4 files (25.00%) - bundling: foo/Bar/file.txt 2/4 files (50.00%) - bundling: foo/file.txt 3/4 files (75.00%) - bundling: quux/file.py 4/4 files (100.00%) + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets - changesets: 1 chunks add changeset ef1ea85a6374 - changesets: 2 chunks add changeset f9cafe1212c8 - changesets: 3 chunks add changeset 911600dab2ae - changesets: 4 chunks add changeset e8fc755d4d82 adding manifests - manifests: 1/4 chunks (25.00%) - manifests: 2/4 chunks (50.00%) - manifests: 3/4 chunks (75.00%) - manifests: 4/4 chunks (100.00%) adding file changes adding abc.txt revisions - files: 1/4 chunks (25.00%) adding foo/Bar/file.txt revisions - files: 2/4 chunks (50.00%) adding foo/file.txt revisions - files: 3/4 chunks (75.00%) adding quux/file.py revisions - files: 4/4 chunks (100.00%) added 4 changesets with 4 changes to 4 files (+1 heads) calling hook pretxnchangegroup.acl: hgext.acl.hook acl: checking access for user "george" @@ -2215,6 +2144,8 @@ acl: acl.allow not enabled acl: acl.deny not enabled error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374") + bundle2-input-part: total payload size 2101 + bundle2-input-bundle: 4 parts total transaction abort! rollback completed abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374") diff -r 501c51d60792 -r 96a38d44ba09 tests/test-add.t --- a/tests/test-add.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-add.t Sat Jul 18 17:32:38 2015 -0500 @@ -75,6 +75,13 @@ $ hg ci -m 0 --traceback + $ hg log -r "heads(. or wdir() & file('**'))" + changeset: 0:* (glob) + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 0 + should fail $ hg add a @@ -99,6 +106,15 @@ M a ? a.orig +wdir doesn't cause a crash, and can be dynamically selected if dirty + + $ hg log -r "heads(. or wdir() & file('**'))" + changeset: 2147483647:ffffffffffff + parent: 2:* (glob) + parent: 1:* (glob) + user: test + date: * (glob) + should fail $ hg add a diff -r 501c51d60792 -r 96a38d44ba09 tests/test-archive-symlinks.t --- a/tests/test-archive-symlinks.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-archive-symlinks.t Sat Jul 18 17:32:38 2015 -0500 @@ -18,7 +18,7 @@ $ cd "$origdir" $ cd archive - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> nothing tar @@ -26,7 +26,7 @@ $ cd "$origdir" $ tar xf archive.tar $ cd tar - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> nothing zip @@ -34,7 +34,7 @@ $ cd "$origdir" $ unzip archive.zip > /dev/null 2>&1 $ cd zip - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> nothing $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-archive.t --- a/tests/test-archive.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-archive.t Sat Jul 18 17:32:38 2015 -0500 @@ -27,11 +27,11 @@ > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log > cat hg.pid >> $DAEMON_PIDS > echo % $1 allowed should give 200 - > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$2" | head -n 1 + > get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1 > echo % $3 and $4 disallowed should both give 403 - > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$3" | head -n 1 - > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$4" | head -n 1 - > "$TESTDIR/killdaemons.py" $DAEMON_PIDS + > get-with-headers.py localhost:$HGPORT "archive/tip.$3" | head -n 1 + > get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1 + > killdaemons.py > cat errors.log > cp .hg/hgrc-base .hg/hgrc > } @@ -63,7 +63,7 @@ invalid arch type should give 404 - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.invalid" | head -n 1 + $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1 404 Unsupported archive type: None $ TIP=`hg id -v | cut -f1 -d' '` @@ -134,7 +134,7 @@ $ python getarchive.py "$TIP" gz relre:baz HTTP Error 404: file(s) not found: relre:baz - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg archive -t tar test.tar $ tar tf test.tar @@ -145,7 +145,7 @@ test/baz/bletch test/foo - $ hg archive --debug -t tbz2 -X baz test.tar.bz2 + $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true archiving: 0/4 files (0.00%) archiving: .hgsub 1/4 files (25.00%) archiving: .hgsubstate 2/4 files (50.00%) @@ -282,18 +282,11 @@ $ hg archive ../with-progress \r (no-eol) (esc) archiving [ ] 0/6\r (no-eol) (esc) - archiving [ ] 0/6\r (no-eol) (esc) archiving [======> ] 1/6\r (no-eol) (esc) - archiving [======> ] 1/6\r (no-eol) (esc) - archiving [=============> ] 2/6\r (no-eol) (esc) archiving [=============> ] 2/6\r (no-eol) (esc) archiving [====================> ] 3/6\r (no-eol) (esc) - archiving [====================> ] 3/6\r (no-eol) (esc) - archiving [===========================> ] 4/6\r (no-eol) (esc) archiving [===========================> ] 4/6\r (no-eol) (esc) archiving [==================================> ] 5/6\r (no-eol) (esc) - archiving [==================================> ] 5/6\r (no-eol) (esc) - archiving [==========================================>] 6/6\r (no-eol) (esc) archiving [==========================================>] 6/6\r (no-eol) (esc) \r (no-eol) (esc) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-backout.t --- a/tests/test-backout.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-backout.t Sat Jul 18 17:32:38 2015 -0500 @@ -42,6 +42,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft commit option @@ -69,6 +70,7 @@ branch: default commit: (clean) update: (current) + phases: 4 draft $ echo ypples > a $ hg commit -d '5 0' -m ypples @@ -83,6 +85,7 @@ branch: default commit: 1 unresolved (clean) update: (current) + phases: 5 draft file that was removed is recreated (this also tests that editor is not invoked if the commit message is @@ -110,6 +113,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft backout of backout is as if nothing happened @@ -124,6 +128,7 @@ branch: default commit: (clean) update: (current) + phases: 4 draft across branch @@ -144,6 +149,7 @@ branch: default commit: (clean) update: 1 new changesets (update) + phases: 2 draft should fail @@ -160,6 +166,7 @@ branch: default commit: (clean) update: 1 new changesets, 2 branch heads (merge) + phases: 3 draft should fail @@ -172,6 +179,7 @@ branch: default commit: (clean) update: 1 new changesets, 2 branch heads (merge) + phases: 3 draft backout with merge @@ -189,6 +197,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft remove line 1 @@ -213,6 +222,7 @@ branch: default commit: (clean) update: (current) + phases: 5 draft check line 1 is back @@ -241,6 +251,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft without --merge $ hg backout -d '3 0' 1 --tool=true @@ -258,6 +269,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft with --merge $ hg backout --merge -d '3 0' 1 --tool=true @@ -302,6 +314,7 @@ branch: default commit: (clean) update: (current) + phases: 5 draft backout of merge should fail @@ -332,6 +345,7 @@ branch: default commit: (clean) update: (current) + phases: 6 draft $ hg rollback repository tip rolled back to revision 4 (undo commit) @@ -344,6 +358,7 @@ branch: default commit: (clean) update: (current) + phases: 5 draft $ hg backout -d '6 0' --parent 3 4 --tool=true removing c @@ -354,6 +369,7 @@ branch: default commit: (clean) update: (current) + phases: 6 draft $ cd .. @@ -373,7 +389,6 @@ adding file1 $ hg branch branch2 marked working directory as branch branch2 - (branches are permanent and global, did you want a bookmark?) $ echo branch2 > file2 $ hg ci -d '2 0' -Am file2 adding file2 @@ -394,6 +409,7 @@ branch: branch2 commit: 1 removed update: (current) + phases: 3 draft with --merge (this also tests that editor is invoked if '--edit' is specified @@ -424,6 +440,7 @@ branch: branch2 commit: 1 removed (merge) update: (current) + phases: 4 draft $ hg update -q -C 2 on branch2 with branch1 not merged, so file1 should still exist: @@ -440,6 +457,7 @@ branch: branch2 commit: (clean) update: 1 new changesets, 2 branch heads (merge) + phases: 4 draft on branch2 with branch1 merged, so file1 should be gone: @@ -458,6 +476,7 @@ branch: branch2 commit: (clean) update: (current) + phases: 5 draft on branch1, so no file1 and file2: @@ -474,6 +493,7 @@ branch: branch1 commit: (clean) update: (current) + phases: 5 draft $ cd .. @@ -553,6 +573,7 @@ branch: default commit: 1 unresolved (clean) update: (current) + phases: 3 draft $ hg resolve --all --debug picked tool 'internal:merge' for foo (binary False symlink False) merging foo @@ -570,6 +591,7 @@ branch: default commit: 1 modified, 1 unknown update: (current) + phases: 3 draft $ cat foo one two diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bad-extension.t --- a/tests/test-bad-extension.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bad-extension.t Sat Jul 18 17:32:38 2015 -0500 @@ -15,3 +15,32 @@ hg help [-ec] [TOPIC] show help for a given topic or a help overview + +show traceback + + $ hg -q help help --traceback 2>&1 | grep -v '^ ' + *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow + Traceback (most recent call last): + Exception: bit bucket overflow + *** failed to import extension badext2: No module named badext2 + Traceback (most recent call last): + ImportError: No module named badext2 + hg help [-ec] [TOPIC] + + show help for a given topic or a help overview + +show traceback for ImportError of hgext.name if debug is set +(note that --debug option isn't applied yet when loading extensions) + + $ hg help help --traceback --config ui.debug=True 2>&1 \ + > | grep -v '^ ' | head -n10 + *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow + Traceback (most recent call last): + Exception: bit bucket overflow + could not import hgext.badext2 (No module named badext2): trying badext2 + Traceback (most recent call last): + ImportError: No module named badext2 + *** failed to import extension badext2: No module named badext2 + Traceback (most recent call last): + ImportError: No module named badext2 + hg help [-ec] [TOPIC] diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bad-pull.t --- a/tests/test-bad-pull.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bad-pull.t Sat Jul 18 17:32:38 2015 -0500 @@ -18,4 +18,4 @@ $ hg clone http://localhost:$HGPORT/foo copy2 abort: HTTP Error 404: * (glob) [255] - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-basic.t --- a/tests/test-basic.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-basic.t Sat Jul 18 17:32:38 2015 -0500 @@ -5,7 +5,7 @@ defaults.commit=-d "0 0" defaults.shelve=--date "0 0" defaults.tag=-d "0 0" - devel.all=true + devel.all-warnings=true largefiles.usercache=$TESTTMP/.cache/largefiles (glob) ui.slash=True ui.interactive=False diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bheads.t --- a/tests/test-bheads.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bheads.t Sat Jul 18 17:32:38 2015 -0500 @@ -37,7 +37,6 @@ $ hg add b $ hg branch b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ hg commit -m "Adding b branch" $ heads 2: Adding b branch (b) @@ -119,7 +118,6 @@ $ hg add c $ hg branch c marked working directory as branch c - (branches are permanent and global, did you want a bookmark?) $ hg commit -m "Adding c branch" $ heads 7: Adding c branch (c) @@ -302,7 +300,6 @@ $ hg up -q null $ hg branch -f b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ echo 1 > bb $ hg ci -Am "b4 (NN): new topo root for branch b" adding bb @@ -317,7 +314,6 @@ $ hg branch -f default marked working directory as branch default - (branches are permanent and global, did you want a bookmark?) $ echo 1 > aa $ hg ci -Am "a6 (BN): new branch root" adding aa @@ -337,7 +333,6 @@ $ hg merge -q 3 $ hg branch -f default marked working directory as branch default - (branches are permanent and global, did you want a bookmark?) $ hg ci -m "a8 (BB): weird new branch root" created new head diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bisect.t --- a/tests/test-bisect.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bisect.t Sat Jul 18 17:32:38 2015 -0500 @@ -190,6 +190,7 @@ branch: default commit: (clean) update: (current) + phases: 32 draft $ hg bisect -g 1 Testing changeset 16:a2e6ea4973e9 (30 changesets remaining, ~4 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bisect3.t --- a/tests/test-bisect3.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bisect3.t Sat Jul 18 17:32:38 2015 -0500 @@ -230,3 +230,20 @@ I 2:e1355ee1f23e G 1:ce7c85e06a9f G 0:b4e73ffab476 + + $ hg --config extensions.color= --color=debug log --quiet --style bisect + [log.bisect| ] 14:cbf2f3105bbf + [log.bisect| ] 13:e07efca37c43 + [log.bisect bisect.bad|B] 12:98c6b56349c0 + [log.bisect bisect.bad|B] 11:03f491376e63 + [log.bisect bisect.bad|B] 10:c012b15e2409 + [log.bisect bisect.untested|U] 9:2197c557e14c + [log.bisect bisect.untested|U] 8:e74a86251f58 + [log.bisect bisect.skipped|S] 7:a5f87041c899 + [log.bisect bisect.good|G] 6:7d997bedcd8d + [log.bisect bisect.good|G] 5:2dd1875f1028 + [log.bisect bisect.good|G] 4:2a1daef14cd4 + [log.bisect bisect.ignored|I] 3:8417d459b90c + [log.bisect bisect.ignored|I] 2:e1355ee1f23e + [log.bisect bisect.good|G] 1:ce7c85e06a9f + [log.bisect bisect.good|G] 0:b4e73ffab476 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bookmarks-current.t --- a/tests/test-bookmarks-current.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bookmarks-current.t Sat Jul 18 17:32:38 2015 -0500 @@ -69,7 +69,7 @@ * Y 0:719295282060 Z -1:000000000000 -Verify that switching to Z updates the current bookmark: +Verify that switching to Z updates the active bookmark: $ hg update Z 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (activating bookmark Z) @@ -118,7 +118,7 @@ * Y 0:719295282060 Z 0:719295282060 -deactivate current bookmark using -i +deactivate active bookmark using -i $ hg bookmark -i Y $ hg bookmarks @@ -137,7 +137,7 @@ * Y 0:719295282060 Z 0:719295282060 -deactivate current bookmark while renaming +deactivate active bookmark while renaming $ hg bookmark -i -m Y X $ hg bookmarks @@ -193,3 +193,12 @@ $ hg up -q . $ test -f .hg/bookmarks.current [1] + +issue 4552 -- simulate a pull moving the active bookmark + + $ hg up -q X + $ printf "Z" > .hg/bookmarks.current + $ hg log -T '{activebookmark}\n' -r Z + Z + $ hg log -T '{bookmarks % "{active}\n"}' -r Z + Z diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bookmarks-pushpull.t --- a/tests/test-bookmarks-pushpull.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bookmarks-pushpull.t Sat Jul 18 17:32:38 2015 -0500 @@ -7,6 +7,9 @@ > publish=False > [experimental] > evolution=createmarkers,exchange + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True > EOF initialize @@ -260,7 +263,14 @@ $ cd .. $ hg clone -q a pull-race - $ hg clone -q pull-race pull-race2 + +We want to use http because it is stateless and therefore more susceptible to +race conditions + + $ hg -R pull-race serve -p $HGPORT -d --pid-file=pull-race.pid -E main-error.log + $ cat pull-race.pid >> $DAEMON_PIDS + + $ hg clone -q http://localhost:$HGPORT/ pull-race2 $ cd pull-race $ hg up -q Y $ echo c4 > f2 @@ -270,13 +280,23 @@ > [hooks] > outgoing.makecommit = hg ci -Am5; echo committed in pull-race > EOF - $ cd ../pull-race2 + +(new config needs a server restart) + + $ cd .. + $ killdaemons.py + $ hg -R pull-race serve -p $HGPORT -d --pid-file=pull-race.pid -E main-error.log + $ cat pull-race.pid >> $DAEMON_PIDS + $ cd pull-race2 + $ hg -R $TESTTMP/pull-race book + @ 1:0d2164f0ce0d + X 1:0d2164f0ce0d + * Y 4:b0a5eff05604 + Z 1:0d2164f0ce0d $ hg pull - pulling from $TESTTMP/pull-race (glob) + pulling from http://localhost:$HGPORT/ searching for changes adding changesets - adding f3 - committed in pull-race adding manifests adding file changes added 1 changesets with 1 changes to 1 files @@ -287,6 +307,47 @@ X 1:0d2164f0ce0d Y 4:b0a5eff05604 Z 1:0d2164f0ce0d + +Update a bookmark right after the initial lookup -B (issue4689) + + $ echo c6 > ../pull-race/f3 # to be committed during the race + $ cat < ../pull-race/.hg/hgrc + > [hooks] + > # If anything to commit, commit it right after the first key listing used + > # during lookup. This makes the commit appear before the actual getbundle + > # call. + > listkeys.makecommit= ((hg st | grep -q M) && (hg commit -m race; echo commited in pull-race)) || exit 0 + > EOF + +(new config need server restart) + + $ killdaemons.py + $ hg -R ../pull-race serve -p $HGPORT -d --pid-file=../pull-race.pid -E main-error.log + $ cat ../pull-race.pid >> $DAEMON_PIDS + + $ hg -R $TESTTMP/pull-race book + @ 1:0d2164f0ce0d + X 1:0d2164f0ce0d + * Y 5:35d1ef0a8d1b + Z 1:0d2164f0ce0d + $ hg pull -B Y + pulling from http://localhost:$HGPORT/ + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + updating bookmark Y + (run 'hg update' to get a working copy) + $ hg book + * @ 1:0d2164f0ce0d + X 1:0d2164f0ce0d + Y 5:35d1ef0a8d1b + Z 1:0d2164f0ce0d + +(done with this section of the test) + + $ killdaemons.py $ cd ../b diverging a remote bookmark fails @@ -368,6 +429,7 @@ remote: adding manifests remote: adding file changes remote: added 2 changesets with 2 changes to 1 files (+1 heads) + remote: 2 new obsolescence markers updating bookmark Y $ hg -R ../a book @ 1:0d2164f0ce0d @@ -436,6 +498,7 @@ adding manifests adding file changes added 5 changesets with 5 changes to 3 files (+2 heads) + 2 new obsolescence markers updating to bookmark @ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R cloned-bookmarks bookmarks @@ -571,6 +634,7 @@ adding manifests adding file changes added 5 changesets with 5 changes to 3 files (+2 heads) + 2 new obsolescence markers updating to bookmark @ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd addmarks @@ -679,7 +743,7 @@ > push_ssl = false > allow_push = * > EOF - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg -R ../issue4455-dest serve -p $HGPORT -d --pid-file=../issue4455.pid -E ../issue4455-error.log $ cat ../issue4455.pid >> $DAEMON_PIDS @@ -691,15 +755,25 @@ searching for changes no changes found pushkey-abort: prepushkey hook exited with status 1 - exporting bookmark @ failed! - [1] + abort: exporting bookmark @ failed! + [255] $ hg -R ../issue4455-dest/ bookmarks no bookmarks set Using ssh --------- - $ hg push -B @ ssh + $ hg push -B @ ssh --config experimental.bundle2-exp=True + pushing to ssh://user@dummy/issue4455-dest + searching for changes + no changes found + remote: pushkey-abort: prepushkey hook exited with status 1 + abort: exporting bookmark @ failed! + [255] + $ hg -R ../issue4455-dest/ bookmarks + no bookmarks set + + $ hg push -B @ ssh --config experimental.bundle2-exp=False pushing to ssh://user@dummy/issue4455-dest searching for changes no changes found @@ -712,7 +786,17 @@ Using http ---------- - $ hg push -B @ http + $ hg push -B @ http --config experimental.bundle2-exp=True + pushing to http://localhost:$HGPORT/ + searching for changes + no changes found + remote: pushkey-abort: prepushkey hook exited with status 1 + abort: exporting bookmark @ failed! + [255] + $ hg -R ../issue4455-dest/ bookmarks + no bookmarks set + + $ hg push -B @ http --config experimental.bundle2-exp=False pushing to http://localhost:$HGPORT/ searching for changes no changes found diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bookmarks-strip.t --- a/tests/test-bookmarks-strip.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bookmarks-strip.t Sat Jul 18 17:32:38 2015 -0500 @@ -63,55 +63,3 @@ $ hg book test 0:5c9ad3787638 test2 0:5c9ad3787638 - -immediate rollback and reentrancy issue - - $ echo "mq=!" >> $HGRCPATH - $ hg init repo - $ cd repo - $ echo a > a - $ hg ci -Am adda - adding a - $ echo b > b - $ hg ci -Am addb - adding b - $ hg bookmarks markb - $ hg rollback - repository tip rolled back to revision 0 (undo commit) - working directory now based on revision 0 - -are you there? - - $ hg bookmarks - no bookmarks set - -can we commit? (issue2692) - - $ echo c > c - $ hg ci -Am rockon - adding c - -can you be added again? - - $ hg bookmarks markb - $ hg bookmarks - * markb 1:fdb34407462c - -rollback dry run with rollback information - - $ hg rollback -n - repository tip rolled back to revision 0 (undo commit) - $ hg bookmarks - * markb 1:fdb34407462c - -rollback dry run with rollback information and no commit undo - - $ rm .hg/store/undo - $ hg rollback -n - no rollback information available - [1] - $ hg bookmarks - * markb 1:fdb34407462c - - $ cd .. - diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bookmarks.t --- a/tests/test-bookmarks.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bookmarks.t Sat Jul 18 17:32:38 2015 -0500 @@ -393,6 +393,7 @@ bookmarks: *Z Y x y commit: (clean) update: 1 new changesets, 2 branch heads (merge) + phases: 3 draft test id @@ -402,10 +403,15 @@ test rollback $ echo foo > f1 + $ hg bookmark tmp-rollback $ hg ci -Amr adding f1 - $ hg bookmark -f Y -r 1 - $ hg bookmark -f Z -r 1 + $ hg bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + Z 2:db815d6d32e6 + * tmp-rollback 3:2bf5cfec5864 + x y 2:db815d6d32e6 $ hg rollback repository tip rolled back to revision 2 (undo commit) working directory now based on revision 2 @@ -413,7 +419,18 @@ X2 1:925d80f479bb Y 2:db815d6d32e6 Z 2:db815d6d32e6 + * tmp-rollback 2:db815d6d32e6 x y 2:db815d6d32e6 + $ hg bookmark -f Z -r 1 + $ hg rollback + repository tip rolled back to revision 2 (undo bookmark) + $ hg bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + Z 2:db815d6d32e6 + * tmp-rollback 2:db815d6d32e6 + x y 2:db815d6d32e6 + $ hg bookmark -d tmp-rollback activate bookmark on working dir parent without --force @@ -529,7 +546,7 @@ added 2 changesets with 2 changes to 2 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) -update to current bookmark if it's not the parent +update to active bookmark if it's not the parent $ hg summary parent: 2:db815d6d32e6 @@ -538,6 +555,7 @@ bookmarks: *Z Y x y commit: 1 added, 1 unknown (new branch head) update: 2 new changesets (update) + phases: 5 draft $ hg update 1 files updated, 0 files merged, 0 files removed, 0 files unresolved updating bookmark Z @@ -655,6 +673,31 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: 0 +test non-linear update not clearing active bookmark + + $ hg up 1 + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + (leaving bookmark four) + $ hg book drop + $ hg up -C + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark drop) + $ hg sum + parent: 2:db815d6d32e6 + 2 + branch: default + bookmarks: should-end-on-two + commit: 2 unknown (clean) + update: 1 new changesets, 2 branch heads (merge) + phases: 4 draft + $ hg book + drop 1:925d80f479bb + four 3:9ba5f110a0b3 + should-end-on-two 2:db815d6d32e6 + $ hg book -d drop + $ hg up four + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark four) test clearing divergent bookmarks of linear ancestors diff -r 501c51d60792 -r 96a38d44ba09 tests/test-branch-option.t --- a/tests/test-branch-option.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-branch-option.t Sat Jul 18 17:32:38 2015 -0500 @@ -14,7 +14,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch c marked working directory as branch c - (branches are permanent and global, did you want a bookmark?) $ echo c > foo $ hg ci -d '0 0' -mc $ hg tag -l z @@ -31,21 +30,18 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ echo b > foo $ hg ci -d '0 0' -mb $ hg up 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg --encoding utf-8 branch æ marked working directory as branch \xc3\xa6 (esc) - (branches are permanent and global, did you want a bookmark?) $ echo ae1 > foo $ hg ci -d '0 0' -mae1 $ hg up 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg --encoding utf-8 branch -f æ marked working directory as branch \xc3\xa6 (esc) - (branches are permanent and global, did you want a bookmark?) $ echo ae2 > foo $ hg ci -d '0 0' -mae2 created new head @@ -53,7 +49,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch -f b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ echo b2 > foo $ hg ci -d '0 0' -mb2 created new head diff -r 501c51d60792 -r 96a38d44ba09 tests/test-branches.t --- a/tests/test-branches.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-branches.t Sat Jul 18 17:32:38 2015 -0500 @@ -13,7 +13,6 @@ $ hg branch q marked working directory as branch q - (branches are permanent and global, did you want a bookmark?) $ echo 'aa' >a $ hg branch -C reset working directory to branch a @@ -25,7 +24,6 @@ $ hg add b $ hg branch b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ hg commit -d '2 0' -m "Adding b branch" $ echo 'bh1' >bh1 @@ -42,7 +40,6 @@ $ hg add c $ hg branch c marked working directory as branch c - (branches are permanent and global, did you want a bookmark?) $ hg commit -d '5 0' -m "Adding c branch" reserved names @@ -101,7 +98,6 @@ $ hg add d $ hg branch 'a branch name much longer than the default justification used by branches' marked working directory as branch a branch name much longer than the default justification used by branches - (branches are permanent and global, did you want a bookmark?) $ hg commit -d '6 0' -m "Adding d branch" $ hg branches @@ -601,7 +597,6 @@ cache is updated when committing $ hg branch i-will-regret-this marked working directory as branch i-will-regret-this - (branches are permanent and global, did you want a bookmark?) $ hg ci -m regrets $ f --size .hg/cache/rbc-* .hg/cache/rbc-names-v1: size=106 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bundle.t --- a/tests/test-bundle.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bundle.t Sat Jul 18 17:32:38 2015 -0500 @@ -211,7 +211,7 @@ Pull ../full.hg into empty (with hook) $ echo "[hooks]" >> .hg/hgrc - $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc doesn't work (yet ?) @@ -629,7 +629,7 @@ == bundling - $ hg bundle bundle.hg part --debug + $ hg bundle bundle.hg part --debug --config progress.debug=true query 1; heads searching for changes all remote heads known locally diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bundle2-exchange.t --- a/tests/test-bundle2-exchange.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bundle2-exchange.t Sat Jul 18 17:32:38 2015 -0500 @@ -28,7 +28,7 @@ > [hooks] > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n" > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n" - > txnclose.env = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" txnclose" + > txnclose.env = sh -c "HG_LOCAL= printenv.py txnclose" > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh" > EOF @@ -173,36 +173,66 @@ add extra data to test their exchange during push $ hg -R main bookmark --rev eea13746799a book_eea1 + pre-close-tip:02de42196ebe draft + postclose-tip:02de42196ebe draft + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a` pre-close-tip:02de42196ebe draft postclose-tip:02de42196ebe draft txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob) $ hg -R main bookmark --rev 02de42196ebe book_02de + pre-close-tip:02de42196ebe draft book_02de + postclose-tip:02de42196ebe draft book_02de + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe` pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob) $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc + pre-close-tip:02de42196ebe draft book_02de + postclose-tip:02de42196ebe draft book_02de + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16` pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob) $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd + pre-close-tip:02de42196ebe draft book_02de + postclose-tip:02de42196ebe draft book_02de + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8` pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob) $ hg -R main bookmark --rev 32af7686d403 book_32af + pre-close-tip:02de42196ebe draft book_02de + postclose-tip:02de42196ebe draft book_02de + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403` pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob) $ hg -R other bookmark --rev cd010b8cd998 book_eea1 + pre-close-tip:24b6387c8c8c public + postclose-tip:24b6387c8c8c public + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R other bookmark --rev cd010b8cd998 book_02de + pre-close-tip:24b6387c8c8c public + postclose-tip:24b6387c8c8c public + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R other bookmark --rev cd010b8cd998 book_42cc + pre-close-tip:24b6387c8c8c public + postclose-tip:24b6387c8c8c public + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R other bookmark --rev cd010b8cd998 book_5fdd + pre-close-tip:24b6387c8c8c public + postclose-tip:24b6387c8c8c public + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R other bookmark --rev cd010b8cd998 book_32af + pre-close-tip:24b6387c8c8c public + postclose-tip:24b6387c8c8c public + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ hg -R main phase --public eea13746799a pre-close-tip:02de42196ebe draft book_02de @@ -466,7 +496,7 @@ > failpush=$TESTTMP/failpush.py > EOF - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log $ cat other.pid >> $DAEMON_PIDS @@ -562,7 +592,7 @@ > txnabort.failpush = sh -c "echo 'Cleaning up the mess...'" > EOF - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log $ cat other.pid >> $DAEMON_PIDS @@ -625,7 +655,7 @@ $ cat << EOF >> $HGRCPATH > pretxnchangegroup = sh -c "echo 'Fail early!'; false" > EOF - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config + $ killdaemons.py # reload http config $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log $ cat other.pid >> $DAEMON_PIDS @@ -717,3 +747,153 @@ remote: rollback completed abort: pretxnchangegroup hook exited with status 1 [255] + +Check abort from mandatory pushkey + + $ cat > mandatorypart.py << EOF + > from mercurial import exchange + > from mercurial import pushkey + > from mercurial import node + > from mercurial import error + > @exchange.b2partsgenerator('failingpuskey') + > def addfailingpushey(pushop, bundler): + > enc = pushkey.encode + > part = bundler.newpart('pushkey') + > part.addparam('namespace', enc('phases')) + > part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex())) + > part.addparam('old', enc(str(0))) # successful update + > part.addparam('new', enc(str(0))) + > def fail(pushop, exc): + > raise error.Abort('Correct phase push failed (because hooks)') + > pushop.pkfailcb[part.id] = fail + > EOF + $ cat >> $HGRCPATH << EOF + > [hooks] + > pretxnchangegroup= + > pretxnclose.failpush= + > prepushkey.failpush = sh -c "echo 'do not push the key !'; false" + > [extensions] + > mandatorypart=$TESTTMP/mandatorypart.py + > EOF + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config + $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log + $ cat other.pid >> $DAEMON_PIDS + +(Failure from a hook) + + $ hg -R main push other -r e7ec4e813ba6 + pushing to other + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + do not push the key ! + pushkey-abort: prepushkey.failpush hook exited with status 1 + transaction abort! + Cleaning up the mess... + rollback completed + abort: Correct phase push failed (because hooks) + [255] + $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6 + pushing to ssh://user@dummy/other + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: do not push the key ! + remote: pushkey-abort: prepushkey.failpush hook exited with status 1 + remote: transaction abort! + remote: Cleaning up the mess... + remote: rollback completed + abort: Correct phase push failed (because hooks) + [255] + $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6 + pushing to http://localhost:$HGPORT2/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: do not push the key ! + remote: pushkey-abort: prepushkey.failpush hook exited with status 1 + remote: transaction abort! + remote: Cleaning up the mess... + remote: rollback completed + abort: Correct phase push failed (because hooks) + [255] + +(Failure from a the pushkey) + + $ cat > mandatorypart.py << EOF + > from mercurial import exchange + > from mercurial import pushkey + > from mercurial import node + > from mercurial import error + > @exchange.b2partsgenerator('failingpuskey') + > def addfailingpushey(pushop, bundler): + > enc = pushkey.encode + > part = bundler.newpart('pushkey') + > part.addparam('namespace', enc('phases')) + > part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex())) + > part.addparam('old', enc(str(4))) # will fail + > part.addparam('new', enc(str(3))) + > def fail(pushop, exc): + > raise error.Abort('Clown phase push failed') + > pushop.pkfailcb[part.id] = fail + > EOF + $ cat >> $HGRCPATH << EOF + > [hooks] + > prepushkey.failpush = + > EOF + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config + $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log + $ cat other.pid >> $DAEMON_PIDS + + $ hg -R main push other -r e7ec4e813ba6 + pushing to other + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + transaction abort! + Cleaning up the mess... + rollback completed + pushkey: lock state after "phases" + lock: free + wlock: free + abort: Clown phase push failed + [255] + $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6 + pushing to ssh://user@dummy/other + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: transaction abort! + remote: Cleaning up the mess... + remote: rollback completed + remote: pushkey: lock state after "phases" + remote: lock: free + remote: wlock: free + abort: Clown phase push failed + [255] + $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6 + pushing to http://localhost:$HGPORT2/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: transaction abort! + remote: Cleaning up the mess... + remote: rollback completed + remote: pushkey: lock state after "phases" + remote: lock: free + remote: wlock: free + abort: Clown phase push failed + [255] + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bundle2-format.t --- a/tests/test-bundle2-format.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bundle2-format.t Sat Jul 18 17:32:38 2015 -0500 @@ -336,11 +336,12 @@ bundling debug - $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2 - start emission of HG20 stream - bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple - start of parts - end of bundle + $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2 --config progress.debug=true --config devel.bundle2.debug=true + bundle2-output-bundle: "HG20", (2 params) 0 parts total + bundle2-output: start emission of HG20 stream + bundle2-output: bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple + bundle2-output: start of parts + bundle2-output: end of bundle file content is ok @@ -349,18 +350,18 @@ unbundling debug - $ hg statbundle2 --debug < ../out.hg2 - start processing of HG20 stream - reading bundle2 stream parameters - ignoring unknown parameter 'e|! 7/' - ignoring unknown parameter 'simple' + $ hg statbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../out.hg2 + bundle2-input: start processing of HG20 stream + bundle2-input: reading bundle2 stream parameters + bundle2-input: ignoring unknown parameter 'e|! 7/' + bundle2-input: ignoring unknown parameter 'simple' options count: 2 - e|! 7/ babar%#==tutu - simple - start extraction of bundle2 parts - part header size: 0 - end of bundle2 stream + bundle2-input: start extraction of bundle2 parts + bundle2-input: part header size: 0 + bundle2-input: end of bundle2 stream parts count: 0 @@ -383,18 +384,49 @@ Test part ================= - $ hg bundle2 --parts ../parts.hg2 --debug - start emission of HG20 stream - bundle parameter: - start of parts - bundle part: "test:empty" - bundle part: "test:empty" - bundle part: "test:song" - bundle part: "test:debugreply" - bundle part: "test:math" - bundle part: "test:song" - bundle part: "test:ping" - end of bundle + $ hg bundle2 --parts ../parts.hg2 --debug --config progress.debug=true --config devel.bundle2.debug=true + bundle2-output-bundle: "HG20", 7 parts total + bundle2-output: start emission of HG20 stream + bundle2-output: bundle parameter: + bundle2-output: start of parts + bundle2-output: bundle part: "test:empty" + bundle2-output-part: "test:empty" (advisory) empty payload + bundle2-output: part 0: "test:empty" + bundle2-output: header chunk size: 17 + bundle2-output: closing payload chunk + bundle2-output: bundle part: "test:empty" + bundle2-output-part: "test:empty" (advisory) empty payload + bundle2-output: part 1: "test:empty" + bundle2-output: header chunk size: 17 + bundle2-output: closing payload chunk + bundle2-output: bundle part: "test:song" + bundle2-output-part: "test:song" (advisory) 178 bytes payload + bundle2-output: part 2: "test:song" + bundle2-output: header chunk size: 16 + bundle2-output: payload chunk size: 178 + bundle2-output: closing payload chunk + bundle2-output: bundle part: "test:debugreply" + bundle2-output-part: "test:debugreply" (advisory) empty payload + bundle2-output: part 3: "test:debugreply" + bundle2-output: header chunk size: 22 + bundle2-output: closing payload chunk + bundle2-output: bundle part: "test:math" + bundle2-output-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) 2 bytes payload + bundle2-output: part 4: "test:math" + bundle2-output: header chunk size: 43 + bundle2-output: payload chunk size: 2 + bundle2-output: closing payload chunk + bundle2-output: bundle part: "test:song" + bundle2-output-part: "test:song" (advisory) (params: 1 mandatory) empty payload + bundle2-output: part 5: "test:song" + bundle2-output: header chunk size: 29 + bundle2-output: closing payload chunk + bundle2-output: bundle part: "test:ping" + bundle2-output-part: "test:ping" (advisory) empty payload + bundle2-output: part 6: "test:ping" + bundle2-output: header chunk size: 16 + bundle2-output: closing payload chunk + bundle2-output: end of bundle $ cat ../parts.hg2 HG20\x00\x00\x00\x00\x00\x00\x00\x11 (esc) @@ -436,78 +468,80 @@ payload: 0 bytes parts count: 7 - $ hg statbundle2 --debug < ../parts.hg2 - start processing of HG20 stream - reading bundle2 stream parameters + $ hg statbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../parts.hg2 + bundle2-input: start processing of HG20 stream + bundle2-input: reading bundle2 stream parameters options count: 0 - start extraction of bundle2 parts - part header size: 17 - part type: "test:empty" - part id: "0" - part parameters: 0 + bundle2-input: start extraction of bundle2 parts + bundle2-input: part header size: 17 + bundle2-input: part type: "test:empty" + bundle2-input: part id: "0" + bundle2-input: part parameters: 0 :test:empty: mandatory: 0 advisory: 0 - payload chunk size: 0 + bundle2-input: payload chunk size: 0 payload: 0 bytes - part header size: 17 - part type: "test:empty" - part id: "1" - part parameters: 0 + bundle2-input: part header size: 17 + bundle2-input: part type: "test:empty" + bundle2-input: part id: "1" + bundle2-input: part parameters: 0 :test:empty: mandatory: 0 advisory: 0 - payload chunk size: 0 + bundle2-input: payload chunk size: 0 payload: 0 bytes - part header size: 16 - part type: "test:song" - part id: "2" - part parameters: 0 + bundle2-input: part header size: 16 + bundle2-input: part type: "test:song" + bundle2-input: part id: "2" + bundle2-input: part parameters: 0 :test:song: mandatory: 0 advisory: 0 - payload chunk size: 178 - payload chunk size: 0 + bundle2-input: payload chunk size: 178 + bundle2-input: payload chunk size: 0 + bundle2-input-part: total payload size 178 payload: 178 bytes - part header size: 22 - part type: "test:debugreply" - part id: "3" - part parameters: 0 + bundle2-input: part header size: 22 + bundle2-input: part type: "test:debugreply" + bundle2-input: part id: "3" + bundle2-input: part parameters: 0 :test:debugreply: mandatory: 0 advisory: 0 - payload chunk size: 0 + bundle2-input: payload chunk size: 0 payload: 0 bytes - part header size: 43 - part type: "test:math" - part id: "4" - part parameters: 3 + bundle2-input: part header size: 43 + bundle2-input: part type: "test:math" + bundle2-input: part id: "4" + bundle2-input: part parameters: 3 :test:math: mandatory: 2 advisory: 1 - payload chunk size: 2 - payload chunk size: 0 + bundle2-input: payload chunk size: 2 + bundle2-input: payload chunk size: 0 + bundle2-input-part: total payload size 2 payload: 2 bytes - part header size: 29 - part type: "test:song" - part id: "5" - part parameters: 1 + bundle2-input: part header size: 29 + bundle2-input: part type: "test:song" + bundle2-input: part id: "5" + bundle2-input: part parameters: 1 :test:song: mandatory: 1 advisory: 0 - payload chunk size: 0 + bundle2-input: payload chunk size: 0 payload: 0 bytes - part header size: 16 - part type: "test:ping" - part id: "6" - part parameters: 0 + bundle2-input: part header size: 16 + bundle2-input: part type: "test:ping" + bundle2-input: part id: "6" + bundle2-input: part parameters: 0 :test:ping: mandatory: 0 advisory: 0 - payload chunk size: 0 + bundle2-input: payload chunk size: 0 payload: 0 bytes - part header size: 0 - end of bundle2 stream + bundle2-input: part header size: 0 + bundle2-input: end of bundle2 stream parts count: 7 Test actual unbundling of test part @@ -515,63 +549,74 @@ Process the bundle - $ hg unbundle2 --debug < ../parts.hg2 - start processing of HG20 stream - reading bundle2 stream parameters - start extraction of bundle2 parts - part header size: 17 - part type: "test:empty" - part id: "0" - part parameters: 0 - ignoring unsupported advisory part test:empty - payload chunk size: 0 - part header size: 17 - part type: "test:empty" - part id: "1" - part parameters: 0 - ignoring unsupported advisory part test:empty - payload chunk size: 0 - part header size: 16 - part type: "test:song" - part id: "2" - part parameters: 0 - found a handler for part 'test:song' + $ hg unbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../parts.hg2 + bundle2-input: start processing of HG20 stream + bundle2-input: reading bundle2 stream parameters + bundle2-input-bundle: with-transaction + bundle2-input: start extraction of bundle2 parts + bundle2-input: part header size: 17 + bundle2-input: part type: "test:empty" + bundle2-input: part id: "0" + bundle2-input: part parameters: 0 + bundle2-input: ignoring unsupported advisory part test:empty + bundle2-input-part: "test:empty" (advisory) unsupported-type + bundle2-input: payload chunk size: 0 + bundle2-input: part header size: 17 + bundle2-input: part type: "test:empty" + bundle2-input: part id: "1" + bundle2-input: part parameters: 0 + bundle2-input: ignoring unsupported advisory part test:empty + bundle2-input-part: "test:empty" (advisory) unsupported-type + bundle2-input: payload chunk size: 0 + bundle2-input: part header size: 16 + bundle2-input: part type: "test:song" + bundle2-input: part id: "2" + bundle2-input: part parameters: 0 + bundle2-input: found a handler for part 'test:song' + bundle2-input-part: "test:song" (advisory) supported The choir starts singing: - payload chunk size: 178 - payload chunk size: 0 + bundle2-input: payload chunk size: 178 + bundle2-input: payload chunk size: 0 + bundle2-input-part: total payload size 178 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko. - part header size: 22 - part type: "test:debugreply" - part id: "3" - part parameters: 0 - found a handler for part 'test:debugreply' + bundle2-input: part header size: 22 + bundle2-input: part type: "test:debugreply" + bundle2-input: part id: "3" + bundle2-input: part parameters: 0 + bundle2-input: found a handler for part 'test:debugreply' + bundle2-input-part: "test:debugreply" (advisory) supported debugreply: no reply - payload chunk size: 0 - part header size: 43 - part type: "test:math" - part id: "4" - part parameters: 3 - ignoring unsupported advisory part test:math - payload chunk size: 2 - payload chunk size: 0 - part header size: 29 - part type: "test:song" - part id: "5" - part parameters: 1 - found a handler for part 'test:song' - ignoring unsupported advisory part test:song - randomparam - payload chunk size: 0 - part header size: 16 - part type: "test:ping" - part id: "6" - part parameters: 0 - found a handler for part 'test:ping' + bundle2-input: payload chunk size: 0 + bundle2-input: part header size: 43 + bundle2-input: part type: "test:math" + bundle2-input: part id: "4" + bundle2-input: part parameters: 3 + bundle2-input: ignoring unsupported advisory part test:math + bundle2-input-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) unsupported-type + bundle2-input: payload chunk size: 2 + bundle2-input: payload chunk size: 0 + bundle2-input-part: total payload size 2 + bundle2-input: part header size: 29 + bundle2-input: part type: "test:song" + bundle2-input: part id: "5" + bundle2-input: part parameters: 1 + bundle2-input: found a handler for part 'test:song' + bundle2-input: ignoring unsupported advisory part test:song - randomparam + bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (['randomparam']) + bundle2-input: payload chunk size: 0 + bundle2-input: part header size: 16 + bundle2-input: part type: "test:ping" + bundle2-input: part id: "6" + bundle2-input: part parameters: 0 + bundle2-input: found a handler for part 'test:ping' + bundle2-input-part: "test:ping" (advisory) supported received ping request (id 6) - payload chunk size: 0 - part header size: 0 - end of bundle2 stream + bundle2-input: payload chunk size: 0 + bundle2-input: part header size: 0 + bundle2-input: end of bundle2 stream + bundle2-input-bundle: 6 parts total 0 unread bytes 3 total verses sung @@ -704,17 +749,21 @@ @ 0:3903775176ed draft test a - $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2 + $ hg bundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true --rev '8+7+5+4' ../rev.hg2 4 changesets found list of changesets: 32af7686d403cf45b5d95f2d70cebea587ac806a 9520eea781bcca16c1e15acc0ba14335a0e8e5ba eea13746799a9e0bfd88f29d3c2e9dc9389f524f 02de42196ebee42ef284b6780a87cdc96e8eaab6 - start emission of HG20 stream - bundle parameter: - start of parts - bundle part: "changegroup" + bundle2-output-bundle: "HG20", 1 parts total + bundle2-output: start emission of HG20 stream + bundle2-output: bundle parameter: + bundle2-output: start of parts + bundle2-output: bundle part: "changegroup" + bundle2-output-part: "changegroup" (advisory) streamed payload + bundle2-output: part 0: "changegroup" + bundle2-output: header chunk size: 18 bundling: 1/4 changesets (25.00%) bundling: 2/4 changesets (50.00%) bundling: 3/4 changesets (75.00%) @@ -726,7 +775,9 @@ bundling: D 1/3 files (33.33%) bundling: E 2/3 files (66.67%) bundling: H 3/3 files (100.00%) - end of bundle + bundle2-output: payload chunk size: 1555 + bundle2-output: closing payload chunk + bundle2-output: end of bundle $ cat ../rev.hg2 HG20\x00\x00\x00\x00\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bundle2-multiple-changegroups.t --- a/tests/test-bundle2-multiple-changegroups.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bundle2-multiple-changegroups.t Sat Jul 18 17:32:38 2015 -0500 @@ -67,9 +67,9 @@ $ cd ../clone $ cat >> .hg/hgrc < [hooks] - > pretxnchangegroup = sh -c "python \"$TESTDIR/printenv.py\" pretxnchangegroup" - > changegroup = sh -c "python \"$TESTDIR/printenv.py\" changegroup" - > incoming = sh -c "python \"$TESTDIR/printenv.py\" incoming" + > pretxnchangegroup = sh -c "printenv.py pretxnchangegroup" + > changegroup = sh -c "printenv.py changegroup" + > incoming = sh -c "printenv.py incoming" > EOF Pull the new commits in the clone diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bundle2-pushback.t --- a/tests/test-bundle2-pushback.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bundle2-pushback.t Sat Jul 18 17:32:38 2015 -0500 @@ -63,11 +63,11 @@ $ hg push pushing to ssh://user@dummy/server searching for changes - remote: pushback not enabled remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files + remote: pushback not enabled $ hg bookmark no bookmarks set diff -r 501c51d60792 -r 96a38d44ba09 tests/test-bundle2-remote-changegroup.t --- a/tests/test-bundle2-remote-changegroup.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-bundle2-remote-changegroup.t Sat Jul 18 17:32:38 2015 -0500 @@ -589,4 +589,4 @@ $ rm -rf clone - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-censor.t --- a/tests/test-censor.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-censor.t Sat Jul 18 17:32:38 2015 -0500 @@ -72,7 +72,10 @@ Censor revision with 2 offenses - $ hg censor -r $C2 -t "remove password" target +(this also tests file pattern matching: path relative to cwd case) + + $ mkdir -p foo/bar/baz + $ hg --cwd foo/bar/baz censor -r $C2 -t "remove password" ../../../target $ hg cat -r $H1 target Tainted file is now sanitized $ hg cat -r $H2 target @@ -89,7 +92,9 @@ Censor revision with 1 offense - $ hg censor -r $C1 target +(this also tests file pattern matching: with 'path:' scheme) + + $ hg --cwd foo/bar/baz censor -r $C1 path:target $ hg cat -r $H1 target Tainted file is now sanitized $ hg cat -r $H2 target diff -r 501c51d60792 -r 96a38d44ba09 tests/test-check-code.t --- a/tests/test-check-code.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-check-code.t Sat Jul 18 17:32:38 2015 -0500 @@ -17,53 +17,6 @@ > ( 4-1 ) """, "( 1+1 )\" and ") > a, '\\\\\\\\', "\\\\\\" x-2", "c-1" > EOF - $ cat > non-py24.py < # Using builtins that does not exist in Python 2.4 - > if any(): - > x = all() - > y = format(x) - > # next(generator) is new in 2.6 - > z = next(x) - > # but generator.next() is okay - > x.next() - > # and we can make our own next - > def next(stuff): - > pass - > - > # Do not complain about our own definition - > def any(x): - > pass - > - > # try/except/finally block does not exist in Python 2.4 - > try: - > pass - > except StandardError, inst: - > pass - > finally: - > pass - > - > # nested try/finally+try/except is allowed - > try: - > try: - > pass - > except StandardError, inst: - > pass - > finally: - > pass - > - > # yield inside a try/finally block is not allowed in Python 2.4 - > try: - > pass - > yield 1 - > finally: - > pass - > try: - > yield - > pass - > finally: - > pass - > - > EOF $ cat > classstyle.py < class newstyle_class(object): > pass @@ -78,7 +31,7 @@ > pass > EOF $ check_code="$TESTDIR"/../contrib/check-code.py - $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py + $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py ./wrong.py:1: > def toto( arg1, arg2): gratuitous whitespace in () or [] @@ -92,33 +45,12 @@ ./quote.py:5: > '"""', 42+1, """and missing whitespace in expression - ./non-py24.py:2: - > if any(): - any/all/format not available in Python 2.4 - ./non-py24.py:3: - > x = all() - any/all/format not available in Python 2.4 - ./non-py24.py:4: - > y = format(x) - any/all/format not available in Python 2.4 - ./non-py24.py:6: - > z = next(x) - no next(foo) in Python 2.4 and 2.5, use foo.next() instead - ./non-py24.py:18: - > try: - no try/except/finally in Python 2.4 - ./non-py24.py:35: - > try: - no yield inside try/finally in Python 2.4 - ./non-py24.py:40: - > try: - no yield inside try/finally in Python 2.4 ./classstyle.py:4: > class oldstyle_class: old-style class, use class foo(object) ./classstyle.py:7: > class empty(): - class foo() not available in Python 2.4, use class foo(object) + class foo() creates old style object, use class foo(object) [1] $ cat > python3-compat.py << EOF > foo <> bar diff -r 501c51d60792 -r 96a38d44ba09 tests/test-check-config-hg.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-config-hg.t Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,27 @@ +#require test-repo + + $ cd "$TESTDIR"/.. + +New errors are not allowed. Warnings are strongly discouraged. + + $ hg files "set:(**.py or **.txt) - tests/**" | + > xargs python contrib/check-config.py + undocumented: convert.cvsps.cache (bool) [True] + undocumented: convert.cvsps.fuzz (str) [60] + undocumented: convert.cvsps.mergefrom (str) + undocumented: convert.cvsps.mergeto (str) + undocumented: convert.git.remoteprefix (str) ['remote'] + undocumented: convert.git.similarity (int) [50] + undocumented: convert.hg.clonebranches (bool) + undocumented: convert.hg.ignoreerrors (bool) + undocumented: convert.hg.revs (str) + undocumented: convert.hg.saverev (bool) + undocumented: convert.hg.sourcename (str) + undocumented: convert.hg.startrev (str) + undocumented: convert.hg.tagsbranch (str) ['default'] + undocumented: convert.hg.usebranchnames (bool) [True] + undocumented: convert.localtimezone (bool) + undocumented: convert.p4.startrev (str) + undocumented: convert.skiptags (bool) + undocumented: convert.svn.debugsvnlog (bool) [True] + undocumented: convert.svn.startrev (str) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-clone-update-order.t --- a/tests/test-clone-update-order.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-clone-update-order.t Sat Jul 18 17:32:38 2015 -0500 @@ -14,7 +14,6 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg branch other marked working directory as branch other - (branches are permanent and global, did you want a bookmark?) $ echo good > bye $ hg commit -Am other adding bye diff -r 501c51d60792 -r 96a38d44ba09 tests/test-clone.t --- a/tests/test-clone.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-clone.t Sat Jul 18 17:32:38 2015 -0500 @@ -64,7 +64,7 @@ No update, with debug option: #if hardlink - $ hg --debug clone -U . ../c + $ hg --debug clone -U . ../c --config progress.debug=true linking: 1 linking: 2 linking: 3 @@ -75,7 +75,7 @@ linking: 8 linked 8 files #else - $ hg --debug clone -U . ../c + $ hg --debug clone -U . ../c --config progress.debug=true linking: 1 copying: 2 copying: 3 @@ -357,7 +357,6 @@ $ hg -R ua branch @ marked working directory as branch @ - (branches are permanent and global, did you want a bookmark?) $ hg -R ua commit -m 'created branch @' $ hg clone ua atbranch updating to branch default @@ -675,4 +674,342 @@ $ hg clone -U -q src dst $ hg -R dst log -q 0:e1bab28bca43 + +Create repositories to test auto sharing functionality + + $ cat >> $HGRCPATH << EOF + > [extensions] + > share= + > EOF + + $ hg init empty + $ hg init source1a + $ cd source1a + $ echo initial1 > foo + $ hg -q commit -A -m initial + $ echo second > foo + $ hg commit -m second $ cd .. + + $ hg init filteredrev0 + $ cd filteredrev0 + $ cat >> .hg/hgrc << EOF + > [experimental] + > evolution=createmarkers + > EOF + $ echo initial1 > foo + $ hg -q commit -A -m initial0 + $ hg -q up -r null + $ echo initial2 > foo + $ hg -q commit -A -m initial1 + $ hg debugobsolete c05d5c47a5cf81401869999f3d05f7d699d2b29a e082c1832e09a7d1e78b7fd49a592d372de854c8 + $ cd .. + + $ hg -q clone --pull source1a source1b + $ cd source1a + $ hg bookmark bookA + $ echo 1a > foo + $ hg commit -m 1a + $ cd ../source1b + $ hg -q up -r 0 + $ echo head1 > foo + $ hg commit -m head1 + created new head + $ hg bookmark head1 + $ hg -q up -r 0 + $ echo head2 > foo + $ hg commit -m head2 + created new head + $ hg bookmark head2 + $ hg -q up -r 0 + $ hg branch branch1 + marked working directory as branch branch1 + (branches are permanent and global, did you want a bookmark?) + $ echo branch1 > foo + $ hg commit -m branch1 + $ hg -q up -r 0 + $ hg branch branch2 + marked working directory as branch branch2 + $ echo branch2 > foo + $ hg commit -m branch2 + $ cd .. + $ hg init source2 + $ cd source2 + $ echo initial2 > foo + $ hg -q commit -A -m initial2 + $ echo second > foo + $ hg commit -m second + $ cd .. + +Clone with auto share from an empty repo should not result in share + + $ mkdir share + $ hg --config share.pool=share clone empty share-empty + (not using pooled storage: remote appears to be empty) + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ ls share + $ test -d share-empty/.hg/store + $ test -f share-empty/.hg/sharedpath + [1] + +Clone with auto share from a repo with filtered revision 0 should not result in share + + $ hg --config share.pool=share clone filteredrev0 share-filtered + (not using pooled storage: unable to resolve identity of remote) + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Clone from repo with content should result in shared store being created + + $ hg --config share.pool=share clone source1a share-dest1a + (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 1 files + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + no changes found + adding remote bookmark bookA + +The shared repo should have been created + + $ ls share + b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1 + +The destination should point to it + + $ cat share-dest1a/.hg/sharedpath; echo + $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg + +The destination should have bookmarks + + $ hg -R share-dest1a bookmarks + bookA 2:e5bfe23c0b47 + +The default path should be the remote, not the share + + $ hg -R share-dest1a config paths.default + $TESTTMP/source1a + +Clone with existing share dir should result in pull + share + + $ hg --config share.pool=share clone source1b share-dest1b + (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 1 files (+4 heads) + adding remote bookmark head1 + adding remote bookmark head2 + + $ ls share + b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1 + + $ cat share-dest1b/.hg/sharedpath; echo + $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg + +We only get bookmarks from the remote, not everything in the share + + $ hg -R share-dest1b bookmarks + head1 3:4a8dc1ab4c13 + head2 4:99f71071f117 + +Default path should be source, not share. + + $ hg -R share-dest1b config paths.default + $TESTTMP/source1a + +Clone from unrelated repo should result in new share + + $ hg --config share.pool=share clone source2 share-dest2 + (sharing from new pooled repository 22aeff664783fd44c6d9b435618173c118c3448e) + requesting all changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + no changes found + + $ ls share + 22aeff664783fd44c6d9b435618173c118c3448e + b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1 + +remote naming mode works as advertised + + $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1a share-remote1a + (sharing from new pooled repository 195bb1fcdb595c14a6c13e0269129ed78f6debde) + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 1 files + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + no changes found + adding remote bookmark bookA + + $ ls shareremote + 195bb1fcdb595c14a6c13e0269129ed78f6debde + + $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1b share-remote1b + (sharing from new pooled repository c0d4f83847ca2a873741feb7048a45085fd47c46) + requesting all changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 6 changes to 1 files (+4 heads) + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + no changes found + adding remote bookmark head1 + adding remote bookmark head2 + + $ ls shareremote + 195bb1fcdb595c14a6c13e0269129ed78f6debde + c0d4f83847ca2a873741feb7048a45085fd47c46 + +request to clone a single revision is respected in sharing mode + + $ hg --config share.pool=sharerevs clone -r 4a8dc1ab4c13 source1b share-1arev + (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + no changes found + adding remote bookmark head1 + + $ hg -R share-1arev log -G + @ changeset: 1:4a8dc1ab4c13 + | bookmark: head1 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: head1 + | + o changeset: 0:b5f04eac9d8f + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + +making another clone should only pull down requested rev + + $ hg --config share.pool=sharerevs clone -r 99f71071f117 source1b share-1brev + (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + adding remote bookmark head1 + adding remote bookmark head2 + + $ hg -R share-1brev log -G + o changeset: 2:99f71071f117 + | bookmark: head2 + | tag: tip + | parent: 0:b5f04eac9d8f + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: head2 + | + | @ changeset: 1:4a8dc1ab4c13 + |/ bookmark: head1 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: head1 + | + o changeset: 0:b5f04eac9d8f + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + +Request to clone a single branch is respected in sharing mode + + $ hg --config share.pool=sharebranch clone -b branch1 source1b share-1bbranch1 + (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + no changes found + + $ hg -R share-1bbranch1 log -G + o changeset: 1:5f92a6c1a1b1 + | branch: branch1 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: branch1 + | + @ changeset: 0:b5f04eac9d8f + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + + $ hg --config share.pool=sharebranch clone -b branch2 source1b share-1bbranch2 + (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + + $ hg -R share-1bbranch2 log -G + o changeset: 2:6bacf4683960 + | branch: branch2 + | tag: tip + | parent: 0:b5f04eac9d8f + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: branch2 + | + | o changeset: 1:5f92a6c1a1b1 + |/ branch: branch1 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: branch1 + | + @ changeset: 0:b5f04eac9d8f + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + +-U is respected in share clone mode + + $ hg --config share.pool=share clone -U source1a share-1anowc + (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) + searching for changes + no changes found + adding remote bookmark bookA + + $ ls share-1anowc diff -r 501c51d60792 -r 96a38d44ba09 tests/test-command-template.t --- a/tests/test-command-template.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-command-template.t Sat Jul 18 17:32:38 2015 -0500 @@ -51,6 +51,18 @@ 8 t 7 f +Working-directory revision has special identifiers, though they are still +experimental: + + $ hg log -r 'wdir()' -T '{rev}:{node}\n' + 2147483647:ffffffffffffffffffffffffffffffffffffffff + +Some keywords are invalid for working-directory revision, but they should +never cause crash: + + $ hg log -r 'wdir()' -T '{manifest}\n' + + Quoting for ui.logtemplate $ hg tip --config "ui.logtemplate={rev}\n" @@ -95,7 +107,7 @@ 8 Add a commit with empty description, to ensure that the templates -following below omit it properly. +below will omit the description line. $ echo c >> c $ hg add c @@ -108,32 +120,52 @@ $ hg log --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg log -T phases > phases.out - $ diff -u log.out phases.out | grep "phase:" + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +phase: draft + @@ -6,0 +8 @@ +phase: draft + @@ -11,0 +14 @@ +phase: draft + @@ -17,0 +21 @@ +phase: draft + @@ -24,0 +29 @@ +phase: draft + @@ -31,0 +37 @@ +phase: draft + @@ -36,0 +43 @@ +phase: draft + @@ -41,0 +49 @@ +phase: draft + @@ -46,0 +55 @@ +phase: draft + @@ -51,0 +61 @@ +phase: draft $ hg log -v > log.out $ hg log -v --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg log -v -T phases > phases.out - $ diff -u log.out phases.out | grep phase: + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +phase: draft + @@ -7,0 +9 @@ +phase: draft + @@ -15,0 +18 @@ +phase: draft + @@ -24,0 +28 @@ +phase: draft + @@ -33,0 +38 @@ +phase: draft + @@ -43,0 +49 @@ +phase: draft + @@ -50,0 +57 @@ +phase: draft + @@ -58,0 +66 @@ +phase: draft + @@ -66,0 +75 @@ +phase: draft + @@ -77,0 +87 @@ +phase: draft $ hg log -q > log.out @@ -148,6 +180,26 @@ $ hg log --debug -T phases > phases.out $ cmp log.out phases.out || diff -u log.out phases.out +Default style of working-directory revision should also be the same (but +date may change while running tests): + + $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out + $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out + $ cmp log.out style.out || diff -u log.out style.out + + $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out + $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out + $ cmp log.out style.out || diff -u log.out style.out + + $ hg log -r 'wdir()' -q > log.out + $ hg log -r 'wdir()' -q --style default > style.out + $ cmp log.out style.out || diff -u log.out style.out + + $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out + $ hg log -r 'wdir()' --debug --style default \ + > | sed 's|^date:.*|date:|' > style.out + $ cmp log.out style.out || diff -u log.out style.out + Default style should also preserve color information (issue2866): $ cp $HGRCPATH $HGRCPATH-bak @@ -160,32 +212,52 @@ $ hg --color=debug log --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg --color=debug log -T phases > phases.out - $ diff -u log.out phases.out | grep phase: + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +[log.phase|phase: draft] + @@ -6,0 +8 @@ +[log.phase|phase: draft] + @@ -11,0 +14 @@ +[log.phase|phase: draft] + @@ -17,0 +21 @@ +[log.phase|phase: draft] + @@ -24,0 +29 @@ +[log.phase|phase: draft] + @@ -31,0 +37 @@ +[log.phase|phase: draft] + @@ -36,0 +43 @@ +[log.phase|phase: draft] + @@ -41,0 +49 @@ +[log.phase|phase: draft] + @@ -46,0 +55 @@ +[log.phase|phase: draft] + @@ -51,0 +61 @@ +[log.phase|phase: draft] $ hg --color=debug -v log > log.out $ hg --color=debug -v log --style default > style.out $ cmp log.out style.out || diff -u log.out style.out $ hg --color=debug -v log -T phases > phases.out - $ diff -u log.out phases.out | grep phase: + $ diff -U 0 log.out phases.out | grep -v '^---\|^+++' + @@ -2,0 +3 @@ +[log.phase|phase: draft] + @@ -7,0 +9 @@ +[log.phase|phase: draft] + @@ -15,0 +18 @@ +[log.phase|phase: draft] + @@ -24,0 +28 @@ +[log.phase|phase: draft] + @@ -33,0 +38 @@ +[log.phase|phase: draft] + @@ -43,0 +49 @@ +[log.phase|phase: draft] + @@ -50,0 +57 @@ +[log.phase|phase: draft] + @@ -58,0 +66 @@ +[log.phase|phase: draft] + @@ -66,0 +75 @@ +[log.phase|phase: draft] + @@ -77,0 +87 @@ +[log.phase|phase: draft] $ hg --color=debug -q log > log.out @@ -952,11 +1024,11 @@ $ hg log --style notexist abort: style 'notexist' not found - (available styles: bisect, changelog, compact, default, phases, xml) + (available styles: bisect, changelog, compact, default, phases, status, xml) [255] $ hg log -T list - available styles: bisect, changelog, compact, default, phases, xml + available styles: bisect, changelog, compact, default, phases, status, xml abort: specify a template [255] @@ -1898,6 +1970,8 @@ Age filter: + $ hg init unstable-hash + $ cd unstable-hash $ hg log --template '{date|age}\n' > /dev/null || exit 1 >>> from datetime import datetime, timedelta @@ -1911,6 +1985,15 @@ $ hg log -l1 --template '{date|age}\n' 7 years from now + $ cd .. + $ rm -rf unstable-hash + +Add a dummy commit to make up for the instability of the above: + + $ echo a > a + $ hg add a + $ hg ci -m future + Count filter: $ hg log -l1 --template '{node|count} {node|short|count}\n' @@ -1953,6 +2036,476 @@ abort: template filter 'upper' is not compatible with keyword 'date' [255] +Add a commit that does all possible modifications at once + + $ echo modify >> third + $ touch b + $ hg add b + $ hg mv fourth fifth + $ hg rm a + $ hg ci -m "Modify, add, remove, rename" + +Check the status template + + $ cat <> $HGRCPATH + > [extensions] + > color= + > EOF + + $ hg log -T status -r 10 + changeset: 10:0f9759ec227a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Modify, add, remove, rename + files: + M third + A b + A fifth + R a + R fourth + + $ hg log -T status -C -r 10 + changeset: 10:0f9759ec227a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Modify, add, remove, rename + files: + M third + A b + A fifth + fourth + R a + R fourth + + $ hg log -T status -C -r 10 -v + changeset: 10:0f9759ec227a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + description: + Modify, add, remove, rename + + files: + M third + A b + A fifth + fourth + R a + R fourth + + $ hg log -T status -C -r 10 --debug + changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c + tag: tip + phase: secret + parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066 + parent: -1:0000000000000000000000000000000000000000 + manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + extra: branch=default + description: + Modify, add, remove, rename + + files: + M third + A b + A fifth + fourth + R a + R fourth + + $ hg log -T status -C -r 10 --quiet + 10:0f9759ec227a + $ hg --color=debug log -T status -r 10 + [log.changeset changeset.secret|changeset: 10:0f9759ec227a] + [log.tag|tag: tip] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [log.summary|summary: Modify, add, remove, rename] + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 + [log.changeset changeset.secret|changeset: 10:0f9759ec227a] + [log.tag|tag: tip] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [log.summary|summary: Modify, add, remove, rename] + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.copied| fourth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 -v + [log.changeset changeset.secret|changeset: 10:0f9759ec227a] + [log.tag|tag: tip] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [ui.note log.description|description:] + [ui.note log.description|Modify, add, remove, rename] + + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.copied| fourth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 --debug + [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c] + [log.tag|tag: tip] + [log.phase|phase: secret] + [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567] + [log.user|user: test] + [log.date|date: Thu Jan 01 00:00:00 1970 +0000] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|Modify, add, remove, rename] + + [ui.note log.files|files:] + [status.modified|M third] + [status.added|A b] + [status.added|A fifth] + [status.copied| fourth] + [status.removed|R a] + [status.removed|R fourth] + + $ hg --color=debug log -T status -C -r 10 --quiet + [log.node|10:0f9759ec227a] + +Check the bisect template + + $ hg bisect -g 1 + $ hg bisect -b 3 --noupdate + Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests) + $ hg log -T bisect -r 0:4 + changeset: 0:1e4e1b8f71e0 + bisect: good (implicit) + user: User Name + date: Mon Jan 12 13:46:40 1970 +0000 + summary: line 1 + + changeset: 1:b608e9d1a3f0 + bisect: good + user: A. N. Other + date: Tue Jan 13 17:33:20 1970 +0000 + summary: other 1 + + changeset: 2:97054abb4ab8 + bisect: untested + user: other@place + date: Wed Jan 14 21:20:00 1970 +0000 + summary: no person + + changeset: 3:10e46f2dcbf4 + bisect: bad + user: person + date: Fri Jan 16 01:06:40 1970 +0000 + summary: no user, no domain + + changeset: 4:bbe44766e73d + bisect: bad (implicit) + branch: foo + user: person + date: Sat Jan 17 04:53:20 1970 +0000 + summary: new branch + + $ hg log --debug -T bisect -r 0:4 + changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f + bisect: good (implicit) + phase: public + parent: -1:0000000000000000000000000000000000000000 + parent: -1:0000000000000000000000000000000000000000 + manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 + user: User Name + date: Mon Jan 12 13:46:40 1970 +0000 + files+: a + extra: branch=default + description: + line 1 + line 2 + + + changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 + bisect: good + phase: public + parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f + parent: -1:0000000000000000000000000000000000000000 + manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55 + user: A. N. Other + date: Tue Jan 13 17:33:20 1970 +0000 + files+: b + extra: branch=default + description: + other 1 + other 2 + + other 3 + + + changeset: 2:97054abb4ab824450e9164180baf491ae0078465 + bisect: untested + phase: public + parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 + parent: -1:0000000000000000000000000000000000000000 + manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1 + user: other@place + date: Wed Jan 14 21:20:00 1970 +0000 + files+: c + extra: branch=default + description: + no person + + + changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 + bisect: bad + phase: public + parent: 2:97054abb4ab824450e9164180baf491ae0078465 + parent: -1:0000000000000000000000000000000000000000 + manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc + user: person + date: Fri Jan 16 01:06:40 1970 +0000 + files: c + extra: branch=default + description: + no user, no domain + + + changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74 + bisect: bad (implicit) + branch: foo + phase: draft + parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 + parent: -1:0000000000000000000000000000000000000000 + manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc + user: person + date: Sat Jan 17 04:53:20 1970 +0000 + extra: branch=foo + description: + new branch + + + $ hg log -v -T bisect -r 0:4 + changeset: 0:1e4e1b8f71e0 + bisect: good (implicit) + user: User Name + date: Mon Jan 12 13:46:40 1970 +0000 + files: a + description: + line 1 + line 2 + + + changeset: 1:b608e9d1a3f0 + bisect: good + user: A. N. Other + date: Tue Jan 13 17:33:20 1970 +0000 + files: b + description: + other 1 + other 2 + + other 3 + + + changeset: 2:97054abb4ab8 + bisect: untested + user: other@place + date: Wed Jan 14 21:20:00 1970 +0000 + files: c + description: + no person + + + changeset: 3:10e46f2dcbf4 + bisect: bad + user: person + date: Fri Jan 16 01:06:40 1970 +0000 + files: c + description: + no user, no domain + + + changeset: 4:bbe44766e73d + bisect: bad (implicit) + branch: foo + user: person + date: Sat Jan 17 04:53:20 1970 +0000 + description: + new branch + + + $ hg --color=debug log -T bisect -r 0:4 + [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0] + [log.bisect bisect.good|bisect: good (implicit)] + [log.user|user: User Name ] + [log.date|date: Mon Jan 12 13:46:40 1970 +0000] + [log.summary|summary: line 1] + + [log.changeset changeset.public|changeset: 1:b608e9d1a3f0] + [log.bisect bisect.good|bisect: good] + [log.user|user: A. N. Other ] + [log.date|date: Tue Jan 13 17:33:20 1970 +0000] + [log.summary|summary: other 1] + + [log.changeset changeset.public|changeset: 2:97054abb4ab8] + [log.bisect bisect.untested|bisect: untested] + [log.user|user: other@place] + [log.date|date: Wed Jan 14 21:20:00 1970 +0000] + [log.summary|summary: no person] + + [log.changeset changeset.public|changeset: 3:10e46f2dcbf4] + [log.bisect bisect.bad|bisect: bad] + [log.user|user: person] + [log.date|date: Fri Jan 16 01:06:40 1970 +0000] + [log.summary|summary: no user, no domain] + + [log.changeset changeset.draft|changeset: 4:bbe44766e73d] + [log.bisect bisect.bad|bisect: bad (implicit)] + [log.branch|branch: foo] + [log.user|user: person] + [log.date|date: Sat Jan 17 04:53:20 1970 +0000] + [log.summary|summary: new branch] + + $ hg --color=debug log --debug -T bisect -r 0:4 + [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f] + [log.bisect bisect.good|bisect: good (implicit)] + [log.phase|phase: public] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0] + [log.user|user: User Name ] + [log.date|date: Mon Jan 12 13:46:40 1970 +0000] + [ui.debug log.files|files+: a] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|line 1 + line 2] + + + [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965] + [log.bisect bisect.good|bisect: good] + [log.phase|phase: public] + [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55] + [log.user|user: A. N. Other ] + [log.date|date: Tue Jan 13 17:33:20 1970 +0000] + [ui.debug log.files|files+: b] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|other 1 + other 2 + + other 3] + + + [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465] + [log.bisect bisect.untested|bisect: untested] + [log.phase|phase: public] + [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1] + [log.user|user: other@place] + [log.date|date: Wed Jan 14 21:20:00 1970 +0000] + [ui.debug log.files|files+: c] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|no person] + + + [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47] + [log.bisect bisect.bad|bisect: bad] + [log.phase|phase: public] + [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc] + [log.user|user: person] + [log.date|date: Fri Jan 16 01:06:40 1970 +0000] + [ui.debug log.files|files: c] + [ui.debug log.extra|extra: branch=default] + [ui.note log.description|description:] + [ui.note log.description|no user, no domain] + + + [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74] + [log.bisect bisect.bad|bisect: bad (implicit)] + [log.branch|branch: foo] + [log.phase|phase: draft] + [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47] + [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000] + [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc] + [log.user|user: person] + [log.date|date: Sat Jan 17 04:53:20 1970 +0000] + [ui.debug log.extra|extra: branch=foo] + [ui.note log.description|description:] + [ui.note log.description|new branch] + + + $ hg --color=debug log -v -T bisect -r 0:4 + [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0] + [log.bisect bisect.good|bisect: good (implicit)] + [log.user|user: User Name ] + [log.date|date: Mon Jan 12 13:46:40 1970 +0000] + [ui.note log.files|files: a] + [ui.note log.description|description:] + [ui.note log.description|line 1 + line 2] + + + [log.changeset changeset.public|changeset: 1:b608e9d1a3f0] + [log.bisect bisect.good|bisect: good] + [log.user|user: A. N. Other ] + [log.date|date: Tue Jan 13 17:33:20 1970 +0000] + [ui.note log.files|files: b] + [ui.note log.description|description:] + [ui.note log.description|other 1 + other 2 + + other 3] + + + [log.changeset changeset.public|changeset: 2:97054abb4ab8] + [log.bisect bisect.untested|bisect: untested] + [log.user|user: other@place] + [log.date|date: Wed Jan 14 21:20:00 1970 +0000] + [ui.note log.files|files: c] + [ui.note log.description|description:] + [ui.note log.description|no person] + + + [log.changeset changeset.public|changeset: 3:10e46f2dcbf4] + [log.bisect bisect.bad|bisect: bad] + [log.user|user: person] + [log.date|date: Fri Jan 16 01:06:40 1970 +0000] + [ui.note log.files|files: c] + [ui.note log.description|description:] + [ui.note log.description|no user, no domain] + + + [log.changeset changeset.draft|changeset: 4:bbe44766e73d] + [log.bisect bisect.bad|bisect: bad (implicit)] + [log.branch|branch: foo] + [log.user|user: person] + [log.date|date: Sat Jan 17 04:53:20 1970 +0000] + [ui.note log.description|description:] + [ui.note log.description|new branch] + + + $ hg bisect --reset + Error on syntax: $ echo 'x = "f' >> t @@ -1960,6 +2513,10 @@ abort: t:3: unmatched quotes [255] + $ hg log -T '{date' + hg: parse error at 1: unterminated template expansion + [255] + Behind the scenes, this will throw TypeError $ hg log -l 3 --template '{date|obfuscate}\n' @@ -1984,6 +2541,16 @@ abort: template filter 'datefilter' is not compatible with keyword 'author' [255] +Error in nested template: + + $ hg log -T '{"date' + hg: parse error at 2: unterminated string + [255] + + $ hg log -T '{"foo{date|=}"}' + hg: parse error at 11: syntax error + [255] + Thrown an error if a template function doesn't exist $ hg tip --template '{foo()}\n' @@ -2249,6 +2816,54 @@ hg: parse error: date expects a date information [255] +Test integer literal: + + $ hg log -Ra -r0 -T '{(0)}\n' + 0 + $ hg log -Ra -r0 -T '{(123)}\n' + 123 + $ hg log -Ra -r0 -T '{(-4)}\n' + -4 + $ hg log -Ra -r0 -T '{(-)}\n' + hg: parse error at 2: integer literal without digits + [255] + $ hg log -Ra -r0 -T '{(-a)}\n' + hg: parse error at 2: integer literal without digits + [255] + +top-level integer literal is interpreted as symbol (i.e. variable name): + + $ hg log -Ra -r0 -T '{1}\n' + + $ hg log -Ra -r0 -T '{if("t", "{1}")}\n' + + $ hg log -Ra -r0 -T '{1|stringify}\n' + + +unless explicit symbol is expected: + + $ hg log -Ra -r0 -T '{desc|1}\n' + hg: parse error: expected a symbol, got 'integer' + [255] + $ hg log -Ra -r0 -T '{1()}\n' + hg: parse error: expected a symbol, got 'integer' + [255] + +Test string literal: + + $ hg log -Ra -r0 -T '{"string with no template fragment"}\n' + string with no template fragment + $ hg log -Ra -r0 -T '{"template: {rev}"}\n' + template: 0 + $ hg log -Ra -r0 -T '{r"rawstring: {rev}"}\n' + rawstring: {rev} + +because map operation requires template, raw string can't be used + + $ hg log -Ra -r0 -T '{files % r"rawstring"}\n' + hg: parse error: expected template specifier + [255] + Test string escaping: $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n' @@ -2291,11 +2906,34 @@ $ hg log -Ra -r0 -T '{r"\\\""}\n' \\\" + + $ hg log -Ra -r0 -T '{"\""}\n' + " + $ hg log -Ra -r0 -T '{"\\\""}\n' + \" + $ hg log -Ra -r0 -T '{r"\""}\n' + \" + $ hg log -Ra -r0 -T '{r"\\\""}\n' + \\\" + +Test exception in quoted template. single backslash before quotation mark is +stripped before parsing: + + $ cat <<'EOF' > escquotetmpl + > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n" + > EOF + $ cd latesttag + $ hg log -r 2 --style ../escquotetmpl + " \" \" \\" head1 + + $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"' + valid + $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'" + valid + Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested _evalifliteral() templates (issue4733): - $ cd latesttag - $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n' "2 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n' @@ -2324,7 +2962,7 @@ $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n' foo $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n' - hg: parse error at 11: unterminated string + hg: parse error at 21: unterminated string [255] $ hg log -r 2 -T '{if(rev, \"\\"")}\n' hg: parse error at 11: syntax error @@ -2338,23 +2976,23 @@ Test leading backslashes: $ cd latesttag - $ hg log -r 2 -T '\{rev} {files % "\{file}"} {files % r"\{file}"}\n' - {rev} {file} \head1 - $ hg log -r 2 -T '\\{rev} {files % "\\{file}"} {files % r"\\{file}"}\n' - \2 \head1 \\head1 - $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"} {files % r"\\\{file}"}\n' - \{rev} \{file} \\\head1 + $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n' + {rev} {file} + $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n' + \2 \head1 + $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n' + \{rev} \{file} $ cd .. Test leading backslashes in "if" expression (issue4714): $ cd latesttag $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n' - {rev} \2 + {rev} \{rev} $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n' - \2 \\2 + \2 \\{rev} $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n' - \{rev} \\\2 + \{rev} \\\{rev} $ cd .. "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice) @@ -2424,8 +3062,6 @@ fourth second third - $ hg log -R a -r 8 --template '{files % r"{file}\n"}\n' - fourth\nsecond\nthird\n Test string escaping in nested expression: @@ -2443,6 +3079,14 @@ 3:\x6eo user, \x6eo domai\x6e 4:\x5c\x786eew bra\x5c\x786ech +Test quotes in nested expression are evaluated just like a $(command) +substitution in POSIX shells: + + $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n' + 8:95c24699272e + $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n' + {8} "95c24699272e" + Test recursive evaluation: $ hg init r @@ -2531,6 +3175,14 @@ 1------------------- {node|short} 0------------------- test +Test template string in pad function + + $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n' + {0} test + + $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n' + \{rev} test + Test ifcontains function $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n' @@ -2585,20 +3237,20 @@ $ hg log --template '{revset("TIP"|lower)}\n' -l1 2 -Test current bookmark templating +Test active bookmark templating $ hg book foo $ hg book bar - $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n" + $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n" 2 bar* foo 1 0 - $ hg log --template "{rev} {currentbookmark}\n" + $ hg log --template "{rev} {activebookmark}\n" 2 bar 1 0 $ hg bookmarks --inactive bar - $ hg log --template "{rev} {currentbookmark}\n" + $ hg log --template "{rev} {activebookmark}\n" 2 1 0 @@ -2623,7 +3275,9 @@ Test splitlines $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}" - @ foo future + @ foo Modify, add, remove, rename + | + o foo future | o foo third | @@ -2657,6 +3311,8 @@ o | o + | + o o |\ @@ -2682,7 +3338,9 @@ Test word function (including index out of bounds graceful failure) $ hg log -Gv -R a --template "{word('1', desc)}" - @ + @ add, + | + o | o | @@ -2706,7 +3364,9 @@ Test word third parameter used as splitter $ hg log -Gv -R a --template "{word('0', desc, 'o')}" - @ future + @ M + | + o future | o third | @@ -2737,8 +3397,31 @@ hg: parse error: word expects two or three arguments, got 7 [255] +Test word for integer literal + + $ hg log -R a --template "{word(2, desc)}\n" -r0 + line + Test word for invalid numbers - $ hg log -Gv -R a --template "{word(2, desc)}" - hg: parse error: Use strings like '3' for numbers passed to word function + $ hg log -Gv -R a --template "{word('a', desc)}" + hg: parse error: word expects an integer index [255] + +Test indent and not adding to empty lines + + $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a + ----- + > line 1 + >> line 2 + ----- + > other 1 + >> other 2 + + >> other 3 + +Test with non-strings like dates + + $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a + 1200000.00 + 1300000.00 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-commandserver.t --- a/tests/test-commandserver.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-commandserver.t Sat Jul 18 17:32:38 2015 -0500 @@ -178,7 +178,7 @@ defaults.commit=-d "0 0" defaults.shelve=--date "0 0" defaults.tag=-d "0 0" - devel.all=true + devel.all-warnings=true largefiles.usercache=$TESTTMP/.cache/largefiles ui.slash=True ui.interactive=False diff -r 501c51d60792 -r 96a38d44ba09 tests/test-commit-amend.t --- a/tests/test-commit-amend.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-commit-amend.t Sat Jul 18 17:32:38 2015 -0500 @@ -72,6 +72,7 @@ branch: default commit: 1 added, 1 unknown update: (current) + phases: 2 draft $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend transaction abort! rollback completed @@ -83,6 +84,7 @@ branch: default commit: 1 added, 1 unknown update: (current) + phases: 2 draft Add new file: $ hg ci --amend -m 'amend base1 new file' @@ -371,7 +373,6 @@ $ hg ci -m 'branch foo' $ hg branch default -f marked working directory as branch default - (branches are permanent and global, did you want a bookmark?) $ hg ci --amend -m 'back to default' saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob) $ hg branches @@ -846,7 +847,6 @@ $ hg up -q default $ hg branch closewithamend marked working directory as branch closewithamend - (branches are permanent and global, did you want a bookmark?) $ echo foo > foo $ hg add foo $ hg ci -m.. @@ -858,7 +858,6 @@ $ hg branch silliness marked working directory as branch silliness - (branches are permanent and global, did you want a bookmark?) $ echo b >> b $ hg ci --close-branch -m'open and close' abort: can only close branch heads diff -r 501c51d60792 -r 96a38d44ba09 tests/test-commit-interactive-curses.t --- a/tests/test-commit-interactive-curses.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-commit-interactive-curses.t Sat Jul 18 17:32:38 2015 -0500 @@ -65,14 +65,16 @@ a a -Committing only one hunk +Committing only one hunk while aborting edition of hunk - Untoggle all the hunks, go down to the second file - unfold it - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike) - toggle the second hunk +- edit the hunk and quit the editor imediately with non-zero status - commit + $ printf "printf 'editor ran\n'; exit 1" > editor.sh $ echo "x" > c $ cat b >> c $ echo "y" >> c @@ -86,9 +88,12 @@ > KEY_DOWN > KEY_DOWN > TOGGLE + > e > X > EOF - $ hg commit -i -m "one hunk" -d "0 0" + $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0" + editor ran + $ rm editor.sh $ hg tip changeset: 2:7d10dfe755a8 tag: tip diff -r 501c51d60792 -r 96a38d44ba09 tests/test-commit-interactive.t --- a/tests/test-commit-interactive.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-commit-interactive.t Sat Jul 18 17:32:38 2015 -0500 @@ -15,6 +15,12 @@ $ touch empty-rw $ hg add empty-rw + $ hg record --config ui.interactive=false + abort: running non-interactively, use commit instead + [255] + $ hg commit -i --config ui.interactive=false + abort: running non-interactively + [255] $ hg commit -i empty-rw< n > EOF @@ -81,6 +87,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft Rename empty file @@ -1290,6 +1297,33 @@ abort: error parsing patch: unhandled transition: range -> range [255] +Exiting editor with status 1, ignores the edit but does not stop the recording +session + + $ HGEDITOR=false hg commit -i < y + > e + > n + > EOF + diff --git a/editedfile b/editedfile + 1 hunks, 3 lines changed + examine changes to 'editedfile'? [Ynesfdaq?] y + + @@ -1,3 +1,3 @@ + -This is the first line + -This change will be committed + -This is the third line + +This change will not be committed + +This is the second line + +This line has been added + record this change to 'editedfile'? [Ynesfdaq?] e + + editor exited with exit code 1 + record this change to 'editedfile'? [Ynesfdaq?] n + + no changes to record + + random text in random positions is still an error $ cat > editor.sh << '__EOF__' @@ -1353,6 +1387,8 @@ record this change to 'subdir/f1'? [Ynesfdaq?] y + $ hg status -A subdir/f1 + C subdir/f1 $ hg tip -p changeset: 28:* (glob) tag: tip @@ -1389,6 +1425,8 @@ +e record this change to 'subdir/f1'? [Ynesfdaq?] y + $ hg status -A subdir/f1 + C subdir/f1 $ hg log --template '{author}\n' -l 1 xyz $ HGUSER="test" @@ -1398,7 +1436,7 @@ Moving files $ hg update -C . - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg mv plain plain3 $ echo somechange >> plain3 $ hg commit -i -d '23 0' -mmoving_files << EOF @@ -1419,6 +1457,8 @@ record this change to 'plain3'? [Ynesfdaq?] y The #if execbit block above changes the hash here on some systems + $ hg status -A plain3 + C plain3 $ hg tip changeset: 30:* (glob) tag: tip @@ -1498,3 +1538,98 @@ +foo $ cd .. + + $ hg status -A folder/bar + C folder/bar + +Clear win32text configuration before size/timestamp sensitive test + + $ cat >> .hg/hgrc < [extensions] + > win32text = ! + > [decode] + > ** = ! + > [encode] + > ** = ! + > [patch] + > eol = strict + > EOF + $ hg update -q -C null + $ hg update -q -C tip + +Test that partially committed file is still treated as "modified", +even if none of mode, size and timestamp is changed on the filesystem +(see also issue4583). + + $ cat > subdir/f1 < A + > a + > a + > b + > c + > d + > E + > EOF + $ hg diff --git subdir/f1 + diff --git a/subdir/f1 b/subdir/f1 + --- a/subdir/f1 + +++ b/subdir/f1 + @@ -1,7 +1,7 @@ + -a + +A + a + a + b + c + d + -e + +E + + $ touch -t 200001010000 subdir/f1 + + $ cat >> .hg/hgrc < # emulate invoking patch.internalpatch() at 2000-01-01 00:00 + > [fakepatchtime] + > fakenow = 200001010000 + > + > [extensions] + > fakepatchtime = $TESTDIR/fakepatchtime.py + > EOF + $ hg commit -i -m 'commit subdir/f1 partially' < y + > y + > n + > EOF + diff --git a/subdir/f1 b/subdir/f1 + 2 hunks, 2 lines changed + examine changes to 'subdir/f1'? [Ynesfdaq?] y + + @@ -1,6 +1,6 @@ + -a + +A + a + a + b + c + d + record change 1/2 to 'subdir/f1'? [Ynesfdaq?] y + + @@ -2,6 +2,6 @@ + a + a + b + c + d + -e + +E + record change 2/2 to 'subdir/f1'? [Ynesfdaq?] n + + $ cat >> .hg/hgrc < [extensions] + > fakepatchtime = ! + > EOF + + $ hg debugstate | grep ' subdir/f1$' + n 0 -1 unset subdir/f1 + $ hg status -A subdir/f1 + M subdir/f1 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-commit-multiple.t --- a/tests/test-commit-multiple.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-commit-multiple.t Sat Jul 18 17:32:38 2015 -0500 @@ -52,7 +52,6 @@ 1 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg branch release marked working directory as branch release - (branches are permanent and global, did you want a bookmark?) $ hg transplant 2 3 applying [0-9a-f]{12} (re) [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-commit.t --- a/tests/test-commit.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-commit.t Sat Jul 18 17:32:38 2015 -0500 @@ -302,7 +302,7 @@ $ cd commitmsg $ echo changed > changed $ echo removed > removed - $ hg book currentbookmark + $ hg book activebookmark $ hg ci -qAm init $ hg rm removed @@ -317,7 +317,7 @@ HG: -- HG: user: test HG: branch 'default' - HG: bookmark 'currentbookmark' + HG: bookmark 'activebookmark' HG: added added HG: changed changed HG: removed removed @@ -354,7 +354,7 @@ HG: -- HG: user: test HG: branch 'default' - HG: bookmark 'currentbookmark' + HG: bookmark 'activebookmark' HG: subrepo sub HG: added .hgsub HG: added added @@ -376,22 +376,22 @@ > [committemplate] > changeset.commit.normal = HG: this is "commit.normal" template > HG: {extramsg} - > {if(currentbookmark, - > "HG: bookmark '{currentbookmark}' is activated\n", + > {if(activebookmark, + > "HG: bookmark '{activebookmark}' is activated\n", > "HG: no bookmark is activated\n")}{subrepos % > "HG: subrepo '{subrepo}' is changed\n"} > > changeset.commit = HG: this is "commit" template > HG: {extramsg} - > {if(currentbookmark, - > "HG: bookmark '{currentbookmark}' is activated\n", + > {if(activebookmark, + > "HG: bookmark '{activebookmark}' is activated\n", > "HG: no bookmark is activated\n")}{subrepos % > "HG: subrepo '{subrepo}' is changed\n"} > > changeset = HG: this is customized commit template > HG: {extramsg} - > {if(currentbookmark, - > "HG: bookmark '{currentbookmark}' is activated\n", + > {if(activebookmark, + > "HG: bookmark '{activebookmark}' is activated\n", > "HG: no bookmark is activated\n")}{subrepos % > "HG: subrepo '{subrepo}' is changed\n"} > EOF @@ -404,7 +404,7 @@ $ HGEDITOR=cat hg commit -S -q HG: this is "commit.normal" template HG: Leave message empty to abort commit. - HG: bookmark 'currentbookmark' is activated + HG: bookmark 'activebookmark' is activated HG: subrepo 'sub' is changed HG: subrepo 'sub2' is changed abort: empty commit message @@ -416,7 +416,7 @@ > # now, "changeset.commit" should be chosen for "hg commit" > EOF - $ hg bookmark --inactive currentbookmark + $ hg bookmark --inactive activebookmark $ hg forget .hgsub $ HGEDITOR=cat hg commit -q HG: this is "commit" template @@ -580,6 +580,18 @@ 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re) 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re) +Test making empty commits + $ hg commit --config ui.allowemptycommit=True -m "empty commit" + $ hg log -r . -v --stat + changeset: 2:d809f3644287 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + description: + empty commit + + + verify pathauditor blocks evil filepaths $ cat > evil-commit.py < from mercurial import ui, hg, context, node @@ -604,7 +616,7 @@ #endif $ hg rollback -f - repository tip rolled back to revision 1 (undo commit) + repository tip rolled back to revision 2 (undo commit) $ cat > evil-commit.py < from mercurial import ui, hg, context, node > notrc = "HG~1/hgrc" @@ -622,7 +634,7 @@ [255] $ hg rollback -f - repository tip rolled back to revision 1 (undo commit) + repository tip rolled back to revision 2 (undo commit) $ cat > evil-commit.py < from mercurial import ui, hg, context, node > notrc = "HG8B6C~2/hgrc" diff -r 501c51d60792 -r 96a38d44ba09 tests/test-completion.t --- a/tests/test-completion.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-completion.t Sat Jul 18 17:32:38 2015 -0500 @@ -96,6 +96,7 @@ debugpushkey debugpvec debugrebuilddirstate + debugrebuildfncache debugrename debugrevlog debugrevspec @@ -234,7 +235,7 @@ debugcommands: debugcomplete: options debugdag: tags, branches, dots, spaces - debugdata: changelog, manifest + debugdata: changelog, manifest, dir debugdate: extended debugdirstate: nodates, datesort debugdiscovery: old, nonheads, ssh, remotecmd, insecure @@ -242,7 +243,7 @@ debugfsinfo: debuggetbundle: head, common, type debugignore: - debugindex: changelog, manifest, format + debugindex: changelog, manifest, dir, format debugindexdot: debuginstall: debugknown: @@ -254,8 +255,9 @@ debugpushkey: debugpvec: debugrebuilddirstate: rev + debugrebuildfncache: debugrename: rev - debugrevlog: changelog, manifest, dump + debugrevlog: changelog, manifest, dir, dump debugrevspec: optimize debugsetparents: debugsub: rev diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-clonebranches.t --- a/tests/test-convert-clonebranches.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-clonebranches.t Sat Jul 18 17:32:38 2015 -0500 @@ -55,13 +55,11 @@ $ cd source $ hg branch branch1 marked working directory as branch branch1 - (branches are permanent and global, did you want a bookmark?) $ echo a > file1 $ hg ci -qAm c1 $ hg up -qC mergeab $ hg branch branch2 marked working directory as branch branch2 - (branches are permanent and global, did you want a bookmark?) $ echo a > file2 $ hg ci -qAm c2 $ hg merge branch1 @@ -69,7 +67,6 @@ (branch merge, don't forget to commit) $ hg branch branch3 marked working directory as branch branch3 - (branches are permanent and global, did you want a bookmark?) $ hg ci -qAm c3 $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-cvs.t --- a/tests/test-convert-cvs.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-cvs.t Sat Jul 18 17:32:38 2015 -0500 @@ -121,7 +121,7 @@ 1 ci0 0 import filtering out empty revision - repository tip rolled back to revision 1 (undo commit) + repository tip rolled back to revision 1 (undo convert) updating tags $ hgcat b/c c diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-datesort.t --- a/tests/test-convert-datesort.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-datesort.t Sat Jul 18 17:32:38 2015 -0500 @@ -21,7 +21,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch branchb marked working directory as branch branchb - (branches are permanent and global, did you want a bookmark?) $ echo b >> b $ hg ci -Am b0 -d '6 0' adding b @@ -42,7 +41,6 @@ $ echo c >> c $ hg branch branchc marked working directory as branch branchc - (branches are permanent and global, did you want a bookmark?) $ hg ci -Am c0 -d '10 0' adding c $ hg up -C brancha diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-filemap.t --- a/tests/test-convert-filemap.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-filemap.t Sat Jul 18 17:32:38 2015 -0500 @@ -438,7 +438,7 @@ $ hg ci -m 'merging something' $ cd .. $ echo "53792d18237d2b64971fa571936869156655338d 6d955580116e82c4b029bd30f321323bae71a7f0" >> branchpruning-hg2/.hg/shamap - $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2 --debug + $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2 --debug --config progress.debug=true run hg source pre-conversion action run hg sink pre-conversion action scanning source... @@ -477,7 +477,7 @@ 2 add 1 rename filtering out empty revision - repository tip rolled back to revision 0 (undo commit) + repository tip rolled back to revision 0 (undo convert) 0 modify $ glog -R renameundo2 o 1 "modify" files: a c diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-git.t --- a/tests/test-convert-git.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-git.t Sat Jul 18 17:32:38 2015 -0500 @@ -442,6 +442,40 @@ abort: --sourcesort is not supported by this data source [255] +test converting certain branches + + $ mkdir git-testrevs + $ cd git-testrevs + $ git init + Initialized empty Git repository in $TESTTMP/git-testrevs/.git/ + $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null + $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null + $ git checkout -b goodbranch 'HEAD^' + Switched to a new branch 'goodbranch' + $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null + $ git checkout -b badbranch 'HEAD^' + Switched to a new branch 'badbranch' + $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null + $ cd .. + $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch + initializing destination hg-testrevs repository + scanning source... + sorting... + converting... + 2 first + 1 good branch commit + 0 master commit + updating bookmarks + $ cd hg-testrevs + $ hg log -G -T '{rev} {bookmarks}' + o 2 master + | + | o 1 goodbranch + |/ + o 0 + + $ cd .. + test sub modules $ mkdir git-repo5 @@ -457,6 +491,53 @@ $ git init-db >/dev/null 2>/dev/null $ git submodule add ${BASE} >/dev/null 2>/dev/null $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null + +test non-tab whitespace .gitmodules + + $ cat >> .gitmodules < [submodule "git-repo5"] + > path = git-repo5 + > url = git-repo5 + > EOF + $ git commit -q -a -m "weird white space submodule" + $ cd .. + $ hg convert git-repo6 hg-repo6 + initializing destination hg-repo6 repository + scanning source... + sorting... + converting... + 1 addsubmodule + 0 weird white space submodule + updating bookmarks + + $ rm -rf hg-repo6 + $ cd git-repo6 + $ git reset --hard 'HEAD^' > /dev/null + +test missing .gitmodules + + $ git submodule add ../git-repo4 >/dev/null 2>/dev/null + $ git checkout HEAD .gitmodules + $ git rm .gitmodules + rm '.gitmodules' + $ git commit -q -m "remove .gitmodules" .gitmodules + $ git commit -q -m "missing .gitmodules" + $ cd .. + $ hg convert git-repo6 hg-repo6 --traceback + fatal: Path '.gitmodules' does not exist in '*' (glob) + initializing destination hg-repo6 repository + scanning source... + sorting... + converting... + 2 addsubmodule + 1 remove .gitmodules + 0 missing .gitmodules + warning: cannot read submodules config file in * (glob) + updating bookmarks + $ rm -rf hg-repo6 + $ cd git-repo6 + $ rm -rf git-repo4 + $ git reset --hard 'HEAD^^' > /dev/null $ cd .. test invalid splicemap1 @@ -561,6 +642,30 @@ $ hg -R git-repo6-hg tip -T "{file_dels}\n" .hgsub .hgsubstate +convert using a different remote prefix + $ git init git-repo7 + Initialized empty Git repository in $TESTTMP/git-repo7/.git/ + $ cd git-repo7 + $ touch a && git add a && git commit -am "commit a" + [master (root-commit) 8ae5f69] commit a + Author: nottest + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 a + $ cd .. + $ git clone git-repo7 git-repo7-client + Cloning into 'git-repo7-client'... + done. + $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7 + initializing destination hg-repo7 repository + scanning source... + sorting... + converting... + 0 commit a + updating bookmarks + $ hg -R hg-repo7 bookmarks + master 0:03bf38caa4c6 + origin/master 0:03bf38caa4c6 + damaged git repository tests: In case the hard-coded hashes change, the following commands can be used to list the hashes and their corresponding types in the repository: diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-hg-sink.t --- a/tests/test-convert-hg-sink.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-hg-sink.t Sat Jul 18 17:32:38 2015 -0500 @@ -41,6 +41,7 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: add foo and bar + $ hg phase --public -r tip $ cd .. $ hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded' initializing destination new repository @@ -52,6 +53,16 @@ 1 add foo/file 0 Added tag some-tag for changeset ad681a868e44 $ cd new + $ hg log -G --template '{rev} {node|short} ({phase}) "{desc}"\n' + o 3 593cbf6fb2b4 (public) "Added tag some-tag for changeset ad681a868e44" + | + o 2 ad681a868e44 (public) "add foo/file" + | + o 1 cbba8ecc03b7 (public) "remove foo" + | + o 0 327daa9251fa (public) "add foo and bar" + + $ hg out ../orig comparing with ../orig searching for changes @@ -132,7 +143,7 @@ $ glog() > { - > hg log -G --template '{rev} {node|short} "{desc}" files: {files}\n' $* + > hg log -G --template '{rev} {node|short} ({phase}) "{desc}" files: {files}\n' $* > } Create a tricky source repo @@ -171,20 +182,21 @@ dir/c dir/d e + $ hg phase --public -r tip $ glog - @ 6 0613c8e59a3d "6: change a" files: a + @ 6 0613c8e59a3d (public) "6: change a" files: a | - o 5 717e9b37cdb7 "5: merge 2 and 3, copy b to dir/d" files: dir/d e + o 5 717e9b37cdb7 (public) "5: merge 2 and 3, copy b to dir/d" files: dir/d e |\ - | o 4 86a55cb968d5 "4: change a" files: a + | o 4 86a55cb968d5 (public) "4: change a" files: a | | - o | 3 0e6e235919dd "3: copy a to e, change b" files: b e + o | 3 0e6e235919dd (public) "3: copy a to e, change b" files: b e | | - o | 2 0394b0d5e4f7 "2: add dir/c" files: dir/c + o | 2 0394b0d5e4f7 (public) "2: add dir/c" files: dir/c |/ - o 1 333546584845 "1: add a and dir/b" files: a dir/b + o 1 333546584845 (public) "1: add a and dir/b" files: a dir/b | - o 0 d1a24e2ebd23 "0: add 0" files: 0 + o 0 d1a24e2ebd23 (public) "0: add 0" files: 0 $ cd .. @@ -209,15 +221,15 @@ Verify that conversion skipped rev 2: $ glog -R dest - o 4 78814e84a217 "6: change a" files: a + o 4 78814e84a217 (draft) "6: change a" files: a | - o 3 f7cff662c5e5 "5: merge 2 and 3, copy b to dir/d" files: e + o 3 f7cff662c5e5 (draft) "5: merge 2 and 3, copy b to dir/d" files: e |\ - | o 2 ab40a95b0072 "4: change a" files: a + | o 2 ab40a95b0072 (draft) "4: change a" files: a | | - o | 1 bd51f17597bf "3: copy a to e, change b" files: b e + o | 1 bd51f17597bf (draft) "3: copy a to e, change b" files: b e |/ - o 0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a + o 0 a4a1dae0fe35 (draft) "1: add a and dir/b" files: 0 a Verify mapping correct in both directions: @@ -347,17 +359,17 @@ e $ glog -r 6: - @ 11 0c8927d1f7f4 "11: source change" files: a + @ 11 0c8927d1f7f4 (draft) "11: source change" files: a | - o 10 9ccb7ee8d261 "10: source merge" files: a + o 10 9ccb7ee8d261 (draft) "10: source merge" files: a |\ - | o 9 f131b1518dba "9: source second branch" files: a + | o 9 f131b1518dba (draft) "9: source second branch" files: a | | - o | 8 669cf0e74b50 "8: source first branch" files: a + o | 8 669cf0e74b50 (draft) "8: source first branch" files: a | | - | o 7 e6d364a69ff1 "change in dest" files: dest + | o 7 e6d364a69ff1 (draft) "change in dest" files: dest |/ - o 6 0613c8e59a3d "6: change a" files: a + o 6 0613c8e59a3d (public) "6: change a" files: a | $ cd .. @@ -371,25 +383,25 @@ 0 11: source change $ glog -R dest - o 9 8432d597b263 "11: source change" files: a + o 9 8432d597b263 (draft) "11: source change" files: a | - o 8 632ffacdcd6f "10: source merge" files: a + o 8 632ffacdcd6f (draft) "10: source merge" files: a |\ - | o 7 049cfee90ee6 "9: source second branch" files: a + | o 7 049cfee90ee6 (draft) "9: source second branch" files: a | | - o | 6 9b6845e036e5 "8: source first branch" files: a + o | 6 9b6845e036e5 (draft) "8: source first branch" files: a | | - | @ 5 a2e0e3cc6d1d "change in dest" files: dest + | @ 5 a2e0e3cc6d1d (draft) "change in dest" files: dest |/ - o 4 78814e84a217 "6: change a" files: a + o 4 78814e84a217 (draft) "6: change a" files: a | - o 3 f7cff662c5e5 "5: merge 2 and 3, copy b to dir/d" files: e + o 3 f7cff662c5e5 (draft) "5: merge 2 and 3, copy b to dir/d" files: e |\ - | o 2 ab40a95b0072 "4: change a" files: a + | o 2 ab40a95b0072 (draft) "4: change a" files: a | | - o | 1 bd51f17597bf "3: copy a to e, change b" files: b e + o | 1 bd51f17597bf (draft) "3: copy a to e, change b" files: b e |/ - o 0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a + o 0 a4a1dae0fe35 (draft) "1: add a and dir/b" files: 0 a $ cd .. @@ -520,7 +532,7 @@ Conversion after rollback $ hg -R a rollback -f - repository tip rolled back to revision 2 (undo commit) + repository tip rolled back to revision 2 (undo convert) $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1:: scanning source... diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-hg-source.t --- a/tests/test-convert-hg-source.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-hg-source.t Sat Jul 18 17:32:38 2015 -0500 @@ -83,7 +83,43 @@ premerge1 3:973ef48a98a4 premerge2 8:3537b15eaaca #endif - $ cd .. + +Test that redoing a convert results in an identical graph + $ cd ../ + $ rm new/.hg/shamap + $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded' + scanning source... + sorting... + converting... + 8 add foo bar + 7 change foo + 6 make bar and baz copies of foo + 5 merge local copy + 4 merge remote copy + 3 Added tag that for changeset 88586c4e9f02 + 2 Removed tag that + 1 Added tag this for changeset c56a7f387039 + 0 mark baz executable + updating bookmarks + $ hg -R new log -G -T '{rev} {desc}' + o 8 mark baz executable + | + o 7 Added tag this for changeset c56a7f387039 + | + o 6 Removed tag that + | + o 5 Added tag that for changeset 88586c4e9f02 + | + o 4 merge remote copy + |\ + +---o 3 merge local copy + | |/ + | o 2 make bar and baz copies of foo + | | + o | 1 change foo + |/ + o 0 add foo bar + check shamap LF and CRLF handling diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-mtn.t --- a/tests/test-convert-mtn.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-mtn.t Sat Jul 18 17:32:38 2015 -0500 @@ -220,7 +220,7 @@ >>> fp = file('large-file', 'wb') >>> for x in xrange(10000): fp.write('%d\n' % x) >>> fp.close() - $ $TESTDIR/md5sum.py large-file + $ md5sum.py large-file 5d6de8a95c3b6bf9e0ffb808ba5299c1 large-file $ mtn add large-file mtn: adding 'large-file' to workspace manifest @@ -386,7 +386,7 @@ test large file support (> 32kB) - $ $TESTDIR/md5sum.py large-file + $ md5sum.py large-file 5d6de8a95c3b6bf9e0ffb808ba5299c1 large-file check branch closing diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-svn-branches.t --- a/tests/test-convert-svn-branches.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-svn-branches.t Sat Jul 18 17:32:38 2015 -0500 @@ -99,7 +99,7 @@ Convert 'trunk' to branch other than 'default' $ cat > branchmap < None hgtrunk + > default hgtrunk > > > EOF @@ -121,9 +121,8 @@ 0 last change to a $ cd C-hg - $ hg branches - hgtrunk 10:745f063703b4 - old 9:aa50d7b8d922 - old2 8:c85a22267b6e (inactive) + $ hg branches --template '{branch}\n' + hgtrunk + old + old2 $ cd .. - diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-svn-encoding.t --- a/tests/test-convert-svn-encoding.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-svn-encoding.t Sat Jul 18 17:32:38 2015 -0500 @@ -10,7 +10,7 @@ Convert while testing all possible outputs - $ hg --debug convert svn-repo A-hg + $ hg --debug convert svn-repo A-hg --config progress.debug=1 initializing destination A-hg repository reparent to file://*/svn-repo (glob) run hg sink pre-conversion action diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-svn-sink.t --- a/tests/test-convert-svn-sink.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-svn-sink.t Sat Jul 18 17:32:38 2015 -0500 @@ -32,7 +32,7 @@ Modify - $ "$TESTDIR/svn-safe-append.py" a a/a + $ svn-safe-append.py a a/a $ hg --cwd a ci -d '1 0' -m 'modify a file' $ hg --cwd a tip -q 1:e0e2b8a9156b @@ -354,12 +354,12 @@ $ hg --cwd b ci -d '0 0' -Ambase adding b - $ "$TESTDIR/svn-safe-append.py" left-1 b/b + $ svn-safe-append.py left-1 b/b $ echo left-1 > b/left-1 $ hg --cwd b ci -d '1 0' -Amleft-1 adding left-1 - $ "$TESTDIR/svn-safe-append.py" left-2 b/b + $ svn-safe-append.py left-2 b/b $ echo left-2 > b/left-2 $ hg --cwd b ci -d '2 0' -Amleft-2 adding left-2 @@ -367,13 +367,13 @@ $ hg --cwd b up 0 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ "$TESTDIR/svn-safe-append.py" right-1 b/b + $ svn-safe-append.py right-1 b/b $ echo right-1 > b/right-1 $ hg --cwd b ci -d '3 0' -Amright-1 adding right-1 created new head - $ "$TESTDIR/svn-safe-append.py" right-2 b/b + $ svn-safe-append.py right-2 b/b $ echo right-2 > b/right-2 $ hg --cwd b ci -d '4 0' -Amright-2 adding right-2 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-svn-source.t --- a/tests/test-convert-svn-source.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-svn-source.t Sat Jul 18 17:32:38 2015 -0500 @@ -46,7 +46,7 @@ Transmitting file data . Committed revision 2. - $ "$TESTDIR/svn-safe-append.py" world 'letter .txt' + $ svn-safe-append.py world 'letter .txt' $ svn ci -m world Sending letter .txt Transmitting file data . @@ -56,7 +56,7 @@ Committed revision 4. - $ "$TESTDIR/svn-safe-append.py" 'nice day today!' 'letter .txt' + $ svn-safe-append.py 'nice day today!' 'letter .txt' $ svn ci -m "nice day" Sending letter .txt Transmitting file data . @@ -86,7 +86,7 @@ Update svn repository again $ cd B - $ "$TESTDIR/svn-safe-append.py" "see second letter" 'letter .txt' + $ svn-safe-append.py "see second letter" 'letter .txt' $ echo "nice to meet you" > letter2.txt $ svn add letter2.txt A letter2.txt @@ -100,7 +100,7 @@ Committed revision 7. - $ "$TESTDIR/svn-safe-append.py" "blah-blah-blah" letter2.txt + $ svn-safe-append.py "blah-blah-blah" letter2.txt $ svn ci -m "work in progress" Sending letter2.txt Transmitting file data . diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert-tagsbranch-topology.t --- a/tests/test-convert-tagsbranch-topology.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert-tagsbranch-topology.t Sat Jul 18 17:32:38 2015 -0500 @@ -45,6 +45,19 @@ $ action tag -m "tag1" tag1 $ cd .. +Convert without tags + + $ hg convert git-repo hg-repo --config convert.skiptags=True + initializing destination hg-repo repository + scanning source... + sorting... + converting... + 0 rev1 + updating bookmarks + $ hg -R hg-repo tags + tip 0:d98c8ad3a4cf + $ rm -rf hg-repo + Do a first conversion $ convertrepo diff -r 501c51d60792 -r 96a38d44ba09 tests/test-convert.t --- a/tests/test-convert.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-convert.t Sat Jul 18 17:32:38 2015 -0500 @@ -259,6 +259,10 @@ for large projects, and is only effective when "convert.git.similarity" is greater than 0. The default is False. + convert.git.remoteprefix + remote refs are converted as bookmarks with + "convert.git.remoteprefix" as a prefix followed by a /. The + default is 'remote'. Perforce Source ############### @@ -279,6 +283,12 @@ Mercurial Destination ##################### + The Mercurial destination will recognize Mercurial subrepositories in the + destination directory, and update the .hgsubstate file automatically if + the destination subrepositories contain the //.hg/shamap file. + Converting a repository with subrepositories requires converting a single + repository at a time, from the bottom up. + The following options are supported: convert.hg.clonebranches @@ -288,12 +298,25 @@ branch name for tag revisions, defaults to "default". convert.hg.usebranchnames preserve branch names. The default is True. + convert.hg.sourcename + records the given string as a 'convert_source' extra value + on each commit made in the target repository. The default is + None. - options: + All Destinations + ################ + + All destination types accept the following options: + + convert.skiptags + does not convert tags from the source repo to the target + repo. The default is False. + + options ([+] can be repeated): -s --source-type TYPE source repository type -d --dest-type TYPE destination repository type - -r --rev REV import up to source revision REV + -r --rev REV [+] import up to source revision REV -A --authormap FILE remap usernames using this file --filemap FILE remap file names using contents of file --full apply filemap changes by converting all files again @@ -488,3 +511,17 @@ date: Thu Jan 01 00:00:04 1970 +0000 summary: e + +test specifying a sourcename + $ echo g > a/g + $ hg -R a ci -d'0 0' -Amg + adding g + $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c + scanning source... + sorting... + converting... + 0 g + $ hg -R c log -r tip --template '{extras % "{extra}\n"}' + branch=default + convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072 + convert_source=mysource diff -r 501c51d60792 -r 96a38d44ba09 tests/test-copy-move-merge.t --- a/tests/test-copy-move-merge.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-copy-move-merge.t Sat Jul 18 17:32:38 2015 -0500 @@ -35,13 +35,11 @@ preserving a for resolve of c removing a b: remote moved from a -> m - updating: b 1/2 files (50.00%) picked tool 'internal:merge' for b (binary False symlink False) merging a and b to b my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc premerge successful c: remote moved from a -> m - updating: c 2/2 files (100.00%) picked tool 'internal:merge' for c (binary False symlink False) merging a and c to c my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc diff -r 501c51d60792 -r 96a38d44ba09 tests/test-copy.t --- a/tests/test-copy.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-copy.t Sat Jul 18 17:32:38 2015 -0500 @@ -19,6 +19,7 @@ branch: default commit: 1 copied update: (current) + phases: 1 draft $ hg --debug commit -m "2" committing files: b @@ -85,13 +86,13 @@ copy: a copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 - $ "$TESTDIR/md5sum.py" .hg/store/data/b.i + $ md5sum.py .hg/store/data/b.i 4999f120a3b88713bbefddd195cf5133 .hg/store/data/b.i $ hg cat b > bsum - $ "$TESTDIR/md5sum.py" bsum + $ md5sum.py bsum 60b725f10c9c85c70d97880dfe8191b3 bsum $ hg cat a > asum - $ "$TESTDIR/md5sum.py" asum + $ md5sum.py asum 60b725f10c9c85c70d97880dfe8191b3 asum $ hg verify checking changesets diff -r 501c51d60792 -r 96a38d44ba09 tests/test-debugbuilddag.t --- a/tests/test-debugbuilddag.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-debugbuilddag.t Sat Jul 18 17:32:38 2015 -0500 @@ -10,37 +10,20 @@ \r (no-eol) (esc) building [ ] 0/12\r (no-eol) (esc) building [ ] 0/12\r (no-eol) (esc) - building [ ] 0/12\r (no-eol) (esc) - building [ ] 0/12\r (no-eol) (esc) - building [==> ] 1/12\r (no-eol) (esc) - building [==> ] 1/12\r (no-eol) (esc) building [==> ] 1/12\r (no-eol) (esc) building [==> ] 1/12\r (no-eol) (esc) building [======> ] 2/12\r (no-eol) (esc) - building [======> ] 2/12\r (no-eol) (esc) - building [=========> ] 3/12\r (no-eol) (esc) building [=========> ] 3/12\r (no-eol) (esc) building [=============> ] 4/12\r (no-eol) (esc) building [=============> ] 4/12\r (no-eol) (esc) building [=============> ] 4/12\r (no-eol) (esc) - building [=============> ] 4/12\r (no-eol) (esc) - building [=============> ] 4/12\r (no-eol) (esc) - building [=============> ] 4/12\r (no-eol) (esc) - building [================> ] 5/12\r (no-eol) (esc) building [================> ] 5/12\r (no-eol) (esc) building [====================> ] 6/12\r (no-eol) (esc) - building [====================> ] 6/12\r (no-eol) (esc) - building [=======================> ] 7/12\r (no-eol) (esc) building [=======================> ] 7/12\r (no-eol) (esc) building [===========================> ] 8/12\r (no-eol) (esc) building [===========================> ] 8/12\r (no-eol) (esc) - building [===========================> ] 8/12\r (no-eol) (esc) - building [===========================> ] 8/12\r (no-eol) (esc) - building [==============================> ] 9/12\r (no-eol) (esc) building [==============================> ] 9/12\r (no-eol) (esc) building [==================================> ] 10/12\r (no-eol) (esc) - building [==================================> ] 10/12\r (no-eol) (esc) - building [=====================================> ] 11/12\r (no-eol) (esc) building [=====================================> ] 11/12\r (no-eol) (esc) \r (no-eol) (esc) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-devel-warnings.t --- a/tests/test-devel-warnings.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-devel-warnings.t Sat Jul 18 17:32:38 2015 -0500 @@ -3,7 +3,7 @@ > """A small extension that acquire locks in the wrong order > """ > - > from mercurial import cmdutil + > from mercurial import cmdutil, repair, revset > > cmdtable = {} > command = cmdutil.command(cmdtable) @@ -38,13 +38,27 @@ > wl = repo.wlock(wait=False) > wl.release() > lo.release() + > + > @command('stripintr', [], '') + > def stripintr(ui, repo): + > lo = repo.lock() + > tr = repo.transaction('foobar') + > try: + > repair.strip(repo.ui, repo, [repo['.'].node()]) + > finally: + > lo.release() + > + > def oldstylerevset(repo, subset, x): + > return list(subset) + > + > revset.symbols['oldstyle'] = oldstylerevset > EOF $ cat << EOF >> $HGRCPATH > [extensions] > buggylocking=$TESTTMP/buggylocking.py > [devel] - > all=1 + > all-warnings=1 > EOF $ hg init lock-checker @@ -87,4 +101,18 @@ $TESTTMP/buggylocking.py:* in buggylocking (glob) $ hg properlocking $ hg nowaitlocking + + $ echo a > a + $ hg add a + $ hg commit -m a + $ hg stripintr + saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob) + abort: programming error: cannot strip from inside a transaction + (contact your extension maintainer) + [255] + + $ hg log -r "oldstyle()" -T '{rev}\n' + devel-warn: revset "oldstyle" use list instead of smartset, (upgrade your code) at: */mercurial/revset.py:* (mfunc) (glob) + 0 + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-doctest.py --- a/tests/test-doctest.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-doctest.py Sat Jul 18 17:32:38 2015 -0500 @@ -21,15 +21,19 @@ testmod('mercurial.minirst') testmod('mercurial.patch') testmod('mercurial.pathutil') +testmod('mercurial.parser') testmod('mercurial.revset') testmod('mercurial.store') testmod('mercurial.subrepo') testmod('mercurial.templatefilters') +testmod('mercurial.templater') testmod('mercurial.ui') testmod('mercurial.url') testmod('mercurial.util') testmod('mercurial.util', testtarget='platform') +testmod('hgext.convert.convcmd') testmod('hgext.convert.cvsps') testmod('hgext.convert.filemap') +testmod('hgext.convert.p4') testmod('hgext.convert.subversion') testmod('hgext.mq') diff -r 501c51d60792 -r 96a38d44ba09 tests/test-double-merge.t --- a/tests/test-double-merge.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-double-merge.t Sat Jul 18 17:32:38 2015 -0500 @@ -38,13 +38,11 @@ preserving foo for resolve of bar preserving foo for resolve of foo bar: remote copied from foo -> m - updating: bar 1/2 files (50.00%) picked tool 'internal:merge' for bar (binary False symlink False) merging foo and bar to bar my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc premerge successful foo: versions differ -> m - updating: foo 2/2 files (100.00%) picked tool 'internal:merge' for foo (binary False symlink False) merging foo my foo@6a0df1dad128+ other foo@484bf6903104 ancestor foo@e6dc8efe11cc diff -r 501c51d60792 -r 96a38d44ba09 tests/test-encoding-align.t --- a/tests/test-encoding-align.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-encoding-align.t Sat Jul 18 17:32:38 2015 -0500 @@ -119,12 +119,10 @@ $ hg book -f $S $ hg branch $M marked working directory as branch MIDDLE_ - (branches are permanent and global, did you want a bookmark?) $ hg tag $M $ hg book -f $M $ hg branch $L marked working directory as branch \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) - (branches are permanent and global, did you want a bookmark?) $ hg tag $L $ hg book -f $L diff -r 501c51d60792 -r 96a38d44ba09 tests/test-extdiff.t --- a/tests/test-extdiff.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-extdiff.t Sat Jul 18 17:32:38 2015 -0500 @@ -48,6 +48,7 @@ -c --change REV change made by revision -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories (some details hidden, use --verbose to show complete help) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-extension.t --- a/tests/test-extension.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-extension.t Sat Jul 18 17:32:38 2015 -0500 @@ -394,6 +394,7 @@ -c --change REV change made by revision -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories (some details hidden, use --verbose to show complete help) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-fetch.t --- a/tests/test-fetch.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-fetch.t Sat Jul 18 17:32:38 2015 -0500 @@ -168,7 +168,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R nbase branch b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ echo b > nbase/b $ hg -R nbase ci -Am b adding b @@ -363,7 +362,6 @@ adding b $ hg --cwd ib1 branch -f default marked working directory as branch default - (branches are permanent and global, did you want a bookmark?) $ echo c > ib1/c $ hg --cwd ib1 ci -Am newdefault adding c diff -r 501c51d60792 -r 96a38d44ba09 tests/test-fileset.t --- a/tests/test-fileset.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-fileset.t Sat Jul 18 17:32:38 2015 -0500 @@ -16,15 +16,21 @@ Test operators and basic patterns - $ fileset a1 + $ fileset -v a1 + ('symbol', 'a1') a1 - $ fileset 'a*' + $ fileset -v 'a*' + ('symbol', 'a*') a1 a2 - $ fileset '"re:a\d"' + $ fileset -v '"re:a\d"' + ('string', 're:a\\d') a1 a2 - $ fileset 'a1 or a2' + $ fileset -v 'a1 or a2' + (or + ('symbol', 'a1') + ('symbol', 'a2')) a1 a2 $ fileset 'a1 | a2' @@ -174,16 +180,54 @@ $ hg -R sub add sub/suba $ hg -R sub ci -m sub $ echo 'sub = sub' > .hgsub + $ hg init sub2 + $ echo b > sub2/b + $ hg -R sub2 ci -Am sub2 + adding b + $ echo 'sub2 = sub2' >> .hgsub $ fileset 'subrepo()' $ hg add .hgsub $ fileset 'subrepo()' sub + sub2 $ fileset 'subrepo("sub")' sub $ fileset 'subrepo("glob:*")' sub + sub2 $ hg ci -m subrepo +Test that .hgsubstate is updated as appropriate during a conversion. The +saverev property is enough to alter the hashes of the subrepo. + + $ hg init ../converted + $ hg --config extensions.convert= convert --config convert.hg.saverev=True \ + > sub ../converted/sub + initializing destination ../converted/sub repository + scanning source... + sorting... + converting... + 0 sub + $ hg clone -U sub2 ../converted/sub2 + $ hg --config extensions.convert= convert --config convert.hg.saverev=True \ + > . ../converted + scanning source... + sorting... + converting... + 4 addfiles + 3 manychanges + 2 diverging + 1 merge + 0 subrepo + no ".hgsubstate" updates will be made for "sub2" + $ hg up -q -R ../converted -r tip + $ hg --cwd ../converted cat sub/suba sub2/b -r tip + a + b + $ oldnode=`hg log -r tip -T "{node}\n"` + $ newnode=`hg log -R ../converted -r tip -T "{node}\n"` + $ [ "$oldnode" != "$newnode" ] || echo "nothing changed" + Test with a revision $ hg log -G --template '{rev} {desc}\n' @@ -235,6 +279,7 @@ $ fileset -r4 'subrepo("re:su.*")' sub + sub2 $ fileset -r4 'subrepo("sub")' sub $ fileset -r4 'b2 or c1' diff -r 501c51d60792 -r 96a38d44ba09 tests/test-fncache.t --- a/tests/test-fncache.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-fncache.t Sat Jul 18 17:32:38 2015 -0500 @@ -48,13 +48,28 @@ checking manifests crosschecking files in changesets and manifests checking files - data/a.i@0: missing revlog! - data/a.i.hg/c.i@2: missing revlog! - data/a.i/b.i@1: missing revlog! + warning: revlog 'data/a.i' not in fncache! + warning: revlog 'data/a.i.hg/c.i' not in fncache! + warning: revlog 'data/a.i/b.i' not in fncache! 3 files, 3 changesets, 3 total revisions - 3 integrity errors encountered! - (first damaged changeset appears to be 0) - [1] + 3 warnings encountered! + hint: run "hg debugrebuildfncache" to recover from corrupt fncache + +Follow the hint to make sure it works + + $ hg debugrebuildfncache + adding data/a.i + adding data/a.i.hg/c.i + adding data/a.i/b.i + 3 items added, 0 removed from fncache + + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 3 files, 3 changesets, 3 total revisions + $ cd .. Non store repo: @@ -285,3 +300,98 @@ 1 files, 1 changesets, 1 total revisions $ cat .hg/store/fncache data/y.i + + $ cd .. + +debugrebuildfncache does nothing unless repo has fncache requirement + + $ hg --config format.usefncache=false init nofncache + $ cd nofncache + $ hg debugrebuildfncache + (not rebuilding fncache because repository does not support fncache + + $ cd .. + +debugrebuildfncache works on empty repository + + $ hg init empty + $ cd empty + $ hg debugrebuildfncache + fncache already up to date + $ cd .. + +debugrebuildfncache on an up to date repository no-ops + + $ hg init repo + $ cd repo + $ echo initial > foo + $ echo initial > .bar + $ hg commit -A -m initial + adding .bar + adding foo + + $ cat .hg/store/fncache | sort + data/.bar.i + data/foo.i + + $ hg debugrebuildfncache + fncache already up to date + +debugrebuildfncache restores deleted fncache file + + $ rm -f .hg/store/fncache + $ hg debugrebuildfncache + adding data/.bar.i + adding data/foo.i + 2 items added, 0 removed from fncache + + $ cat .hg/store/fncache | sort + data/.bar.i + data/foo.i + +Rebuild after rebuild should no-op + + $ hg debugrebuildfncache + fncache already up to date + +A single missing file should get restored, an extra file should be removed + + $ cat > .hg/store/fncache << EOF + > data/foo.i + > data/bad-entry.i + > EOF + + $ hg debugrebuildfncache + removing data/bad-entry.i + adding data/.bar.i + 1 items added, 1 removed from fncache + + $ cat .hg/store/fncache | sort + data/.bar.i + data/foo.i + + $ cd .. + +Try a simple variation without dotencode to ensure fncache is ignorant of encoding + + $ hg --config format.dotencode=false init nodotencode + $ cd nodotencode + $ echo initial > foo + $ echo initial > .bar + $ hg commit -A -m initial + adding .bar + adding foo + + $ cat .hg/store/fncache | sort + data/.bar.i + data/foo.i + + $ rm .hg/store/fncache + $ hg debugrebuildfncache + adding data/.bar.i + adding data/foo.i + 2 items added, 0 removed from fncache + + $ cat .hg/store/fncache | sort + data/.bar.i + data/foo.i diff -r 501c51d60792 -r 96a38d44ba09 tests/test-globalopts.t --- a/tests/test-globalopts.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-globalopts.t Sat Jul 18 17:32:38 2015 -0500 @@ -88,6 +88,7 @@ [255] $ hg -R b ann a/a abort: a/a not under root '$TESTTMP/b' (glob) + (consider using '--cwd b') [255] $ hg log abort: no repository found in '$TESTTMP' (.hg not found)! diff -r 501c51d60792 -r 96a38d44ba09 tests/test-glog.t --- a/tests/test-glog.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-glog.t Sat Jul 18 17:32:38 2015 -0500 @@ -89,7 +89,7 @@ > if opts.get('print_revset'): > expr = cmdutil.getgraphlogrevs(repo, pats, opts)[1] > if expr: - > tree = revset.parse(expr)[0] + > tree = revset.parse(expr) > else: > tree = [] > ui.write('%r\n' % (opts.get('rev', []),)) @@ -1469,13 +1469,12 @@ (group (group (or - (or - (func - ('symbol', 'branch') - ('string', 'default')) - (func - ('symbol', 'branch') - ('string', 'branch'))) + (func + ('symbol', 'branch') + ('string', 'default')) + (func + ('symbol', 'branch') + ('string', 'branch')) (func ('symbol', 'branch') ('string', 'branch'))))) @@ -2393,4 +2392,12 @@ summary: add a +working-directory revision + + $ hg log -G -qr '. + wdir()' + o 2147483647:ffffffffffff + | + @ 3:5918b8d165d1 + | + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-gpg.t --- a/tests/test-gpg.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-gpg.t Sat Jul 18 17:32:38 2015 -0500 @@ -38,7 +38,7 @@ verify that this test has not modified the trustdb.gpg file back in the main hg working dir - $ "$TESTDIR/md5sum.py" "$TESTDIR/gpg/trustdb.gpg" + $ md5sum.py "$TESTDIR/gpg/trustdb.gpg" f6b9c78c65fa9536e7512bb2ceb338ae */gpg/trustdb.gpg (glob) don't leak any state to next test run diff -r 501c51d60792 -r 96a38d44ba09 tests/test-graft.t --- a/tests/test-graft.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-graft.t Sat Jul 18 17:32:38 2015 -0500 @@ -154,7 +154,6 @@ ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6 preserving b for resolve of b b: local copied/moved from a -> m - updating: b 1/1 files (100.00%) picked tool 'internal:merge' for b (binary False symlink False) merging b and a to b my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622 @@ -170,7 +169,6 @@ ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746 e: remote is newer -> g getting e - updating: e 1/1 files (100.00%) b: remote unchanged -> k committing files: e @@ -184,10 +182,8 @@ preserving e for resolve of e d: remote is newer -> g getting d - updating: d 1/2 files (50.00%) b: remote unchanged -> k e: versions differ -> m - updating: e 2/2 files (100.00%) picked tool 'internal:merge' for e (binary False symlink False) merging e my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622 @@ -496,10 +492,12 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: 2 -Test that template correctly expands more than one 'extra' (issue4362) - $ hg -R ../converted log -r 7 --template "{extras % ' Extra: {extra}\n'}" +Test that template correctly expands more than one 'extra' (issue4362), and that +'intermediate-source' is converted. + $ hg -R ../converted log -r 13 --template "{extras % ' Extra: {extra}\n'}" Extra: branch=default - Extra: convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82 + Extra: convert_revision=7a4785234d87ec1aa420ed6b11afe40fa73e12a9 + Extra: intermediate-source=7ae846e9111fc8f57745634250c7b9ac0a60689b Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d The transplant case diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hardlinks.t --- a/tests/test-hardlinks.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hardlinks.t Sat Jul 18 17:32:38 2015 -0500 @@ -57,7 +57,7 @@ Create hardlinked clone r2: - $ hg clone -U --debug r1 r2 + $ hg clone -U --debug r1 r2 --config progress.debug=true linking: 1 linking: 2 linking: 3 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-help.t --- a/tests/test-help.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-help.t Sat Jul 18 17:32:38 2015 -0500 @@ -264,7 +264,6 @@ notify hooks for sending email push notifications pager browse command output with an external pager patchbomb command to send changesets as (a series of) patch emails - progress show progress bars for some actions purge command to delete untracked files from the working directory record commands to interactively select changes for @@ -797,6 +796,8 @@ debugrebuilddirstate rebuild the dirstate as it would look like for the given revision + debugrebuildfncache + rebuild the fncache file debugrename dump rename information debugrevlog show data and statistics about a revlog debugrevspec parse and apply a revision specification @@ -1116,6 +1117,10 @@ abort: help section not found [255] + $ hg help template.files + files List of strings. All files modified, added, or removed by + this changeset. + Test dynamic list of merge tools only shows up once $ hg help merge-tools Merge Tools @@ -1251,7 +1256,7 @@ $ hg serve -R "$TESTTMP/test" -n test -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help" + $ get-with-headers.py 127.0.0.1:$HGPORT "help" 200 Script output follows @@ -1802,7 +1807,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help/add" + $ get-with-headers.py 127.0.0.1:$HGPORT "help/add" 200 Script output follows @@ -1962,7 +1967,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help/remove" + $ get-with-headers.py 127.0.0.1:$HGPORT "help/remove" 200 Script output follows @@ -2155,7 +2160,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help/revisions" + $ get-with-headers.py 127.0.0.1:$HGPORT "help/revisions" 200 Script output follows @@ -2250,6 +2255,6 @@ - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hghave.t --- a/tests/test-hghave.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hghave.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,3 +1,39 @@ Testing that hghave does not crash when checking features - $ "$TESTDIR/hghave" --test-features 2>/dev/null + $ hghave --test-features 2>/dev/null + +Testing hghave extensibility for third party tools + + $ cat > hghaveaddon.py < import hghave + > @hghave.check("custom", "custom hghave feature") + > def has_custom(): + > return True + > EOF + +(invocation via run-tests.py) + + $ cat > test-hghaveaddon.t < #require custom + > $ echo foo + > foo + > EOF + $ run-tests.py test-hghaveaddon.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + +(invocation via command line) + + $ unset TESTDIR + $ hghave custom + +(terminate with exit code 2 at failure of importing hghaveaddon.py) + + $ rm hghaveaddon.* + $ cat > hghaveaddon.py < importing this file should cause syntax error + > EOF + + $ hghave custom + failed to import hghaveaddon.py from '.': invalid syntax (hghaveaddon.py, line 1) + [2] diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgignore.t --- a/tests/test-hgignore.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgignore.t Sat Jul 18 17:32:38 2015 -0500 @@ -167,3 +167,78 @@ ? a.c ? a.o ? syntax + +Check using 'include:' in ignore file + + $ hg purge --all --config extensions.purge= + $ touch foo.included + + $ echo ".*.included" > otherignore + $ hg status -I "include:otherignore" + ? foo.included + + $ echo "include:otherignore" >> .hgignore + $ hg status + A dir/b.o + ? .hgignore + ? otherignore + +Check recursive uses of 'include:' + + $ echo "include:nestedignore" >> otherignore + $ echo "glob:*ignore" > nestedignore + $ hg status + A dir/b.o + + $ cp otherignore goodignore + $ echo "include:badignore" >> otherignore + $ hg status + skipping unreadable pattern file 'badignore': No such file or directory + A dir/b.o + + $ mv goodignore otherignore + +Check including subincludes + + $ hg revert -q --all + $ hg purge --all --config extensions.purge= + $ echo ".hgignore" > .hgignore + $ mkdir dir1 dir2 + $ touch dir1/file1 dir1/file2 dir2/file1 dir2/file2 + $ echo "subinclude:dir2/.hgignore" >> .hgignore + $ echo "glob:file*2" > dir2/.hgignore + $ hg status + ? dir1/file1 + ? dir1/file2 + ? dir2/file1 + +Check including subincludes with regexs + + $ echo "subinclude:dir1/.hgignore" >> .hgignore + $ echo "regexp:f.le1" > dir1/.hgignore + + $ hg status + ? dir1/file2 + ? dir2/file1 + +Check multiple levels of sub-ignores + + $ mkdir dir1/subdir + $ touch dir1/subdir/subfile1 dir1/subdir/subfile3 dir1/subdir/subfile4 + $ echo "subinclude:subdir/.hgignore" >> dir1/.hgignore + $ echo "glob:subfil*3" >> dir1/subdir/.hgignore + + $ hg status + ? dir1/file2 + ? dir1/subdir/subfile4 + ? dir2/file1 + +Check include subignore at the same level + + $ mv dir1/subdir/.hgignore dir1/.hgignoretwo + $ echo "regexp:f.le1" > dir1/.hgignore + $ echo "subinclude:.hgignoretwo" >> dir1/.hgignore + $ echo "glob:file*2" > dir1/.hgignoretwo + + $ hg status | grep file2 + [1] diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-bundle.t --- a/tests/test-hgweb-bundle.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-bundle.t Sat Jul 18 17:32:38 2015 -0500 @@ -27,7 +27,7 @@ Ensure we're serving from the bundle - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw') + $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw') 200 Script output follows diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-commands.t Sat Jul 18 17:32:38 2015 -0500 @@ -26,7 +26,6 @@ $ hg ci -Ambranch $ hg branch unstable marked working directory as branch unstable - (branches are permanent and global, did you want a bookmark?) >>> open('msg', 'wb').write('branch commit with null character: \0\n') $ hg ci -l msg $ rm msg @@ -54,7 +53,7 @@ Logs and changes - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=atom' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log/?style=atom' 200 Script output follows @@ -240,7 +239,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=rss' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log/?style=rss' 200 Script output follows @@ -414,7 +413,7 @@ (no-eol) - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=atom' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/?style=atom' 200 Script output follows @@ -514,7 +513,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=rss' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/?style=rss' 200 Script output follows @@ -608,7 +607,7 @@ (no-eol) - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=atom' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/foo/?style=atom' 200 Script output follows @@ -663,7 +662,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=rss' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/foo/?style=rss' 200 Script output follows @@ -684,7 +683,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/' + $ get-with-headers.py 127.0.0.1:$HGPORT 'shortlog/' 200 Script output follows @@ -711,14 +710,14 @@
                    - + - + - + - +
                    changeset {rev}:{node|short}
                    {node|short}
                    author:{author|obfuscate}
                    {author|obfuscate}
                    date:{date|rfc822date}
                    {date|rfc822date}
                    permissions:{permissions|permissions}
                    {permissions|permissions}
                    description: {desc|strip|escape|websub|addbreaks|nonempty} drwxr-xr-x      - [up] + [up]
                        - {basename|escape}/ - + {basename|escape}/ + {emptydirs|urlescape} ' @@ -41,7 +41,7 @@ {permissions|permissions}  {date|isodate}  {size}  - {basename|escape}' + {basename|escape}' filerevision = filerevision.tmpl fileannotate = fileannotate.tmpl @@ -183,7 +183,7 @@
                    Thu, 01 Jan 1970 00:00:00 +0000 testbranch commit with null character: unstable tip something + branch commit with null character: + unstable tip something +
                    Thu, 01 Jan 1970 00:00:00 +0000 testbranchstable + branch + stable +
                    Thu, 01 Jan 1970 00:00:00 +0000 testAdded tag 1.0 for changeset 2ef0ac749a14default + Added tag 1.0 for changeset 2ef0ac749a14 + default +
                    Thu, 01 Jan 1970 00:00:00 +0000 testbase1.0 anotherthing + base + 1.0 anotherthing +
                    @@ -813,7 +824,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/0/' + $ get-with-headers.py 127.0.0.1:$HGPORT 'rev/0/' 200 Script output follows @@ -834,16 +845,16 @@ mercurial
                      @@ -856,7 +867,10 @@
                      -

                      changeset 0:2ef0ac749a14 1.0 anotherthing

                      +

                      + changeset 0:2ef0ac749a14 + 1.0 anotherthing +

                    + @@ -1024,7 +1040,10 @@ Thu, 01 Jan 1970 00:00:00 +0000 test - base1.0 anotherthing + + base + 1.0 anotherthing + @@ -1044,12 +1063,12 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=stable&style=raw' | grep 'revision:' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=stable&style=raw' | grep 'revision:' revision: 2 Search with revset syntax - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=tip^&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=tip^&style=raw' 200 Script output follows @@ -1066,7 +1085,7 @@ branch: stable - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(all(),2)^&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=last(all(),2)^&style=raw' 200 Script output follows @@ -1090,7 +1109,7 @@ branch: default - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(all(,2)^&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=last(all(,2)^&style=raw' 200 Script output follows @@ -1100,7 +1119,7 @@ # Mode literal keyword search - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(al(),2)^&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=last(al(),2)^&style=raw' 200 Script output follows @@ -1110,7 +1129,7 @@ # Mode literal keyword search - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=bookmark(anotherthing)&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=bookmark(anotherthing)&style=raw' 200 Script output follows @@ -1128,7 +1147,7 @@ bookmark: anotherthing - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=bookmark(abc)&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=bookmark(abc)&style=raw' 200 Script output follows @@ -1138,7 +1157,7 @@ # Mode literal keyword search - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=deadbeef:&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=deadbeef:&style=raw' 200 Script output follows @@ -1149,7 +1168,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=user("test")&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=user("test")&style=raw' 200 Script output follows @@ -1190,7 +1209,7 @@ bookmark: anotherthing - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=user("re:test")&style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=user("re:test")&style=raw' 200 Script output follows @@ -1203,11 +1222,11 @@ File-related - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/1/foo/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file/1/foo/?style=raw' 200 Script output follows foo - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/1/foo/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'annotate/1/foo/?style=raw' 200 Script output follows @@ -1216,7 +1235,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/1/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file/1/?style=raw' 200 Script output follows @@ -1232,7 +1251,7 @@ $ hg parents --template "{node|short}\n" -r 1 foo 2ef0ac749a14 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/1/foo' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file/1/foo' 200 Script output follows @@ -1254,24 +1273,24 @@ mercurial
                    • help
                    • @@ -1280,7 +1299,10 @@
                      -

                      view foo @ 1:a4f92ed23982

                      +

                      + view foo @ 1:a4f92ed23982 + +

                      @@ -1326,7 +1348,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'filediff/0/foo/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'filediff/0/foo/?style=raw' 200 Script output follows @@ -1340,7 +1362,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'filediff/1/foo/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'filediff/1/foo/?style=raw' 200 Script output follows @@ -1356,7 +1378,7 @@ $ hg parents --template "{node|short}\n" -r 2 foo 2ef0ac749a14 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/2/foo' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file/2/foo' 200 Script output follows @@ -1378,24 +1400,24 @@ mercurial
                      • help
                      • @@ -1404,7 +1426,10 @@
                        -

                        view foo @ 2:1d22e65f027e

                        +

                        + view foo @ 2:1d22e65f027e + stable +

                        @@ -1454,23 +1479,23 @@ Overviews - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'raw-tags' + $ get-with-headers.py 127.0.0.1:$HGPORT 'raw-tags' 200 Script output follows tip cad8025a2e87f88c06259790adfa15acb4080123 1.0 2ef0ac749a14e4f57a5a822464a0902c6f7f448f - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'raw-branches' + $ get-with-headers.py 127.0.0.1:$HGPORT 'raw-branches' 200 Script output follows unstable cad8025a2e87f88c06259790adfa15acb4080123 open stable 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe inactive default a4f92ed23982be056b9852de5dfe873eaac7f0de inactive - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'raw-bookmarks' + $ get-with-headers.py 127.0.0.1:$HGPORT 'raw-bookmarks' 200 Script output follows anotherthing 2ef0ac749a14e4f57a5a822464a0902c6f7f448f something cad8025a2e87f88c06259790adfa15acb4080123 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'summary/?style=gitweb' + $ get-with-headers.py 127.0.0.1:$HGPORT 'summary/?style=gitweb' 200 Script output follows @@ -1509,7 +1534,7 @@ tags | bookmarks | branches | - files | + files | help
                        @@ -1672,7 +1697,7 @@ - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/?style=gitweb' + $ get-with-headers.py 127.0.0.1:$HGPORT 'graph/?style=gitweb' 200 Script output follows @@ -1707,16 +1732,16 @@ @@ -1788,8 +1813,8 @@ @@ -1819,7 +1844,7 @@ raw graph - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'graph/?style=raw' 200 Script output follows @@ -1869,28 +1894,28 @@ capabilities - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities'; echo 200 Script output follows lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1*%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (glob) heads - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads' + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=heads' 200 Script output follows cad8025a2e87f88c06259790adfa15acb4080123 branches - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000' + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000' 200 Script output follows 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 changegroup - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000' + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000' 200 Script output follows x\x9c\xbd\x94MHTQ\x14\xc7'+\x9d\xc66\x81\x89P\xc1\xa3\x14\xcct\xba\xef\xbe\xfb\xde\xbb\xcfr0\xb3"\x02\x11[%\x98\xdcO\xa7\xd2\x19\x98y\xd2\x07h"\x96\xa0e\xda\xa6lUY-\xca\x08\xa2\x82\x16\x96\xd1\xa2\xf0#\xc8\x95\x1b\xdd$!m*"\xc8\x82\xea\xbe\x9c\x01\x85\xc9\x996\x1d\xf8\xc1\xe3~\x9d\xff9\xef\x7f\xaf\xcf\xe7\xbb\x19\xfc4\xec^\xcb\x9b\xfbz\xa6\xbe\xb3\x90_\xef/\x8d\x9e\xad\xbe\xe4\xcb0\xd2\xec\xad\x12X:\xc8\x12\x12\xd9:\x95\xba \x1cG\xb7$\xc5\xc44\x1c(\x1d\x03\x03\xdb\x84\x0cK#\xe0\x8a\xb8\x1b\x00\x1a\x08p\xb2SF\xa3\x01\x8f\x00%q\xa1Ny{k!8\xe5t>[{\xe2j\xddl\xc3\xcf\xee\xd0\xddW\x9ff3U\x9djobj\xbb\x87E\x88\x05l\x001\x12\x18\x13\xc6 \xb7(\xe3\x02a\x80\x81\xcel.u\x9b\x1b\x8c\x91\x80Z\x0c\x15\x15 (esc) @@ -1901,14 +1926,14 @@ stream_out - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out' + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=stream_out' 200 Script output follows 1 failing unbundle, requires POST request - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle' + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=unbundle' 405 push requires POST request 0 @@ -1917,7 +1942,7 @@ Static files - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'static/style.css' + $ get-with-headers.py 127.0.0.1:$HGPORT 'static/style.css' 200 Script output follows a { text-decoration:none; } @@ -2029,7 +2054,7 @@ Stop and restart with HGENCODING=cp932 and preferuncompressed - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ HGENCODING=cp932 hg serve --config server.preferuncompressed=True -n test \ > -p $HGPORT -d --pid-file=hg.pid -E errors.log $ cat hg.pid >> $DAEMON_PIDS @@ -2041,7 +2066,7 @@ Graph json escape of multibyte character - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/' > out + $ get-with-headers.py 127.0.0.1:$HGPORT 'graph/' > out >>> for line in open("out"): ... if line.startswith("var data ="): ... print line, @@ -2049,7 +2074,7 @@ capabilities - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities'; echo 200 Script output follows lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream-preferred stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1*%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (glob) @@ -2059,7 +2084,7 @@ ERRORS ENCOUNTERED $ cat errors.log - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cd .. @@ -2098,23 +2123,23 @@ Test paging - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \ + $ get-with-headers.py 127.0.0.1:$HGPORT \ > 'graph/?style=raw' | grep changeset changeset: aed2d9c1d0e7 changeset: b60a39a85a01 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \ + $ get-with-headers.py 127.0.0.1:$HGPORT \ > 'graph/?style=raw&revcount=3' | grep changeset changeset: aed2d9c1d0e7 changeset: b60a39a85a01 changeset: ada793dcc118 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \ + $ get-with-headers.py 127.0.0.1:$HGPORT \ > 'graph/e06180cbfb0?style=raw&revcount=3' | grep changeset changeset: e06180cbfb0c changeset: b4e73ffab476 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \ + $ get-with-headers.py 127.0.0.1:$HGPORT \ > 'graph/b4e73ffab47?style=raw&revcount=3' | grep changeset changeset: b4e73ffab476 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-descend-empties.t --- a/tests/test-hgweb-descend-empties.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-descend-empties.t Sat Jul 18 17:32:38 2015 -0500 @@ -3,7 +3,7 @@ Test chains of near empty directories, terminating 3 different ways: - a1: file at level 4 (deepest) - b1: two dirs at level 3 -- e1: file at level 2 +- d1: file at level 2 Set up the repo @@ -11,25 +11,25 @@ $ cd test $ mkdir -p a1/a2/a3/a4 $ mkdir -p b1/b2/b3/b4 - $ mkdir -p b1/b2/c3/c4 + $ mkdir -p b1/b2/b3/c4 $ mkdir -p d1/d2/d3/d4 $ echo foo > a1/a2/a3/a4/foo $ echo foo > b1/b2/b3/b4/foo - $ echo foo > b1/b2/c3/c4/foo + $ echo foo > b1/b2/b3/c4/foo $ echo foo > d1/d2/d3/d4/foo $ echo foo > d1/d2/foo $ hg ci -Ama adding a1/a2/a3/a4/foo adding b1/b2/b3/b4/foo - adding b1/b2/c3/c4/foo + adding b1/b2/b3/c4/foo adding d1/d2/d3/d4/foo adding d1/d2/foo $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log $ cat hg.pid >> $DAEMON_PIDS -manifest with descending +manifest with descending (paper) - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file' 200 Script output follows @@ -40,7 +40,7 @@ - test: 9087c84a0f5d / + test: c9f45f7a1659 / @@ -51,14 +51,14 @@ mercurial
                          @@ -71,7 +71,10 @@
                          -

                          directory / @ 0:9087c84a0f5d tip

                          +

                          + directory / @ 0:c9f45f7a1659 + tip +

                          @@ -90,17 +93,17 @@ - [up] + [up] drwxr-xr-x - + dir. a1/ - + a2/a3/a4 @@ -109,11 +112,11 @@ - + dir. b1/ - - b2 + + b2/b3 @@ -121,10 +124,129 @@ - + dir. d1/ - + + d2 + + + + drwxr-xr-x + + + + +
                          + + + + + + + + +manifest with descending (coal) + + $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=coal' + 200 Script output follows + + + + + + + + + + test: c9f45f7a1659 / + + + +
                          + + +
                          + +

                          + directory / @ 0:c9f45f7a1659 + tip +

                          + + + +

                          +
                          Find changesets by keywords (author, files, the commit message), revision + number or hash, or revset expression.
                          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -143,6 +265,307 @@ +manifest with descending (monoblue) + + $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=monoblue' + 200 Script output follows + + + + + + + + + + test: files + + + + + +
                          + + + + + +

                          / default tip

                          + +
                          namesizepermissions
                          [up]drwxr-xr-x
                          + + dir. a1/ + + + a2/a3/a4 + + drwxr-xr-x
                          + + dir. b1/ + + + b2/b3 + + drwxr-xr-x
                          + + dir. d1/ + + d2
                          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                          drwxr-xr-x[up]
                          drwxr-xr-x + a1 + a2/a3/a4 + files
                          drwxr-xr-x + b1 + b2/b3 + files
                          drwxr-xr-x + d1 + d2 + files
                          + + + + +
                          +

                          mercurial

                          +
                          + +
                          +
                          +
                          +
                          + +
                          + + + + + +manifest with descending (gitweb) + + $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=gitweb' + 200 Script output follows + + + + + + + + + + + test: files + + + + + + + + + +
                          / default tip
                          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                          drwxr-xr-x[up]
                          drwxr-xr-x + a1 + a2/a3/a4 +
                          drwxr-xr-x + b1 + b2/b3 +
                          drwxr-xr-x + d1 + d2 +
                          + + + + + + + +manifest with descending (spartan) + + $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=spartan' + 200 Script output follows + + + + + + + + + + test: files for changeset c9f45f7a1659 + + + + + +

                          Mercurial / files for changeset c9f45f7a1659: /

                          + + + + + + + + +
                          drwxr-xr-x  +   +   + [up] +
                          drwxr-xr-x  +   +   + + a1/ + + a2/a3/a4 + +
                          drwxr-xr-x  +   +   + + b1/ + + b2/b3 + +
                          drwxr-xr-x  +   +   + + d1/ + + d2 + + +
                          + + + + + + + + $ cat errors.log $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-diffs.t --- a/tests/test-hgweb-diffs.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-diffs.t Sat Jul 18 17:32:38 2015 -0500 @@ -36,7 +36,7 @@ revision - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/0' + $ get-with-headers.py localhost:$HGPORT 'rev/0' 200 Script output follows @@ -57,16 +57,16 @@ mercurial
                            @@ -79,7 +79,10 @@
                            -

                            changeset 0:0cd96de13884

                            +

                            + changeset 0:0cd96de13884 + +

                            • help
                            • @@ -249,7 +252,10 @@
                              -

                              diff b @ 1:559edbd9ed20

                              +

                              + diff b @ 1:559edbd9ed20 + tip +

                              @@ -302,13 +308,13 @@ set up hgweb with git diffs - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg serve --config 'diff.git=1' -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS revision - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/0' + $ get-with-headers.py localhost:$HGPORT 'rev/0' 200 Script output follows @@ -329,16 +335,16 @@ mercurial
                                @@ -351,7 +357,10 @@
                                -

                                changeset 0:0cd96de13884

                                +

                                + changeset 0:0cd96de13884 + +

                                @@ -442,7 +451,7 @@ revision - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-rev/0' + $ get-with-headers.py localhost:$HGPORT 'raw-rev/0' 200 Script output follows @@ -477,7 +486,7 @@ $ hg parents --template "{node|short}\n" -r tip a 0cd96de13884 - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/a' + $ get-with-headers.py localhost:$HGPORT 'diff/tip/a' 200 Script output follows @@ -499,24 +508,24 @@ mercurial
                                • help
                                • @@ -525,7 +534,10 @@
                                  -

                                  diff a @ 1:559edbd9ed20

                                  +

                                  + diff a @ 1:559edbd9ed20 + tip +

                                  @@ -580,7 +592,7 @@ $ hg log --template "{rev}:{node|short}\n" -r 0 0:0cd96de13884 - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/0/a' + $ get-with-headers.py localhost:$HGPORT 'comparison/0/a' 200 Script output follows @@ -602,24 +614,24 @@ mercurial
                                  • help
                                  • @@ -628,7 +640,10 @@
                                    -

                                    comparison a @ 0:0cd96de13884

                                    +

                                    + comparison a @ 0:0cd96de13884 + +

                                    @@ -707,7 +722,7 @@ $ hg log --template "{rev}:{node|short}\n" -r tip 2:d73db4d812ff - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/tip/a' + $ get-with-headers.py localhost:$HGPORT 'comparison/tip/a' 200 Script output follows @@ -729,24 +744,24 @@ mercurial
                                    • help
                                    • @@ -755,7 +770,10 @@
                                      -

                                      comparison a @ 2:d73db4d812ff

                                      +

                                      + comparison a @ 2:d73db4d812ff + tip +

                                      @@ -836,7 +854,7 @@ $ hg log --template "{rev}:{node|short}\n" -r tip 3:20e80271eb7a - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/tip/a' + $ get-with-headers.py localhost:$HGPORT 'comparison/tip/a' 200 Script output follows @@ -858,24 +876,24 @@ mercurial
                                      • help
                                      • @@ -884,7 +902,10 @@
                                        -

                                        comparison a @ 3:20e80271eb7a

                                        +

                                        + comparison a @ 3:20e80271eb7a + tip +

                                        @@ -971,7 +992,7 @@ $ hg parents --template "{rev}:{node|short}\n" -r tip e 4:402bea3b0976 - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/tip/e' + $ get-with-headers.py localhost:$HGPORT 'comparison/tip/e' 200 Script output follows @@ -993,24 +1014,24 @@ mercurial
                                        • help
                                        • @@ -1019,7 +1040,10 @@
                                          -

                                          comparison e @ 5:41d9fc4a6ae1

                                          +

                                          + comparison e @ 5:41d9fc4a6ae1 + tip +

                                          @@ -1094,7 +1118,7 @@ raw revision with diff block numbers - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat < .hg/hgrc > [web] > templates = rawdiff @@ -1114,7 +1138,7 @@ > EOF $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-rev/0' + $ get-with-headers.py localhost:$HGPORT 'raw-rev/0' 200 Script output follows Block: 1 @@ -1131,7 +1155,7 @@ @@ -0,0 +1,1 @@ +b - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ rm .hg/hgrc rawdiff/map $ rmdir rawdiff $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-empty.t --- a/tests/test-hgweb-empty.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-empty.t Sat Jul 18 17:32:38 2015 -0500 @@ -6,7 +6,7 @@ $ cd test $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'shortlog') + $ (get-with-headers.py localhost:$HGPORT 'shortlog') 200 Script output follows @@ -33,14 +33,14 @@
                                            @@ -67,8 +67,8 @@ @@ -86,8 +86,8 @@ @@ -117,7 +117,7 @@ $ echo babar babar - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log') + $ (get-with-headers.py localhost:$HGPORT 'log') 200 Script output follows @@ -144,14 +144,14 @@
                                              @@ -178,8 +178,8 @@ @@ -197,8 +197,8 @@ @@ -226,7 +226,7 @@ - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'graph') + $ (get-with-headers.py localhost:$HGPORT 'graph') 200 Script output follows @@ -253,15 +253,15 @@ mercurial
                                              • help
                                              • @@ -285,8 +285,8 @@ @@ -355,8 +355,8 @@ @@ -380,7 +380,7 @@ - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file') + $ (get-with-headers.py localhost:$HGPORT 'file') 200 Script output follows @@ -402,14 +402,14 @@ mercurial
                                                  @@ -422,7 +422,10 @@
                                                  -

                                                  directory / @ -1:000000000000 tip

                                                  +

                                                  + directory / @ -1:000000000000 + tip +

                                                  @@ -175,8 +197,8 @@ @@ -191,20 +213,26 @@ - + - +
                                                  Thu, 01 Jan 1970 00:00:00 +0000 testsecond a + second a + a-branch +
                                                  Thu, 01 Jan 1970 00:00:00 +0000 testfirst a + first a + a-tag a-bookmark +
                                                  @@ -220,7 +248,7 @@ second version - two revisions - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/3/a') + $ (get-with-headers.py localhost:$HGPORT 'log/4/a') 200 Script output follows @@ -246,29 +274,29 @@ mercurial @@ -286,8 +314,8 @@ @@ -302,20 +330,26 @@ - + - +
                                                  Thu, 01 Jan 1970 00:00:00 +0000 testsecond a + second a + a-branch +
                                                  Thu, 01 Jan 1970 00:00:00 +0000 testfirst a + first a + a-tag a-bookmark +
                                                  @@ -331,7 +365,7 @@ first deleted - one revision - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/2/a') + $ (get-with-headers.py localhost:$HGPORT 'log/3/a') 200 Script output follows @@ -357,23 +391,23 @@ mercurial
                                                  • help
                                                  • @@ -397,8 +431,8 @@ @@ -413,15 +447,18 @@ - +
                                                    Thu, 01 Jan 1970 00:00:00 +0000 testfirst a + first a + a-tag a-bookmark +
                                                    @@ -437,7 +474,7 @@ first version - one revision - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/1/a') + $ (get-with-headers.py localhost:$HGPORT 'log/1/a') 200 Script output follows @@ -463,23 +500,23 @@ mercurial
                                                    • help
                                                    • @@ -503,8 +540,8 @@ @@ -519,15 +556,18 @@ - +
                                                      Thu, 01 Jan 1970 00:00:00 +0000 testfirst a + first a + a-tag a-bookmark +
                                                      @@ -543,7 +583,7 @@ before addition - error - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/0/a') + $ (get-with-headers.py localhost:$HGPORT 'log/0/a') 404 Not Found @@ -609,7 +649,7 @@ should show base link, use spartan because it shows it - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/tip/c?style=spartan') + $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=spartan') 200 Script output follows @@ -634,8 +674,8 @@ graph tags branches - file - annotate + file + annotate help rss atom @@ -643,19 +683,19 @@

                                                      Mercurial / c revision history

                                                      -

                                                      navigate: (0) tip

                                                      +

                                                      navigate: (0) tip

                                                      - + @@ -673,14 +713,14 @@
                                                      Thu, 01 Jan 1970 00:00:00 +0000:change cchange c
                                                      revision 1: - b7682196df1c - (diff) - (annotate) + 46c1a66bd8fc + (diff) + (annotate)
                                                      - + @@ -718,7 +758,7 @@ rss log - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rss-log/tip/a') + $ (get-with-headers.py localhost:$HGPORT 'rss-log/tip/a') 200 Script output follows @@ -731,7 +771,7 @@ a revision history second a - http://*:$HGPORT/log01de2d66a28d/a (glob) + http://*:$HGPORT/log3f41bc784e7e/a (glob) test Thu, 01 Jan 1970 00:00:00 +0000 @@ -749,7 +789,7 @@ atom log - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'atom-log/tip/a') + $ (get-with-headers.py localhost:$HGPORT 'atom-log/tip/a') 200 Script output follows @@ -760,9 +800,9 @@ 1970-01-01T00:00:00+00:00 - second a - http://*:$HGPORT/#changeset-01de2d66a28df5549090991dccda788726948517 (glob) - (glob) + [a-branch] second a + http://*:$HGPORT/#changeset-3f41bc784e7e73035c6d47112c6cc7efb673adf8 (glob) + (glob) test test @@ -773,11 +813,11 @@
                                                      Thu, 01 Jan 1970 00:00:00 +0000:mv bmv b
                                                      revision 0: - 1a6696706df2 - (diff) - (annotate) + c9637d3cc8ef + (diff) + (annotate)
                                                      - + - + @@ -824,11 +864,11 @@ - + - + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-json.t --- a/tests/test-hgweb-json.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-json.t Sat Jul 18 17:32:38 2015 -0500 @@ -2,7 +2,7 @@ #require serve $ request() { - > $TESTDIR/get-with-headers.py --json localhost:$HGPORT "$1" + > get-with-headers.py --json localhost:$HGPORT "$1" > } $ hg init test diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-raw.t --- a/tests/test-hgweb-raw.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-raw.t Sat Jul 18 17:32:38 2015 -0500 @@ -17,9 +17,9 @@ $ hg serve -p $HGPORT -A access.log -E error.log -d --pid-file=hg.pid $ cat hg.pid >> $DAEMON_PIDS - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt + $ (get-with-headers.py localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt - $ "$TESTDIR/killdaemons.py" hg.pid + $ killdaemons.py hg.pid $ cat getoutput.txt 200 Script output follows @@ -39,8 +39,8 @@ > --config web.guessmime=True $ cat hg.pid >> $DAEMON_PIDS - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt - $ "$TESTDIR/killdaemons.py" hg.pid + $ (get-with-headers.py localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt + $ killdaemons.py hg.pid $ cat getoutput.txt 200 Script output follows diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-removed.t --- a/tests/test-hgweb-removed.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb-removed.t Sat Jul 18 17:32:38 2015 -0500 @@ -17,7 +17,7 @@ revision - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip' + $ get-with-headers.py localhost:$HGPORT 'rev/tip' 200 Script output follows @@ -38,16 +38,16 @@ mercurial
                                                        @@ -60,7 +60,10 @@
                                                        -

                                                        changeset 1:c78f6c5cbea9 tip

                                                        +

                                                        + changeset 1:c78f6c5cbea9 + tip +

                                                        @@ -137,7 +140,7 @@ diff removed file - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/a' + $ get-with-headers.py localhost:$HGPORT 'diff/tip/a' 200 Script output follows @@ -159,24 +162,24 @@ mercurial
                                                        • help
                                                        • @@ -185,7 +188,10 @@
                                                          -

                                                          diff a @ 1:c78f6c5cbea9

                                                          +

                                                          + diff a @ 1:c78f6c5cbea9 + tip +

                                                          diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb-symrev.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hgweb-symrev.t Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,1034 @@ +#require serve + +Test symbolic revision usage in links produced by hgweb pages. There are +multiple issues related to this: +- issue2296 +- issue2826 +- issue3594 +- issue3634 + +Set up the repo + + $ hg init test + $ cd test + $ echo 0 > foo + $ mkdir dir + $ echo 0 > dir/bar + $ hg ci -Am 'first' + adding dir/bar + adding foo + $ echo 1 >> foo + $ hg ci -m 'second' + $ echo 2 >> foo + $ hg ci -m 'third' + $ hg bookmark -r1 xyzzy + + $ hg log -G --template '{rev}:{node|short} {tags} {bookmarks}\n' + @ 2:9d8c40cba617 tip + | + o 1:a7c1559b7bba xyzzy + | + o 0:43c799df6e75 + + $ hg serve --config web.allow_archive=zip -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log + $ cat hg.pid >> $DAEMON_PIDS + + $ REVLINKS='href=[^>]+(rev=|/)(43c799df6e75|0|a7c1559b7bba|1|xyzzy|9d8c40cba617|2|tip|default)' + +(De)referencing symbolic revisions (paper) + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=paper' | egrep $REVLINKS +
                                                        • graph
                                                        • +
                                                        • changeset
                                                        • +
                                                        • browse
                                                        • + zip + less + more + | rev 2: (0) tip + third + second + first + less + more + | rev 2: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=paper' | egrep $REVLINKS +
                                                        • log
                                                        • +
                                                        • changeset
                                                        • +
                                                        • browse
                                                        • + less + more + | rev 2: (0) tip + less + more + | rev 2: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=paper' | egrep $REVLINKS +
                                                        • log
                                                        • +
                                                        • graph
                                                        • +
                                                        • changeset
                                                        • + zip + directory / @ 2:9d8c40cba617 +
                                                      + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=paper' | egrep $REVLINKS + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=paper' | egrep $REVLINKS + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=paper' | egrep $REVLINKS + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=paper&rev=all()' | egrep $REVLINKS + third + second + first + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=paper' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • raw
                                                    • +
                                                    • browse
                                                    • + zip + changeset 1:a7c1559b7bba + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=paper' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • + zip + less + more + | rev 1: (0)tip + second + first + less + more + | rev 1: (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=paper' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • + less + more + | rev 1: (0)tip + less + more + | rev 1: (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=paper' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • + zip + directory / @ 1:a7c1559b7bba + + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=paper' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • latest
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • annotate
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + view foo @ 1:a7c1559b7bba + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=paper' | egrep $REVLINKS + href="/atom-log/tip/foo" title="Atom feed for test:foo" /> + href="/rss-log/tip/foo" title="RSS feed for test:foo" /> +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • annotate
                                                    • +
                                                    • raw
                                                    • + + less + more + | (0)tip + second + first + less + more + | (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=paper' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • latest
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + annotate foo @ 1:a7c1559b7bba + + + log +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • latest
                                                    • +
                                                    • comparison
                                                    • +
                                                    • annotate
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + diff foo @ 1:a7c1559b7bba + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=paper' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • latest
                                                    • +
                                                    • diff
                                                    • +
                                                    • annotate
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + comparison foo @ 1:a7c1559b7bba + + + +(De)referencing symbolic revisions (coal) + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=coal' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • + zip + less + more + | rev 2: (0)tip + third + second + first + less + more + | rev 2: (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • + less + more + | rev 2: (0)tip + less + more + | rev 2: (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • + zip + directory / @ 2:9d8c40cba617 + + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=coal' | egrep $REVLINKS + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=coal' | egrep $REVLINKS + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=coal' | egrep $REVLINKS + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=coal&rev=all()' | egrep $REVLINKS + third + second + first + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • raw
                                                    • +
                                                    • browse
                                                    • + zip + changeset 1:a7c1559b7bba + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=coal' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • + zip + less + more + | rev 1: (0)tip + second + first + less + more + | rev 1: (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • + less + more + | rev 1: (0)tip + less + more + | rev 1: (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • + zip + directory / @ 1:a7c1559b7bba + + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • latest
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • annotate
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + view foo @ 1:a7c1559b7bba + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=coal' | egrep $REVLINKS + href="/atom-log/tip/foo" title="Atom feed for test:foo" /> + href="/rss-log/tip/foo" title="RSS feed for test:foo" /> +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • annotate
                                                    • +
                                                    • raw
                                                    • + + less + more + | (0)tip + second + first + less + more + | (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • latest
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + annotate foo @ 1:a7c1559b7bba + + + log +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • latest
                                                    • +
                                                    • comparison
                                                    • +
                                                    • annotate
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + diff foo @ 1:a7c1559b7bba + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=coal' | egrep $REVLINKS +
                                                    • log
                                                    • +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • browse
                                                    • +
                                                    • file
                                                    • +
                                                    • latest
                                                    • +
                                                    • diff
                                                    • +
                                                    • annotate
                                                    • +
                                                    • file log
                                                    • +
                                                    • raw
                                                    • + comparison foo @ 1:a7c1559b7bba + + + +(De)referencing symbolic revisions (gitweb) + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'summary?style=gitweb' | egrep $REVLINKS + files | zip | + + changeset | + files + + changeset | + files + + changeset | + files + + changeset | + changelog | + files + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=gitweb' | egrep $REVLINKS + changelog | + files | zip | +
                                                      (0)tip
                                                      + + changeset | + files + + changeset | + files + + changeset | + files + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?style=gitweb' | egrep $REVLINKS + shortlog | + files | zip | + (0)tip
                                                      + Thu, 01 Jan 1970 00:00:00 +0000third default tip + changeset
                                                      + Thu, 01 Jan 1970 00:00:00 +0000second xyzzy + changeset
                                                      + Thu, 01 Jan 1970 00:00:00 +0000first + changeset
                                                      + (0)tip
                                                      + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=gitweb' | egrep $REVLINKS + changelog | + files | + less + more + | (0)tip
                                                      + less + more + | (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=gitweb' | egrep $REVLINKS + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=gitweb' | egrep $REVLINKS + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=gitweb' | egrep $REVLINKS + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=gitweb' | egrep $REVLINKS + changeset | zip | + + dir + + files + foo + file | + revisions | + annotate + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=gitweb&rev=all()' | egrep $REVLINKS + files | zip + Thu, 01 Jan 1970 00:00:00 +0000third default tip + changeset
                                                      + Thu, 01 Jan 1970 00:00:00 +0000second xyzzy + changeset
                                                      + Thu, 01 Jan 1970 00:00:00 +0000first + changeset
                                                      + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=gitweb' | egrep $REVLINKS + shortlog | + changelog | + files | + raw | zip | + second xyzzy + + 43c799df6e75 + 9d8c40cba617 + + file | + annotate | + diff | + comparison | + revisions + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=gitweb' | egrep $REVLINKS + changelog | + files | zip | +
                                                      (0)tip
                                                      + + changeset | + files + + changeset | + files + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy?style=gitweb' | egrep $REVLINKS + shortlog | + files | zip | + (0)tip
                                                      + Thu, 01 Jan 1970 00:00:00 +0000second xyzzy + changeset
                                                      + Thu, 01 Jan 1970 00:00:00 +0000first + changeset
                                                      + (0)tip
                                                      + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=gitweb' | egrep $REVLINKS + changelog | + files | + less + more + | (0)tip
                                                      + less + more + | (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=gitweb' | egrep $REVLINKS + changeset | zip | + + dir + + files + foo + file | + revisions | + annotate + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=gitweb' | egrep $REVLINKS + files | + changeset | + latest | + revisions | + annotate | + diff | + comparison | + raw | + + + 9d8c40cba617 + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=gitweb' | egrep $REVLINKS + file | + annotate | + diff | + comparison | + rss | + (0)tip + + file | + diff | + annotate + + file | + diff | + annotate + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=gitweb' | egrep $REVLINKS + files | + changeset | + file | + latest | + revisions | + diff | + comparison | + raw | + + + 9d8c40cba617 + files | + changeset | + file | + latest | + revisions | + annotate | + comparison | + raw | + + + 9d8c40cba617 + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=gitweb' | egrep $REVLINKS + files | + changeset | + file | + latest | + revisions | + annotate | + diff | + raw | + + + 9d8c40cba617 + +(De)referencing symbolic revisions (monoblue) + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'summary?style=monoblue' | egrep $REVLINKS + + changeset | + files + + changeset | + files + + changeset | + files + + changeset | + changelog | + files + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • zip
                                                    • + + changeset | + files + + changeset | + files + + changeset | + files + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • zip
                                                    • +

                                                      third default tip

                                                      +

                                                      second xyzzy

                                                      +

                                                      first

                                                      + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=monoblue' | egrep $REVLINKS +
                                                    • files
                                                    • + less + more + | (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=monoblue' | egrep $REVLINKS + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=monoblue' | egrep $REVLINKS + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=monoblue' | egrep $REVLINKS + + changeset | + changelog | + files + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • zip
                                                    • + + dir + + + + file | + revisions | + annotate + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=monoblue&rev=all()' | egrep $REVLINKS +
                                                    • zip
                                                    • +

                                                      third default tip

                                                      +

                                                      second xyzzy

                                                      +

                                                      first

                                                      + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • raw
                                                    • +
                                                    • zip
                                                    • +

                                                      second xyzzy

                                                      +
                                                      a7c1559b7bba
                                                      +
                                                      43c799df6e75
                                                      +
                                                      9d8c40cba617
                                                      + + file | + annotate | + diff | + comparison | + revisions + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • zip
                                                    • + + changeset | + files + + changeset | + files + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • zip
                                                    • +

                                                      second xyzzy

                                                      +

                                                      first

                                                      + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=monoblue' | egrep $REVLINKS +
                                                    • files
                                                    • + less + more + | (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • changeset
                                                    • +
                                                    • zip
                                                    • + + dir + + + + file | + revisions | + annotate + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • revisions
                                                    • +
                                                    • annotate
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • raw
                                                    • +
                                                      a7c1559b7bba
                                                      + + 9d8c40cba617 + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • file
                                                    • +
                                                    • annotate
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • rss
                                                    • + + file | diff | annotate + + file | diff | annotate + (0)tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • file
                                                    • +
                                                    • revisions
                                                    • +
                                                    • diff
                                                    • +
                                                    • comparison
                                                    • +
                                                    • raw
                                                    • +
                                                      a7c1559b7bba
                                                      + + 9d8c40cba617 + graph +
                                                    • files
                                                    • +
                                                    • file
                                                    • +
                                                    • revisions
                                                    • +
                                                    • annotate
                                                    • +
                                                    • comparison
                                                    • +
                                                    • raw
                                                    • +
                                                      a7c1559b7bba
                                                      +
                                                      43c799df6e75
                                                      +
                                                      9d8c40cba617
                                                      + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=monoblue' | egrep $REVLINKS +
                                                    • graph
                                                    • +
                                                    • files
                                                    • +
                                                    • file
                                                    • +
                                                    • revisions
                                                    • +
                                                    • annotate
                                                    • +
                                                    • diff
                                                    • +
                                                    • raw
                                                    • +
                                                      a7c1559b7bba
                                                      +
                                                      43c799df6e75
                                                      +
                                                      9d8c40cba617
                                                      + +(De)referencing symbolic revisions (spartan) + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=spartan' | egrep $REVLINKS + changelog + graph + files + zip + navigate: (0) tip + + + + navigate: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?style=spartan' | egrep $REVLINKS + shortlog + graph + files + zip + navigate: (0) tip + + + + + + + + + + navigate: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=spartan' | egrep $REVLINKS + changelog + shortlog + files + navigate: (0) tip + navigate: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=spartan' | egrep $REVLINKS + tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=spartan' | egrep $REVLINKS + default + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=spartan' | egrep $REVLINKS + changelog + shortlog + graph + changeset + zip +

                                                      Mercurial / files for changeset 9d8c40cba617: /

                                                      + + a7c1559b7bba + + + + 43c799df6e75 + + + + + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=spartan' | egrep $REVLINKS + changelog + shortlog + graph + files + raw + zip + + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=spartan' | egrep $REVLINKS + changelog + graph + files + zip + navigate: (0) tip + + + navigate: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy?style=spartan' | egrep $REVLINKS + shortlog + graph + files + zip + navigate: (0) tip + + + + + + + navigate: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=spartan' | egrep $REVLINKS + changelog + shortlog + files + navigate: (0) tip + navigate: (0) tip + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=spartan' | egrep $REVLINKS + changelog + shortlog + graph + changeset + zip +

                                                      Mercurial / files for changeset a7c1559b7bba: /

                                                      + + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=spartan' | egrep $REVLINKS + href="/atom-log/tip/foo" title="Atom feed for test:foo"> + href="/rss-log/tip/foo" title="RSS feed for test:foo"> + file + annotate + rss + atom +

                                                      navigate: (0) tip

                                                      + + a7c1559b7bba + (diff) + (annotate) + + 43c799df6e75 + (diff) + (annotate) + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=spartan' | egrep $REVLINKS + changelog + shortlog + graph + changeset + files + file + revisions + raw + + + + changelog + shortlog + graph + changeset + file + revisions + annotate + raw + + + + +Done + + $ cat errors.log + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgweb.t --- a/tests/test-hgweb.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgweb.t Sat Jul 18 17:32:38 2015 -0500 @@ -10,12 +10,15 @@ $ hg ci -Ambase adding da/foo adding foo + $ hg bookmark -r0 '@' + $ hg bookmark -r0 'a b c' + $ hg bookmark -r0 'd/e/f' $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS manifest - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw') + $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw') 200 Script output follows @@ -23,7 +26,7 @@ -rw-r--r-- 4 foo - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw') + $ (get-with-headers.py localhost:$HGPORT 'file/tip/da?style=raw') 200 Script output follows @@ -33,14 +36,14 @@ plain file - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw' + $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?style=raw' 200 Script output follows foo should give a 404 - static file that does not exist - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus' + $ get-with-headers.py localhost:$HGPORT 'static/bogus' 404 Not Found @@ -106,7 +109,7 @@ should give a 404 - bad revision - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw' + $ get-with-headers.py localhost:$HGPORT 'file/spam/foo?style=raw' 404 Not Found @@ -115,40 +118,40 @@ should give a 400 - bad command - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw' + $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw' 400* (glob) error: no such method: spam [1] - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam' + $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam' 400 no such method: spam [1] should give a 400 - bad command as a part of url path (issue4071) - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam' + $ get-with-headers.py --headeronly localhost:$HGPORT 'spam' 400 no such method: spam [1] - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam' + $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam' 400 no such method: spam [1] - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo' + $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo' 400 no such method: spam [1] should give a 404 - file does not exist - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw' + $ get-with-headers.py localhost:$HGPORT 'file/tip/bork?style=raw' 404 Not Found error: bork@2ef0ac749a14: not found in manifest [1] - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork' + $ get-with-headers.py localhost:$HGPORT 'file/tip/bork' 404 Not Found @@ -211,7 +214,7 @@ [1] - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw' + $ get-with-headers.py localhost:$HGPORT 'diff/tip/bork?style=raw' 404 Not Found @@ -220,7 +223,7 @@ try bad style - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar') + $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=foobar') 200 Script output follows @@ -242,14 +245,14 @@ mercurial
                                                        @@ -262,7 +265,10 @@
                                                        -

                                                        directory / @ 0:2ef0ac749a14 tip

                                                        +

                                                        + directory / @ 0:2ef0ac749a14 + tip @ a b c d/e/f +

                                                        @@ -281,17 +287,17 @@
                                                      - + @@ -301,7 +307,7 @@ @@ -321,7 +327,7 @@ stop and restart - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log $ cat hg.pid >> $DAEMON_PIDS @@ -332,7 +338,7 @@ static file - $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server + $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server 200 Script output follows content-length: 5372 content-type: text/css @@ -540,7 +546,7 @@ $ echo bar >> foo $ hg ci -msecret --secret - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw' + $ get-with-headers.py localhost:$HGPORT 'log?style=raw' 200 Script output follows @@ -554,10 +560,13 @@ summary: base branch: default tag: tip + bookmark: @ + bookmark: a b c + bookmark: d/e/f $ hg phase --draft tip - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw' + $ get-with-headers.py localhost:$HGPORT 'log?style=raw' 200 Script output follows @@ -577,9 +586,30 @@ user: test date: Thu, 01 Jan 1970 00:00:00 +0000 summary: base + bookmark: @ + bookmark: a b c + bookmark: d/e/f +access bookmarks + + $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:' + 200 Script output follows + changeset 0:2ef0ac749a14 + + $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:' + 200 Script output follows + changeset 0:2ef0ac749a14 + + $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:' + 200 Script output follows + changeset 0:2ef0ac749a14 + + $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:' + 200 Script output follows + changeset 0:2ef0ac749a14 + no style can be loaded from directories other than the specified paths $ mkdir -p x/templates/fallback @@ -594,27 +624,27 @@ > mimetype = 'text/plain' > EOF - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \ > --config web.style=fallback --config web.templates=x/templates $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "?style=`pwd`/x" + $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x" 200 Script output follows fall back to default - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=..' + $ get-with-headers.py localhost:$HGPORT '?style=..' 200 Script output follows fall back to default - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=./..' + $ get-with-headers.py localhost:$HGPORT '?style=./..' 200 Script output follows fall back to default - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=.../.../' + $ get-with-headers.py localhost:$HGPORT '?style=.../.../' 200 Script output follows fall back to default @@ -625,18 +655,18 @@ Uncaught exceptions result in a logged error and canned HTTP response - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type + $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type 500 Internal Server Error transfer-encoding: chunked Internal Server Error (no-eol) [1] - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ head -1 errors.log .* Exception happened during processing request '/raiseerror': (re) @@ -644,7 +674,7 @@ $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type + $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type 200 Script output follows transfer-encoding: chunked content-type: text/plain @@ -652,5 +682,5 @@ partial content Internal Server Error (no-eol) - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgwebdir.t --- a/tests/test-hgwebdir.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgwebdir.t Sat Jul 18 17:32:38 2015 -0500 @@ -84,7 +84,7 @@ should give a 404 - file does not exist - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/file/tip/bork?style=raw' + $ get-with-headers.py localhost:$HGPORT 'a/file/tip/bork?style=raw' 404 Not Found @@ -93,25 +93,25 @@ should succeed - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=raw' + $ get-with-headers.py localhost:$HGPORT '?style=raw' 200 Script output follows /a/ /b/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT 'a/file/tip/a?style=raw' 200 Script output follows a - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'b/file/tip/b?style=raw' + $ get-with-headers.py localhost:$HGPORT 'b/file/tip/b?style=raw' 200 Script output follows b should give a 404 - repo is not published - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'c/file/tip/c?style=raw' + $ get-with-headers.py localhost:$HGPORT 'c/file/tip/c?style=raw' 404 Not Found @@ -120,14 +120,14 @@ atom-log without basedir - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/atom-log' | grep ' (glob) (glob) (glob) rss-log without basedir - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/rss-log' | grep 'http://*:$HGPORT/a/rev/8580ff50825a (glob) $ cat > paths.conf < [paths] @@ -145,7 +145,7 @@ should succeed, slashy names - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw' + $ get-with-headers.py localhost:$HGPORT1 '?style=raw' 200 Script output follows @@ -184,7 +184,7 @@ /astar/ /astar/.hg/patches/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=paper' + $ get-with-headers.py localhost:$HGPORT1 '?style=paper' 200 Script output follows @@ -672,19 +672,19 @@ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't?style=raw' + $ get-with-headers.py localhost:$HGPORT1 't?style=raw' 200 Script output follows /t/a/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 't/?style=raw' 200 Script output follows /t/a/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=paper' + $ get-with-headers.py localhost:$HGPORT1 't/?style=paper' 200 Script output follows @@ -743,7 +743,7 @@ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/a?style=atom' + $ get-with-headers.py localhost:$HGPORT1 't/a?style=atom' 200 Script output follows @@ -800,7 +800,7 @@ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/a/?style=atom' + $ get-with-headers.py localhost:$HGPORT1 't/a/?style=atom' 200 Script output follows @@ -857,14 +857,14 @@ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/a/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT1 't/a/file/tip/a?style=raw' 200 Script output follows a Test [paths] '*' extension - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'coll/?style=raw' 200 Script output follows @@ -875,14 +875,14 @@ /coll/notrepo/e/ /coll/notrepo/f/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/a/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'coll/a/file/tip/a?style=raw' 200 Script output follows a Test [paths] '**' extension - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/?style=raw' 200 Script output follows @@ -896,14 +896,14 @@ /rcoll/notrepo/f/ /rcoll/notrepo/f/f2/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw' 200 Script output follows d Test collapse = True - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat >> paths.conf < [web] > collapse=true @@ -912,7 +912,7 @@ $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \ > -A access-paths.log -E error-paths-3.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'coll/?style=raw' 200 Script output follows @@ -922,11 +922,11 @@ /coll/c/ /coll/notrepo/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/a/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'coll/a/file/tip/a?style=raw' 200 Script output follows a - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/?style=raw' 200 Script output follows @@ -937,7 +937,7 @@ /rcoll/c/ /rcoll/notrepo/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw' 200 Script output follows d @@ -952,7 +952,7 @@ > hidden = True > EOF - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/?style=raw' 200 Script output follows @@ -963,7 +963,7 @@ Subrepo parent not hidden $ mv $root/notrepo/f/.hg/hgrc.bak $root/notrepo/f/.hg/hgrc - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/?style=raw' 200 Script output follows @@ -975,28 +975,28 @@ Test repositories inside intermediate directories - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw' 200 Script output follows e Test subrepositories inside intermediate directories - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw' 200 Script output follows f2 Test descend = False - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat >> paths.conf < descend=false > EOF $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \ > -A access-paths.log -E error-paths-4.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'coll/?style=raw' 200 Script output follows @@ -1004,11 +1004,11 @@ /coll/b/ /coll/c/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/a/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'coll/a/file/tip/a?style=raw' 200 Script output follows a - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/?style=raw' 200 Script output follows @@ -1016,14 +1016,14 @@ /rcoll/b/ /rcoll/c/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw' 200 Script output follows d Test intermediate directories - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/?style=raw' 200 Script output follows @@ -1033,14 +1033,14 @@ Test repositories inside intermediate directories - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw' 200 Script output follows e Test subrepositories inside intermediate directories - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw' + $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw' 200 Script output follows f2 @@ -1050,7 +1050,7 @@ $ hg id http://localhost:$HGPORT1/astar 8580ff50825a - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat > paths.conf < [paths] > t/a = $root/a @@ -1060,7 +1060,7 @@ $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \ > -A access-paths.log -E error-paths-5.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw' + $ get-with-headers.py localhost:$HGPORT1 '?style=raw' 200 Script output follows @@ -1068,7 +1068,7 @@ /t/b/ /c/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 't/?style=raw' 200 Script output follows @@ -1078,7 +1078,7 @@ Test collapse = True - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat >> paths.conf < [web] > collapse=true @@ -1086,14 +1086,14 @@ $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \ > -A access-paths.log -E error-paths-6.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw' + $ get-with-headers.py localhost:$HGPORT1 '?style=raw' 200 Script output follows /t/ /c/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 't/?style=raw' 200 Script output follows @@ -1103,27 +1103,27 @@ test descend = False - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat >> paths.conf < descend=false > EOF $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \ > -A access-paths.log -E error-paths-7.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw' + $ get-with-headers.py localhost:$HGPORT1 '?style=raw' 200 Script output follows /c/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw' + $ get-with-headers.py localhost:$HGPORT1 't/?style=raw' 200 Script output follows /t/a/ /t/b/ - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cat > paths.conf < [paths] > nostore = $root/nostore @@ -1135,7 +1135,7 @@ test inexistent and inaccessible repo should be ignored silently - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '' + $ get-with-headers.py localhost:$HGPORT1 '' 200 Script output follows @@ -1192,7 +1192,7 @@ collections: should succeed - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '?style=raw' + $ get-with-headers.py localhost:$HGPORT2 '?style=raw' 200 Script output follows @@ -1203,31 +1203,31 @@ /notrepo/e/ /notrepo/f/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT2 'a/file/tip/a?style=raw' 200 Script output follows a - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'b/file/tip/b?style=raw' + $ get-with-headers.py localhost:$HGPORT2 'b/file/tip/b?style=raw' 200 Script output follows b - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'c/file/tip/c?style=raw' + $ get-with-headers.py localhost:$HGPORT2 'c/file/tip/c?style=raw' 200 Script output follows c atom-log with basedir / - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/atom-log' | grep ' rss-log with basedir / - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/rss-log' | grep 'http://hg.example.com:8080/a/rev/8580ff50825a - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ hg serve --config web.baseurl=http://hg.example.com:8080/foo/ -p $HGPORT2 -d \ > --pid-file=hg.pid --webdir-conf collections.conf \ > -A access-collections-2.log -E error-collections-2.log @@ -1235,14 +1235,14 @@ atom-log with basedir /foo/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/atom-log' | grep ' rss-log with basedir /foo/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/rss-log' | grep 'http://hg.example.com:8080/foo/a/rev/8580ff50825a paths errors 1 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hgwebdirsym.t --- a/tests/test-hgwebdirsym.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hgwebdirsym.t Sat Jul 18 17:32:38 2015 -0500 @@ -33,7 +33,7 @@ should succeed - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=raw' + $ get-with-headers.py localhost:$HGPORT '?style=raw' 200 Script output follows @@ -41,34 +41,34 @@ /b/ /c/ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'al/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT 'al/file/tip/a?style=raw' 200 Script output follows a - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'b/file/tip/b?style=raw' + $ get-with-headers.py localhost:$HGPORT 'b/file/tip/b?style=raw' 200 Script output follows b - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'c/file/tip/c?style=raw' + $ get-with-headers.py localhost:$HGPORT 'c/file/tip/c?style=raw' 200 Script output follows c should fail - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'circle/al/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT 'circle/al/file/tip/a?style=raw' 404 Not Found error: repository circle/al/file/tip/a not found [1] - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'circle/b/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT 'circle/b/file/tip/a?style=raw' 404 Not Found error: repository circle/b/file/tip/a not found [1] - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'circle/c/file/tip/a?style=raw' + $ get-with-headers.py localhost:$HGPORT 'circle/c/file/tip/a?style=raw' 404 Not Found diff -r 501c51d60792 -r 96a38d44ba09 tests/test-highlight.t --- a/tests/test-highlight.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-highlight.t Sat Jul 18 17:32:38 2015 -0500 @@ -55,7 +55,7 @@ hgweb filerevision, html - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/primes.py') \ + $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py') \ > | sed "s/class=\"k\"/class=\"kn\"/g" | sed "s/class=\"mf\"/class=\"mi\"/g" 200 Script output follows @@ -79,24 +79,24 @@ mercurial
                                                      • help
                                                      • @@ -105,7 +105,10 @@
                                                        -

                                                        view primes.py @ 0:853dcd4de2a6

                                                        +

                                                        + view primes.py @ 0:853dcd4de2a6 + tip +

                                                        @@ -185,7 +188,7 @@ hgweb fileannotate, html - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/primes.py') \ + $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py') \ > | sed "s/class=\"k\"/class=\"kn\"/g" | sed "s/class=\"mi\"/class=\"mf\"/g" 200 Script output follows @@ -209,25 +212,25 @@ mercurial
                                                        • help
                                                        • @@ -236,7 +239,10 @@
                                                          -

                                                          annotate primes.py @ 0:853dcd4de2a6

                                                          +

                                                          + annotate primes.py @ 0:853dcd4de2a6 + tip +

                                                          @@ -515,7 +521,7 @@ hgweb fileannotate, raw - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/primes.py?style=raw') \ + $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py?style=raw') \ > | sed "s/test@//" > a $ echo "200 Script output follows" > b $ echo "" >> b @@ -529,7 +535,7 @@ hgweb filerevision, raw - $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/primes.py?style=raw') \ + $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py?style=raw') \ > > a $ echo "200 Script output follows" > b $ echo "" >> b @@ -538,7 +544,7 @@ hgweb highlightcss friendly - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'highlightcss' > out + $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out $ head -n 4 out 200 Script output follows @@ -549,7 +555,7 @@ errors encountered $ cat errors.log - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py Change the pygments style @@ -565,7 +571,7 @@ hgweb highlightcss fruity - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'highlightcss' > out + $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out $ head -n 4 out 200 Script output follows @@ -583,13 +589,13 @@ $ hg ci -Ama adding eucjp.txt $ hgserveget () { - > "$TESTDIR/killdaemons.py" $DAEMON_PIDS + > killdaemons.py > echo % HGENCODING="$1" hg serve > HGENCODING="$1" hg serve -p $HGPORT -d -n test --pid-file=hg.pid -E errors.log > cat hg.pid >> $DAEMON_PIDS > > echo % hgweb filerevision, html - > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "file/tip/$2" \ + > get-with-headers.py localhost:$HGPORT "file/tip/$2" \ > | grep '
                                                          ' > echo % errors encountered > cat errors.log diff -r 501c51d60792 -r 96a38d44ba09 tests/test-histedit-edit.t --- a/tests/test-histedit-edit.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-histedit-edit.t Sat Jul 18 17:32:38 2015 -0500 @@ -246,6 +246,7 @@ branch: default commit: 1 added (new branch head) update: 1 new changesets (update) + phases: 7 draft hist: 1 remaining (histedit --continue) (test also that editor is invoked if histedit is continued for @@ -435,3 +436,32 @@ $ HGEDITOR=true hg histedit --continue 0 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob) + + $ hg log -G + @ changeset: 0:0efcea34f18a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a + + $ echo foo >> b + $ hg addr + adding b + $ hg ci -m 'add b' + $ echo foo >> a + $ hg ci -m 'extend a' + $ hg phase --public 1 +Attempting to fold a change into a public change should not work: + $ cat > ../edit.sh < cat "\$1" | sed s/pick/fold/ > tmp + > mv tmp "\$1" + > EOF + $ HGEDITOR="sh ../edit.sh" hg histedit 2 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + reverting a + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + abort: cannot fold into public change 18aa70c8ad22 + [255] +TODO: this abort shouldn't be required, but it is for now to leave the repo in +a clean state. + $ hg histedit --abort diff -r 501c51d60792 -r 96a38d44ba09 tests/test-histedit-no-change.t --- a/tests/test-histedit-no-change.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-histedit-no-change.t Sat Jul 18 17:32:38 2015 -0500 @@ -183,6 +183,7 @@ branch: default commit: 1 added, 1 unknown (new branch head) update: 6 new changesets (update) + phases: 7 draft hist: 2 remaining (histedit --continue) $ hg histedit --abort 2>&1 | fixbundle diff -r 501c51d60792 -r 96a38d44ba09 tests/test-histedit-obsolete.t --- a/tests/test-histedit-obsolete.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-histedit-obsolete.t Sat Jul 18 17:32:38 2015 -0500 @@ -64,12 +64,16 @@ > fold e860deea161a 4 e > pick 652413bf663e 5 f > EOF - saved backup bundle to $TESTTMP/base/.hg/strip-backup/96e494a2d553-3c6c5d92-backup.hg (glob) + [1] $ hg log --graph --hidden - @ 8:cacdfd884a93 f + @ 10:cacdfd884a93 f + | + o 9:59d9f330561f d | - o 7:59d9f330561f d - | + | x 8:b558abc46d09 fold-temp-revision e860deea161a + | | + | x 7:96e494a2d553 d + |/ o 6:b346ab9a313d c | | x 5:652413bf663e f @@ -90,6 +94,8 @@ 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob) e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob) 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob) + 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob) + b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob) Ensure hidden revision does not prevent histedit @@ -105,7 +111,7 @@ 0 files updated, 0 files merged, 3 files removed, 0 files unresolved 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log --graph - @ 9:c13eb81022ca f + @ 11:c13eb81022ca f | o 6:b346ab9a313d c | @@ -127,7 +133,7 @@ $ hg up '.^' 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -r 'children(.)' - 9:c13eb81022ca f (no-eol) + 11:c13eb81022ca f (no-eol) $ hg histedit -r '.' --commands - < edit b346ab9a313d 6 c > EOF @@ -141,12 +147,12 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -r 'unstable()' - 9:c13eb81022ca f (no-eol) + 11:c13eb81022ca f (no-eol) stabilise $ hg rebase -r 'unstable()' -d . - rebasing 9:c13eb81022ca "f" + rebasing 11:c13eb81022ca "f" $ hg up tip -q Test dropping of changeset on the top of the stack @@ -166,7 +172,7 @@ > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -G - @ 10:40db8afa467b c + @ 12:40db8afa467b c | o 0:cb9a9f314b8b a @@ -188,9 +194,9 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -G - @ 15:ee6544123ab8 c + @ 17:ee6544123ab8 c | - o 14:269e713e9eae g + o 16:269e713e9eae g | o 0:cb9a9f314b8b a @@ -212,14 +218,15 @@ $ hg ph -pv '.^' phase changed for 2 changesets $ hg log -G - @ 11:b449568bf7fc (draft) f + @ 13:b449568bf7fc (draft) f | - o 10:40db8afa467b (public) c + o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a $ hg histedit -r '.~2' - abort: cannot edit immutable changeset: cb9a9f314b8b + abort: cannot edit public changeset: cb9a9f314b8b + (see "hg help phases" for details) [255] @@ -233,19 +240,19 @@ > done $ hg phase --force --secret .~2 $ hg log -G - @ 16:ee118ab9fa44 (secret) k + @ 18:ee118ab9fa44 (secret) k | - o 15:3a6c53ee7f3d (secret) j + o 17:3a6c53ee7f3d (secret) j | - o 14:b605fb7503f2 (secret) i + o 16:b605fb7503f2 (secret) i | - o 13:7395e1ff83bd (draft) h + o 15:7395e1ff83bd (draft) h | - o 12:6b70183d2492 (draft) g + o 14:6b70183d2492 (draft) g | - o 11:b449568bf7fc (draft) f + o 13:b449568bf7fc (draft) f | - o 10:40db8afa467b (public) c + o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a @@ -283,19 +290,19 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -G - @ 22:12e89af74238 (secret) k + @ 24:12e89af74238 (secret) k | - o 21:636a8687b22e (secret) j + o 23:636a8687b22e (secret) j | - o 20:ccaf0a38653f (secret) i + o 22:ccaf0a38653f (secret) i | - o 19:11a89d1c2613 (draft) h + o 21:11a89d1c2613 (draft) h | - o 18:c1dec7ca82ea (draft) g + o 20:c1dec7ca82ea (draft) g | - o 17:087281e68428 (draft) f + o 19:087281e68428 (draft) f | - o 10:40db8afa467b (public) c + o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a @@ -332,19 +339,19 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -G - @ 22:12e89af74238 (secret) k + @ 24:12e89af74238 (secret) k | - o 21:636a8687b22e (secret) j + o 23:636a8687b22e (secret) j | - o 20:ccaf0a38653f (secret) i + o 22:ccaf0a38653f (secret) i | - o 19:11a89d1c2613 (draft) h + o 21:11a89d1c2613 (draft) h | - o 18:c1dec7ca82ea (draft) g + o 20:c1dec7ca82ea (draft) g | - o 17:087281e68428 (draft) f + o 19:087281e68428 (draft) f | - o 10:40db8afa467b (public) c + o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a @@ -374,19 +381,19 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -G - @ 21:558246857888 (secret) k + @ 23:558246857888 (secret) k | - o 20:28bd44768535 (secret) h + o 22:28bd44768535 (secret) h | - o 19:d5395202aeb9 (secret) i + o 21:d5395202aeb9 (secret) i | - o 18:21edda8e341b (secret) g + o 20:21edda8e341b (secret) g | - o 17:5ab64f3a4832 (secret) j + o 19:5ab64f3a4832 (secret) j | - o 11:b449568bf7fc (draft) f + o 13:b449568bf7fc (draft) f | - o 10:40db8afa467b (public) c + o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a @@ -427,33 +434,30 @@ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved 2 files updated, 0 files merged, 0 files removed, 0 files unresolved 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - saved backup bundle to $TESTTMP/folding/.hg/strip-backup/58019c66f35f-96092fce-backup.hg (glob) - saved backup bundle to $TESTTMP/folding/.hg/strip-backup/83d1858e070b-f3469cf8-backup.hg (glob) - saved backup bundle to $TESTTMP/folding/.hg/strip-backup/859969f5ed7e-d89a19d7-backup.hg (glob) $ hg log -G - @ 19:f9daec13fb98 (secret) i + @ 27:f9daec13fb98 (secret) i | - o 18:49807617f46a (secret) g + o 24:49807617f46a (secret) g | - o 17:050280826e04 (draft) h + o 21:050280826e04 (draft) h | - o 10:40db8afa467b (public) c + o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a - $ hg co 18 + $ hg co 24 0 files updated, 0 files merged, 2 files removed, 0 files unresolved $ echo wat >> wat $ hg add wat $ hg ci -m 'add wat' created new head - $ hg merge 19 + $ hg merge 27 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m 'merge' $ echo not wat > wat $ hg ci -m 'modify wat' - $ hg histedit 17 + $ hg histedit 21 abort: cannot edit history that contains merges [255] $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hook.t --- a/tests/test-hook.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hook.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,6 +1,13 @@ commit hooks can see env vars (and post-transaction one are run unlocked) + $ cat << EOF >> $HGRCPATH + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True + > EOF + $ cat > $TESTTMP/txnabort.checkargs.py < def showargs(ui, repo, hooktype, **kwargs): > ui.write('%s python hook: %s\n' % (hooktype, ','.join(sorted(kwargs)))) @@ -10,19 +17,19 @@ $ cd a $ cat > .hg/hgrc < [hooks] - > commit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit" - > commit.b = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit.b" - > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= python \"$TESTDIR/printenv.py\" precommit" - > pretxncommit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxncommit" + > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit" + > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b" + > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit" + > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit" > pretxncommit.tip = hg -q tip - > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1 - > pre-cat = python "$TESTDIR/printenv.py" pre-cat - > post-cat = python "$TESTDIR/printenv.py" post-cat - > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen" - > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose" - > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose" + > pre-identify = printenv.py pre-identify 1 + > pre-cat = printenv.py pre-cat + > post-cat = printenv.py post-cat + > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen" + > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose" + > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose" > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs - > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnabort" + > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort" > txnclose.checklock = sh -c "hg debuglock > /dev/null" > EOF $ echo a > a @@ -46,9 +53,9 @@ $ cat > .hg/hgrc < [hooks] - > prechangegroup = python "$TESTDIR/printenv.py" prechangegroup - > changegroup = python "$TESTDIR/printenv.py" changegroup - > incoming = python "$TESTDIR/printenv.py" incoming + > prechangegroup = printenv.py prechangegroup + > changegroup = printenv.py changegroup + > incoming = printenv.py incoming > EOF pretxncommit and commit hooks can see both parents of merge @@ -121,8 +128,8 @@ $ cd ../a $ cat >> .hg/hgrc < pretag = python "$TESTDIR/printenv.py" pretag - > tag = sh -c "HG_PARENT1= HG_PARENT2= python \"$TESTDIR/printenv.py\" tag" + > pretag = printenv.py pretag + > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag" > EOF $ hg tag -d '3 0' a pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a @@ -141,7 +148,7 @@ pretag hook can forbid tagging - $ echo "pretag.forbid = python \"$TESTDIR/printenv.py\" pretag.forbid 1" >> .hg/hgrc + $ echo "pretag.forbid = printenv.py pretag.forbid 1" >> .hg/hgrc $ hg tag -d '4 0' fa pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa @@ -157,7 +164,7 @@ more there after $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc - $ echo "pretxncommit.forbid1 = python \"$TESTDIR/printenv.py\" pretxncommit.forbid 1" >> .hg/hgrc + $ echo "pretxncommit.forbid1 = printenv.py pretxncommit.forbid 1" >> .hg/hgrc $ echo z > z $ hg add z $ hg -q tip @@ -195,7 +202,7 @@ precommit hook can prevent commit - $ echo "precommit.forbid = python \"$TESTDIR/printenv.py\" precommit.forbid 1" >> .hg/hgrc + $ echo "precommit.forbid = printenv.py precommit.forbid 1" >> .hg/hgrc $ hg commit -m 'fail' -d '4 0' precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 @@ -206,14 +213,14 @@ preupdate hook can prevent update - $ echo "preupdate = python \"$TESTDIR/printenv.py\" preupdate" >> .hg/hgrc + $ echo "preupdate = printenv.py preupdate" >> .hg/hgrc $ hg update 1 preupdate hook: HG_PARENT1=ab228980c14d 0 files updated, 0 files merged, 2 files removed, 0 files unresolved update hook - $ echo "update = python \"$TESTDIR/printenv.py\" update" >> .hg/hgrc + $ echo "update = printenv.py update" >> .hg/hgrc $ hg update preupdate hook: HG_PARENT1=539e4b31b6dc update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc @@ -221,38 +228,41 @@ pushkey hook - $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey" >> .hg/hgrc + $ echo "pushkey = printenv.py pushkey" >> .hg/hgrc $ cd ../b $ hg bookmark -r null foo $ hg push -B foo ../a pushing to ../a searching for changes no changes found - pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob) - pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob) - txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob) + pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob) + pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob) pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1 + txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob) exporting bookmark foo [1] $ cd ../a listkeys hook - $ echo "listkeys = python \"$TESTDIR/printenv.py\" listkeys" >> .hg/hgrc + $ echo "listkeys = printenv.py listkeys" >> .hg/hgrc $ hg bookmark -r null bar + pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) + pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ cd ../b $ hg pull -B bar ../a pulling from ../a listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} - listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} no changes found + listkeys hook: HG_NAMESPACE=phase HG_VALUES={} + adding remote bookmark bar listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'} - adding remote bookmark bar $ cd ../a test that prepushkey can prevent incoming keys - $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey.forbid 1" >> .hg/hgrc + $ echo "prepushkey = printenv.py prepushkey.forbid 1" >> .hg/hgrc $ cd ../b $ hg bookmark -r null baz $ hg push -B baz ../a @@ -261,17 +271,20 @@ listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'} listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} no changes found - listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'} - prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 + pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob) + prepushkey.forbid hook: HG_BUNDLE2=1 HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_SOURCE=push HG_TXNID=TXN:* HG_URL=push (glob) pushkey-abort: prepushkey hook exited with status 1 - exporting bookmark baz failed! - [1] + abort: exporting bookmark baz failed! + [255] $ cd ../a test that prelistkeys can prevent listing keys - $ echo "prelistkeys = python \"$TESTDIR/printenv.py\" prelistkeys.forbid 1" >> .hg/hgrc + $ echo "prelistkeys = printenv.py prelistkeys.forbid 1" >> .hg/hgrc $ hg bookmark -r null quux + pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) + pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) + txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob) $ cd ../b $ hg pull -B quux ../a pulling from ../a @@ -288,7 +301,7 @@ 3:07f3376c1e65 $ cat > .hg/hgrc < [hooks] - > prechangegroup.forbid = python "$TESTDIR/printenv.py" prechangegroup.forbid 1 + > prechangegroup.forbid = printenv.py prechangegroup.forbid 1 > EOF $ hg pull ../a pulling from ../a @@ -303,7 +316,7 @@ $ cat > .hg/hgrc < [hooks] > pretxnchangegroup.forbid0 = hg tip -q - > pretxnchangegroup.forbid1 = python "$TESTDIR/printenv.py" pretxnchangegroup.forbid 1 + > pretxnchangegroup.forbid1 = printenv.py pretxnchangegroup.forbid 1 > EOF $ hg pull ../a pulling from ../a @@ -326,15 +339,15 @@ $ rm .hg/hgrc $ cat > ../a/.hg/hgrc < [hooks] - > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing - > outgoing = python "$TESTDIR/printenv.py" outgoing + > preoutgoing = printenv.py preoutgoing + > outgoing = printenv.py outgoing > EOF $ hg pull ../a pulling from ../a searching for changes preoutgoing hook: HG_SOURCE=pull + outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull adding changesets - outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull adding manifests adding file changes added 1 changesets with 1 changes to 1 files @@ -345,7 +358,7 @@ preoutgoing hook can prevent outgoing changes - $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> ../a/.hg/hgrc + $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> ../a/.hg/hgrc $ hg pull ../a pulling from ../a searching for changes @@ -359,8 +372,8 @@ $ cd .. $ cat > a/.hg/hgrc < [hooks] - > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing - > outgoing = python "$TESTDIR/printenv.py" outgoing + > preoutgoing = printenv.py preoutgoing + > outgoing = printenv.py outgoing > EOF $ hg clone a c preoutgoing hook: HG_SOURCE=clone @@ -371,7 +384,7 @@ preoutgoing hook can prevent outgoing changes for local clones - $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> a/.hg/hgrc + $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> a/.hg/hgrc $ hg clone a zzz preoutgoing hook: HG_SOURCE=clone preoutgoing.forbid hook: HG_SOURCE=clone diff -r 501c51d60792 -r 96a38d44ba09 tests/test-http-branchmap.t --- a/tests/test-http-branchmap.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-http-branchmap.t Sat Jul 18 17:32:38 2015 -0500 @@ -54,7 +54,7 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: foo - $ "$TESTDIR/killdaemons.py" hg.pid + $ killdaemons.py hg.pid verify 7e7d56fe4833 (encoding fallback in branchmap to maintain compatibility with 1.3.x) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-http-bundle1.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-http-bundle1.t Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,333 @@ +#require serve + +This test is a duplicate of 'test-http.t', feel free to factor out +parts that are not bundle1/bundle2 specific. + + $ cat << EOF >> $HGRCPATH + > [experimental] + > # This test is dedicated to interaction through old bundle + > bundle2-exp = False + > EOF + + $ hg init test + $ cd test + $ echo foo>foo + $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg + $ echo foo>foo.d/foo + $ echo bar>foo.d/bAr.hg.d/BaR + $ echo bar>foo.d/baR.d.hg/bAR + $ hg commit -A -m 1 + adding foo + adding foo.d/bAr.hg.d/BaR + adding foo.d/baR.d.hg/bAR + adding foo.d/foo + $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log + $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid + +Test server address cannot be reused + +#if windows + $ hg serve -p $HGPORT1 2>&1 + abort: cannot start server at ':$HGPORT1': * (glob) + [255] +#else + $ hg serve -p $HGPORT1 2>&1 + abort: cannot start server at ':$HGPORT1': Address already in use + [255] +#endif + $ cd .. + $ cat hg1.pid hg2.pid >> $DAEMON_PIDS + +clone via stream + + $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1 + streaming all changes + 6 files to transfer, 606 bytes of data + transferred * bytes in * seconds (*/sec) (glob) + searching for changes + no changes found + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R copy + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 4 files, 1 changesets, 4 total revisions + +try to clone via stream, should use pull instead + + $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2 + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 4 changes to 4 files + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + +clone via pull + + $ hg clone http://localhost:$HGPORT1/ copy-pull + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 4 changes to 4 files + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R copy-pull + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 4 files, 1 changesets, 4 total revisions + $ cd test + $ echo bar > bar + $ hg commit -A -d '1 0' -m 2 + adding bar + $ cd .. + +clone over http with --update + + $ hg clone http://localhost:$HGPORT1/ updated --update 0 + requesting all changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 5 changes to 5 files + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -r . -R updated + changeset: 0:8b6053c928fe + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 1 + + $ rm -rf updated + +incoming via HTTP + + $ hg clone http://localhost:$HGPORT1/ --rev 0 partial + adding changesets + adding manifests + adding file changes + added 1 changesets with 4 changes to 4 files + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd partial + $ touch LOCAL + $ hg ci -qAm LOCAL + $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n' + comparing with http://localhost:$HGPORT1/ + searching for changes + 2 + $ cd .. + +pull + + $ cd copy-pull + $ echo '[hooks]' >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc + $ hg pull + pulling from http://localhost:$HGPORT1/ + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT1/ (glob) + (run 'hg update' to get a working copy) + $ cd .. + +clone from invalid URL + + $ hg clone http://localhost:$HGPORT/bad + abort: HTTP Error 404: Not Found + [255] + +test http authentication ++ use the same server to test server side streaming preference + + $ cd test + $ cat << EOT > userpass.py + > import base64 + > from mercurial.hgweb import common + > def perform_authentication(hgweb, req, op): + > auth = req.env.get('HTTP_AUTHORIZATION') + > if not auth: + > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who', + > [('WWW-Authenticate', 'Basic Realm="mercurial"')]) + > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']: + > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no') + > def extsetup(): + > common.permhooks.insert(0, perform_authentication) + > EOT + $ hg --config extensions.x=userpass.py serve -p $HGPORT2 -d --pid-file=pid \ + > --config server.preferuncompressed=True \ + > --config web.push_ssl=False --config web.allow_push=* -A ../access.log + $ cat pid >> $DAEMON_PIDS + + $ cat << EOF > get_pass.py + > import getpass + > def newgetpass(arg): + > return "pass" + > getpass.getpass = newgetpass + > EOF + + $ hg id http://localhost:$HGPORT2/ + abort: http authorization required for http://localhost:$HGPORT2/ + [255] + $ hg id http://localhost:$HGPORT2/ + abort: http authorization required for http://localhost:$HGPORT2/ + [255] + $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/ + http authorization required for http://localhost:$HGPORT2/ + realm: mercurial + user: user + password: 5fed3813f7f5 + $ hg id http://user:pass@localhost:$HGPORT2/ + 5fed3813f7f5 + $ echo '[auth]' >> .hg/hgrc + $ echo 'l.schemes=http' >> .hg/hgrc + $ echo 'l.prefix=lo' >> .hg/hgrc + $ echo 'l.username=user' >> .hg/hgrc + $ echo 'l.password=pass' >> .hg/hgrc + $ hg id http://localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg id http://localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg id http://user@localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1 + streaming all changes + 7 files to transfer, 916 bytes of data + transferred * bytes in * seconds (*/sec) (glob) + searching for changes + no changes found + updating to branch default + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved +--pull should override server's preferuncompressed + $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1 + requesting all changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 5 changes to 5 files + updating to branch default + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg id http://user2@localhost:$HGPORT2/ + abort: http authorization required for http://localhost:$HGPORT2/ + [255] + $ hg id http://user:pass2@localhost:$HGPORT2/ + abort: HTTP Error 403: no + [255] + + $ hg -R dest tag -r tip top + $ hg -R dest push http://user:pass@localhost:$HGPORT2/ + pushing to http://user:***@localhost:$HGPORT2/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + $ hg rollback -q + + $ cut -c38- ../access.log + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=branchmap HTTP/1.1" 200 - + "GET /?cmd=stream_out HTTP/1.1" 401 - + "GET /?cmd=stream_out HTTP/1.1" 200 - + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D + "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=branchmap HTTP/1.1" 200 - + "GET /?cmd=branchmap HTTP/1.1" 200 - + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524 + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases + + $ cd .. + +clone of serve with repo in root and unserved subrepo (issue2970) + + $ hg --cwd test init sub + $ echo empty > test/sub/empty + $ hg --cwd test/sub add empty + $ hg --cwd test/sub commit -qm 'add empty' + $ hg --cwd test/sub tag -r 0 something + $ echo sub = sub > test/.hgsub + $ hg --cwd test add .hgsub + $ hg --cwd test commit -qm 'add subrepo' + $ hg clone http://localhost:$HGPORT noslash-clone + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 7 changes to 7 files + updating to branch default + abort: HTTP Error 404: Not Found + [255] + $ hg clone http://localhost:$HGPORT/ slash-clone + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 7 changes to 7 files + updating to branch default + abort: HTTP Error 404: Not Found + [255] + +check error log + + $ cat error.log diff -r 501c51d60792 -r 96a38d44ba09 tests/test-http-proxy.t --- a/tests/test-http-proxy.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-http-proxy.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,4 +1,10 @@ #require serve + $ cat << EOF >> $HGRCPATH + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True + > EOF $ hg init a $ cd a @@ -8,7 +14,7 @@ $ hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid >> $DAEMON_PIDS $ cd .. - $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 proxy.log 2>&1 > $DAEMON_PIDS @@ -103,26 +109,22 @@ * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phase%2Cbookmarks (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phase%2Cbookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - *- - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) - *- - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) - *- - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) - *- - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phase%2Cbookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phase%2Cbookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phase%2Cbookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-http.t --- a/tests/test-http.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-http.t Sat Jul 18 17:32:38 2015 -0500 @@ -119,7 +119,7 @@ $ cd copy-pull $ echo '[hooks]' >> .hg/hgrc - $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc $ hg pull pulling from http://localhost:$HGPORT1/ searching for changes @@ -166,7 +166,6 @@ > getpass.getpass = newgetpass > EOF -#if python243 $ hg id http://localhost:$HGPORT2/ abort: http authorization required for http://localhost:$HGPORT2/ [255] @@ -180,7 +179,6 @@ password: 5fed3813f7f5 $ hg id http://user:pass@localhost:$HGPORT2/ 5fed3813f7f5 -#endif $ echo '[auth]' >> .hg/hgrc $ echo 'l.schemes=http' >> .hg/hgrc $ echo 'l.prefix=lo' >> .hg/hgrc @@ -192,7 +190,6 @@ 5fed3813f7f5 $ hg id http://user@localhost:$HGPORT2/ 5fed3813f7f5 -#if python243 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1 streaming all changes 7 files to transfer, 916 bytes of data @@ -264,14 +261,13 @@ "GET /?cmd=branchmap HTTP/1.1" 200 - "GET /?cmd=stream_out HTTP/1.1" 401 - "GET /?cmd=stream_out HTTP/1.1" 200 - - "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d + "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phase%2Cbookmarks "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases "GET /?cmd=capabilities HTTP/1.1" 200 - - "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks - "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D - "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d + "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phase%2Cbookmarks + "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phase%2Cbookmarks "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases "GET /?cmd=capabilities HTTP/1.1" 200 - "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip @@ -288,10 +284,9 @@ "GET /?cmd=branchmap HTTP/1.1" 200 - "GET /?cmd=branchmap HTTP/1.1" 200 - "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks - "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524 + "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases -#endif $ cd .. clone of serve with repo in root and unserved subrepo (issue2970) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-https.t --- a/tests/test-https.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-https.t Sat Jul 18 17:32:38 2015 -0500 @@ -81,6 +81,53 @@ > EOT $ cat priv.pem pub-expired.pem > server-expired.pem +Client certificates created with: + openssl genrsa -aes128 -passout pass:1234 -out client-key.pem 512 + openssl rsa -in client-key.pem -passin pass:1234 -out client-key-decrypted.pem + printf '.\n.\n.\n.\n.\n.\nhg-client@localhost\n.\n.\n' | \ + openssl req -new -key client-key.pem -passin pass:1234 -out client-csr.pem + openssl x509 -req -days 9000 -in client-csr.pem -CA pub.pem -CAkey priv.pem \ + -set_serial 01 -out client-cert.pem + + $ cat << EOT > client-key.pem + > -----BEGIN RSA PRIVATE KEY----- + > Proc-Type: 4,ENCRYPTED + > DEK-Info: AES-128-CBC,C8B8F103A61A336FB0716D1C0F8BB2E8 + > + > JolMlCFjEW3q3JJjO9z99NJWeJbFgF5DpUOkfSCxH56hxxtZb9x++rBvBZkxX1bF + > BAIe+iI90+jdCLwxbILWuFcrJUaLC5WmO14XDKYVmr2eW9e4MiCYOlO0Q6a9rDFS + > jctRCfvubOXFHbBGLH8uKEMpXEkP7Lc60FiIukqjuQEivJjrQirVtZCGwyk3qUi7 + > Eyh4Lo63IKGu8T1Bkmn2kaMvFhu7nC/CQLBjSq0YYI1tmCOkVb/3tPrz8oqgDJp2 + > u7bLS3q0xDNZ52nVrKIoZC/UlRXGlPyzPpa70/jPIdfCbkwDaBpRVXc+62Pj2n5/ + > CnO2xaKwfOG6pDvanBhFD72vuBOkAYlFZPiEku4sc2WlNggsSWCPCIFwzmiHjKIl + > bWmdoTq3nb7sNfnBbV0OCa7fS1dFwCm4R1NC7ELENu0= + > -----END RSA PRIVATE KEY----- + > EOT + + $ cat << EOT > client-key-decrypted.pem + > -----BEGIN RSA PRIVATE KEY----- + > MIIBOgIBAAJBAJs4LS3glAYU92bg5kPgRPNW84ewB0fWJfAKccCp1ACHAdZPeaKb + > FCinVMYKAVbVqBkyrZ/Tyr8aSfMz4xO4+KsCAwEAAQJAeKDr25+Q6jkZHEbkLRP6 + > AfMtR+Ixhk6TJT24sbZKIC2V8KuJTDEvUhLU0CAr1nH79bDqiSsecOiVCr2HHyfT + > AQIhAM2C5rHbTs9R3PkywFEqq1gU3ztCnpiWglO7/cIkuGBhAiEAwVpMSAf77kop + > 4h/1kWsgMALQTJNsXd4CEUK4BOxvJIsCIQCbarVAKBQvoT81jfX27AfscsxnKnh5 + > +MjSvkanvdFZwQIgbbcTefwt1LV4trtz2SR0i0nNcOZmo40Kl0jIquKO3qkCIH01 + > mJHzZr3+jQqeIFtr5P+Xqi30DJxgrnEobbJ0KFjY + > -----END RSA PRIVATE KEY----- + > EOT + + $ cat << EOT > client-cert.pem + > -----BEGIN CERTIFICATE----- + > MIIBPjCB6QIBATANBgkqhkiG9w0BAQsFADAxMRIwEAYDVQQDDAlsb2NhbGhvc3Qx + > GzAZBgkqhkiG9w0BCQEWDGhnQGxvY2FsaG9zdDAeFw0xNTA1MDcwNjI5NDVaFw0z + > OTEyMjcwNjI5NDVaMCQxIjAgBgkqhkiG9w0BCQEWE2hnLWNsaWVudEBsb2NhbGhv + > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAmzgtLeCUBhT3ZuDmQ+BE81bzh7AH + > R9Yl8ApxwKnUAIcB1k95opsUKKdUxgoBVtWoGTKtn9PKvxpJ8zPjE7j4qwIDAQAB + > MA0GCSqGSIb3DQEBCwUAA0EAfBTqBG5pYhuGk+ZnyUufgS+d7Nk/sZAZjNdCAEj/ + > NFPo5fR1jM6jlEWoWbeg298+SkjV7tfO+2nt0otUFkdM6A== + > -----END CERTIFICATE----- + > EOT + $ hg init test $ cd test $ echo foo>foo @@ -154,7 +201,7 @@ $ cd copy-pull $ echo '[hooks]' >> .hg/hgrc - $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc $ hg pull $DISABLEOSXDUMMYCERT pulling from https://localhost:$HGPORT/ warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting) @@ -255,11 +302,11 @@ 5fed3813f7f5 HGPORT1 is reused below for tinyproxy tests. Kill that server. - $ "$TESTDIR/killdaemons.py" hg1.pid + $ killdaemons.py hg1.pid Prepare for connecting through proxy - $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log &1 & + $ tinyproxy.py $HGPORT1 localhost >proxy.log &1 & $ while [ ! -f proxy.pid ]; do sleep 0; done $ cat proxy.pid >> $DAEMON_PIDS @@ -297,3 +344,60 @@ pulling from https://localhost:$HGPORT2/ abort: error: *certificate verify failed* (glob) [255] + + + $ killdaemons.py hg0.pid + +#if sslcontext + +Start patched hgweb that requires client certificates: + + $ cat << EOT > reqclientcert.py + > import ssl + > from mercurial.hgweb import server + > class _httprequesthandlersslclientcert(server._httprequesthandlerssl): + > @staticmethod + > def preparehttpserver(httpserver, ssl_cert): + > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + > sslcontext.verify_mode = ssl.CERT_REQUIRED + > sslcontext.load_cert_chain(ssl_cert) + > # verify clients by server certificate + > sslcontext.load_verify_locations(ssl_cert) + > httpserver.socket = sslcontext.wrap_socket(httpserver.socket, + > server_side=True) + > server._httprequesthandlerssl = _httprequesthandlersslclientcert + > EOT + $ cd test + $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \ + > --config extensions.reqclientcert=../reqclientcert.py + $ cat ../hg0.pid >> $DAEMON_PIDS + $ cd .. + +without client certificate: + + $ P=`pwd` hg id https://localhost:$HGPORT/ + abort: error: *handshake failure* (glob) + [255] + +with client certificate: + + $ cat << EOT >> $HGRCPATH + > [auth] + > l.prefix = localhost + > l.cert = client-cert.pem + > l.key = client-key.pem + > EOT + + $ P=`pwd` hg id https://localhost:$HGPORT/ \ + > --config auth.l.key=client-key-decrypted.pem + 5fed3813f7f5 + + $ printf '1234\n' | env P=`pwd` hg id https://localhost:$HGPORT/ \ + > --config ui.interactive=True --config ui.nontty=True + passphrase for client-key.pem: 5fed3813f7f5 + + $ env P=`pwd` hg id https://localhost:$HGPORT/ + abort: error: * (glob) + [255] + +#endif diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hup.t --- a/tests/test-hup.t Fri Jul 03 18:10:58 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -#require serve fifo - -Test hangup signal in the middle of transaction - - $ hg init - $ mkfifo p - $ hg serve --stdio < p 1>out 2>&1 & - $ P=$! - -Do test while holding fifo open - - $ ( - > echo lock - > echo addchangegroup - > start=`date +%s` - > # 10 second seems much enough to let the server catch up - > deadline=`expr $start + 10` - > while [ ! -s .hg/store/journal ]; do - > sleep 0; - > if [ `date +%s` -gt $deadline ]; then - > echo "transaction did not start after 10 seconds" >&2; - > exit 1; - > fi - > done - > kill -HUP $P - > ) > p - - $ wait - $ cat out - 0 - 0 - adding changesets - transaction abort! - rollback completed - killed! - - $ ls -1d .hg/* .hg/store/* - .hg/00changelog.i - .hg/journal.bookmarks - .hg/journal.branch - .hg/journal.desc - .hg/journal.dirstate - .hg/requires - .hg/store - .hg/store/00changelog.i - .hg/store/00changelog.i.a - .hg/store/journal.phaseroots diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hybridencode.py --- a/tests/test-hybridencode.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hybridencode.py Sat Jul 18 17:32:38 2015 -0500 @@ -460,3 +460,9 @@ 'VWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxx' 'xxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwww' 'wwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i') + +print "paths outside data/ can be encoded" +show('metadata/dir/00manifest.i') +show('metadata/12345678/12345678/12345678/12345678/12345678/' + '12345678/12345678/12345678/12345678/12345678/12345678/' + '12345678/12345678/00manifest.i') diff -r 501c51d60792 -r 96a38d44ba09 tests/test-hybridencode.py.out --- a/tests/test-hybridencode.py.out Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-hybridencode.py.out Sat Jul 18 17:32:38 2015 -0500 @@ -491,3 +491,10 @@ A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTUVWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxxxxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i' B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx93352aa50377751d9e5ebdf52da1e6e69a6887a6.i' +paths outside data/ can be encoded +A = 'metadata/dir/00manifest.i' +B = 'metadata/dir/00manifest.i' + +A = 'metadata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manifest.i' +B = 'dh/ata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manife0a4da1f89aa2aa9eb0896eb451288419049781b4.i' + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-import.t --- a/tests/test-import.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-import.t Sat Jul 18 17:32:38 2015 -0500 @@ -492,6 +492,13 @@ $ echo line0 >> a $ hg ci -m brancha created new head + $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch + applying fuzzy-tip.patch + patching file a + Hunk #1 FAILED at 0 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + abort: patch failed to apply + [255] $ hg import --no-commit -v fuzzy-tip.patch applying fuzzy-tip.patch patching file a @@ -990,6 +997,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft $ hg diff --git -c tip diff --git a/lib/place-holder b/lib/place-holder @@ -1018,6 +1026,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft $ hg diff --git -c tip diff --git a/lib/place-holder b/lib/place-holder diff -r 501c51d60792 -r 96a38d44ba09 tests/test-init.t diff -r 501c51d60792 -r 96a38d44ba09 tests/test-issue1802.t --- a/tests/test-issue1802.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-issue1802.t Sat Jul 18 17:32:38 2015 -0500 @@ -58,7 +58,6 @@ branchmerge: True, force: False, partial: False ancestor: a03b0deabf2b, local: d6fa54f68ae1+, remote: 2d8bcf2dda39 a: update permissions -> e - updating: a 1/1 files (100.00%) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-issue522.t --- a/tests/test-issue522.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-issue522.t Sat Jul 18 17:32:38 2015 -0500 @@ -33,7 +33,6 @@ ancestor: bbd179dfa0a7, local: 71766447bdbb+, remote: 4d9e78aaceee foo: remote is newer -> g getting foo - updating: foo 1/1 files (100.00%) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-issue672.t --- a/tests/test-issue672.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-issue672.t Sat Jul 18 17:32:38 2015 -0500 @@ -36,10 +36,8 @@ ancestor: 81f4b099af3d, local: c64f439569a9+, remote: c12dcd37c90a 1: other deleted -> r removing 1 - updating: 1 1/2 files (50.00%) 1a: remote created -> g getting 1a - updating: 1a 2/2 files (100.00%) 2: remote unchanged -> k 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -68,7 +66,6 @@ ancestor: c64f439569a9, local: e327dca35ac8+, remote: 746e9549ea96 preserving 1a for resolve of 1a 1a: local copied/moved from 1 -> m - updating: 1a 1/1 files (100.00%) picked tool 'internal:merge' for 1a (binary False symlink False) merging 1a and 1 to 1a my 1a@e327dca35ac8+ other 1@746e9549ea96 ancestor 1@81f4b099af3d @@ -92,7 +89,6 @@ preserving 1 for resolve of 1a removing 1 1a: remote moved from 1 -> m - updating: 1a 1/1 files (100.00%) picked tool 'internal:merge' for 1a (binary False symlink False) merging 1 and 1a to 1a my 1a@746e9549ea96+ other 1a@e327dca35ac8 ancestor 1@81f4b099af3d diff -r 501c51d60792 -r 96a38d44ba09 tests/test-keyword.t --- a/tests/test-keyword.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-keyword.t Sat Jul 18 17:32:38 2015 -0500 @@ -980,14 +980,14 @@ $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw' + $ get-with-headers.py localhost:$HGPORT 'file/tip/a/?style=raw' 200 Script output follows expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw' + $ get-with-headers.py localhost:$HGPORT 'annotate/tip/a/?style=raw' 200 Script output follows @@ -999,7 +999,7 @@ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw' + $ get-with-headers.py localhost:$HGPORT 'rev/tip/?style=raw' 200 Script output follows @@ -1019,7 +1019,7 @@ +xxx $ +$Xinfo$ - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw' + $ get-with-headers.py localhost:$HGPORT 'diff/bb948857c743/a?style=raw' 200 Script output follows diff -r 501c51d60792 -r 96a38d44ba09 tests/test-known.t --- a/tests/test-known.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-known.t Sat Jul 18 17:32:38 2015 -0500 @@ -35,5 +35,5 @@ $ hg debugknown http://localhost:$HGPORT/ $ cat error.log - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-largefiles-misc.t --- a/tests/test-largefiles-misc.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-largefiles-misc.t Sat Jul 18 17:32:38 2015 -0500 @@ -228,6 +228,7 @@ branch: default commit: 1 subrepos update: (current) + phases: 2 draft $ hg st $ hg st -S A subrepo/large.txt @@ -245,6 +246,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft $ echo "rev 2" > subrepo/large.txt $ hg st -S M subrepo/large.txt @@ -254,6 +256,7 @@ branch: default commit: 1 subrepos update: (current) + phases: 3 draft $ hg ci -m "this commit should fail without -S" abort: uncommitted changes in subrepository 'subrepo' (use --subrepos for recursive commit) @@ -567,6 +570,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft largefiles: (no remote repo) check messages when there is no files to upload: @@ -581,6 +585,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft largefiles: (no files to upload) $ hg -R clone2 outgoing --large comparing with $TESTTMP/issue3651/src (glob) @@ -608,6 +613,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft largefiles: 1 entities for 1 files to upload $ hg -R clone2 outgoing --large comparing with $TESTTMP/issue3651/src (glob) @@ -643,6 +649,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft largefiles: 1 entities for 3 files to upload $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" comparing with $TESTTMP/issue3651/src (glob) @@ -656,7 +663,7 @@ $ hg -R clone2 cat -r 1 clone2/.hglf/b 89e6c98d92887913cadf06b2adb97f26cde4849b - $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true comparing with $TESTTMP/issue3651/src (glob) query 1; heads searching for changes @@ -692,6 +699,7 @@ branch: default commit: (clean) update: (current) + phases: 6 draft largefiles: 3 entities for 3 files to upload $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" comparing with $TESTTMP/issue3651/src (glob) @@ -710,7 +718,7 @@ c801c9cfe94400963fcb683246217d5db77f9a9a $ hg -R clone2 cat -r 4 clone2/.hglf/b 13f9ed0898e315bf59dc2973fec52037b6f441a2 - $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true comparing with $TESTTMP/issue3651/src (glob) query 1; heads searching for changes @@ -750,6 +758,7 @@ branch: default commit: (clean) update: (current) + phases: 6 draft largefiles: 2 entities for 1 files to upload $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" comparing with $TESTTMP/issue3651/src (glob) @@ -761,7 +770,7 @@ largefiles to upload (2 entities): b - $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true comparing with $TESTTMP/issue3651/src (glob) query 1; heads searching for changes @@ -975,7 +984,7 @@ > EOF $ hg -R pull-dst -q pull -u http://localhost:$HGPORT - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif Test overridden functions work correctly even for repos disabling @@ -999,10 +1008,6 @@ > EOF $ hg clone -q enabled-but-no-largefiles no-largefiles -(test rebasing implied by pull: precommit while rebasing unexpectedly -shows "normal3" as "?", because lfdirstate isn't yet written out at -that time) - $ echo normal2 > enabled-but-no-largefiles/normal2 $ hg -R enabled-but-no-largefiles add enabled-but-no-largefiles/normal2 $ hg -R enabled-but-no-largefiles commit -m '#1@enabled-but-no-largefiles' @@ -1017,7 +1022,7 @@ $ hg -R no-largefiles -q pull --rebase Invoking status precommit hook - ? normal3 + A normal3 (test reverting) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-largefiles-update.t --- a/tests/test-largefiles-update.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-largefiles-update.t Sat Jul 18 17:32:38 2015 -0500 @@ -20,6 +20,20 @@ $ echo 'large1 in #1' > large1 $ echo 'normal1 in #1' > normal1 $ hg commit -m '#1' + $ hg extdiff -r '.^' --config extensions.extdiff= + diff -Npru repo.0d9d9b8dc9a3/.hglf/large1 repo/.hglf/large1 + --- repo.0d9d9b8dc9a3/.hglf/large1 * (glob) + +++ repo/.hglf/large1 * (glob) + @@ -1 +1 @@ + -4669e532d5b2c093a78eca010077e708a071bb64 + +58e24f733a964da346e2407a2bee99d9001184f5 + diff -Npru repo.0d9d9b8dc9a3/normal1 repo/normal1 + --- repo.0d9d9b8dc9a3/normal1 * (glob) + +++ repo/normal1 * (glob) + @@ -1 +1 @@ + -normal1 + +normal1 in #1 + [1] $ hg update -q -C 0 $ echo 'large2 in #2' > large2 $ hg commit -m '#2' diff -r 501c51d60792 -r 96a38d44ba09 tests/test-largefiles-wireproto.t --- a/tests/test-largefiles-wireproto.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-largefiles-wireproto.t Sat Jul 18 17:32:38 2015 -0500 @@ -106,16 +106,19 @@ [255] used all HGPORTs, kill all daemons - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif vanilla clients locked out from largefiles ssh repos $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5 - abort: remote error: - - This repository uses the largefiles extension. - - Please enable it in your Mercurial config file. + remote: + remote: This repository uses the largefiles extension. + remote: + remote: Please enable it in your Mercurial config file. + remote: + remote: - + abort: remote error + (check previous remote output) [255] #if serve @@ -264,7 +267,7 @@ largefiles pulled on update - no server side problems: $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/ - $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache + $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache --config progress.debug=true resolving manifests branchmerge: False, force: False, partial: False ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936 @@ -288,6 +291,6 @@ $ rm -rf empty http-clone* used all HGPORTs, kill all daemons - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif diff -r 501c51d60792 -r 96a38d44ba09 tests/test-largefiles.t --- a/tests/test-largefiles.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-largefiles.t Sat Jul 18 17:32:38 2015 -0500 @@ -19,6 +19,10 @@ > usercache=${USERCACHE} > [hooks] > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status" + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True > EOF Create the repo with a couple of revisions of both large and normal @@ -67,6 +71,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft largefiles: (no remote repo) Commit preserved largefile contents. @@ -191,7 +196,7 @@ $ hg serve -d -p $HGPORT --pid-file ../hg.pid $ cat ../hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file/tip/?style=raw' 200 Script output follows @@ -200,7 +205,7 @@ -rw-r--r-- 9 normal3 - $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/sub/?style=raw' + $ get-with-headers.py 127.0.0.1:$HGPORT 'file/tip/sub/?style=raw' 200 Script output follows @@ -208,7 +213,7 @@ -rw-r--r-- 9 normal4 - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif Test archiving the various revisions. These hit corner cases known with @@ -999,6 +1004,7 @@ branch: default commit: (clean) update: 7 new changesets (update) + phases: 8 draft $ rm "${USERCACHE}"/* $ hg clone --all-largefiles -u 1 a a-clone1 @@ -1021,6 +1027,7 @@ branch: default commit: (clean) update: 6 new changesets (update) + phases: 8 draft $ rm "${USERCACHE}"/* $ hg clone --all-largefiles -U a a-clone-u @@ -1030,6 +1037,7 @@ branch: default commit: (clean) update: 8 new changesets (update) + phases: 8 draft Show computed destination directory: @@ -1094,18 +1102,18 @@ searching for changes all local heads known remotely 6 changesets found + uncompressed size of bundle content: + 1333 (changelog) + 1599 (manifests) + 254 .hglf/large1 + 564 .hglf/large3 + 572 .hglf/sub/large4 + 182 .hglf/sub2/large6 + 182 .hglf/sub2/large7 + 212 normal1 + 457 normal3 + 465 sub/normal4 adding changesets - uncompressed size of bundle content: - 1213 (changelog) - 1479 (manifests) - 234 .hglf/large1 - 504 .hglf/large3 - 512 .hglf/sub/large4 - 162 .hglf/sub2/large6 - 162 .hglf/sub2/large7 - 192 normal1 - 397 normal3 - 405 sub/normal4 adding manifests adding file changes added 6 changesets with 16 changes to 8 files diff -r 501c51d60792 -r 96a38d44ba09 tests/test-lfconvert.t --- a/tests/test-lfconvert.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-lfconvert.t Sat Jul 18 17:32:38 2015 -0500 @@ -89,7 +89,7 @@ normal $ cat sub/normal2 alsonormal - $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat + $ md5sum.py large sub/maybelarge.dat ec87a838931d4d5d2e94a04644788a55 large 1276481102f218c981e0324180bafd9f sub/maybelarge.dat @@ -115,7 +115,7 @@ $ echo blah >> normal3 $ echo blah >> sub/normal2 $ echo blah >> sub/maybelarge.dat - $ "$TESTDIR/md5sum.py" sub/maybelarge.dat + $ md5sum.py sub/maybelarge.dat 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat $ hg commit -A -m"add normal3, modify sub/*" adding normal3 @@ -193,7 +193,7 @@ $ cat stuff/normal2 alsonormal blah - $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat + $ md5sum.py stuff/maybelarge.dat 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat $ cat .hglf/stuff/maybelarge.dat 76236b6a2c6102826c61af4297dd738fb3b1de38 @@ -226,6 +226,7 @@ $ hg commit -m "add anotherlarge (should be a largefile)" $ cat .hglf/anotherlarge 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3 + $ hg tag mytag $ cd .. round-trip: converting back to a normal (non-largefiles) repo with @@ -233,25 +234,30 @@ $ cd largefiles-repo $ hg lfconvert --to-normal . ../normal-repo initializing destination ../normal-repo + 0 additional largefiles cached + scanning source... + sorting... + converting... + 7 add large, normal1 + 6 add sub/* + 5 rename sub/ to stuff/ + 4 add normal3, modify sub/* + 3 remove large, normal3 + 2 merge + 1 add anotherlarge (should be a largefile) + 0 Added tag mytag for changeset abacddda7028 $ cd ../normal-repo $ cat >> .hg/hgrc < [extensions] > largefiles = ! > EOF -# Hmmm: the changeset ID for rev 5 is different from the original -# normal repo (../bigfile-repo), because the changelog filelist -# differs between the two incarnations of rev 5: this repo includes -# 'large' in the list, but ../bigfile-repo does not. Since rev 5 -# removes 'large' relative to the first parent in both repos, it seems -# to me that lfconvert is doing a *better* job than -# "hg remove" + "hg merge" + "hg commit". -# $ hg -R ../bigfile-repo debugdata -c 5 -# $ hg debugdata -c 5 $ hg log -G --template "{rev}:{node|short} {desc|firstline}\n" - o 6:1635824e6f59 add anotherlarge (should be a largefile) + o 7:b5fedc110b9d Added tag mytag for changeset 867ab992ecf4 | - o 5:7215f8deeaaf merge + o 6:867ab992ecf4 add anotherlarge (should be a largefile) + | + o 5:4884f215abda merge |\ | o 4:7285f817b77e remove large, normal3 | | @@ -264,8 +270,9 @@ o 0:117b8328f97a add large, normal1 $ hg update - 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg locate + .hgtags anotherlarge normal1 stuff/maybelarge.dat @@ -284,15 +291,18 @@ scanning source... sorting... converting... - 6 add large, normal1 - 5 add sub/* - 4 rename sub/ to stuff/ - 3 add normal3, modify sub/* - 2 remove large, normal3 - 1 merge - 0 add anotherlarge (should be a largefile) + 7 add large, normal1 + 6 add sub/* + 5 rename sub/ to stuff/ + 4 add normal3, modify sub/* + 3 remove large, normal3 + 2 merge + 1 add anotherlarge (should be a largefile) + 0 Added tag mytag for changeset abacddda7028 $ hg -R largefiles-repo-hg log -G --template "{rev}:{node|short} {desc|firstline}\n" + o 7:2f08f66459b7 Added tag mytag for changeset 17126745edfd + | o 6:17126745edfd add anotherlarge (should be a largefile) | o 5:9cc5aa7204f0 merge @@ -310,12 +320,20 @@ Verify will fail (for now) if the usercache is purged before converting, since largefiles are not cached in the converted repo's local store by the conversion process. + $ cd largefiles-repo-hg + $ cat >> .hg/hgrc < [experimental] + > evolution=createmarkers + > EOF + $ hg debugobsolete `hg log -r tip -T "{node}"` + $ cd .. + $ hg -R largefiles-repo-hg verify --large --lfa checking changesets checking manifests crosschecking files in changesets and manifests checking files - 8 files, 7 changesets, 12 total revisions + 9 files, 8 changesets, 13 total revisions searching 7 changesets for largefiles changeset 0:d4892ec57ce2: large references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/2e000fa7e85759c7f4c254d4d9c33ef481e459a7 (glob) changeset 1:334e5237836d: sub/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c (glob) @@ -336,6 +354,18 @@ $ rm -f "${USERCACHE}"/* $ hg lfconvert --to-normal issue3519 normalized3519 initializing destination normalized3519 + 4 additional largefiles cached + scanning source... + sorting... + converting... + 7 add large, normal1 + 6 add sub/* + 5 rename sub/ to stuff/ + 4 add normal3, modify sub/* + 3 remove large, normal3 + 2 merge + 1 add anotherlarge (should be a largefile) + 0 Added tag mytag for changeset abacddda7028 Ensure the abort message is useful if a largefile is entirely unavailable $ rm -rf normalized3519 @@ -344,8 +374,20 @@ $ rm largefiles-repo/.hg/largefiles/* $ hg lfconvert --to-normal issue3519 normalized3519 initializing destination normalized3519 + anotherlarge: largefile 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3 not available from file:/*/$TESTTMP/largefiles-repo (glob) + stuff/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob) + stuff/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob) + sub/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob) large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob) - abort: missing largefile 'large' from revision d4892ec57ce212905215fad1d9018f56b99202ad + sub/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob) + large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob) + stuff/maybelarge.dat: largefile 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c not available from file:/*/$TESTTMP/largefiles-repo (glob) + large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob) + sub/maybelarge.dat: largefile 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c not available from file:/*/$TESTTMP/largefiles-repo (glob) + large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob) + 0 additional largefiles cached + 11 largefiles failed to download + abort: all largefiles must be present locally [255] diff -r 501c51d60792 -r 96a38d44ba09 tests/test-log.t --- a/tests/test-log.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-log.t Sat Jul 18 17:32:38 2015 -0500 @@ -148,7 +148,7 @@ $ hg log -f -l1 --style something abort: style 'something' not found - (available styles: bisect, changelog, compact, default, phases, xml) + (available styles: bisect, changelog, compact, default, phases, status, xml) [255] -f, phases style @@ -1626,15 +1626,16 @@ working-directory revision requires special treatment $ hg log -r 'wdir()' - changeset: 0:65624cd9070a+ + changeset: 2147483647:ffffffffffff + parent: 0:65624cd9070a user: test date: [A-Za-z0-9:+ ]+ (re) $ hg log -r 'wdir()' -q - 0:65624cd9070a+ + 2147483647:ffffffffffff $ hg log -r 'wdir()' --debug - changeset: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08+ + changeset: 2147483647:ffffffffffffffffffffffffffffffffffffffff phase: draft parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08 parent: -1:0000000000000000000000000000000000000000 @@ -1653,7 +1654,7 @@ "date": [*, 0], (glob) "desc": "", "bookmarks": [], - "tags": ["tip"], + "tags": [], "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"] } ] @@ -1677,7 +1678,7 @@ "date": [*, 0], (glob) "desc": "", "bookmarks": [], - "tags": ["tip"], + "tags": [], "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"], "manifest": null, "extra": {"branch": "default"}, diff -r 501c51d60792 -r 96a38d44ba09 tests/test-manifest.py --- a/tests/test-manifest.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-manifest.py Sat Jul 18 17:32:38 2015 -0500 @@ -325,21 +325,21 @@ try: self.parsemanifest(backwards) self.fail('Should have raised ValueError') - except ValueError, v: + except ValueError as v: self.assertIn('Manifest lines not in sorted order.', str(v)) def testNoTerminalNewline(self): try: self.parsemanifest(A_SHORT_MANIFEST + 'wat') self.fail('Should have raised ValueError') - except ValueError, v: + except ValueError as v: self.assertIn('Manifest did not end in a newline.', str(v)) def testNoNewLineAtAll(self): try: self.parsemanifest('wat') self.fail('Should have raised ValueError') - except ValueError, v: + except ValueError as v: self.assertIn('Manifest did not end in a newline.', str(v)) def testHugeManifest(self): diff -r 501c51d60792 -r 96a38d44ba09 tests/test-manifestv2.t --- a/tests/test-manifestv2.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-manifestv2.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,4 +1,69 @@ -Check that entry is added to .hg/requires +Create repo with old manifest + + $ hg init existing + $ cd existing + $ echo footext > foo + $ hg add foo + $ hg commit -m initial + +We're using v1, so no manifestv2 entry is in requires yet. + + $ grep manifestv2 .hg/requires + [1] + +Let's clone this with manifestv2 enabled to switch to the new format for +future commits. + + $ cd .. + $ hg clone --pull existing new --config experimental.manifestv2=1 + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd new + +Check that entry was added to .hg/requires. + + $ grep manifestv2 .hg/requires + manifestv2 + +Make a new commit. + + $ echo newfootext > foo + $ hg commit -m new + +Check that the manifest actually switched to v2. + + $ hg debugdata -m 0 + foo\x0021e958b1dca695a60ee2e9cf151753204ee0f9e9 (esc) + + $ hg debugdata -m 1 + \x00 (esc) + \x00foo\x00 (esc) + I\xab\x7f\xb8(\x83\xcas\x15\x9d\xc2\xd3\xd3:5\x08\xbad5_ (esc) + +Check that manifestv2 is used if the requirement is present, even if it's +disabled in the config. + + $ echo newerfootext > foo + $ hg --config experimental.manifestv2=False commit -m newer + + $ hg debugdata -m 2 + \x00 (esc) + \x00foo\x00 (esc) + \xa6\xb1\xfb\xef]\x91\xa1\x19`\xf3.#\x90S\xf8\x06 \xe2\x19\x00 (esc) + +Check that we can still read v1 manifests. + + $ hg files -r 0 + foo + + $ cd .. + +Check that entry is added to .hg/requires on repo creation $ hg --config experimental.manifestv2=True init repo $ cd repo diff -r 501c51d60792 -r 96a38d44ba09 tests/test-merge-commit.t --- a/tests/test-merge-commit.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-merge-commit.t Sat Jul 18 17:32:38 2015 -0500 @@ -73,7 +73,6 @@ ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28 preserving bar for resolve of bar bar: versions differ -> m - updating: bar 1/1 files (100.00%) picked tool 'internal:merge' for bar (binary False symlink False) merging bar my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9 @@ -160,7 +159,6 @@ ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0 preserving bar for resolve of bar bar: versions differ -> m - updating: bar 1/1 files (100.00%) picked tool 'internal:merge' for bar (binary False symlink False) merging bar my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-merge-criss-cross.t --- a/tests/test-merge-criss-cross.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-merge-criss-cross.t Sat Jul 18 17:32:38 2015 -0500 @@ -82,9 +82,7 @@ preserving f2 for resolve of f2 f1: remote is newer -> g getting f1 - updating: f1 1/2 files (50.00%) f2: versions differ -> m - updating: f2 2/2 files (100.00%) picked tool 'internal:dump' for f2 (binary False symlink False) merging f2 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c @@ -151,7 +149,6 @@ f1: remote is newer -> g getting f1 - updating: f1 1/1 files (100.00%) f2: remote unchanged -> k 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -194,7 +191,6 @@ f2: remote is newer -> g getting f2 - updating: f2 1/1 files (100.00%) f1: remote unchanged -> k 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -259,7 +255,6 @@ f1: remote is newer -> g getting f1 - updating: f1 1/1 files (100.00%) f2: remote unchanged -> k 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-merge-tools.t --- a/tests/test-merge-tools.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-merge-tools.t Sat Jul 18 17:32:38 2015 -0500 @@ -601,6 +601,17 @@ update is a merge ... +(this also tests that files reverted with '--rev REV' are treated as +"modified", even if none of mode, size and timestamp of them isn't +changed on the filesystem (see also issue4583)) + + $ cat >> $HGRCPATH < [fakedirstatewritetime] + > # emulate invoking dirstate.write() via repo.status() + > # at 2000-01-01 00:00 + > fakenow = 200001010000 + > EOF + $ beforemerge [merge-tools] false.whatever= @@ -611,8 +622,16 @@ $ f -s f f: size=17 $ touch -t 200001010000 f - $ hg status f + $ hg debugrebuildstate + $ cat >> $HGRCPATH < [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > EOF $ hg revert -q -r 1 . + $ cat >> $HGRCPATH < [extensions] + > fakedirstatewritetime = ! + > EOF $ f -s f f: size=17 $ touch -t 200001010000 f @@ -646,8 +665,16 @@ $ f -s f f: size=17 $ touch -t 200001010000 f - $ hg status f + $ hg debugrebuildstate + $ cat >> $HGRCPATH < [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > EOF $ hg revert -q -r 1 . + $ cat >> $HGRCPATH < [extensions] + > fakedirstatewritetime = ! + > EOF $ f -s f f: size=17 $ touch -t 200001010000 f diff -r 501c51d60792 -r 96a38d44ba09 tests/test-merge-types.t --- a/tests/test-merge-types.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-merge-types.t Sat Jul 18 17:32:38 2015 -0500 @@ -36,7 +36,6 @@ ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c preserving a for resolve of a a: versions differ -> m - updating: a 1/1 files (100.00%) picked tool 'internal:merge' for a (binary False symlink True) merging a my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da @@ -70,7 +69,6 @@ ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a a: versions differ -> m - updating: a 1/1 files (100.00%) picked tool 'internal:merge' for a (binary False symlink True) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da @@ -104,7 +102,6 @@ ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f preserving a for resolve of a a: versions differ -> m - updating: a 1/1 files (100.00%) (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re) picked tool ':prompt' for a (binary False symlink True) no tool found to merge a diff -r 501c51d60792 -r 96a38d44ba09 tests/test-merge1.t --- a/tests/test-merge1.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-merge1.t Sat Jul 18 17:32:38 2015 -0500 @@ -40,6 +40,7 @@ branch: default commit: (interrupted update) update: 1 new changesets (update) + phases: 2 draft $ rmdir b $ hg up 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -49,6 +50,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft Prepare a basic merge @@ -204,4 +206,91 @@ $ hg revert -r -2 b $ hg up -q -- -2 +Test that updated files are treated as "modified", when +'merge.update()' is aborted before 'merge.recordupdates()' (= parents +aren't changed), even if none of mode, size and timestamp of them +isn't changed on the filesystem (see also issue4583). + + $ cat > $TESTTMP/abort.py < # emulate aborting before "recordupdates()". in this case, files + > # are changed without updating dirstate + > from mercurial import extensions, merge, util + > def applyupdates(orig, *args, **kwargs): + > orig(*args, **kwargs) + > raise util.Abort('intentional aborting') + > def extsetup(ui): + > extensions.wrapfunction(merge, "applyupdates", applyupdates) + > EOF + + $ cat >> .hg/hgrc < [fakedirstatewritetime] + > # emulate invoking dirstate.write() via repo.status() + > # at 2000-01-01 00:00 + > fakenow = 200001010000 + > EOF + +(file gotten from other revision) + + $ hg update -q -C 2 + $ echo 'THIS IS FILE B5' > b + $ hg commit -m 'commit #5' + + $ hg update -q -C 3 + $ cat b + This is file b1 + $ touch -t 200001010000 b + $ hg debugrebuildstate + + $ cat >> .hg/hgrc < [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > abort = $TESTTMP/abort.py + > EOF + $ hg merge 5 + abort: intentional aborting + [255] + $ cat >> .hg/hgrc < [extensions] + > fakedirstatewritetime = ! + > abort = ! + > EOF + + $ cat b + THIS IS FILE B5 + $ touch -t 200001010000 b + $ hg status -A b + M b + +(file merged from other revision) + + $ hg update -q -C 3 + $ echo 'this is file b6' > b + $ hg commit -m 'commit #6' + created new head + + $ cat b + this is file b6 + $ touch -t 200001010000 b + $ hg debugrebuildstate + + $ cat >> .hg/hgrc < [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > abort = $TESTTMP/abort.py + > EOF + $ hg merge --tool internal:other 5 + abort: intentional aborting + [255] + $ cat >> .hg/hgrc < [extensions] + > fakedirstatewritetime = ! + > abort = ! + > EOF + + $ cat b + THIS IS FILE B5 + $ touch -t 200001010000 b + $ hg status -A b + M b + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-merge7.t --- a/tests/test-merge7.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-merge7.t Sat Jul 18 17:32:38 2015 -0500 @@ -86,7 +86,6 @@ ancestor: 96b70246a118, local: 50c3a7e29886+, remote: 40d11a4173a8 preserving test.txt for resolve of test.txt test.txt: versions differ -> m - updating: test.txt 1/1 files (100.00%) picked tool 'internal:merge' for test.txt (binary False symlink False) merging test.txt my test.txt@50c3a7e29886+ other test.txt@40d11a4173a8 ancestor test.txt@96b70246a118 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-module-imports.t --- a/tests/test-module-imports.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-module-imports.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,9 +1,5 @@ #require test-repo -This code uses the ast module, which was new in 2.6, so we'll skip -this test on anything earlier. - $ $PYTHON -c 'import sys ; assert sys.version_info >= (2, 6)' || exit 80 - $ import_checker="$TESTDIR"/../contrib/import-checker.py Run the doctests from the import checker, and make sure @@ -12,6 +8,101 @@ $ export TERM $ python -m doctest $import_checker +Run additional tests for the import checker + + $ mkdir testpackage + + $ cat > testpackage/multiple.py << EOF + > from __future__ import absolute_import + > import os, sys + > EOF + + $ cat > testpackage/unsorted.py << EOF + > from __future__ import absolute_import + > import sys + > import os + > EOF + + $ cat > testpackage/stdafterlocal.py << EOF + > from __future__ import absolute_import + > from . import unsorted + > import os + > EOF + + $ cat > testpackage/requirerelative.py << EOF + > from __future__ import absolute_import + > import testpackage.unsorted + > EOF + + $ cat > testpackage/importalias.py << EOF + > from __future__ import absolute_import + > import ui + > EOF + + $ cat > testpackage/relativestdlib.py << EOF + > from __future__ import absolute_import + > from .. import os + > EOF + + $ cat > testpackage/symbolimport.py << EOF + > from __future__ import absolute_import + > from .unsorted import foo + > EOF + + $ cat > testpackage/latesymbolimport.py << EOF + > from __future__ import absolute_import + > from . import unsorted + > from mercurial.node import hex + > EOF + + $ cat > testpackage/multiplegroups.py << EOF + > from __future__ import absolute_import + > from . import unsorted + > from . import more + > EOF + + $ mkdir testpackage/subpackage + $ cat > testpackage/subpackage/levelpriority.py << EOF + > from __future__ import absolute_import + > from . import foo + > from .. import parent + > EOF + + $ cat > testpackage/sortedentries.py << EOF + > from __future__ import absolute_import + > from . import ( + > foo, + > bar, + > ) + > EOF + + $ cat > testpackage/importfromalias.py << EOF + > from __future__ import absolute_import + > from . import ui + > EOF + + $ cat > testpackage/importfromrelative.py << EOF + > from __future__ import absolute_import + > from testpackage.unsorted import foo + > EOF + + $ python "$import_checker" testpackage/*.py testpackage/subpackage/*.py + testpackage/importalias.py ui module must be "as" aliased to uimod + testpackage/importfromalias.py ui from testpackage must be "as" aliased to uimod + testpackage/importfromrelative.py import should be relative: testpackage.unsorted + testpackage/importfromrelative.py direct symbol import from testpackage.unsorted + testpackage/latesymbolimport.py symbol import follows non-symbol import: mercurial.node + testpackage/multiple.py multiple imported names: os, sys + testpackage/multiplegroups.py multiple "from . import" statements + testpackage/relativestdlib.py relative import of stdlib module + testpackage/requirerelative.py import should be relative: testpackage.unsorted + testpackage/sortedentries.py imports from testpackage not lexically sorted: bar < foo + testpackage/stdafterlocal.py stdlib import follows local import: os + testpackage/subpackage/levelpriority.py higher-level import should come first: testpackage + testpackage/symbolimport.py direct symbol import from testpackage.unsorted + testpackage/unsorted.py imports not lexically sorted: os < sys + [1] + $ cd "$TESTDIR"/.. There are a handful of cases here that require renaming a module so it @@ -20,10 +111,7 @@ hidden by deduplication algorithm in the cycle detector, so fixing these may expose other cycles. - $ hg locate 'mercurial/**.py' | sed 's-\\-/-g' | xargs python "$import_checker" - mercurial/crecord.py mixed imports - stdlib: fcntl, termios - relative: curses + $ hg locate 'mercurial/**.py' 'hgext/**.py' | sed 's-\\-/-g' | python "$import_checker" - mercurial/dispatch.py mixed imports stdlib: commands relative: error, extensions, fancyopts, hg, hook, util @@ -38,5 +126,8 @@ relative: config, error, templatefilters, templatekw, util mercurial/ui.py mixed imports stdlib: formatter - relative: config, error, scmutil, util + relative: config, error, progress, scmutil, util Import cycle: mercurial.cmdutil -> mercurial.context -> mercurial.subrepo -> mercurial.cmdutil + Import cycle: hgext.largefiles.basestore -> hgext.largefiles.localstore -> hgext.largefiles.basestore + Import cycle: mercurial.commands -> mercurial.commandserver -> mercurial.dispatch -> mercurial.commands + [1] diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-qclone-http.t --- a/tests/test-mq-qclone-http.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-qclone-http.t Sat Jul 18 17:32:38 2015 -0500 @@ -34,7 +34,7 @@ $ hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf collections.conf \ > -A access-paths.log -E error-paths-1.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=raw' + $ get-with-headers.py localhost:$HGPORT '?style=raw' 200 Script output follows @@ -73,7 +73,7 @@ $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf collections1.conf \ > -A access-paths.log -E error-paths-1.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw' + $ get-with-headers.py localhost:$HGPORT1 '?style=raw' 200 Script output follows @@ -112,7 +112,7 @@ $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --webdir-conf collections2.conf \ > -A access-paths.log -E error-paths-1.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '?style=raw' + $ get-with-headers.py localhost:$HGPORT2 '?style=raw' 200 Script output follows @@ -152,5 +152,5 @@ $ hg --cwd d log --mq --template '{rev} {desc|firstline}\n' 0 b.patch - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-qimport.t --- a/tests/test-mq-qimport.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-qimport.t Sat Jul 18 17:32:38 2015 -0500 @@ -289,4 +289,4 @@ $ cd .. - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-qnew.t --- a/tests/test-mq-qnew.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-qnew.t Sat Jul 18 17:32:38 2015 -0500 @@ -21,6 +21,7 @@ > hg qnew .mqfoo > hg qnew 'foo#bar' > hg qnew 'foo:bar' + > hg qnew "`echo foo; echo bar`" > > hg qinit -c > @@ -108,8 +109,9 @@ abort: ".." cannot be used as the name of a patch abort: patch name cannot begin with ".hg" abort: patch name cannot begin with ".mq" - abort: "#" cannot be used in the name of a patch - abort: ":" cannot be used in the name of a patch + abort: '#' cannot be used in the name of a patch + abort: ':' cannot be used in the name of a patch + abort: '\n' cannot be used in the name of a patch % qnew with name containing slash abort: path ends in directory separator: foo/ (glob) abort: "foo" already exists as a directory @@ -176,8 +178,9 @@ abort: ".." cannot be used as the name of a patch abort: patch name cannot begin with ".hg" abort: patch name cannot begin with ".mq" - abort: "#" cannot be used in the name of a patch - abort: ":" cannot be used in the name of a patch + abort: '#' cannot be used in the name of a patch + abort: ':' cannot be used in the name of a patch + abort: '\n' cannot be used in the name of a patch % qnew with name containing slash abort: path ends in directory separator: foo/ (glob) abort: "foo" already exists as a directory diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-qpush-fail.t --- a/tests/test-mq-qpush-fail.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-qpush-fail.t Sat Jul 18 17:32:38 2015 -0500 @@ -34,7 +34,23 @@ $ $PYTHON -c 'print "\xe9"' > message $ cat .hg/patches/bad-patch >> message $ mv message .hg/patches/bad-patch - $ hg qpush -a && echo 'qpush succeeded?!' + $ cat > $TESTTMP/wrapplayback.py < import os + > from mercurial import extensions, transaction + > def wrapplayback(orig, + > journal, report, opener, vfsmap, entries, backupentries, + > unlink=True): + > orig(journal, report, opener, vfsmap, entries, backupentries, unlink) + > # Touching files truncated at "transaction.abort" causes + > # forcible re-loading invalidated filecache properties + > # (including repo.changelog) + > for f, o, _ignore in entries: + > if o or not unlink: + > os.utime(opener.join(f), (0.0, 0.0)) + > def extsetup(ui): + > extensions.wrapfunction(transaction, '_playback', wrapplayback) + > EOF + $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py && echo 'qpush succeeded?!' applying patch1 applying patch2 applying bad-patch diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-qrefresh-interactive.t --- a/tests/test-mq-qrefresh-interactive.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-qrefresh-interactive.t Sat Jul 18 17:32:38 2015 -0500 @@ -123,6 +123,12 @@ $ hg add 1.txt 2.txt dir/a.txt $ hg commit -m aaa + $ hg qrecord --config ui.interactive=false patch + abort: running non-interactively, use qnew instead + [255] + $ hg qnew -i --config ui.interactive=false patch + abort: running non-interactively + [255] $ hg qnew -d '0 0' patch Changing files @@ -171,6 +177,9 @@ partial qrefresh + $ hg qrefresh -i --config ui.interactive=false + abort: running non-interactively + [255] $ hg qrefresh -i -d '0 0' < y > y diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-safety.t --- a/tests/test-mq-safety.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-safety.t Sat Jul 18 17:32:38 2015 -0500 @@ -25,17 +25,17 @@ $ hg phase --public qbase $ echo babar >> foo $ hg qref - abort: cannot refresh immutable revision + abort: cannot refresh public revision (see "hg help phases" for details) [255] $ hg revert -a reverting foo $ hg qpop - abort: popping would remove an immutable revision + abort: popping would remove a public revision (see "hg help phases" for details) [255] $ hg qfold bar - abort: cannot refresh immutable revision + abort: cannot refresh public revision (see "hg help phases" for details) [255] $ hg revert -a diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-subrepo.t --- a/tests/test-mq-subrepo.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-subrepo.t Sat Jul 18 17:32:38 2015 -0500 @@ -106,6 +106,7 @@ [255] % update substate when adding .hgsub w/clean updated subrepo A .hgsub + A sub/a % qnew -X path:no-effect -m0 0.diff path sub source sub @@ -121,6 +122,7 @@ [255] % update substate when modifying .hgsub w/clean updated subrepo M .hgsub + A sub2/a % qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff path sub source sub @@ -165,6 +167,7 @@ [255] % update substate when adding .hgsub w/clean updated subrepo A .hgsub + A sub/a % qrefresh path sub source sub @@ -181,6 +184,7 @@ [255] % update substate when modifying .hgsub w/clean updated subrepo M .hgsub + A sub2/a % qrefresh path sub source sub @@ -304,6 +308,7 @@ [255] % update substate when adding .hgsub w/clean updated subrepo A .hgsub + A sub/a % qrecord --config ui.interactive=1 -m0 0.diff diff --git a/.hgsub b/.hgsub new file mode 100644 @@ -339,6 +344,7 @@ [255] % update substate when modifying .hgsub w/clean updated subrepo M .hgsub + A sub2/a % qrecord --config ui.interactive=1 -m1 1.diff diff --git a/.hgsub b/.hgsub 1 hunks, 1 lines changed diff -r 501c51d60792 -r 96a38d44ba09 tests/test-mq-symlinks.t --- a/tests/test-mq-symlinks.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-mq-symlinks.t Sat Jul 18 17:32:38 2015 -0500 @@ -11,7 +11,7 @@ $ echo ccc > c $ hg add a b c $ hg qrefresh - $ "$TESTDIR/readlink.py" a + $ readlink.py a a -> a not a symlink @@ -21,7 +21,7 @@ $ rm a $ ln -s b a $ hg qrefresh --git - $ "$TESTDIR/readlink.py" a + $ readlink.py a a -> b $ hg qpop @@ -30,7 +30,7 @@ $ hg qpush applying symlink.patch now at: symlink.patch - $ "$TESTDIR/readlink.py" a + $ readlink.py a a -> b @@ -39,7 +39,7 @@ $ rm a $ ln -s c a $ hg qnew --git -f updatelink - $ "$TESTDIR/readlink.py" a + $ readlink.py a a -> c $ hg qpop popping updatelink @@ -52,7 +52,7 @@ committing manifest committing changelog now at: updatelink - $ "$TESTDIR/readlink.py" a + $ readlink.py a a -> c $ hg st @@ -107,5 +107,5 @@ $ hg qpush applying movelink now at: movelink - $ "$TESTDIR/readlink.py" linkb + $ readlink.py linkb linkb -> linkb diff -r 501c51d60792 -r 96a38d44ba09 tests/test-newbranch.t --- a/tests/test-newbranch.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-newbranch.t Sat Jul 18 17:32:38 2015 -0500 @@ -25,7 +25,6 @@ $ hg ci -m "add branch name" $ hg branch bar marked working directory as branch bar - (branches are permanent and global, did you want a bookmark?) $ hg ci -m "change branch name" Branch shadowing: @@ -37,7 +36,6 @@ $ hg branch -f default marked working directory as branch default - (branches are permanent and global, did you want a bookmark?) $ hg ci -m "clear branch name" created new head @@ -67,11 +65,9 @@ $ hg branch -f bar marked working directory as branch bar - (branches are permanent and global, did you want a bookmark?) $ hg branch foo marked working directory as branch foo - (branches are permanent and global, did you want a bookmark?) $ echo bleah > a $ hg ci -m "modify a branch" @@ -94,13 +90,11 @@ $ hg branch default marked working directory as branch default - (branches are permanent and global, did you want a bookmark?) set (first) parent branch as branch name $ hg branch foo marked working directory as branch foo - (branches are permanent and global, did you want a bookmark?) $ hg ci -m "merge" @@ -215,7 +209,6 @@ $ hg branch foobar marked working directory as branch foobar - (branches are permanent and global, did you want a bookmark?) $ hg up abort: branch foobar not found @@ -225,7 +218,6 @@ $ hg branch ff marked working directory as branch ff - (branches are permanent and global, did you want a bookmark?) $ echo ff > ff $ hg ci -Am'fast forward' diff -r 501c51d60792 -r 96a38d44ba09 tests/test-obsolete.t --- a/tests/test-obsolete.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-obsolete.t Sat Jul 18 17:32:38 2015 -0500 @@ -4,6 +4,10 @@ > publish=false > [ui] > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n" + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True > EOF $ mkcommit() { > echo "$1" > "$1" @@ -164,6 +168,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft remote: 3 outgoing $ hg summary --remote --hidden @@ -172,6 +177,7 @@ branch: default commit: (clean) update: 3 new changesets, 4 branch heads (merge) + phases: 6 draft remote: 3 outgoing check that various commands work well with filtering @@ -305,34 +311,35 @@ adding manifests adding file changes added 4 changesets with 4 changes to 4 files (+1 heads) + 5 new obsolescence markers (run 'hg heads' to see heads, 'hg merge' to merge) $ hg debugobsolete + 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} - cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} + 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} - 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} - 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} + cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} Rollback//Transaction support $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb $ hg debugobsolete + 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} - cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} + 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} - 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} - 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} + cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 (Thu Jan 01 00:22:20 1970 +0000) {'user': 'test'} $ hg rollback -n repository tip rolled back to revision 3 (undo debugobsolete) $ hg rollback repository tip rolled back to revision 3 (undo debugobsolete) $ hg debugobsolete + 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} - cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} + 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} - 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} - 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} + cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} $ cd .. @@ -346,6 +353,7 @@ adding manifests adding file changes added 4 changesets with 4 changes to 4 files (+1 heads) + 5 new obsolescence markers $ hg -R tmpd debugobsolete | sort 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} @@ -408,14 +416,15 @@ adding manifests adding file changes added 4 changesets with 4 changes to 4 files (+1 heads) + 5 new obsolescence markers (run 'hg heads' to see heads, 'hg merge' to merge) $ hg debugobsolete 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} + 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} - cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} + 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} - 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} - 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} + cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} On push @@ -424,13 +433,14 @@ pushing to ../tmpc searching for changes no changes found + 1 new obsolescence markers [1] $ hg -R ../tmpc debugobsolete + 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} - cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} + 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} - 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} - 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} + cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} detect outgoing obsolete and unstable @@ -506,6 +516,7 @@ adding manifests adding file changes added 6 changesets with 6 changes to 6 files (+1 heads) + 7 new obsolescence markers no warning displayed @@ -545,6 +556,7 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) + 1 new obsolescence markers test relevance computation --------------------------------------- @@ -574,11 +586,11 @@ $ hg debugobsolete 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} + 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} - cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} + 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} - 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'} - 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'} + cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'} 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'} cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob) @@ -646,37 +658,37 @@ check changelog view - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'shortlog/' + $ get-with-headers.py --headeronly localhost:$HGPORT 'shortlog/' 200 Script output follows check graph view - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'graph' + $ get-with-headers.py --headeronly localhost:$HGPORT 'graph' 200 Script output follows check filelog view - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar' + $ get-with-headers.py --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar' 200 Script output follows - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/68' + $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/68' 200 Script output follows - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67' + $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67' 404 Not Found [1] check that web.view config option: - $ "$TESTDIR/killdaemons.py" hg.pid + $ killdaemons.py hg.pid $ cat >> .hg/hgrc << EOF > [web] > view=all > EOF $ wait $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67' + $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67' 200 Script output follows - $ "$TESTDIR/killdaemons.py" hg.pid + $ killdaemons.py hg.pid Checking _enable=False warning if obsolete marker exists @@ -744,7 +756,7 @@ no changes found [1] - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif @@ -762,6 +774,7 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files + 2 new obsolescence markers $ hg out ../repo-issue3814 comparing with ../repo-issue3814 searching for changes @@ -872,15 +885,84 @@ $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/1' + $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/1' 404 Not Found [1] - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'file/tip/bar' + $ get-with-headers.py --headeronly localhost:$HGPORT 'file/tip/bar' 200 Script output follows - $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'annotate/tip/bar' + $ get-with-headers.py --headeronly localhost:$HGPORT 'annotate/tip/bar' 200 Script output follows - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif +Test heads computation on pending index changes with obsolescence markers + $ cd .. + $ cat >$TESTTMP/test_extension.py << EOF + > from mercurial import cmdutil + > from mercurial.i18n import _ + > + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > @command("amendtransient",[], _('hg amendtransient [rev]')) + > def amend(ui, repo, *pats, **opts): + > def commitfunc(ui, repo, message, match, opts): + > return repo.commit(message, repo['.'].user(), repo['.'].date(), match) + > opts['message'] = 'Test' + > opts['logfile'] = None + > cmdutil.amend(ui, repo, commitfunc, repo['.'], {}, pats, opts) + > print repo.changelog.headrevs() + > EOF + $ cat >> $HGRCPATH << EOF + > [extensions] + > testextension=$TESTTMP/test_extension.py + > EOF + $ hg init repo-issue-nativerevs-pending-changes + $ cd repo-issue-nativerevs-pending-changes + $ mkcommit a + $ mkcommit b + $ hg up ".^" + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo aa > a + $ hg amendtransient + [1, 3] + +Test cache consistency for the visible filter +1) We want to make sure that the cached filtered revs are invalidated when +bookmarks change + $ cd .. + $ cat >$TESTTMP/test_extension.py << EOF + > from mercurial import cmdutil, extensions, bookmarks, repoview + > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs): + > repo = bkmstoreinst._repo + > ret = orig(bkmstoreinst, *args, **kwargs) + > hidden1 = repoview.computehidden(repo) + > hidden = repoview.filterrevs(repo, 'visible') + > if sorted(hidden1) != sorted(hidden): + > print "cache inconsistency" + > return ret + > def extsetup(ui): + > extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged) + > EOF + + $ hg init repo-cache-inconsistency + $ cd repo-issue-nativerevs-pending-changes + $ mkcommit a + a already tracked! + $ mkcommit b + $ hg id + 13bedc178fce tip + $ echo "hello" > b + $ hg commit --amend -m "message" + $ hg book bookb -r 13bedc178fce --hidden + $ hg log -r 13bedc178fce + 5:13bedc178fce (draft) [ bookb] add b + $ hg book -d bookb + $ hg log -r 13bedc178fce + abort: hidden revision '13bedc178fce'! + (use --hidden to access hidden revisions) + [255] + + + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-parseindex.t --- a/tests/test-parseindex.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-parseindex.t Sat Jul 18 17:32:38 2015 -0500 @@ -59,3 +59,62 @@ 26333235a41c $ cd .. + +Test corrupted p1/p2 fields that could cause SEGV at parsers.c: + + $ mkdir invalidparent + $ cd invalidparent + + $ hg clone --pull -q --config phases.publish=False ../a limit + $ hg clone --pull -q --config phases.publish=False ../a segv + $ rm -R limit/.hg/cache segv/.hg/cache + + $ python < data = open("limit/.hg/store/00changelog.i", "rb").read() + > for n, p in [('limit', '\0\0\0\x02'), ('segv', '\0\x01\0\0')]: + > # corrupt p1 at rev0 and p2 at rev1 + > d = data[:24] + p + data[28:127 + 28] + p + data[127 + 32:] + > open(n + "/.hg/store/00changelog.i", "wb").write(d) + > EOF + + $ hg debugindex -f1 limit/.hg/store/00changelog.i + rev flag offset length size base link p1 p2 nodeid + 0 0000 0 63 62 0 0 2 -1 7c31755bf9b5 + 1 0000 63 66 65 1 1 0 2 26333235a41c + $ hg debugindex -f1 segv/.hg/store/00changelog.i + rev flag offset length size base link p1 p2 nodeid + 0 0000 0 63 62 0 0 65536 -1 7c31755bf9b5 + 1 0000 63 66 65 1 1 0 65536 26333235a41c + + $ cat < test.py + > import sys + > from mercurial import changelog, scmutil + > cl = changelog.changelog(scmutil.vfs(sys.argv[1])) + > n0, n1 = cl.node(0), cl.node(1) + > ops = [ + > ('compute_phases_map_sets', lambda: cl.computephases([[0], []])), + > ('index_headrevs', lambda: cl.headrevs()), + > ('find_gca_candidates', lambda: cl.commonancestorsheads(n0, n1)), + > ('find_deepest', lambda: cl.ancestor(n0, n1)), + > ] + > for l, f in ops: + > print l + ':', + > try: + > f() + > print 'uncaught buffer overflow?' + > except ValueError, inst: + > print inst + > EOF + + $ python test.py limit/.hg/store + compute_phases_map_sets: parent out of range + index_headrevs: parent out of range + find_gca_candidates: parent out of range + find_deepest: parent out of range + $ python test.py segv/.hg/store + compute_phases_map_sets: parent out of range + index_headrevs: parent out of range + find_gca_candidates: parent out of range + find_deepest: parent out of range + + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-patchbomb.t --- a/tests/test-patchbomb.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-patchbomb.t Sat Jul 18 17:32:38 2015 -0500 @@ -289,18 +289,11 @@ \r (no-eol) (esc) sending [ ] 0/3\r (no-eol) (esc) - sending [ ] 0/3\r (no-eol) (esc) - \r (no-eol) (esc) - \r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) sending [==============> ] 1/3\r (no-eol) (esc) - sending [==============> ] 1/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) - \r (no-eol) (esc) - \r (no-eol) (esc) - sending [=============================> ] 2/3\r (no-eol) (esc) sending [=============================> ] 2/3\r (no-eol) (esc) \r (esc) sending [PATCH 0 of 2] test ... diff -r 501c51d60792 -r 96a38d44ba09 tests/test-phases-exchange.t --- a/tests/test-phases-exchange.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-phases-exchange.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,12 @@ #require killdaemons + $ cat << EOF >> $HGRCPATH + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True + > EOF + $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; } $ mkcommit() { @@ -765,9 +772,9 @@ searching for changes 1 changesets found uncompressed size of bundle content: - 172 (changelog) - 145 (manifests) - 111 a-H + 192 (changelog) + 165 (manifests) + 131 a-H adding changesets adding manifests adding file changes @@ -1041,7 +1048,16 @@ $ cat ../beta.pid >> $DAEMON_PIDS $ cd ../gamma - $ hg pull http://localhost:$HGPORT/ + $ hg pull http://localhost:$HGPORT/ --config experimental.bundle2-exp=True + pulling from http://localhost:$HGPORT/ + searching for changes + no changes found + $ hg phase f54f1bb90ff3 + 2: draft + +enforce bundle1 + + $ hg pull http://localhost:$HGPORT/ --config experimental.bundle2-exp=False pulling from http://localhost:$HGPORT/ searching for changes no changes found @@ -1177,6 +1193,6 @@ $ cd .. - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py #endif diff -r 501c51d60792 -r 96a38d44ba09 tests/test-phases.t --- a/tests/test-phases.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-phases.t Sat Jul 18 17:32:38 2015 -0500 @@ -36,6 +36,8 @@ Draft commit are properly created over public one: $ hg phase --public . + $ hg phase + 1: public $ hglog 1 0 B 0 0 A @@ -86,6 +88,9 @@ $ hg merge 4 # E 3 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) + $ hg phase + 6: draft + 4: secret $ hg ci -m "merge B' and E" $ hglog 7 2 merge B' and E @@ -242,6 +247,24 @@ 1 0 B 0 0 A +Test summary + + $ hg summary -R clone-dest --verbose + parent: -1:000000000000 (no revision checked out) + branch: default + commit: (clean) + update: 5 new changesets (update) + $ hg summary -R initialrepo + parent: 7:17a481b3bccb tip + merge B' and E + branch: default + commit: (clean) (secret) + update: 1 new changesets, 2 branch heads (merge) + phases: 3 draft, 3 secret + $ hg summary -R initialrepo --quiet + parent: 7:17a481b3bccb tip + update: 1 new changesets, 2 branch heads (merge) + Test revset $ cd initialrepo diff -r 501c51d60792 -r 96a38d44ba09 tests/test-progress.t --- a/tests/test-progress.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-progress.t Sat Jul 18 17:32:38 2015 -0500 @@ -76,7 +76,8 @@ loop [===============> ] 1/3\r (no-eol) (esc) loop [===============================> ] 2/3\r (no-eol) (esc) \r (no-eol) (esc) - +no progress with --quiet + $ hg -y loop 3 --quiet test nested short-lived topics (which shouldn't display with nestdelay): diff -r 501c51d60792 -r 96a38d44ba09 tests/test-pull-branch.t --- a/tests/test-pull-branch.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-pull-branch.t Sat Jul 18 17:32:38 2015 -0500 @@ -33,7 +33,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch branchB marked working directory as branch branchB - (branches are permanent and global, did you want a bookmark?) $ echo b1 > foo $ hg ci -mb1 # 3 @@ -141,7 +140,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch branchC marked working directory as branch branchC - (branches are permanent and global, did you want a bookmark?) $ echo b1 > bar $ hg ci -Am "commit on branchC on tt" adding bar diff -r 501c51d60792 -r 96a38d44ba09 tests/test-pull-http.t --- a/tests/test-pull-http.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-pull-http.t Sat Jul 18 17:32:38 2015 -0500 @@ -40,7 +40,7 @@ [ui] # name and email (local to this repository, optional), e.g. # username = Jane Doe - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py expect error, cloning not allowed @@ -48,10 +48,14 @@ $ echo 'allowpull = false' >> .hg/hgrc $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log $ cat hg.pid >> $DAEMON_PIDS - $ hg clone http://localhost:$HGPORT/ test4 + $ hg clone http://localhost:$HGPORT/ test4 --config experimental.bundle2-exp=True + requesting all changes abort: authorization failed [255] - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ hg clone http://localhost:$HGPORT/ test4 --config experimental.bundle2-exp=False + abort: authorization failed + [255] + $ killdaemons.py serve errors @@ -60,7 +64,7 @@ > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log > cat hg.pid >> $DAEMON_PIDS > hg --cwd ../test pull http://localhost:$HGPORT/ - > "$TESTDIR/killdaemons.py" hg.pid + > killdaemons.py hg.pid > echo % serve errors > cat errors.log > } @@ -69,6 +73,7 @@ $ req pulling from http://localhost:$HGPORT/ + searching for changes abort: authorization failed % serve errors diff -r 501c51d60792 -r 96a38d44ba09 tests/test-push-hook-lock.t --- a/tests/test-push-hook-lock.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-push-hook-lock.t Sat Jul 18 17:32:38 2015 -0500 @@ -26,7 +26,7 @@ $ echo bar >> 3/foo $ hg --cwd 3 ci -m bar - $ hg --cwd 3 push ../2 + $ hg --cwd 3 push ../2 --config experimental.bundle2-exp=False pushing to ../2 searching for changes adding changesets @@ -36,3 +36,15 @@ lock: user *, process * (*s) (glob) wlock: free + $ hg --cwd 1 --config extensions.strip= strip tip -q + $ hg --cwd 2 --config extensions.strip= strip tip -q + $ hg --cwd 3 push ../2 --config experimental.bundle2-exp=True + pushing to ../2 + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + lock: user *, process * (*s) (glob) + wlock: user *, process * (*s) (glob) + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-push-http-bundle1.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-http-bundle1.t Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,169 @@ +#require killdaemons + +This test checks behavior related to bundle1 that changed or is likely +to change with bundle2. Feel free to factor out any part of the test +which does not need to exist to keep bundle1 working. + + $ cat << EOF >> $HGRCPATH + > [experimental] + > # This test is dedicated to interaction through old bundle + > bundle2-exp = False + > EOF + + $ hg init test + $ cd test + $ echo a > a + $ hg ci -Ama + adding a + $ cd .. + $ hg clone test test2 + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd test2 + $ echo a >> a + $ hg ci -mb + $ req() { + > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log + > cat hg.pid >> $DAEMON_PIDS + > hg --cwd ../test2 push http://localhost:$HGPORT/ + > exitstatus=$? + > killdaemons.py + > echo % serve errors + > cat errors.log + > return $exitstatus + > } + $ cd ../test + +expect ssl error + + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + abort: HTTP Error 403: ssl required + % serve errors + [255] + +expect authorization error + + $ echo '[web]' > .hg/hgrc + $ echo 'push_ssl = false' >> .hg/hgrc + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + abort: authorization failed + % serve errors + [255] + +expect authorization error: must have authorized user + + $ echo 'allow_push = unperson' >> .hg/hgrc + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + abort: authorization failed + % serve errors + [255] + +expect success + + $ echo 'allow_push = *' >> .hg/hgrc + $ echo '[hooks]' >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup 0" >> .hg/hgrc + $ echo "pushkey = printenv.py pushkey 0" >> .hg/hgrc + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + % serve errors + $ hg rollback + repository tip rolled back to revision 0 (undo serve) + +expect success, server lacks the httpheader capability + + $ CAP=httpheader + $ . "$TESTDIR/notcapable" + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + % serve errors + $ hg rollback + repository tip rolled back to revision 0 (undo serve) + +expect success, server lacks the unbundlehash capability + + $ CAP=unbundlehash + $ . "$TESTDIR/notcapable" + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + % serve errors + $ hg rollback + repository tip rolled back to revision 0 (undo serve) + +expect push success, phase change failure + + $ cat > .hg/hgrc < [web] + > push_ssl = false + > allow_push = * + > [hooks] + > prepushkey = printenv.py prepushkey 1 + > EOF + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + % serve errors + +expect phase change success + + $ echo "prepushkey = printenv.py prepushkey 0" >> .hg/hgrc + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + no changes found + % serve errors + [1] + $ hg rollback + repository tip rolled back to revision 0 (undo serve) + +expect authorization error: all users denied + + $ echo '[web]' > .hg/hgrc + $ echo 'push_ssl = false' >> .hg/hgrc + $ echo 'deny_push = *' >> .hg/hgrc + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + abort: authorization failed + % serve errors + [255] + +expect authorization error: some users denied, users must be authenticated + + $ echo 'deny_push = unperson' >> .hg/hgrc + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + abort: authorization failed + % serve errors + [255] + + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-push-http.t --- a/tests/test-push-http.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-push-http.t Sat Jul 18 17:32:38 2015 -0500 @@ -17,7 +17,7 @@ > cat hg.pid >> $DAEMON_PIDS > hg --cwd ../test2 push http://localhost:$HGPORT/ > exitstatus=$? - > "$TESTDIR/killdaemons.py" $DAEMON_PIDS + > killdaemons.py > echo % serve errors > cat errors.log > return $exitstatus @@ -58,8 +58,8 @@ $ echo 'allow_push = *' >> .hg/hgrc $ echo '[hooks]' >> .hg/hgrc - $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup 0" >> .hg/hgrc - $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey 0" >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup 0" >> .hg/hgrc + $ echo "pushkey = printenv.py pushkey 0" >> .hg/hgrc $ req pushing to http://localhost:$HGPORT/ searching for changes @@ -67,7 +67,8 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files - remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1 + remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) % serve errors $ hg rollback repository tip rolled back to revision 0 (undo serve) @@ -83,7 +84,8 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files - remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1 + remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) % serve errors $ hg rollback repository tip rolled back to revision 0 (undo serve) @@ -99,7 +101,8 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files - remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1 + remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) % serve errors $ hg rollback repository tip rolled back to revision 0 (undo serve) @@ -111,7 +114,7 @@ > push_ssl = false > allow_push = * > [hooks] - > prepushkey = python "$TESTDIR/printenv.py" prepushkey 1 + > prepushkey = printenv.py prepushkey 1 > EOF $ req pushing to http://localhost:$HGPORT/ @@ -120,17 +123,26 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files + remote: prepushkey hook: HG_BUNDLE2=1 HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) + remote: pushkey-abort: prepushkey hook exited with status 1 + remote: transaction abort! + remote: rollback completed + abort: updating ba677d0156c1 to public failed % serve errors + [255] expect phase change success - $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey 0" >> .hg/hgrc + $ echo "prepushkey = printenv.py prepushkey 0" >> .hg/hgrc $ req pushing to http://localhost:$HGPORT/ searching for changes - no changes found + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: prepushkey hook: HG_BUNDLE2=1 HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob) % serve errors - [1] $ hg rollback repository tip rolled back to revision 0 (undo serve) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-push-warn.t --- a/tests/test-push-warn.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-push-warn.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,3 +1,9 @@ + $ cat << EOF >> $HGRCPATH + > [experimental] + > # drop me once bundle2 is the default, + > # added to get test change early. + > bundle2-exp = True + > EOF $ hg init a $ cd a $ echo foo > t1 @@ -40,7 +46,6 @@ query 1; heads searching for changes taking quick initial sample - searching: 2 queries query 2; still undecided: 1, sample size is: 1 2 total queries listing keys for "phases" @@ -151,9 +156,9 @@ searching for changes 2 changesets found uncompressed size of bundle content: - 308 (changelog) - 286 (manifests) - 213 foo + 348 (changelog) + 326 (manifests) + 253 foo adding changesets adding manifests adding file changes @@ -463,7 +468,6 @@ $ hg -R j ci -m a1 $ hg -R k branch b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ echo b > k/foo $ hg -R k ci -m b $ hg -R k up 0 @@ -533,7 +537,6 @@ adding a $ hg branch B marked working directory as branch B - (branches are permanent and global, did you want a bookmark?) $ echo b >b $ hg ci -Amb adding b @@ -612,7 +615,6 @@ adding a $ hg branch B marked working directory as branch B - (branches are permanent and global, did you want a bookmark?) $ echo b >b $ hg ci -Amb adding b @@ -704,7 +706,6 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg branch B marked working directory as branch B - (branches are permanent and global, did you want a bookmark?) $ echo b0 >b $ hg ci -Amb0 adding b @@ -719,7 +720,6 @@ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg branch -f B marked working directory as branch B - (branches are permanent and global, did you want a bookmark?) $ echo a3 >a $ hg ci -ma3 created new head @@ -727,7 +727,6 @@ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg branch -f A marked working directory as branch A - (branches are permanent and global, did you want a bookmark?) $ echo b3 >b $ hg ci -mb3 created new head diff -r 501c51d60792 -r 96a38d44ba09 tests/test-qrecord.t --- a/tests/test-qrecord.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-qrecord.t Sat Jul 18 17:32:38 2015 -0500 @@ -58,8 +58,7 @@ -A --addremove mark new/missing files as added/removed before committing - --close-branch mark a branch as closed, hiding it from the branch - list + --close-branch mark a branch head as closed --amend amend the parent of the working directory -s --secret use the secret phase for committing -e --edit invoke editor on commit messages diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-abort.t --- a/tests/test-rebase-abort.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-abort.t Sat Jul 18 17:32:38 2015 -0500 @@ -321,3 +321,4 @@ branch: default commit: (clean) update: 1 new changesets, 2 branch heads (merge) + phases: 4 draft diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-cache.t --- a/tests/test-rebase-cache.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-cache.t Sat Jul 18 17:32:38 2015 -0500 @@ -31,7 +31,6 @@ $ hg branch branch2 marked working directory as branch branch2 - (branches are permanent and global, did you want a bookmark?) $ hg ci -m 'branch2' $ echo c > C @@ -42,7 +41,6 @@ $ hg branch -f branch2 marked working directory as branch branch2 - (branches are permanent and global, did you want a bookmark?) $ echo d > d $ hg ci -Am D adding d @@ -57,7 +55,6 @@ $ hg branch branch3 marked working directory as branch branch3 - (branches are permanent and global, did you want a bookmark?) $ hg ci -m 'branch3' $ echo f > f @@ -308,12 +305,10 @@ $ hg branch branch2 marked working directory as branch branch2 - (branches are permanent and global, did you want a bookmark?) $ hg ci -m 'branch2' $ hg branch -f branch1 marked working directory as branch branch1 - (branches are permanent and global, did you want a bookmark?) $ echo a > A $ hg ci -Am A diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-collapse.t --- a/tests/test-rebase-collapse.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-collapse.t Sat Jul 18 17:32:38 2015 -0500 @@ -571,7 +571,6 @@ $ hg branch 'two' marked working directory as branch two - (branches are permanent and global, did you want a bookmark?) $ echo 'c' > c $ hg ci -Am 'C' adding c diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-conflicts.t --- a/tests/test-rebase-conflicts.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-conflicts.t Sat Jul 18 17:32:38 2015 -0500 @@ -223,7 +223,6 @@ ignoring null merge rebase of 6 ignoring null merge rebase of 8 rebasing 9:e31216eec445 "more changes to f1" - rebasing: 9:e31216eec445 5/6 changesets (83.33%) future parents are 2 and -1 rebase status stored update to 2:4bc80088dc6b @@ -232,10 +231,8 @@ ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b f2.txt: other deleted -> r removing f2.txt - updating: f2.txt 1/2 files (50.00%) f1.txt: remote created -> g getting f1.txt - updating: f1.txt 2/2 files (100.00%) merge against 9:e31216eec445 detach base 8:8e4e2c1a07ae searching for copies back to rev 3 @@ -244,14 +241,12 @@ ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445 f1.txt: remote is newer -> g getting f1.txt - updating: f1.txt 1/1 files (100.00%) committing files: f1.txt committing manifest committing changelog rebased as 19c888675e13 rebasing 10:2f2496ddf49d "merge" (tip) - rebasing: 10:2f2496ddf49d 6/6 changesets (100.00%) future parents are 11 and 7 rebase status stored already in target @@ -263,7 +258,6 @@ ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d f1.txt: remote is newer -> g getting f1.txt - updating: f1.txt 1/1 files (100.00%) committing files: f1.txt committing manifest @@ -276,47 +270,26 @@ ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0 f1.txt: other deleted -> r removing f1.txt - updating: f1.txt 1/2 files (50.00%) f2.txt: remote created -> g getting f2.txt - updating: f2.txt 2/2 files (100.00%) 2 changesets found list of changesets: e31216eec445e44352c5f01588856059466a24c9 2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2 - bundling: 1/2 changesets (50.00%) - bundling: 2/2 changesets (100.00%) - bundling: 1/2 manifests (50.00%) - bundling: 2/2 manifests (100.00%) - bundling: f1.txt 1/1 files (100.00%) saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-15f7a814-backup.hg (glob) 3 changesets found list of changesets: 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c 19c888675e133ab5dff84516926a65672eaf04d9 2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf - bundling: 1/3 changesets (33.33%) - bundling: 2/3 changesets (66.67%) - bundling: 3/3 changesets (100.00%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 3/3 manifests (100.00%) - bundling: f1.txt 1/1 files (100.00%) adding branch adding changesets - changesets: 1 chunks add changeset 4c9fbe56a16f - changesets: 2 chunks add changeset 19c888675e13 - changesets: 3 chunks add changeset 2a7f09cac94c adding manifests - manifests: 1/2 chunks (50.00%) - manifests: 2/2 chunks (100.00%) - manifests: 3/2 chunks (150.00%) adding file changes adding f1.txt revisions - files: 1/1 chunks (100.00%) added 2 changesets with 2 changes to 1 files invalid branchheads cache (served): tip differs rebase completed diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-interruptions.t --- a/tests/test-rebase-interruptions.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-interruptions.t Sat Jul 18 17:32:38 2015 -0500 @@ -258,7 +258,7 @@ Abort the rebasing: $ hg rebase --abort - warning: can't clean up immutable changesets 45396c49d53b + warning: can't clean up public changesets 45396c49d53b rebase aborted $ hg tglogp diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-named-branches.t --- a/tests/test-rebase-named-branches.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-named-branches.t Sat Jul 18 17:32:38 2015 -0500 @@ -36,7 +36,6 @@ 2 files updated, 0 files merged, 3 files removed, 0 files unresolved $ hg branch dev-two marked working directory as branch dev-two - (branches are permanent and global, did you want a bookmark?) $ echo x > x @@ -128,7 +127,6 @@ 3 files updated, 0 files merged, 3 files removed, 0 files unresolved $ hg branch dev-one marked working directory as branch dev-one - (branches are permanent and global, did you want a bookmark?) $ hg ci -m 'dev-one named branch' $ hg tglog diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-parameters.t --- a/tests/test-rebase-parameters.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-parameters.t Sat Jul 18 17:32:38 2015 -0500 @@ -476,6 +476,7 @@ branch: default commit: 1 modified, 1 unresolved (merge) update: (current) + phases: 3 draft rebase: 0 rebased, 1 remaining (rebase --continue) $ hg resolve -l diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rebase-scenario-global.t --- a/tests/test-rebase-scenario-global.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rebase-scenario-global.t Sat Jul 18 17:32:38 2015 -0500 @@ -308,7 +308,7 @@ nothing to rebase [1] $ hg rebase -d 5 -b 6 - abort: can't rebase immutable changeset e1c4361dd923 + abort: can't rebase public changeset e1c4361dd923 (see "hg help phases" for details) [255] @@ -397,7 +397,7 @@ abort: can't remove original changesets with unrebased descendants (use --keep to keep original changesets) [255] - $ hg rebase -r '2::8' -d 1 --keep + $ hg rebase -r '2::8' -d 1 -k rebasing 2:c9e50f6cdc55 "C" rebasing 3:ffd453c31098 "D" rebasing 6:3d8a618087a7 "G" diff -r 501c51d60792 -r 96a38d44ba09 tests/test-record.t --- a/tests/test-record.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-record.t Sat Jul 18 17:32:38 2015 -0500 @@ -45,8 +45,7 @@ -A --addremove mark new/missing files as added/removed before committing - --close-branch mark a branch as closed, hiding it from the branch - list + --close-branch mark a branch head as closed --amend amend the parent of the working directory -s --secret use the secret phase for committing -e --edit invoke editor on commit messages diff -r 501c51d60792 -r 96a38d44ba09 tests/test-relink.t --- a/tests/test-relink.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-relink.t Sat Jul 18 17:32:38 2015 -0500 @@ -70,7 +70,7 @@ relink - $ hg relink --debug | fix_path + $ hg relink --debug --config progress.debug=true | fix_path relinking $TESTTMP/repo/.hg/store to $TESTTMP/clone/.hg/store tip has 2 files, estimated total number of files: 3 collecting: 00changelog.i 1/3 files (33.33%) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rename-dir-merge.t --- a/tests/test-rename-dir-merge.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rename-dir-merge.t Sat Jul 18 17:32:38 2015 -0500 @@ -43,14 +43,11 @@ removing a/a a/b: other deleted -> r removing a/b - updating: a/b 2/5 files (40.00%) b/a: remote created -> g getting b/a b/b: remote created -> g getting b/b - updating: b/b 4/5 files (80.00%) b/c: remote directory rename - move from a/c -> dm - updating: b/c 5/5 files (100.00%) moving a/c to b/c (glob) 3 files updated, 0 files merged, 2 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -89,7 +86,6 @@ branchmerge: True, force: False, partial: False ancestor: f9b20c0d4c51, local: 397f8b00a740+, remote: ce36d17b18fb b/c: local directory rename - get from a/c -> dg - updating: b/c 1/1 files (100.00%) getting a/c to b/c 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rename-merge1.t --- a/tests/test-rename-merge1.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rename-merge1.t Sat Jul 18 17:32:38 2015 -0500 @@ -40,9 +40,7 @@ removing a b2: remote created -> g getting b2 - updating: b2 1/2 files (50.00%) b: remote moved from a -> m - updating: b 2/2 files (100.00%) picked tool 'internal:merge' for b (binary False symlink False) merging a and b to b my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c @@ -181,7 +179,6 @@ ancestor: 19d7f95df299, local: 0084274f6b67+, remote: 5d32493049f0 newfile: remote created -> g getting newfile - updating: newfile 1/1 files (100.00%) note: possible conflict - file was deleted and renamed to: newfile 1 files updated, 0 files merged, 0 files removed, 0 files unresolved diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rename-merge2.t --- a/tests/test-rename-merge2.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rename-merge2.t Sat Jul 18 17:32:38 2015 -0500 @@ -90,13 +90,11 @@ preserving rev for resolve of rev a: remote unchanged -> k b: remote copied from a -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging a and b to b my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337 premerge successful rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337 @@ -128,15 +126,12 @@ preserving rev for resolve of rev a: remote is newer -> g getting a - updating: a 1/3 files (33.33%) b: local copied/moved from a -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337 @@ -168,13 +163,11 @@ preserving rev for resolve of rev removing a b: remote moved from a -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging a and b to b my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337 premerge successful rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337 @@ -204,13 +197,11 @@ preserving b for resolve of b preserving rev for resolve of rev b: local copied/moved from a -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337 @@ -240,9 +231,7 @@ preserving rev for resolve of rev b: remote created -> g getting b - updating: b 1/2 files (50.00%) rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337 @@ -271,7 +260,6 @@ ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 97c705ade336 preserving rev for resolve of rev rev: versions differ -> m - updating: rev 1/1 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337 @@ -301,12 +289,9 @@ preserving rev for resolve of rev a: other deleted -> r removing a - updating: a 1/3 files (33.33%) b: remote created -> g getting b - updating: b 2/3 files (66.67%) rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337 @@ -334,7 +319,6 @@ ancestor: 924404dff337, local: 02963e448370+, remote: 97c705ade336 preserving rev for resolve of rev rev: versions differ -> m - updating: rev 1/1 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337 @@ -360,14 +344,12 @@ preserving b for resolve of b preserving rev for resolve of rev b: both renamed from a -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337 @@ -402,9 +384,7 @@ preserving rev for resolve of rev c: remote created -> g getting c - updating: c 1/2 files (50.00%) rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 @@ -434,14 +414,12 @@ preserving b for resolve of b preserving rev for resolve of rev b: both created -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337 @@ -469,16 +447,13 @@ preserving rev for resolve of rev a: other deleted -> r removing a - updating: a 1/3 files (33.33%) b: both created -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 @@ -505,16 +480,13 @@ preserving rev for resolve of rev a: remote is newer -> g getting a - updating: a 1/3 files (33.33%) b: both created -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 @@ -542,16 +514,13 @@ preserving rev for resolve of rev a: other deleted -> r removing a - updating: a 1/3 files (33.33%) b: both created -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 @@ -578,16 +547,13 @@ preserving rev for resolve of rev a: remote is newer -> g getting a - updating: a 1/3 files (33.33%) b: both created -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 @@ -615,14 +581,12 @@ preserving rev for resolve of rev a: remote unchanged -> k b: both created -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337 @@ -652,16 +616,13 @@ preserving rev for resolve of rev a: prompt recreating -> g getting a - updating: a 1/3 files (33.33%) b: both created -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@02963e448370+ other b@8dbce441892a ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337 @@ -690,16 +651,13 @@ preserving b for resolve of b preserving rev for resolve of rev a: prompt keep -> a - updating: a 1/3 files (33.33%) b: both created -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@0b76e65c8289+ other b@bdb19105162a ancestor b@000000000000 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337 @@ -730,14 +688,12 @@ preserving rev for resolve of rev removing a b: remote moved from a -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging a and b to b my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337 @@ -767,14 +723,12 @@ preserving b for resolve of b preserving rev for resolve of rev b: local copied/moved from a -> m - updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob) merge tool returned: 0 rev: versions differ -> m - updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337 @@ -810,15 +764,12 @@ preserving rev for resolve of rev c: remote created -> g getting c - updating: c 1/3 files (33.33%) b: local copied/moved from a -> m - updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337 premerge successful rev: versions differ -> m - updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337 @@ -875,7 +826,7 @@ $ mkdir 7 8 $ echo m > 7/f $ echo m > 8/f - $ hg merge -f --tool internal:dump -v --debug -r2 | sed '/^updating:/,$d' 2> /dev/null + $ hg merge -f --tool internal:dump -v --debug -r2 | sed '/^ 0\/f: both created -> m/,$d' 2> /dev/null searching for copies back to rev 1 unmatched files in local: 5/g diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rename.t --- a/tests/test-rename.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rename.t Sat Jul 18 17:32:38 2015 -0500 @@ -20,6 +20,7 @@ branch: default commit: 1 renamed update: (current) + phases: 1 draft $ hg status -C A d2/c d1/d11/a1 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-revert-interactive.t --- a/tests/test-revert-interactive.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-revert-interactive.t Sat Jul 18 17:32:38 2015 -0500 @@ -52,12 +52,12 @@ reverting folder1/g (glob) removing folder1/i (glob) reverting folder2/h (glob) - diff -r 89ac3d72e4a4 f + diff --git a/f b/f 2 hunks, 2 lines changed examine changes to 'f'? [Ynesfdaq?] y - @@ -1,6 +1,5 @@ - -a + @@ -1,5 +1,6 @@ + +a 1 2 3 @@ -65,21 +65,21 @@ 5 record change 1/6 to 'f'? [Ynesfdaq?] y - @@ -2,6 +1,5 @@ + @@ -1,5 +2,6 @@ 1 2 3 4 5 - -b + +b record change 2/6 to 'f'? [Ynesfdaq?] y - diff -r 89ac3d72e4a4 folder1/g + diff --git a/folder1/g b/folder1/g 2 hunks, 2 lines changed examine changes to 'folder1/g'? [Ynesfdaq?] y - @@ -1,6 +1,5 @@ - -c + @@ -1,5 +1,6 @@ + +c 1 2 3 @@ -87,16 +87,16 @@ 5 record change 3/6 to 'folder1/g'? [Ynesfdaq?] y - @@ -2,6 +1,5 @@ + @@ -1,5 +2,6 @@ 1 2 3 4 5 - -d + +d record change 4/6 to 'folder1/g'? [Ynesfdaq?] n - diff -r 89ac3d72e4a4 folder2/h + diff --git a/folder2/h b/folder2/h 2 hunks, 2 lines changed examine changes to 'folder2/h'? [Ynesfdaq?] n @@ -127,7 +127,7 @@ $ echo q | hg revert -i -r 2 reverting folder1/g (glob) reverting folder2/h (glob) - diff -r 89ac3d72e4a4 folder1/g + diff --git a/folder1/g b/folder1/g 1 hunks, 1 lines changed examine changes to 'folder1/g'? [Ynesfdaq?] q @@ -151,12 +151,12 @@ reverting folder1/g (glob) removing folder1/i (glob) reverting folder2/h (glob) - diff -r 89ac3d72e4a4 f + diff --git a/f b/f 2 hunks, 2 lines changed examine changes to 'f'? [Ynesfdaq?] y - @@ -1,6 +1,5 @@ - -a + @@ -1,5 +1,6 @@ + +a 1 2 3 @@ -164,21 +164,21 @@ 5 record change 1/6 to 'f'? [Ynesfdaq?] y - @@ -2,6 +1,5 @@ + @@ -1,5 +2,6 @@ 1 2 3 4 5 - -b + +b record change 2/6 to 'f'? [Ynesfdaq?] y - diff -r 89ac3d72e4a4 folder1/g + diff --git a/folder1/g b/folder1/g 2 hunks, 2 lines changed examine changes to 'folder1/g'? [Ynesfdaq?] y - @@ -1,6 +1,5 @@ - -c + @@ -1,5 +1,6 @@ + +c 1 2 3 @@ -186,16 +186,16 @@ 5 record change 3/6 to 'folder1/g'? [Ynesfdaq?] y - @@ -2,6 +1,5 @@ + @@ -1,5 +2,6 @@ 1 2 3 4 5 - -d + +d record change 4/6 to 'folder1/g'? [Ynesfdaq?] n - diff -r 89ac3d72e4a4 folder2/h + diff --git a/folder2/h b/folder2/h 2 hunks, 2 lines changed examine changes to 'folder2/h'? [Ynesfdaq?] n @@ -230,12 +230,12 @@ > n > n > EOF - diff -r 59dd6e4ab63a f + diff --git a/f b/f 2 hunks, 2 lines changed examine changes to 'f'? [Ynesfdaq?] y - @@ -1,5 +1,6 @@ - +a + @@ -1,6 +1,5 @@ + -a 1 2 3 @@ -243,13 +243,13 @@ 5 record change 1/2 to 'f'? [Ynesfdaq?] y - @@ -1,5 +2,6 @@ + @@ -2,6 +1,5 @@ 1 2 3 4 5 - +b + -b record change 2/2 to 'f'? [Ynesfdaq?] n $ hg st @@ -270,3 +270,110 @@ 3 4 5 + $ rm f.orig + $ hg update -C . + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Check editing files newly added by a revert + +1) Create a dummy editor changing 1 to 42 + $ cat > $TESTTMP/editor.sh << '__EOF__' + > cat "$1" | sed "s/1/42/g" > tt + > mv tt "$1" + > __EOF__ + +2) Add k + $ printf "1\n" > k + $ hg add k + $ hg commit -m "add k" + +3) Use interactive revert with editing (replacing +1 with +42): + $ printf "0\n2\n" > k + $ HGEDITOR="\"sh\" \"${TESTTMP}/editor.sh\"" hg revert -i < y + > e + > EOF + reverting k + diff --git a/k b/k + 1 hunks, 2 lines changed + examine changes to 'k'? [Ynesfdaq?] y + + @@ -1,1 +1,2 @@ + -1 + +0 + +2 + record this change to 'k'? [Ynesfdaq?] e + + $ cat k + 42 + +Check the experimental config to invert the selection: + $ cat <> $HGRCPATH + > [experimental] + > revertalternateinteractivemode=False + > EOF + + + $ hg up -C . + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ printf 'firstline\nc\n1\n2\n3\n 3\n5\nd\nlastline\n' > folder1/g + $ hg diff --nodates + diff -r a3d963a027aa folder1/g + --- a/folder1/g + +++ b/folder1/g + @@ -1,7 +1,9 @@ + +firstline + c + 1 + 2 + 3 + -4 + + 3 + 5 + d + +lastline + $ hg revert -i < y + > y + > y + > n + > EOF + reverting folder1/g (glob) + diff --git a/folder1/g b/folder1/g + 3 hunks, 3 lines changed + examine changes to 'folder1/g'? [Ynesfdaq?] y + + @@ -1,5 +1,4 @@ + -firstline + c + 1 + 2 + 3 + record change 1/3 to 'folder1/g'? [Ynesfdaq?] y + + @@ -2,7 +1,7 @@ + c + 1 + 2 + 3 + - 3 + +4 + 5 + d + record change 2/3 to 'folder1/g'? [Ynesfdaq?] y + + @@ -7,3 +6,2 @@ + 5 + d + -lastline + record change 3/3 to 'folder1/g'? [Ynesfdaq?] n + + $ hg diff --nodates + diff -r a3d963a027aa folder1/g + --- a/folder1/g + +++ b/folder1/g + @@ -5,3 +5,4 @@ + 4 + 5 + d + +lastline diff -r 501c51d60792 -r 96a38d44ba09 tests/test-revert.t --- a/tests/test-revert.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-revert.t Sat Jul 18 17:32:38 2015 -0500 @@ -175,6 +175,46 @@ executable #endif +Test that files reverted to other than the parent are treated as +"modified", even if none of mode, size and timestamp of it isn't +changed on the filesystem (see also issue4583). + + $ echo 321 > e + $ hg diff --git + diff --git a/e b/e + --- a/e + +++ b/e + @@ -1,1 +1,1 @@ + -123 + +321 + $ hg commit -m 'ambiguity from size' + + $ cat e + 321 + $ touch -t 200001010000 e + $ hg debugrebuildstate + + $ cat >> .hg/hgrc < [fakedirstatewritetime] + > # emulate invoking dirstate.write() via repo.status() + > # at 2000-01-01 00:00 + > fakenow = 200001010000 + > + > [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > EOF + $ hg revert -r 0 e + $ cat >> .hg/hgrc < [extensions] + > fakedirstatewritetime = ! + > EOF + + $ cat e + 123 + $ touch -t 200001010000 e + $ hg status -A e + M e + $ cd .. @@ -360,6 +400,7 @@ branch: default commit: 2 modified, 1 removed (merge) update: (current) + phases: 3 draft clarifies who added what diff -r 501c51d60792 -r 96a38d44ba09 tests/test-revset.t --- a/tests/test-revset.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-revset.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,5 +1,27 @@ $ HGENCODING=utf-8 $ export HGENCODING + $ cat > testrevset.py << EOF + > import mercurial.revset + > + > baseset = mercurial.revset.baseset + > + > def r3232(repo, subset, x): + > """"simple revset that return [3,2,3,2] + > + > revisions duplicated on purpose. + > """ + > if 3 not in subset: + > if 2 in subset: + > return baseset([2,2]) + > return baseset() + > return baseset([3,3,2,2]) + > + > mercurial.revset.symbols['r3232'] = r3232 + > EOF + $ cat >> $HGRCPATH << EOF + > [extensions] + > testrevset=$TESTTMP/testrevset.py + > EOF $ try() { > hg debugrevspec --debug "$@" @@ -21,13 +43,11 @@ $ echo b > b $ hg branch b marked working directory as branch b - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm1 $ rm a $ hg branch a-b-c- marked working directory as branch a-b-c- - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm2 -u Bob $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n' @@ -44,7 +64,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch +a+b+c+ marked working directory as branch +a+b+c+ - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm3 $ hg co 2 # interleave @@ -52,14 +71,12 @@ $ echo bb > b $ hg branch -- -a-b-c- marked working directory as branch -a-b-c- - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm4 -d "May 12 2005" $ hg co 3 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch !a/b/c/ marked working directory as branch !a/b/c/ - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm"5 bug" $ hg merge 4 @@ -67,23 +84,19 @@ (branch merge, don't forget to commit) $ hg branch _a_b_c_ marked working directory as branch _a_b_c_ - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm"6 issue619" $ hg branch .a.b.c. marked working directory as branch .a.b.c. - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm7 $ hg branch all marked working directory as branch all - (branches are permanent and global, did you want a bookmark?) $ hg co 4 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch é marked working directory as branch \xc3\xa9 (esc) - (branches are permanent and global, did you want a bookmark?) $ hg ci -Aqm9 $ hg tag -r6 1.0 @@ -104,6 +117,25 @@ 0 1 + $ try --optimize : + (rangeall + None) + * optimized: + (range + ('string', '0') + ('string', 'tip')) + * set: + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 $ try 3::6 (dagrange ('symbol', '3') @@ -115,16 +147,11 @@ 6 $ try '0|1|2' (or - (or - ('symbol', '0') - ('symbol', '1')) + ('symbol', '0') + ('symbol', '1') ('symbol', '2')) * set: - , - >, - > + 0 1 2 @@ -260,9 +287,7 @@ * set: , - , - >> + > 1 2 3 @@ -281,7 +306,7 @@ hg: parse error: date requires a string [255] $ log 'date' - hg: parse error: can't use date here + abort: unknown revision 'date'! [255] $ log 'date(' hg: parse error at 5: not a prefix: end @@ -289,11 +314,70 @@ $ log 'date(tip)' abort: invalid date: 'tip' [255] - $ log '"date"' + $ log '0:date' + abort: unknown revision 'date'! + [255] + $ log '::"date"' abort: unknown revision 'date'! [255] + $ hg book date -r 4 + $ log '0:date' + 0 + 1 + 2 + 3 + 4 + $ log '::date' + 0 + 1 + 2 + 4 + $ log '::"date"' + 0 + 1 + 2 + 4 $ log 'date(2005) and 1::' 4 + $ hg book -d date + +keyword arguments + + $ log 'extra(branch, value=a)' + 0 + + $ log 'extra(branch, a, b)' + hg: parse error: extra takes at most 2 arguments + [255] + $ log 'extra(a, label=b)' + hg: parse error: extra got multiple values for keyword argument 'label' + [255] + $ log 'extra(label=branch, default)' + hg: parse error: extra got an invalid argument + [255] + $ log 'extra(branch, foo+bar=baz)' + hg: parse error: extra got an invalid argument + [255] + $ log 'extra(unknown=branch)' + hg: parse error: extra got an unexpected keyword argument 'unknown' + [255] + + $ try 'foo=bar|baz' + (keyvalue + ('symbol', 'foo') + (or + ('symbol', 'bar') + ('symbol', 'baz'))) + hg: parse error: can't use a key-value pair in this context + [255] + +Test that symbols only get parsed as functions if there's an opening +parenthesis. + + $ hg book only -r 9 + $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark + 8 + 9 ancestor can accept 0 or more arguments @@ -311,6 +395,9 @@ 0 $ log 'ancestor(1,2,3,4,5)' 1 + +test ancestors + $ log 'ancestors(5)' 0 1 @@ -318,6 +405,12 @@ 5 $ log 'ancestor(ancestors(5))' 0 + $ log '::r3232()' + 0 + 1 + 2 + 3 + $ log 'author(bob)' 2 $ log 'author("re:bob|test")' @@ -555,6 +648,24 @@ 8 9 + $ try --optimize '(9)%(5)' + (only + (group + ('symbol', '9')) + (group + ('symbol', '5'))) + * optimized: + (func + ('symbol', 'only') + (list + ('symbol', '9') + ('symbol', '5'))) + * set: + + 2 + 4 + 8 + 9 Test the order of operations @@ -629,11 +740,29 @@ Test working-directory revision $ hg debugrevspec 'wdir()' - None -BROKEN: should include 'None' + 2147483647 $ hg debugrevspec 'tip or wdir()' 9 + 2147483647 $ hg debugrevspec '0:tip and wdir()' + $ log '0:wdir()' | tail -3 + 8 + 9 + 2147483647 + $ log 'wdir():0' | head -3 + 2147483647 + 9 + 8 + $ log 'wdir():wdir()' + 2147483647 + $ log '(all() + wdir()) & min(. + wdir())' + 9 + $ log '(all() + wdir()) & max(. + wdir())' + 2147483647 + $ log '(all() + wdir()) & first(wdir() + .)' + 2147483647 + $ log '(all() + wdir()) & last(. + wdir())' + 2147483647 $ log 'outgoing()' 8 @@ -796,6 +925,239 @@ 4 5 +test that more than one `-r`s are combined in the right order and deduplicated: + + $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)' + 3 + 4 + 5 + 2 + 0 + 1 + +test that `or` operation skips duplicated revisions from right-hand side + + $ try 'reverse(1::5) or ancestors(4)' + (or + (func + ('symbol', 'reverse') + (dagrange + ('symbol', '1') + ('symbol', '5'))) + (func + ('symbol', 'ancestors') + ('symbol', '4'))) + * set: + , + > + 5 + 3 + 1 + 0 + 2 + 4 + $ try 'sort(ancestors(4) or reverse(1::5))' + (func + ('symbol', 'sort') + (or + (func + ('symbol', 'ancestors') + ('symbol', '4')) + (func + ('symbol', 'reverse') + (dagrange + ('symbol', '1') + ('symbol', '5'))))) + * set: + , + > + 0 + 1 + 2 + 3 + 4 + 5 + +test optimization of trivial `or` operation + + $ try --optimize '0|(1)|"2"|-2|tip|null' + (or + ('symbol', '0') + (group + ('symbol', '1')) + ('string', '2') + (negate + ('symbol', '2')) + ('symbol', 'tip') + ('symbol', 'null')) + * optimized: + (func + ('symbol', '_list') + ('string', '0\x001\x002\x00-2\x00tip\x00null')) + * set: + + 0 + 1 + 2 + 8 + 9 + -1 + + $ try --optimize '0|1|2:3' + (or + ('symbol', '0') + ('symbol', '1') + (range + ('symbol', '2') + ('symbol', '3'))) + * optimized: + (or + (func + ('symbol', '_list') + ('string', '0\x001')) + (range + ('symbol', '2') + ('symbol', '3'))) + * set: + , + > + 0 + 1 + 2 + 3 + + $ try --optimize '0:1|2|3:4|5|6' + (or + (range + ('symbol', '0') + ('symbol', '1')) + ('symbol', '2') + (range + ('symbol', '3') + ('symbol', '4')) + ('symbol', '5') + ('symbol', '6')) + * optimized: + (or + (range + ('symbol', '0') + ('symbol', '1')) + ('symbol', '2') + (range + ('symbol', '3') + ('symbol', '4')) + (func + ('symbol', '_list') + ('string', '5\x006'))) + * set: + , + >, + , + >> + 0 + 1 + 2 + 3 + 4 + 5 + 6 + +test that `_list` should be narrowed by provided `subset` + + $ log '0:2 and (null|1|2|3)' + 1 + 2 + +test that `_list` should remove duplicates + + $ log '0|1|2|1|2|-1|tip' + 0 + 1 + 2 + 9 + +test unknown revision in `_list` + + $ log '0|unknown' + abort: unknown revision 'unknown'! + [255] + +test integer range in `_list` + + $ log '-1|-10' + 9 + 0 + + $ log '-10|-11' + abort: unknown revision '-11'! + [255] + + $ log '9|10' + abort: unknown revision '10'! + [255] + +test '0000' != '0' in `_list` + + $ log '0|0000' + 0 + -1 + +test that chained `or` operations make balanced addsets + + $ try '0:1|1:2|2:3|3:4|4:5' + (or + (range + ('symbol', '0') + ('symbol', '1')) + (range + ('symbol', '1') + ('symbol', '2')) + (range + ('symbol', '2') + ('symbol', '3')) + (range + ('symbol', '3') + ('symbol', '4')) + (range + ('symbol', '4') + ('symbol', '5'))) + * set: + , + >, + , + , + >>> + 0 + 1 + 2 + 3 + 4 + 5 + +test that chained `or` operations never eat up stack (issue4624) +(uses `0:1` instead of `0` to avoid future optimization of trivial revisions) + + $ hg log -T '{rev}\n' -r "`python -c "print '|'.join(['0:1'] * 500)"`" + 0 + 1 + +test that repeated `-r` options never eat up stack (issue4565) +(uses `-r 0::1` to avoid possible optimization at old-style parser) + + $ hg log -T '{rev}\n' `python -c "for i in xrange(500): print '-r 0::1 ',"` + 0 + 1 + check that conversion to only works $ try --optimize '::3 - ::1' (minus @@ -1039,6 +1401,17 @@ hg: parse error: unknown identifier: babar [255] +Bogus function with a similar internal name doesn't suggest the internal name + $ log 'matches()' + hg: parse error: unknown identifier: matches + (did you mean 'matching'?) + [255] + +Undocumented functions aren't suggested as similar either + $ log 'wdir2()' + hg: parse error: unknown identifier: wdir2 + [255] + multiple revspecs $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n' @@ -1211,9 +1584,7 @@ * set: , - , - >> + > 3 1 2 @@ -1238,6 +1609,44 @@ 5 +test chained `or` operations are flattened at parsing phase + + $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc + $ try 'chainedorops(0:1, 1:2, 2:3)' + (func + ('symbol', 'chainedorops') + (list + (list + (range + ('symbol', '0') + ('symbol', '1')) + (range + ('symbol', '1') + ('symbol', '2'))) + (range + ('symbol', '2') + ('symbol', '3')))) + (or + (range + ('symbol', '0') + ('symbol', '1')) + (range + ('symbol', '1') + ('symbol', '2')) + (range + ('symbol', '2') + ('symbol', '3'))) + * set: + , + , + >> + 0 + 1 + 2 + 3 + test variable isolation, variable placeholders are rewritten as string then parsed and matched again as string. Check they do not leak too far away. @@ -1306,8 +1715,7 @@ , >>> + >> 9 $ try 'd(2:5)' diff -r 501c51d60792 -r 96a38d44ba09 tests/test-rollback.t --- a/tests/test-rollback.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-rollback.t Sat Jul 18 17:32:38 2015 -0500 @@ -74,16 +74,16 @@ $ hg bookmark foo $ hg commit -m'modify a again' $ echo b > b + $ hg bookmark bar -r default #making bar active, before the transaction $ hg commit -Am'add b' adding b $ hg log --template '{rev} {branch} {desc|firstline}\n' 2 test add b 1 test modify a again 0 default add a again - $ hg update default + $ hg update bar 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - (leaving bookmark foo) - $ hg bookmark bar + (activating bookmark bar) $ cat .hg/undo.branch ; echo test $ hg rollback -f @@ -94,7 +94,7 @@ default $ cat .hg/bookmarks.current ; echo bar - $ hg bookmark --delete foo + $ hg bookmark --delete foo bar rollback by pretxncommit saves commit message (issue1635) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-run-tests.py --- a/tests/test-run-tests.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-run-tests.py Sat Jul 18 17:32:38 2015 -0500 @@ -3,6 +3,7 @@ run-test.t only checks positive matches and can not see warnings (both by design) """ +from __future__ import print_function import os, re # this is hack to make sure no escape characters are inserted into the output @@ -11,27 +12,37 @@ import doctest run_tests = __import__('run-tests') +def prn(ex): + m = ex.args[0] + if isinstance(m, str): + print(m) + else: + print(m.decode('utf-8')) + def lm(expected, output): r"""check if output matches expected does it generally work? - >>> lm('H*e (glob)\n', 'Here\n') + >>> lm(b'H*e (glob)\n', b'Here\n') True fail on bad test data - >>> try: lm('a\n','a') - ... except AssertionError, ex: print ex + >>> try: lm(b'a\n',b'a') + ... except AssertionError as ex: print(ex) missing newline - >>> try: lm('single backslash\n', 'single \backslash\n') - ... except AssertionError, ex: print ex + >>> try: lm(b'single backslash\n', b'single \backslash\n') + ... except AssertionError as ex: prn(ex) single backslash or unknown char """ - assert expected.endswith('\n') and output.endswith('\n'), 'missing newline' - assert not re.search(r'[^ \w\\/\r\n()*?]', expected + output), \ - 'single backslash or unknown char' + assert (expected.endswith(b'\n') + and output.endswith(b'\n')), 'missing newline' + assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \ + b'single backslash or unknown char' match = run_tests.TTest.linematch(expected, output) if isinstance(match, str): return 'special: ' + match + elif isinstance(match, bytes): + return 'special: ' + match.decode('utf-8') else: return bool(match) # do not return match object @@ -43,15 +54,15 @@ >>> os.altsep = True valid match on windows - >>> lm('g/a*/d (glob)\n', 'g\\abc/d\n') + >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n') True direct matching, glob unnecessary - >>> lm('g/b (glob)\n', 'g/b\n') + >>> lm(b'g/b (glob)\n', b'g/b\n') 'special: -glob' missing glob - >>> lm('/g/c/d/fg\n', '\\g\\c\\d/fg\n') + >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n') 'special: +glob' restore os.altsep @@ -67,15 +78,15 @@ >>> os.altsep = False backslash does not match slash - >>> lm('h/a* (glob)\n', 'h\\ab\n') + >>> lm(b'h/a* (glob)\n', b'h\\ab\n') False direct matching glob can not be recognized - >>> lm('h/b (glob)\n', 'h/b\n') + >>> lm(b'h/b (glob)\n', b'h/b\n') True missing glob can not not be recognized - >>> lm('/h/c/df/g/\n', '\\h/c\\df/g\\\n') + >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n') False restore os.altsep diff -r 501c51d60792 -r 96a38d44ba09 tests/test-run-tests.t --- a/tests/test-run-tests.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-run-tests.t Sat Jul 18 17:32:38 2015 -0500 @@ -10,7 +10,7 @@ Smoke test ============ - $ $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE + $ run-tests.py $HGTEST_RUN_TESTS_PURE # Ran 0 tests, 0 skipped, 0 warned, 0 failed. @@ -21,10 +21,12 @@ > $ echo babar > babar > $ echo xyzzy + > never happens (?) > xyzzy + > nor this (?) > EOF - $ $TESTDIR/run-tests.py --with-hg=`which hg` + $ run-tests.py --with-hg=`which hg` . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. @@ -39,10 +41,10 @@ > EOF >>> fh = open('test-failure-unicode.t', 'wb') - >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) - >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) + >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None + >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None - $ $TESTDIR/run-tests.py --with-hg=`which hg` + $ run-tests.py --with-hg=`which hg` --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -71,7 +73,7 @@ [1] test --xunit support - $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml + $ run-tests.py --with-hg=`which hg` --xunit=xunit.xml --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -127,7 +129,7 @@ test for --retest ==================== - $ $TESTDIR/run-tests.py --with-hg=`which hg` --retest + $ run-tests.py --with-hg=`which hg` --retest --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -150,18 +152,18 @@ successful - $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t + $ run-tests.py --with-hg=`which hg` test-success.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. success w/ keyword - $ $TESTDIR/run-tests.py --with-hg=`which hg` -k xyzzy + $ run-tests.py --with-hg=`which hg` -k xyzzy . # Ran 2 tests, 1 skipped, 0 warned, 0 failed. failed - $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t + $ run-tests.py --with-hg=`which hg` test-failure.t --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -180,7 +182,7 @@ [1] failure w/ keyword - $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes + $ run-tests.py --with-hg=`which hg` -k rataxes --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -206,7 +208,7 @@ $ cat > test-serve-fail.t < $ echo 'abort: child process failed to start blah' > EOF - $ $TESTDIR/run-tests.py --with-hg=`which hg` test-serve-fail.t + $ run-tests.py --with-hg=`which hg` test-serve-fail.t ERROR: test-serve-fail.t output changed ! @@ -222,7 +224,7 @@ Running In Debug Mode ====================== - $ $TESTDIR/run-tests.py --with-hg=`which hg` --debug 2>&1 | grep -v pwd + $ run-tests.py --with-hg=`which hg` --debug 2>&1 | grep -v pwd + echo *SALT* 0 0 (glob) *SALT* 0 0 (glob) + echo babar @@ -237,8 +239,8 @@ *SALT* 2 0 (glob) + echo xyzzy xyzzy - + echo *SALT* 4 0 (glob) - *SALT* 4 0 (glob) + + echo *SALT* 6 0 (glob) + *SALT* 6 0 (glob) . # Ran 2 tests, 0 skipped, 0 warned, 0 failed. @@ -248,7 +250,7 @@ (duplicate the failing test to get predictable output) $ cp test-failure.t test-failure-copy.t - $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t -n + $ run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t -n !! Failed test-failure*.t: output changed (glob) Failed test-failure*.t: output changed (glob) @@ -258,9 +260,9 @@ failures in parallel with --first should only print one failure >>> f = open('test-nothing.t', 'w') - >>> f.write('foo\n' * 1024) - >>> f.write(' $ sleep 1') - $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 --first + >>> f.write('foo\n' * 1024) and None + >>> f.write(' $ sleep 1') and None + $ run-tests.py --with-hg=`which hg` --jobs 2 --first --- $TESTTMP/test-failure*.t (glob) +++ $TESTTMP/test-failure*.t.err (glob) @@ -290,7 +292,7 @@ Refuse the fix - $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i + $ echo 'n' | run-tests.py --with-hg=`which hg` -i --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -316,7 +318,7 @@ Interactive with custom view - $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i --view echo + $ echo 'n' | run-tests.py --with-hg=`which hg` -i --view echo $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob) Accept this change? [n]* (glob) ERROR: test-failure.t output changed @@ -328,7 +330,7 @@ View the fix - $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` --view echo + $ echo 'y' | run-tests.py --with-hg=`which hg` --view echo $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob) ERROR: test-failure.t output changed @@ -346,7 +348,7 @@ $ echo " saved backup bundle to \$TESTTMP/foo.hg (glob)" >> test-failure.t $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t $ echo " saved backup bundle to \$TESTTMP/*.hg (glob)" >> test-failure.t - $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` -i 2>&1 | \ + $ echo 'y' | run-tests.py --with-hg=`which hg` -i 2>&1 | \ > sed -e 's,(glob)$,&<,g' --- $TESTTMP/test-failure.t @@ -384,7 +386,7 @@ No Diff =============== - $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff + $ run-tests.py --with-hg=`which hg` --nodiff !. Failed test-failure.t: output changed # Ran 2 tests, 0 skipped, 0 warned, 1 failed. @@ -394,22 +396,22 @@ test for --time ================== - $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time + $ run-tests.py --with-hg=`which hg` test-success.t --time . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. # Producing time report - cuser csys real Test - \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) + start end cuser csys real Test + \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) test for --time with --job enabled ==================================== - $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2 + $ run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2 . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. # Producing time report - cuser csys real Test - \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) + start end cuser csys real Test + \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re) Skips ================ @@ -417,7 +419,7 @@ > $ echo xyzzy > #require false > EOF - $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff + $ run-tests.py --with-hg=`which hg` --nodiff !.s Skipped test-skip.t: skipped Failed test-failure.t: output changed @@ -425,13 +427,13 @@ python hash seed: * (glob) [1] - $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy + $ run-tests.py --with-hg=`which hg` --keyword xyzzy .s Skipped test-skip.t: skipped # Ran 2 tests, 2 skipped, 0 warned, 0 failed. Skips with xml - $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \ + $ run-tests.py --with-hg=`which hg` --keyword xyzzy \ > --xunit=xunit.xml .s Skipped test-skip.t: skipped @@ -444,7 +446,7 @@ Missing skips or blacklisted skips don't count as executed: $ echo test-failure.t > blacklist - $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \ + $ run-tests.py --with-hg=`which hg` --blacklist=blacklist \ > test-failure.t test-bogus.t ss Skipped test-bogus.t: Doesn't exist @@ -456,7 +458,7 @@ test for --json ================== - $ $TESTDIR/run-tests.py --with-hg=`which hg` --json + $ run-tests.py --with-hg=`which hg` --json --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -480,23 +482,75 @@ "test-failure.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) "result": "failure", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) "time": "\s*[\d\.]{4,5}" (re) }, ? (re) "test-skip.t": { "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) "result": "skip", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) "time": "\s*[\d\.]{4,5}" (re) }, ? (re) "test-success.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) "result": "success", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) "time": "\s*[\d\.]{4,5}" (re) } } (no-eol) +Test that failed test accepted through interactive are properly reported: + + $ cp test-failure.t backup + $ echo y | run-tests.py --with-hg=`which hg` --json -i + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,4 +1,4 @@ + $ echo babar + - rataxes + + babar + This is a noop statement so that + this test is still more bytes than success. + Accept this change? [n] ..s + Skipped test-skip.t: skipped + # Ran 2 tests, 1 skipped, 0 warned, 0 failed. + + $ cat report.json + testreport ={ + "test-failure.t": [\{] (re) + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) + "result": "success", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + }, ? (re) + "test-skip.t": { + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) + "result": "skip", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + }, ? (re) + "test-success.t": [\{] (re) + "csys": "\s*[\d\.]{4,5}", ? (re) + "cuser": "\s*[\d\.]{4,5}", ? (re) + "end": "\s*[\d\.]{4,5}", ? (re) + "result": "success", ? (re) + "start": "\s*[\d\.]{4,5}", ? (re) + "time": "\s*[\d\.]{4,5}" (re) + } + } (no-eol) + $ mv backup test-failure.t + #endif backslash on end of line with glob matching is handled properly @@ -506,9 +560,64 @@ > foo * \ (glob) > EOF - $ $TESTDIR/run-tests.py --with-hg=`which hg` test-glob-backslash.t + $ run-tests.py --with-hg=`which hg` test-glob-backslash.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. $ rm -f test-glob-backslash.t +Test reusability for third party tools +====================================== + + $ mkdir "$TESTTMP"/anothertests + $ cd "$TESTTMP"/anothertests + +test that `run-tests.py` can execute hghave, even if it runs not in +Mercurial source tree. + + $ cat > test-hghave.t < #require true + > $ echo foo + > foo + > EOF + $ run-tests.py test-hghave.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + +test that RUNTESTDIR refers the directory, in which `run-tests.py` now +running is placed. + + $ cat > test-runtestdir.t < - $TESTDIR, in which test-run-tests.t is placed + > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime) + > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime) + > + > $ test "\$TESTDIR" = "$TESTTMP"/anothertests + > $ test "\$RUNTESTDIR" = "$TESTDIR" + > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py + > #!/usr/bin/env python + > # + > # check-code - a style and portability checker for Mercurial + > EOF + $ run-tests.py test-runtestdir.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + +#if execbit + +test that TESTDIR is referred in PATH + + $ cat > custom-command.sh < #!/bin/sh + > echo "hello world" + > EOF + $ chmod +x custom-command.sh + $ cat > test-testdir-path.t < $ custom-command.sh + > hello world + > EOF + $ run-tests.py test-testdir-path.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + +#endif diff -r 501c51d60792 -r 96a38d44ba09 tests/test-serve.t --- a/tests/test-serve.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-serve.t Sat Jul 18 17:32:38 2015 -0500 @@ -9,7 +9,7 @@ > cat hg.pid >> "$DAEMON_PIDS" > echo % errors > cat errors.log - > "$TESTDIR/killdaemons.py" hg.pid + > killdaemons.py hg.pid > } $ hg init test diff -r 501c51d60792 -r 96a38d44ba09 tests/test-setdiscovery.t --- a/tests/test-setdiscovery.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-setdiscovery.t Sat Jul 18 17:32:38 2015 -0500 @@ -14,13 +14,13 @@ > hg -R a debugdiscovery b --verbose --old > echo > echo "% -- a -> b set" - > hg -R a debugdiscovery b --verbose --debug + > hg -R a debugdiscovery b --verbose --debug --config progress.debug=true > echo > echo "% -- b -> a tree" - > hg -R b debugdiscovery a --verbose --old + > hg -R b debugdiscovery a --verbose --old --config > echo > echo "% -- b -> a set" - > hg -R b debugdiscovery a --verbose --debug + > hg -R b debugdiscovery a --verbose --debug --config progress.debug=true > cd .. > } @@ -305,7 +305,7 @@ updating to branch b 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R a debugdiscovery b --debug --verbose + $ hg -R a debugdiscovery b --debug --verbose --config progress.debug=true comparing with b query 1; heads searching for changes @@ -346,7 +346,7 @@ searching for changes e64a39e7da8b - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling "GET /?cmd=capabilities HTTP/1.1" 200 - "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db diff -r 501c51d60792 -r 96a38d44ba09 tests/test-share.t --- a/tests/test-share.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-share.t Sat Jul 18 17:32:38 2015 -0500 @@ -99,7 +99,7 @@ $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid >> $DAEMON_PIDS - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-file/' + $ get-with-headers.py localhost:$HGPORT 'raw-file/' 200 Script output follows @@ -299,5 +299,5 @@ Explicitly kill daemons to let the test exit on Windows - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-shelve.t --- a/tests/test-shelve.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-shelve.t Sat Jul 18 17:32:38 2015 -0500 @@ -5,6 +5,8 @@ > [defaults] > diff = --nodates --git > qnew = --date '0 0' + > [shelve] + > maxbackups = 2 > EOF $ hg init repo @@ -60,6 +62,7 @@ -m --message TEXT use text as shelve message -n --name NAME use the given name for the shelved commit -p --patch show patch + -i --interactive interactive mode, only works while creating a shelve --stat output diffstat-style summary of changes -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns @@ -84,6 +87,12 @@ nothing changed [1] +make sure shelve files were backed up + + $ ls .hg/shelve-backup + default.hg + default.patch + create an mq patch - shelving should work fine with a patch applied $ echo n > n @@ -153,6 +162,14 @@ $ hg shelve -d default $ hg qfinish -a -q +ensure shelve backups aren't overwritten + + $ ls .hg/shelve-backup/ + default-1.hg + default-1.patch + default.hg + default.patch + local edits should not prevent a shelved change from applying $ printf "z\na\n" > a/a @@ -168,6 +185,16 @@ apply it and make sure our state is as expected +(this also tests that same timestamp prevents backups from being +removed, even though there are more than 'maxbackups' backups) + + $ f -t .hg/shelve-backup/default.hg + .hg/shelve-backup/default.hg: file + $ touch -t 200001010000 .hg/shelve-backup/default.hg + $ f -t .hg/shelve-backup/default-1.hg + .hg/shelve-backup/default-1.hg: file + $ touch -t 200001010000 .hg/shelve-backup/default-1.hg + $ hg unshelve unshelving change 'default-01' $ hg status -C @@ -179,6 +206,17 @@ R b/b $ hg shelve -l +(both of default.hg and default-1.hg should be still kept, because it +is difficult to decide actual order of them from same timestamp) + + $ ls .hg/shelve-backup/ + default-01.hg + default-01.patch + default-1.hg + default-1.patch + default.hg + default.patch + $ hg unshelve abort: no shelved changes to apply! [255] @@ -233,6 +271,14 @@ c R b/b +ensure old shelve backups are being deleted automatically + + $ ls .hg/shelve-backup/ + default-01.hg + default-01.patch + wibble.hg + wibble.patch + cause unshelving to result in a merge with 'a' conflicting $ hg shelve -q @@ -782,6 +828,7 @@ bookmarks: *test commit: 2 unknown (clean) update: (current) + phases: 5 draft $ hg shelve --delete --stat abort: options '--delete' and '--stat' may not be used together @@ -813,6 +860,9 @@ $ cat foo/foo foo a + $ hg shelve --interactive --config ui.interactive=false + abort: running non-interactively + [255] $ hg shelve --interactive << EOF > y > y @@ -862,4 +912,45 @@ c x x - $ cd .. + +shelve --patch and shelve --stat should work with a single valid shelfname + + $ hg up --clean . + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg shelve --list + $ echo 'patch a' > shelf-patch-a + $ hg add shelf-patch-a + $ hg shelve + shelved as default + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo 'patch b' > shelf-patch-b + $ hg add shelf-patch-b + $ hg shelve + shelved as default-01 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg shelve --patch default default-01 + abort: --patch expects a single shelf + [255] + $ hg shelve --stat default default-01 + abort: --stat expects a single shelf + [255] + $ hg shelve --patch default + default (* ago) changes to 'create conflict' (glob) + + diff --git a/shelf-patch-a b/shelf-patch-a + new file mode 100644 + --- /dev/null + +++ b/shelf-patch-a + @@ -0,0 +1,1 @@ + +patch a + $ hg shelve --stat default + default (* ago) changes to 'create conflict' (glob) + shelf-patch-a | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + $ hg shelve --patch nonexistentshelf + abort: cannot find shelf nonexistentshelf + [255] + $ hg shelve --stat nonexistentshelf + abort: cannot find shelf nonexistentshelf + [255] + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-ssh-bundle1.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ssh-bundle1.t Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,512 @@ +This test is a duplicate of 'test-http.t' feel free to factor out +parts that are not bundle1/bundle2 specific. + + $ cat << EOF >> $HGRCPATH + > [experimental] + > # This test is dedicated to interaction through old bundle + > bundle2-exp = False + > EOF + + +This test tries to exercise the ssh functionality with a dummy script + +creating 'remote' repo + + $ hg init remote + $ cd remote + $ echo this > foo + $ echo this > fooO + $ hg ci -A -m "init" foo fooO + +insert a closed branch (issue4428) + + $ hg up null + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg branch closed + marked working directory as branch closed + (branches are permanent and global, did you want a bookmark?) + $ hg ci -mc0 + $ hg ci --close-branch -mc1 + $ hg up -q default + +configure for serving + + $ cat < .hg/hgrc + > [server] + > uncompressed = True + > + > [hooks] + > changegroup = printenv.py changegroup-in-remote 0 ../dummylog + > EOF + $ cd .. + +repo not found error + + $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local + remote: abort: there is no Mercurial repository here (.hg not found)! + abort: no suitable response from remote hg! + [255] + +non-existent absolute path + + $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local + remote: abort: there is no Mercurial repository here (.hg not found)! + abort: no suitable response from remote hg! + [255] + +clone remote via stream + + $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream + streaming all changes + 4 files to transfer, 615 bytes of data + transferred 615 bytes in * seconds (*) (glob) + searching for changes + no changes found + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd local-stream + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 2 files, 3 changesets, 2 total revisions + $ hg branches + default 0:1160648e36ce + $ cd .. + +clone bookmarks via stream + + $ hg -R local-stream book mybook + $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2 + streaming all changes + 4 files to transfer, 615 bytes of data + transferred 615 bytes in * seconds (*) (glob) + searching for changes + no changes found + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd stream2 + $ hg book + mybook 0:1160648e36ce + $ cd .. + $ rm -rf local-stream stream2 + +clone remote via pull + + $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 2 files + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + +verify + + $ cd local + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 2 files, 3 changesets, 2 total revisions + $ echo '[hooks]' >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup-in-local 0 ../dummylog" >> .hg/hgrc + +empty default pull + + $ hg paths + default = ssh://user@dummy/remote + $ hg pull -e "python \"$TESTDIR/dummyssh\"" + pulling from ssh://user@dummy/remote + searching for changes + no changes found + +pull from wrong ssh URL + + $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist + pulling from ssh://user@dummy/doesnotexist + remote: abort: there is no Mercurial repository here (.hg not found)! + abort: no suitable response from remote hg! + [255] + +local change + + $ echo bleah > foo + $ hg ci -m "add" + +updating rc + + $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc + $ echo "[ui]" >> .hg/hgrc + $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc + +find outgoing + + $ hg out ssh://user@dummy/remote + comparing with ssh://user@dummy/remote + searching for changes + changeset: 3:a28a9d1a809c + tag: tip + parent: 0:1160648e36ce + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add + + +find incoming on the remote side + + $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local + comparing with ssh://user@dummy/local + searching for changes + changeset: 3:a28a9d1a809c + tag: tip + parent: 0:1160648e36ce + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add + + +find incoming on the remote side (using absolute path) + + $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`" + comparing with ssh://user@dummy/$TESTTMP/local + searching for changes + changeset: 3:a28a9d1a809c + tag: tip + parent: 0:1160648e36ce + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add + + +push + + $ hg push + pushing to ssh://user@dummy/remote + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + $ cd ../remote + +check remote tip + + $ hg tip + changeset: 3:a28a9d1a809c + tag: tip + parent: 0:1160648e36ce + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add + + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 2 files, 4 changesets, 3 total revisions + $ hg cat -r tip foo + bleah + $ echo z > z + $ hg ci -A -m z z + created new head + +test pushkeys and bookmarks + + $ cd ../local + $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces + bookmarks + namespaces + phases + $ hg book foo -r 0 + $ hg out -B + comparing with ssh://user@dummy/remote + searching for changed bookmarks + foo 1160648e36ce + $ hg push -B foo + pushing to ssh://user@dummy/remote + searching for changes + no changes found + exporting bookmark foo + [1] + $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks + foo 1160648e36cec0054048a7edc4110c6f84fde594 + $ hg book -f foo + $ hg push --traceback + pushing to ssh://user@dummy/remote + searching for changes + no changes found + updating bookmark foo + [1] + $ hg book -d foo + $ hg in -B + comparing with ssh://user@dummy/remote + searching for changed bookmarks + foo a28a9d1a809c + $ hg book -f -r 0 foo + $ hg pull -B foo + pulling from ssh://user@dummy/remote + no changes found + updating bookmark foo + $ hg book -d foo + $ hg push -B foo + pushing to ssh://user@dummy/remote + searching for changes + no changes found + deleting remote bookmark foo + [1] + +a bad, evil hook that prints to stdout + + $ cat < $TESTTMP/badhook + > import sys + > sys.stdout.write("KABOOM\n") + > EOF + + $ echo '[hooks]' >> ../remote/.hg/hgrc + $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc + $ echo r > r + $ hg ci -A -m z r + +push should succeed even though it has an unexpected response + + $ hg push + pushing to ssh://user@dummy/remote + searching for changes + remote has heads on branch 'default' that are not known locally: 6c0482d977a3 + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: KABOOM + $ hg -R ../remote heads + changeset: 5:1383141674ec + tag: tip + parent: 3:a28a9d1a809c + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: z + + changeset: 4:6c0482d977a3 + parent: 0:1160648e36ce + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: z + + +clone bookmarks + + $ hg -R ../remote bookmark test + $ hg -R ../remote bookmarks + * test 4:6c0482d977a3 + $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks + requesting all changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 5 changes to 4 files (+1 heads) + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R local-bookmarks bookmarks + test 4:6c0482d977a3 + +passwords in ssh urls are not supported +(we use a glob here because different Python versions give different +results here) + + $ hg push ssh://user:erroneouspwd@dummy/remote + pushing to ssh://user:*@dummy/remote (glob) + abort: password in URL not supported! + [255] + + $ cd .. + +hide outer repo + $ hg init + +Test remote paths with spaces (issue2983): + + $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo" + $ touch "$TESTTMP/a repo/test" + $ hg -R 'a repo' commit -A -m "test" + adding test + $ hg -R 'a repo' tag tag + $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo" + 73649e48688a + + $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO" + abort: unknown revision 'noNoNO'! + [255] + +Test (non-)escaping of remote paths with spaces when cloning (issue3145): + + $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo" + destination directory: a repo + abort: destination 'a repo' is not empty + [255] + +Test hg-ssh using a helper script that will restore PYTHONPATH (which might +have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right +parameters: + + $ cat > ssh.sh << EOF + > userhost="\$1" + > SSH_ORIGINAL_COMMAND="\$2" + > export SSH_ORIGINAL_COMMAND + > PYTHONPATH="$PYTHONPATH" + > export PYTHONPATH + > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo" + > EOF + + $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo" + 73649e48688a + + $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo" + remote: Illegal repository "$TESTTMP/a'repo" (glob) + abort: no suitable response from remote hg! + [255] + + $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo" + remote: Illegal command "hacking -R 'a'\''repo' serve --stdio" + abort: no suitable response from remote hg! + [255] + + $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh" + Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation + [255] + +Test hg-ssh in read-only mode: + + $ cat > ssh.sh << EOF + > userhost="\$1" + > SSH_ORIGINAL_COMMAND="\$2" + > export SSH_ORIGINAL_COMMAND + > PYTHONPATH="$PYTHONPATH" + > export PYTHONPATH + > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote" + > EOF + + $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local + requesting all changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 5 changes to 4 files (+1 heads) + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ cd read-only-local + $ echo "baz" > bar + $ hg ci -A -m "unpushable commit" bar + $ hg push --ssh "sh ../ssh.sh" + pushing to ssh://user@dummy/*/remote (glob) + searching for changes + remote: Permission denied + remote: abort: pretxnopen.hg-ssh hook failed + remote: Permission denied + remote: pushkey-abort: prepushkey.hg-ssh hook failed + updating 6c0482d977a3 to public failed! + [1] + + $ cd .. + +stderr from remote commands should be printed before stdout from local code (issue4336) + + $ hg clone remote stderr-ordering + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd stderr-ordering + $ cat >> localwrite.py << EOF + > from mercurial import exchange, extensions + > + > def wrappedpush(orig, repo, *args, **kwargs): + > res = orig(repo, *args, **kwargs) + > repo.ui.write('local stdout\n') + > return res + > + > def extsetup(ui): + > extensions.wrapfunction(exchange, 'push', wrappedpush) + > EOF + + $ cat >> .hg/hgrc << EOF + > [paths] + > default-push = ssh://user@dummy/remote + > [ui] + > ssh = python "$TESTDIR/dummyssh" + > [extensions] + > localwrite = localwrite.py + > EOF + + $ echo localwrite > foo + $ hg commit -m 'testing localwrite' + $ hg push + pushing to ssh://user@dummy/remote + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: KABOOM + local stdout + +debug output + + $ hg pull --debug ssh://user@dummy/remote + pulling from ssh://user@dummy/remote + running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re) + sending hello command + sending between command + remote: 345 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 + remote: 1 + preparing listkeys for "bookmarks" + sending listkeys command + received listkey for "bookmarks": 45 bytes + query 1; heads + sending batch command + searching for changes + all remote heads known locally + no changes found + preparing listkeys for "phases" + sending listkeys command + received listkey for "phases": 15 bytes + checking for updated bookmarks + + $ cd .. + + $ cat dummylog + Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio + Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R local serve --stdio + Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + Got arguments 1:user@dummy 2:hg init 'a repo' + Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio + Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio + Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio + Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio + Got arguments 1:user@dummy 2:hg -R remote serve --stdio + changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + Got arguments 1:user@dummy 2:hg -R remote serve --stdio diff -r 501c51d60792 -r 96a38d44ba09 tests/test-ssh-clone-r.t diff -r 501c51d60792 -r 96a38d44ba09 tests/test-ssh.t --- a/tests/test-ssh.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-ssh.t Sat Jul 18 17:32:38 2015 -0500 @@ -1,4 +1,3 @@ - This test tries to exercise the ssh functionality with a dummy script @@ -28,7 +27,7 @@ > uncompressed = True > > [hooks] - > changegroup = python "$TESTDIR/printenv.py" changegroup-in-remote 0 ../dummylog + > changegroup = printenv.py changegroup-in-remote 0 ../dummylog > EOF $ cd .. @@ -105,7 +104,7 @@ checking files 2 files, 3 changesets, 2 total revisions $ echo '[hooks]' >> .hg/hgrc - $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup-in-local 0 ../dummylog" >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup-in-local 0 ../dummylog" >> .hg/hgrc empty default pull @@ -398,11 +397,8 @@ pushing to ssh://user@dummy/*/remote (glob) searching for changes remote: Permission denied - remote: abort: prechangegroup.hg-ssh hook failed - remote: Permission denied - remote: pushkey-abort: prepushkey.hg-ssh hook failed - updating 6c0482d977a3 to public failed! - [1] + abort: pretxnopen.hg-ssh hook failed + [255] $ cd .. @@ -445,6 +441,32 @@ remote: KABOOM local stdout +debug output + + $ hg pull --debug ssh://user@dummy/remote + pulling from ssh://user@dummy/remote + running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re) + sending hello command + sending between command + remote: 345 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 + remote: 1 + query 1; heads + sending batch command + searching for changes + all remote heads known locally + no changes found + sending getbundle command + bundle2-input-bundle: with-transaction + bundle2-input-part: "listkeys" (params: 1 mandatory) supported + bundle2-input-part: "listkeys" (params: 1 mandatory) supported + bundle2-input-part: total payload size 45 + bundle2-input-bundle: 1 parts total + checking for updated bookmarks + preparing listkeys for "phases" + sending listkeys command + received listkey for "phases": 15 bytes + $ cd .. $ cat dummylog @@ -459,7 +481,7 @@ Got arguments 1:user@dummy 2:hg -R local serve --stdio Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio - changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio @@ -469,7 +491,7 @@ Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio - changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg init 'a repo' Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio @@ -477,4 +499,5 @@ Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio - changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob) + Got arguments 1:user@dummy 2:hg -R remote serve --stdio diff -r 501c51d60792 -r 96a38d44ba09 tests/test-static-http.t --- a/tests/test-static-http.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-static-http.t Sat Jul 18 17:32:38 2015 -0500 @@ -60,7 +60,7 @@ $ rm .hg/cache/* $ cd ../local $ echo '[hooks]' >> .hg/hgrc - $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc + $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc $ hg pull pulling from static-http://localhost:$HGPORT/remote searching for changes @@ -129,6 +129,7 @@ crosschecking files in changesets and manifests checking files 3 files, 1 changesets, 3 total revisions + checking subrepo links $ cat a a $ hg paths @@ -159,4 +160,4 @@ $ hg clone static-http://localhost:$HGPORT/notarepo local3 abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository! [255] - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-status.t --- a/tests/test-status.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-status.t Sat Jul 18 17:32:38 2015 -0500 @@ -212,6 +212,33 @@ $ mkdir ignoreddir $ touch ignoreddir/file +Test templater support: + + $ hg status -AT "[{status}]\t{if(copy, '{copy} -> ')}{path}\n" + [M] .hgignore + [A] added + [A] modified -> copied + [R] removed + [!] deleted + [?] ignored + [?] unknown + [I] ignoreddir/file + [C] modified + $ hg status -AT default + M .hgignore + A added + A copied + modified + R removed + ! deleted + ? ignored + ? unknown + I ignoreddir/file + C modified + $ hg status -T compact + abort: "status" not in template map + [255] + hg status ignoreddir/file: $ hg status ignoreddir/file diff -r 501c51d60792 -r 96a38d44ba09 tests/test-strip.t --- a/tests/test-strip.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-strip.t Sat Jul 18 17:32:38 2015 -0500 @@ -526,6 +526,7 @@ branch: default commit: 1 modified, 1 unknown, 1 unresolved update: (current) + phases: 2 draft mq: 3 unapplied $ echo c > b @@ -553,6 +554,7 @@ branch: default commit: 1 modified, 1 unknown update: (current) + phases: 1 draft mq: 3 unapplied Strip adds, removes, modifies with --keep @@ -781,17 +783,11 @@ removing c d: other deleted -> r removing d - updating: d 2/2 files (100.00%) 0 files updated, 0 files merged, 2 files removed, 0 files unresolved 2 changesets found list of changesets: 6625a516847449b6f0fa3737b9ba56e9f0f3032c d8db9d1372214336d2b5570f20ee468d2c72fa8b - bundling: 1/2 changesets (50.00%) - bundling: 2/2 changesets (100.00%) - bundling: 1/2 manifests (50.00%) - bundling: 2/2 manifests (100.00%) - bundling: d 1/1 files (100.00%) saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg (glob) invalid branchheads cache (served): tip differs truncating cache/rbc-revs-v1 to 24 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo-deep-nested-change.t --- a/tests/test-subrepo-deep-nested-change.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo-deep-nested-change.t Sat Jul 18 17:32:38 2015 -0500 @@ -168,7 +168,60 @@ A foo/bar/abc A sub1/foo R sub1/sub2/test.txt + +Archive wdir() with subrepos + $ hg rm main + $ hg archive -S -r 'wdir()' ../wdir + $ diff -r . ../wdir | grep -v '\.hg$' + Only in ../wdir: .hg_archival.txt + + $ find ../wdir -type f | sort + ../wdir/.hg_archival.txt + ../wdir/.hgsub + ../wdir/.hgsubstate + ../wdir/foo/bar/abc + ../wdir/sub1/.hgsub + ../wdir/sub1/.hgsubstate + ../wdir/sub1/foo + ../wdir/sub1/sub1 + ../wdir/sub1/sub2/folder/test.txt + ../wdir/sub1/sub2/sub2 + + $ cat ../wdir/.hg_archival.txt + repo: 7f491f53a367861f47ee64a80eb997d1f341b77a + node: 9bb10eebee29dc0f1201dcf5977b811a540255fd+ + branch: default + latesttag: null + latesttagdistance: 4 + changessincelatesttag: 4 + +Attempting to archive 'wdir()' with a missing file is handled gracefully + $ rm sub1/sub1 + $ rm -r ../wdir + $ hg archive -v -S -r 'wdir()' ../wdir + $ find ../wdir -type f | sort + ../wdir/.hg_archival.txt + ../wdir/.hgsub + ../wdir/.hgsubstate + ../wdir/foo/bar/abc + ../wdir/sub1/.hgsub + ../wdir/sub1/.hgsubstate + ../wdir/sub1/foo + ../wdir/sub1/sub2/folder/test.txt + ../wdir/sub1/sub2/sub2 + +Continue relative path printing + subrepos $ hg update -Cq + $ rm -r ../wdir + $ hg archive -S -r 'wdir()' ../wdir + $ cat ../wdir/.hg_archival.txt + repo: 7f491f53a367861f47ee64a80eb997d1f341b77a + node: 9bb10eebee29dc0f1201dcf5977b811a540255fd + branch: default + latesttag: null + latesttagdistance: 4 + changessincelatesttag: 4 + $ touch sub1/sub2/folder/bar $ hg addremove sub1/sub2 adding sub1/sub2/folder/bar (glob) @@ -209,6 +262,41 @@ sub1/sub2/folder/bar (glob) sub1/sub2/x.txt (glob) + $ hg files -S "set:eol('dos') or eol('unix') or size('<= 0')" + .hgsub + .hgsubstate + foo/bar/abc (glob) + main + sub1/.hgsub (glob) + sub1/.hgsubstate (glob) + sub1/foo (glob) + sub1/sub1 (glob) + sub1/sub2/folder/bar (glob) + sub1/sub2/x.txt (glob) + + $ hg files -r '.^' -S "set:eol('dos') or eol('unix')" + .hgsub + .hgsubstate + main + sub1/.hgsub (glob) + sub1/.hgsubstate (glob) + sub1/sub1 (glob) + sub1/sub2/folder/test.txt (glob) + sub1/sub2/sub2 (glob) + sub1/sub2/test.txt (glob) + + $ hg files sub1 + sub1/.hgsub (glob) + sub1/.hgsubstate (glob) + sub1/foo (glob) + sub1/sub1 (glob) + sub1/sub2/folder/bar (glob) + sub1/sub2/x.txt (glob) + + $ hg files sub1/sub2 + sub1/sub2/folder/bar (glob) + sub1/sub2/x.txt (glob) + $ hg files -S -r '.^' sub1/sub2/folder sub1/sub2/folder/test.txt (glob) @@ -216,7 +304,7 @@ sub1/sub2/missing: no such file in rev 78026e779ea6 (glob) [1] - $ hg files -S -r '.^' sub1/ + $ hg files -r '.^' sub1/ sub1/.hgsub (glob) sub1/.hgsubstate (glob) sub1/sub1 (glob) @@ -224,7 +312,7 @@ sub1/sub2/sub2 (glob) sub1/sub2/test.txt (glob) - $ hg files -S -r '.^' sub1/sub2 + $ hg files -r '.^' sub1/sub2 sub1/sub2/folder/test.txt (glob) sub1/sub2/sub2 (glob) sub1/sub2/test.txt (glob) @@ -329,17 +417,17 @@ Exclude normal files from main and sub-sub repo - $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf.tgz + $ hg --config extensions.largefiles= archive -S -X '**.txt' -p '.' ../archive_lf.tgz $ tar -tzf ../archive_lf.tgz | sort - archive_lf/.hgsub - archive_lf/.hgsubstate - archive_lf/large.bin - archive_lf/main - archive_lf/sub1/.hgsub - archive_lf/sub1/.hgsubstate - archive_lf/sub1/sub1 - archive_lf/sub1/sub2/large.bin - archive_lf/sub1/sub2/sub2 + .hgsub + .hgsubstate + large.bin + main + sub1/.hgsub + sub1/.hgsubstate + sub1/sub1 + sub1/sub2/large.bin + sub1/sub2/sub2 Include normal files from within a largefiles subrepo @@ -434,8 +522,69 @@ ? sub1/sub2/untracked.txt ? sub1/sub2/x.txt $ hg add sub1/sub2 + + $ hg archive -S -r 'wdir()' ../wdir2 + $ diff -r . ../wdir2 | grep -v '\.hg$' + Only in ../wdir2: .hg_archival.txt + Only in .: .hglf + Only in .: foo + Only in ./sub1/sub2: large.bin + Only in ./sub1/sub2: test.txt + Only in ./sub1/sub2: untracked.txt + Only in ./sub1/sub2: x.txt + $ find ../wdir2 -type f | sort + ../wdir2/.hg_archival.txt + ../wdir2/.hgsub + ../wdir2/.hgsubstate + ../wdir2/large.bin + ../wdir2/main + ../wdir2/sub1/.hgsub + ../wdir2/sub1/.hgsubstate + ../wdir2/sub1/sub1 + ../wdir2/sub1/sub2/folder/test.txt + ../wdir2/sub1/sub2/large.dat + ../wdir2/sub1/sub2/sub2 + $ hg status -S -mac -n | sort + .hgsub + .hgsubstate + large.bin + main + sub1/.hgsub + sub1/.hgsubstate + sub1/sub1 + sub1/sub2/folder/test.txt + sub1/sub2/large.dat + sub1/sub2/sub2 + $ hg ci -Sqm 'forget testing' +Test 'wdir()' modified file archiving with largefiles + $ echo 'mod' > main + $ echo 'mod' > large.bin + $ echo 'mod' > sub1/sub2/large.dat + $ hg archive -S -r 'wdir()' ../wdir3 + $ diff -r . ../wdir3 | grep -v '\.hg$' + Only in ../wdir3: .hg_archival.txt + Only in .: .hglf + Only in .: foo + Only in ./sub1/sub2: large.bin + Only in ./sub1/sub2: test.txt + Only in ./sub1/sub2: untracked.txt + Only in ./sub1/sub2: x.txt + $ find ../wdir3 -type f | sort + ../wdir3/.hg_archival.txt + ../wdir3/.hgsub + ../wdir3/.hgsubstate + ../wdir3/large.bin + ../wdir3/main + ../wdir3/sub1/.hgsub + ../wdir3/sub1/.hgsubstate + ../wdir3/sub1/sub1 + ../wdir3/sub1/sub2/folder/test.txt + ../wdir3/sub1/sub2/large.dat + ../wdir3/sub1/sub2/sub2 + $ hg up -Cq + Test issue4330: commit a directory where only normal files have changed $ touch foo/bar/large.dat $ hg add --large foo/bar/large.dat @@ -560,4 +709,96 @@ $ hg rollback -q $ hg update -Cq . +Interaction with extdiff, largefiles and subrepos + + $ hg --config extensions.extdiff= extdiff -S + + $ hg --config extensions.extdiff= extdiff -r '.^' -S + diff -Npru cloned.*/.hgsub cloned/.hgsub (glob) + --- cloned.*/.hgsub * +0000 (glob) + +++ cloned/.hgsub * +0000 (glob) + @@ -1,2 +1 @@ + sub1 = ../sub1 + -sub3 = sub3 + diff -Npru cloned.*/.hgsubstate cloned/.hgsubstate (glob) + --- cloned.*/.hgsubstate * +0000 (glob) + +++ cloned/.hgsubstate * +0000 (glob) + @@ -1,2 +1 @@ + 7a36fa02b66e61f27f3d4a822809f159479b8ab2 sub1 + -b1a26de6f2a045a9f079323693614ee322f1ff7e sub3 + [1] + + $ hg --config extensions.extdiff= extdiff -r 0 -r '.^' -S + diff -Npru cloned.*/.hglf/b.dat cloned.*/.hglf/b.dat (glob) + --- cloned.*/.hglf/b.dat * (glob) + +++ cloned.*/.hglf/b.dat * (glob) + @@ -0,0 +1 @@ + +da39a3ee5e6b4b0d3255bfef95601890afd80709 + diff -Npru cloned.*/.hglf/foo/bar/large.dat cloned.*/.hglf/foo/bar/large.dat (glob) + --- cloned.*/.hglf/foo/bar/large.dat * (glob) + +++ cloned.*/.hglf/foo/bar/large.dat * (glob) + @@ -0,0 +1 @@ + +2f6933b5ee0f5fdd823d9717d8729f3c2523811b + diff -Npru cloned.*/.hglf/large.bin cloned.*/.hglf/large.bin (glob) + --- cloned.*/.hglf/large.bin * (glob) + +++ cloned.*/.hglf/large.bin * (glob) + @@ -0,0 +1 @@ + +7f7097b041ccf68cc5561e9600da4655d21c6d18 + diff -Npru cloned.*/.hgsub cloned.*/.hgsub (glob) + --- cloned.*/.hgsub * (glob) + +++ cloned.*/.hgsub * (glob) + @@ -1 +1,2 @@ + sub1 = ../sub1 + +sub3 = sub3 + diff -Npru cloned.*/.hgsubstate cloned.*/.hgsubstate (glob) + --- cloned.*/.hgsubstate * (glob) + +++ cloned.*/.hgsubstate * (glob) + @@ -1 +1,2 @@ + -fc3b4ce2696f7741438c79207583768f2ce6b0dd sub1 + +7a36fa02b66e61f27f3d4a822809f159479b8ab2 sub1 + +b1a26de6f2a045a9f079323693614ee322f1ff7e sub3 + diff -Npru cloned.*/foo/bar/def cloned.*/foo/bar/def (glob) + --- cloned.*/foo/bar/def * (glob) + +++ cloned.*/foo/bar/def * (glob) + @@ -0,0 +1 @@ + +changed + diff -Npru cloned.*/main cloned.*/main (glob) + --- cloned.*/main * (glob) + +++ cloned.*/main * (glob) + @@ -1 +1 @@ + -main + +foo + diff -Npru cloned.*/sub1/.hgsubstate cloned.*/sub1/.hgsubstate (glob) + --- cloned.*/sub1/.hgsubstate * (glob) + +++ cloned.*/sub1/.hgsubstate * (glob) + @@ -1 +1 @@ + -c57a0840e3badd667ef3c3ef65471609acb2ba3c sub2 + +c77908c81ccea3794a896c79e98b0e004aee2e9e sub2 + diff -Npru cloned.*/sub1/sub2/folder/test.txt cloned.*/sub1/sub2/folder/test.txt (glob) + --- cloned.*/sub1/sub2/folder/test.txt * (glob) + +++ cloned.*/sub1/sub2/folder/test.txt * (glob) + @@ -0,0 +1 @@ + +subfolder + diff -Npru cloned.*/sub1/sub2/sub2 cloned.*/sub1/sub2/sub2 (glob) + --- cloned.*/sub1/sub2/sub2 * (glob) + +++ cloned.*/sub1/sub2/sub2 * (glob) + @@ -1 +1 @@ + -sub2 + +modified + diff -Npru cloned.*/sub3/a.txt cloned.*/sub3/a.txt (glob) + --- cloned.*/sub3/a.txt * (glob) + +++ cloned.*/sub3/a.txt * (glob) + @@ -0,0 +1 @@ + +xyz + [1] + + $ echo mod > sub1/sub2/sub2 + $ hg --config extensions.extdiff= extdiff -S + --- */cloned.*/sub1/sub2/sub2 * (glob) + +++ */cloned/sub1/sub2/sub2 * (glob) + @@ -1 +1 @@ + -modified + +mod + [1] + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo-git.t --- a/tests/test-subrepo-git.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo-git.t Sat Jul 18 17:32:38 2015 -0500 @@ -325,13 +325,13 @@ ../archive_x/s ../archive_x/s/g - $ hg -R ../tc archive -S ../archive.tgz 2>/dev/null - $ tar -tzf ../archive.tgz | sort - archive/.hg_archival.txt - archive/.hgsub - archive/.hgsubstate - archive/a - archive/s/g + $ hg -R ../tc archive -S ../archive.tgz --prefix '.' 2>/dev/null + $ tar -tzf ../archive.tgz | sort | grep -v pax_global_header + .hg_archival.txt + .hgsub + .hgsubstate + a + s/g create nested repo @@ -1105,5 +1105,21 @@ ? s/c.c ? s/cpp.cpp ? s/foobar.orig + $ hg revert --all -q + +make sure we show changed files, rather than changed subtrees + $ mkdir s/foo + $ touch s/foo/bwuh + $ hg add s/foo/bwuh + $ hg commit -S -m "add bwuh" + committing subrepository s + $ hg status -S --change . + M .hgsubstate + A s/foo/bwuh + ? s/barfoo + ? s/c.c + ? s/cpp.cpp + ? s/foobar.orig + ? s/snake.python.orig $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo-missing.t --- a/tests/test-subrepo-missing.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo-missing.t Sat Jul 18 17:32:38 2015 -0500 @@ -23,8 +23,8 @@ $ cp .hgsubstate .hgsubstate.old >>> file('.hgsubstate', 'wb').write('\ninvalid') - $ hg st --subrepos - abort: invalid subrepository revision specifier in '.hgsubstate' line 2 + $ hg st --subrepos --cwd $TESTTMP -R $TESTTMP/repo + abort: invalid subrepository revision specifier in 'repo/.hgsubstate' line 2 (glob) [255] $ mv .hgsubstate.old .hgsubstate @@ -44,9 +44,9 @@ delete .hgsub and update $ rm .hgsub - $ hg up 0 - warning: subrepo spec file '.hgsub' not found - warning: subrepo spec file '.hgsub' not found + $ hg up 0 --cwd $TESTTMP -R $TESTTMP/repo + warning: subrepo spec file 'repo/.hgsub' not found (glob) + warning: subrepo spec file 'repo/.hgsub' not found (glob) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg st warning: subrepo spec file '.hgsub' not found @@ -106,4 +106,19 @@ $ hg --hidden cat subrepo/a foo +verify will warn if locked-in subrepo revisions are hidden or missing + + $ hg ci -m "amended subrepo (again)" + $ hg --config extensions.strip= --hidden strip -R subrepo -qr 'tip' + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 2 files, 5 changesets, 5 total revisions + checking subrepo links + subrepo 'subrepo' is hidden in revision a66de08943b6 + subrepo 'subrepo' is hidden in revision 674d05939c1e + subrepo 'subrepo' not found in revision a7d05d9055a4 + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo-recursion.t --- a/tests/test-subrepo-recursion.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo-recursion.t Sat Jul 18 17:32:38 2015 -0500 @@ -258,6 +258,7 @@ > [extensions] > progress = > [progress] + > disable=False > assume-tty = 1 > delay = 0 > # set changedelay really large so we don't see nested topics @@ -273,28 +274,18 @@ $ hg archive --subrepos ../archive \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) - archiving [ ] 0/3\r (no-eol) (esc) - archiving [=============> ] 1/3\r (no-eol) (esc) archiving [=============> ] 1/3\r (no-eol) (esc) archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [==========================================>] 3/3\r (no-eol) (esc) archiving [==========================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) archiving (foo) [ ] 0/3\r (no-eol) (esc) - archiving (foo) [ ] 0/3\r (no-eol) (esc) - archiving (foo) [===========> ] 1/3\r (no-eol) (esc) archiving (foo) [===========> ] 1/3\r (no-eol) (esc) archiving (foo) [=======================> ] 2/3\r (no-eol) (esc) - archiving (foo) [=======================> ] 2/3\r (no-eol) (esc) - archiving (foo) [====================================>] 3/3\r (no-eol) (esc) archiving (foo) [====================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc) - archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc) - archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) \r (no-eol) (esc) $ find ../archive | sort @@ -312,34 +303,41 @@ Test archiving to zip file (unzip output is unstable): - $ hg archive --subrepos ../archive.zip + $ hg archive --subrepos --prefix '.' ../archive.zip \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) - archiving [ ] 0/3\r (no-eol) (esc) archiving [=============> ] 1/3\r (no-eol) (esc) - archiving [=============> ] 1/3\r (no-eol) (esc) archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [==========================================>] 3/3\r (no-eol) (esc) archiving [==========================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) archiving (foo) [ ] 0/3\r (no-eol) (esc) - archiving (foo) [ ] 0/3\r (no-eol) (esc) - archiving (foo) [===========> ] 1/3\r (no-eol) (esc) archiving (foo) [===========> ] 1/3\r (no-eol) (esc) archiving (foo) [=======================> ] 2/3\r (no-eol) (esc) - archiving (foo) [=======================> ] 2/3\r (no-eol) (esc) - archiving (foo) [====================================>] 3/3\r (no-eol) (esc) archiving (foo) [====================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc) - archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc) - archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) \r (no-eol) (esc) +(unzip date formating is unstable, we do not care about it and glob it out) + + $ unzip -l ../archive.zip + Archive: ../archive.zip + Length [ ]* Date [ ]* Time [ ]* Name (re) + [\- ]* (re) + 172 [0-9:\- ]* .hg_archival.txt (re) + 10 [0-9:\- ]* .hgsub (re) + 45 [0-9:\- ]* .hgsubstate (re) + 3 [0-9:\- ]* x.txt (re) + 10 [0-9:\- ]* foo/.hgsub (re) + 45 [0-9:\- ]* foo/.hgsubstate (re) + 9 [0-9:\- ]* foo/y.txt (re) + 9 [0-9:\- ]* foo/bar/z.txt (re) + [\- ]* (re) + 303 [ ]* 8 files (re) + Test archiving a revision that references a subrepo that is not yet cloned: @@ -363,15 +361,11 @@ $ cd ../empty #if hardlink - $ hg archive --subrepos -r tip ../archive.tar.gz + $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) - archiving [ ] 0/3\r (no-eol) (esc) archiving [=============> ] 1/3\r (no-eol) (esc) - archiving [=============> ] 1/3\r (no-eol) (esc) archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [==========================================>] 3/3\r (no-eol) (esc) archiving [==========================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) @@ -386,12 +380,8 @@ \r (no-eol) (esc) \r (no-eol) (esc) archiving (foo) [ ] 0/3\r (no-eol) (esc) - archiving (foo) [ ] 0/3\r (no-eol) (esc) - archiving (foo) [===========> ] 1/3\r (no-eol) (esc) archiving (foo) [===========> ] 1/3\r (no-eol) (esc) archiving (foo) [=======================> ] 2/3\r (no-eol) (esc) - archiving (foo) [=======================> ] 2/3\r (no-eol) (esc) - archiving (foo) [====================================>] 3/3\r (no-eol) (esc) archiving (foo) [====================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) @@ -404,8 +394,6 @@ \r (no-eol) (esc) \r (no-eol) (esc) archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc) - archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc) - archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc) \r (no-eol) (esc) cloning subrepo foo from $TESTTMP/repo/foo @@ -413,38 +401,29 @@ #else Note there's a slight output glitch on non-hardlink systems: the last "linking" progress topic never gets closed, leading to slight output corruption on that platform. - $ hg archive --subrepos -r tip ../archive.tar.gz + $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz \r (no-eol) (esc) archiving [ ] 0/3\r (no-eol) (esc) - archiving [ ] 0/3\r (no-eol) (esc) archiving [=============> ] 1/3\r (no-eol) (esc) - archiving [=============> ] 1/3\r (no-eol) (esc) archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [===========================> ] 2/3\r (no-eol) (esc) - archiving [==========================================>] 3/3\r (no-eol) (esc) archiving [==========================================>] 3/3\r (no-eol) (esc) \r (no-eol) (esc) \r (no-eol) (esc) linking [ <=> ] 1\r (no-eol) (esc) - \r (no-eol) (esc) - \r (no-eol) (esc) - \r (no-eol) (esc) - \r (no-eol) (esc) - linking [ <=> ] 1cloning subrepo foo from $TESTTMP/repo/foo cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob) #endif Archive + subrepos uses '/' for all component separators $ tar -tzf ../archive.tar.gz | sort - archive/.hg_archival.txt - archive/.hgsub - archive/.hgsubstate - archive/foo/.hgsub - archive/foo/.hgsubstate - archive/foo/bar/z.txt - archive/foo/y.txt - archive/x.txt + .hg_archival.txt + .hgsub + .hgsubstate + foo/.hgsub + foo/.hgsubstate + foo/bar/z.txt + foo/y.txt + x.txt The newly cloned subrepos contain no working copy: diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo-relative-path.t --- a/tests/test-subrepo-relative-path.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo-relative-path.t Sat Jul 18 17:32:38 2015 -0500 @@ -70,7 +70,7 @@ source ../sub revision 863c1745b441bd97a8c4a096e87793073f4fb215 - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py subrepo paths with ssh urls diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo-svn.t --- a/tests/test-subrepo-svn.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo-svn.t Sat Jul 18 17:32:38 2015 -0500 @@ -72,6 +72,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft $ hg ci -moops nothing changed [1] @@ -96,6 +97,7 @@ branch: default commit: 1 modified, 1 subrepos update: (current) + phases: 2 draft $ hg commit --subrepos -m 'Message!' | grep -v Updating committing subrepository s Sending*s/alpha (glob) @@ -136,6 +138,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft $ echo a > s/a @@ -548,7 +551,7 @@ Test archive - $ hg archive -S ../archive-all --debug + $ hg archive -S ../archive-all --debug --config progress.debug=true archiving: 0/2 files (0.00%) archiving: .hgsub 1/2 files (50.00%) archiving: .hgsubstate 2/2 files (100.00%) @@ -560,7 +563,7 @@ archiving (s): 1/2 files (50.00%) archiving (s): 2/2 files (100.00%) - $ hg archive -S ../archive-exclude --debug -X **old + $ hg archive -S ../archive-exclude --debug --config progress.debug=true -X **old archiving: 0/2 files (0.00%) archiving: .hgsub 1/2 files (50.00%) archiving: .hgsubstate 2/2 files (100.00%) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-subrepo.t --- a/tests/test-subrepo.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-subrepo.t Sat Jul 18 17:32:38 2015 -0500 @@ -38,6 +38,7 @@ branch: default commit: 1 added, 1 subrepos update: (current) + phases: 1 draft $ hg ci -m1 test handling .hgsubstate "added" explicitly. @@ -83,6 +84,7 @@ branch: default commit: 1 subrepos update: (current) + phases: 2 draft $ hg co -C 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg sum @@ -91,6 +93,7 @@ branch: default commit: (clean) update: (current) + phases: 2 draft commands that require a clean repo should respect subrepos @@ -113,6 +116,7 @@ branch: default commit: 1 subrepos update: (current) + phases: 2 draft $ hg ci -m2 committing subrepository s committing subrepository s/ss (glob) @@ -122,6 +126,7 @@ branch: default commit: (clean) update: (current) + phases: 3 draft test handling .hgsubstate "modified" explicitly. @@ -255,7 +260,6 @@ branchmerge: True, force: False, partial: False ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4 .hgsubstate: versions differ -> m - updating: .hgsubstate 1/1 files (100.00%) subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg getting subrepo t @@ -264,7 +268,6 @@ ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a t: remote is newer -> g getting t - updating: t 1/1 files (100.00%) 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg debugsub @@ -283,7 +286,6 @@ branchmerge: True, force: False, partial: False ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf .hgsubstate: versions differ -> m - updating: .hgsubstate 1/1 files (100.00%) subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4 subrepo t: both sides changed subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198) @@ -295,7 +297,6 @@ ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198 preserving t for resolve of t t: versions differ -> m - updating: t 1/1 files (100.00%) picked tool 'internal:merge' for t (binary False symlink False) merging t my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a @@ -938,14 +939,32 @@ test if untracked file is not overwritten +(this also tests that updated .hgsubstate is treated as "modified", +when 'merge.update()' is aborted before 'merge.recordupdates()', even +if none of mode, size and timestamp of it isn't changed on the +filesystem (see also issue4583)) + $ echo issue3276_ok > repo/s/b $ hg -R repo2 push -f -q $ touch -t 200001010000 repo/.hgsubstate - $ hg -R repo status --config debug.dirstate.delaywrite=2 repo/.hgsubstate + + $ cat >> repo/.hg/hgrc < [fakedirstatewritetime] + > # emulate invoking dirstate.write() via repo.status() + > # at 2000-01-01 00:00 + > fakenow = 200001010000 + > + > [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > EOF $ hg -R repo update b: untracked file differs abort: untracked files in working directory differ from files in requested revision (in subrepo s) [255] + $ cat >> repo/.hg/hgrc < [extensions] + > fakedirstatewritetime = ! + > EOF $ cat repo/s/b issue3276_ok @@ -1489,7 +1508,17 @@ > [paths] > default=../issue3781-dest/ > EOF - $ hg push + $ hg push --config experimental.bundle2-exp=False + pushing to $TESTTMP/issue3781-dest (glob) + pushing subrepo s to $TESTTMP/issue3781-dest/s + searching for changes + no changes found + searching for changes + no changes found + [1] +# clean the push cache + $ rm s/.hg/cache/storehash/* + $ hg push --config experimental.bundle2-exp=True pushing to $TESTTMP/issue3781-dest (glob) pushing subrepo s to $TESTTMP/issue3781-dest/s searching for changes @@ -1692,4 +1721,37 @@ [paths] default = $TESTTMP/t/t default-push = /foo/bar/t + + $ cd $TESTTMP/t + $ hg up -qC 0 + $ echo 'bar' > bar.txt + $ hg ci -Am 'branch before subrepo add' + adding bar.txt + created new head + $ hg merge -r "first(subrepo('s'))" + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg status -S -X '.hgsub*' + A s/a + ? s/b + ? s/c + ? s/f1 + $ hg status -S --rev 'p2()' + A bar.txt + ? s/b + ? s/c + ? s/f1 + $ hg diff -S -X '.hgsub*' --nodates + diff -r 000000000000 s/a + --- /dev/null + +++ b/s/a + @@ -0,0 +1,1 @@ + +a + $ hg diff -S --rev 'p2()' --nodates + diff -r 7cf8cfea66e4 bar.txt + --- /dev/null + +++ b/bar.txt + @@ -0,0 +1,1 @@ + +bar + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-symlinks.t --- a/tests/test-symlinks.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-symlinks.t Sat Jul 18 17:32:38 2015 -0500 @@ -214,13 +214,13 @@ $ hg manifest --debug 2564acbe54bbbedfbf608479340b359f04597f80 644 @ dangling - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> nothing $ rm dangling $ ln -s void dangling $ hg commit -m 'change symlink' - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> void @@ -228,7 +228,7 @@ $ rm dangling $ ln -s empty dangling - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> empty @@ -236,13 +236,13 @@ $ hg revert -r 0 -a reverting dangling - $ "$TESTDIR/readlink.py" dangling + $ readlink.py dangling dangling -> nothing backups: - $ "$TESTDIR/readlink.py" *.orig + $ readlink.py *.orig dangling.orig -> empty $ rm *.orig $ hg up -C @@ -255,7 +255,7 @@ $ hg st -Cmard A dangling2 dangling - $ "$TESTDIR/readlink.py" dangling dangling2 + $ readlink.py dangling dangling2 dangling -> void dangling2 -> void diff -r 501c51d60792 -r 96a38d44ba09 tests/test-tag.t --- a/tests/test-tag.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-tag.t Sat Jul 18 17:32:38 2015 -0500 @@ -416,6 +416,12 @@ $ hg ci -A -m0 adding f0 $ hg tag tbase + $ hg up -qr '.^' + $ hg log -r 'wdir()' -T "{latesttagdistance}\n" + 1 + $ hg up -q + $ hg log -r 'wdir()' -T "{latesttagdistance}\n" + 2 $ cd .. $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone updating to branch default @@ -437,10 +443,28 @@ $ hg ci -A -m3 adding f3 $ hg tag -f t4 t5 t6 + + $ hg up -q '.^' + $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n" + 1 changes since t4:t5:t6 + $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n" + 0 changes since t4:t5:t6 + $ echo c5 > f3 + $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n" + 1 changes since t4:t5:t6 + $ hg up -qC + $ hg tag --remove t5 $ echo c4 > f4 + $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n" + 2 changes since t4:t6 + $ hg log -r '.' -T "{latesttag % '{latesttag}\n'}" + t4 + t6 $ hg ci -A -m4 adding f4 + $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n" + 4 changes since t4:t6 $ hg tag t2 $ hg tag -f t6 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-tags.t --- a/tests/test-tags.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-tags.t Sat Jul 18 17:32:38 2015 -0500 @@ -47,6 +47,8 @@ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg identify acb14030fe0a tip + $ hg identify -r 'wdir()' + acb14030fe0a tip $ cacheexists tag cache exists No fnodes cache because .hgtags file doesn't exist @@ -174,6 +176,8 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg id acb14030fe0a+ first + $ hg id -r 'wdir()' + acb14030fe0a+ first $ hg -v id acb14030fe0a+ first $ hg status @@ -625,3 +629,77 @@ globaltag 0:bbd179dfa0a7 $ cd .. + +Create a repository with tags data to test .hgtags fnodes transfer + + $ hg init tagsserver + $ cd tagsserver + $ cat > .hg/hgrc << EOF + > [experimental] + > bundle2-exp=True + > EOF + $ touch foo + $ hg -q commit -A -m initial + $ hg tag -m 'tag 0.1' 0.1 + $ echo second > foo + $ hg commit -m second + $ hg tag -m 'tag 0.2' 0.2 + $ hg tags + tip 3:40f0358cb314 + 0.2 2:f63cc8fe54e4 + 0.1 0:96ee1d7354c4 + $ cd .. + +Cloning should pull down hgtags fnodes mappings and write the cache file + + $ hg --config experimental.bundle2-exp=True clone --pull tagsserver tagsclient + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 2 files + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Missing tags2* files means the cache wasn't written through the normal mechanism. + + $ ls tagsclient/.hg/cache + branch2-served + hgtagsfnodes1 + rbc-names-v1 + rbc-revs-v1 + +Cache should contain the head only, even though other nodes have tags data + + $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1 + tagsclient/.hg/cache/hgtagsfnodes1: size=96 + 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....| + 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.| + +Running hg tags should produce tags2* file and not change cache + + $ hg -R tagsclient tags + tip 3:40f0358cb314 + 0.2 2:f63cc8fe54e4 + 0.1 0:96ee1d7354c4 + + $ ls tagsclient/.hg/cache + branch2-served + hgtagsfnodes1 + rbc-names-v1 + rbc-revs-v1 + tags2-visible + + $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1 + tagsclient/.hg/cache/hgtagsfnodes1: size=96 + 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....| + 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.| + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-transplant.t --- a/tests/test-transplant.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-transplant.t Sat Jul 18 17:32:38 2015 -0500 @@ -789,5 +789,92 @@ Explicitly kill daemons to let the test exit on Windows - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py + +Test that patch-ed files are treated as "modified", when transplant is +aborted by failure of patching, even if none of mode, size and +timestamp of them isn't changed on the filesystem (see also issue4583) + + $ cd t + + $ cat > $TESTTMP/abort.py < # emulate that patch.patch() is aborted at patching on "abort" file + > from mercurial import extensions, patch as patchmod + > def patch(orig, ui, repo, patchname, + > strip=1, prefix='', files=None, + > eolmode='strict', similarity=0): + > if files is None: + > files = set() + > r = orig(ui, repo, patchname, + > strip=strip, prefix=prefix, files=files, + > eolmode=eolmode, similarity=similarity) + > if 'abort' in files: + > raise patchmod.PatchError('intentional error while patching') + > return r + > def extsetup(ui): + > extensions.wrapfunction(patchmod, 'patch', patch) + > EOF + + $ echo X1 > r1 + $ hg diff --nodates r1 + diff -r a53251cdf717 r1 + --- a/r1 + +++ b/r1 + @@ -1,1 +1,1 @@ + -r1 + +X1 + $ hg commit -m "X1 as r1" + $ echo 'marking to abort patching' > abort + $ hg add abort + $ echo Y1 > r1 + $ hg diff --nodates r1 + diff -r 22c515968f13 r1 + --- a/r1 + +++ b/r1 + @@ -1,1 +1,1 @@ + -X1 + +Y1 + $ hg commit -m "Y1 as r1" + + $ hg update -q -C d11e3596cc1a + $ cat r1 + r1 + + $ cat >> .hg/hgrc < [fakedirstatewritetime] + > # emulate invoking dirstate.write() via repo.status() or markcommitted() + > # at 2000-01-01 00:00 + > fakenow = 200001010000 + > + > # emulate invoking patch.internalpatch() at 2000-01-01 00:00 + > [fakepatchtime] + > fakenow = 200001010000 + > + > [extensions] + > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py + > fakepatchtime = $TESTDIR/fakepatchtime.py + > abort = $TESTTMP/abort.py + > EOF + $ hg transplant "22c515968f13::" + applying 22c515968f13 + 22c515968f13 transplanted to * (glob) + applying e38700ba9dd3 + intentional error while patching + abort: fix up the merge and run hg transplant --continue + [255] + $ cat >> .hg/hgrc < [hooks] + > fakedirstatewritetime = ! + > fakepatchtime = ! + > abort = ! + > EOF + + $ cat r1 + Y1 + $ hg debugstate | grep ' r1$' + n 644 3 unset r1 + $ hg status -A r1 + M r1 + + $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-treediscovery-legacy.t --- a/tests/test-treediscovery-legacy.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-treediscovery-legacy.t Sat Jul 18 17:32:38 2015 -0500 @@ -8,7 +8,7 @@ > EOF $ cp $HGRCPATH $HGRCPATH-withcap - $ CAP="getbundle known changegroupsubset" + $ CAP="getbundle known changegroupsubset bundle2" $ . "$TESTDIR/notcapable" $ cp $HGRCPATH $HGRCPATH-nocap $ cp $HGRCPATH-withcap $HGRCPATH @@ -33,7 +33,7 @@ > cat hg.pid >> $DAEMON_PIDS > } $ tstop() { - > "$TESTDIR/killdaemons.py" $DAEMON_PIDS + > killdaemons.py > cp $HGRCPATH-withcap $HGRCPATH > } diff -r 501c51d60792 -r 96a38d44ba09 tests/test-treediscovery.t --- a/tests/test-treediscovery.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-treediscovery.t Sat Jul 18 17:32:38 2015 -0500 @@ -2,7 +2,7 @@ Tests discovery against servers without getbundle support: - $ CAP=getbundle + $ CAP="getbundle bundle2" $ . "$TESTDIR/notcapable" $ cat >> $HGRCPATH < [ui] @@ -21,7 +21,7 @@ > cat hg.pid >> $DAEMON_PIDS > } $ tstop() { - > "$TESTDIR/killdaemons.py" $DAEMON_PIDS + > killdaemons.py > [ "$1" ] && cut -d' ' -f6- access.log && cat errors.log > rm access.log errors.log > } @@ -516,7 +516,6 @@ "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 "GET /?cmd=capabilities HTTP/1.1" 200 - "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks - "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks "GET /?cmd=heads HTTP/1.1" 200 - "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-treemanifest.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-treemanifest.t Sat Jul 18 17:32:38 2015 -0500 @@ -0,0 +1,386 @@ + +Set up repo + + $ hg --config experimental.treemanifest=True init repo + $ cd repo + +Requirements get set on init + + $ grep treemanifest .hg/requires + treemanifest + +Without directories, looks like any other repo + + $ echo 0 > a + $ echo 0 > b + $ hg ci -Aqm initial + $ hg debugdata -m 0 + a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + +Submanifest is stored in separate revlog + + $ mkdir dir1 + $ echo 1 > dir1/a + $ echo 1 > dir1/b + $ echo 1 > e + $ hg ci -Aqm 'add dir1' + $ hg debugdata -m 1 + a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) + dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc) + e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) + $ hg debugdata --dir dir1 0 + a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) + b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) + +Can add nested directories + + $ mkdir dir1/dir1 + $ echo 2 > dir1/dir1/a + $ echo 2 > dir1/dir1/b + $ mkdir dir1/dir2 + $ echo 2 > dir1/dir2/a + $ echo 2 > dir1/dir2/b + $ hg ci -Aqm 'add dir1/dir1' + $ hg files -r . + a + b + dir1/a (glob) + dir1/b (glob) + dir1/dir1/a (glob) + dir1/dir1/b (glob) + dir1/dir2/a (glob) + dir1/dir2/b (glob) + e + +Revision is not created for unchanged directory + + $ mkdir dir2 + $ echo 3 > dir2/a + $ hg add dir2 + adding dir2/a (glob) + $ hg debugindex --dir dir1 > before + $ hg ci -qm 'add dir2' + $ hg debugindex --dir dir1 > after + $ diff before after + $ rm before after + +Removing directory does not create an revlog entry + + $ hg rm dir1/dir1 + removing dir1/dir1/a (glob) + removing dir1/dir1/b (glob) + $ hg debugindex --dir dir1/dir1 > before + $ hg ci -qm 'remove dir1/dir1' + $ hg debugindex --dir dir1/dir1 > after + $ diff before after + $ rm before after + +Check that hg files (calls treemanifest.walk()) works +without loading all directory revlogs + + $ hg co 'desc("add dir2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup + $ hg files -r . dir1 + dir1/a (glob) + dir1/b (glob) + dir1/dir1/a (glob) + dir1/dir1/b (glob) + dir1/dir2/a (glob) + dir1/dir2/b (glob) + +Check that status between revisions works (calls treemanifest.matches()) +without loading all directory revlogs + + $ hg status --rev 'desc("add dir1")' --rev . dir1 + A dir1/dir1/a + A dir1/dir1/b + A dir1/dir2/a + A dir1/dir2/b + $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2 + +Merge creates 2-parent revision of directory revlog + + $ echo 5 > dir1/a + $ hg ci -Aqm 'modify dir1/a' + $ hg co '.^' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 6 > dir1/b + $ hg ci -Aqm 'modify dir1/b' + $ hg merge 'desc("modify dir1/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'conflict-free merge involving dir1/' + $ cat dir1/a + 5 + $ cat dir1/b + 6 + $ hg debugindex --dir dir1 + rev offset length base linkrev nodeid p1 p2 + 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000 + 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000 + 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000 + 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000 + 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000 + 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72 + +Merge keeping directory from parent 1 does not create revlog entry. (Note that +dir1's manifest does change, but only because dir1/a's filelog changes.) + + $ hg co 'desc("add dir2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 8 > dir2/a + $ hg ci -m 'modify dir2/a' + created new head + + $ hg debugindex --dir dir2 > before + $ hg merge 'desc("modify dir1/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg revert -r 'desc("modify dir2/a")' . + reverting dir1/a (glob) + $ hg ci -m 'merge, keeping parent 1' + $ hg debugindex --dir dir2 > after + $ diff before after + $ rm before after + +Merge keeping directory from parent 2 does not create revlog entry. (Note that +dir2's manifest does change, but only because dir2/a's filelog changes.) + + $ hg co 'desc("modify dir2/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg debugindex --dir dir1 > before + $ hg merge 'desc("modify dir1/a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg revert -r 'desc("modify dir1/a")' . + reverting dir2/a (glob) + $ hg ci -m 'merge, keeping parent 2' + created new head + $ hg debugindex --dir dir1 > after + $ diff before after + $ rm before after + +Create flat source repo for tests with mixed flat/tree manifests + + $ cd .. + $ hg init repo-flat + $ cd repo-flat + +Create a few commits with flat manifest + + $ echo 0 > a + $ echo 0 > b + $ echo 0 > e + $ for d in dir1 dir1/dir1 dir1/dir2 dir2 + > do + > mkdir $d + > echo 0 > $d/a + > echo 0 > $d/b + > done + $ hg ci -Aqm initial + + $ echo 1 > a + $ echo 1 > dir1/a + $ echo 1 > dir1/dir1/a + $ hg ci -Aqm 'modify on branch 1' + + $ hg co 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 2 > b + $ echo 2 > dir1/b + $ echo 2 > dir1/dir1/b + $ hg ci -Aqm 'modify on branch 2' + + $ hg merge 1 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'merge of flat manifests to new flat manifest' + +Create clone with tree manifests enabled + + $ cd .. + $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 17 changes to 11 files + updating to branch default + 11 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd repo-mixed + $ test -f .hg/store/meta + [1] + $ grep treemanifest .hg/requires + treemanifest + +Commit should store revlog per directory + + $ hg co 1 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 3 > a + $ echo 3 > dir1/a + $ echo 3 > dir1/dir1/a + $ hg ci -m 'first tree' + created new head + $ find .hg/store/meta | sort + .hg/store/meta + .hg/store/meta/dir1 + .hg/store/meta/dir1/00manifest.i + .hg/store/meta/dir1/dir1 + .hg/store/meta/dir1/dir1/00manifest.i + .hg/store/meta/dir1/dir2 + .hg/store/meta/dir1/dir2/00manifest.i + .hg/store/meta/dir2 + .hg/store/meta/dir2/00manifest.i + +Merge of two trees + + $ hg co 2 + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 1 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'merge of flat manifests to new tree manifest' + created new head + $ hg diff -r 3 + +Parent of tree root manifest should be flat manifest, and two for merge + + $ hg debugindex -m + rev offset length base linkrev nodeid p1 p2 + 0 0 80 0 0 40536115ed9e 000000000000 000000000000 + 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000 + 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000 + 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255 + 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000 + 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255 + + +Status across flat/tree boundary should work + + $ hg status --rev '.^' --rev . + M a + M dir1/a + M dir1/dir1/a + + +Turning off treemanifest config has no effect + + $ hg debugindex .hg/store/meta/dir1/00manifest.i + rev offset length base linkrev nodeid p1 p2 + 0 0 125 0 4 63c9c0557d24 000000000000 000000000000 + 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000 + $ echo 2 > dir1/a + $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a' + $ hg debugindex .hg/store/meta/dir1/00manifest.i + rev offset length base linkrev nodeid p1 p2 + 0 0 125 0 4 63c9c0557d24 000000000000 000000000000 + 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000 + 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000 + +Create deeper repo with tree manifests. + + $ cd .. + $ hg --config experimental.treemanifest=True init deeprepo + $ cd deeprepo + + $ mkdir a + $ mkdir b + $ mkdir b/bar + $ mkdir b/bar/orange + $ mkdir b/bar/orange/fly + $ mkdir b/foo + $ mkdir b/foo/apple + $ mkdir b/foo/apple/bees + + $ touch a/one.txt + $ touch a/two.txt + $ touch b/bar/fruits.txt + $ touch b/bar/orange/fly/gnat.py + $ touch b/bar/orange/fly/housefly.txt + $ touch b/foo/apple/bees/flower.py + $ touch c.txt + $ touch d.py + + $ hg ci -Aqm 'initial' + +We'll see that visitdir works by removing some treemanifest revlogs and running +the files command with various parameters. + +Test files from the root. + + $ hg files -r . + a/one.txt (glob) + a/two.txt (glob) + b/bar/fruits.txt (glob) + b/bar/orange/fly/gnat.py (glob) + b/bar/orange/fly/housefly.txt (glob) + b/foo/apple/bees/flower.py (glob) + c.txt + d.py + +Excludes with a glob should not exclude everything from the glob's root + + $ hg files -r . -X 'b/fo?' b + b/bar/fruits.txt (glob) + b/bar/orange/fly/gnat.py (glob) + b/bar/orange/fly/housefly.txt (glob) + +Test files for a subdirectory. + + $ mv .hg/store/meta/a oldmf + $ hg files -r . b + b/bar/fruits.txt (glob) + b/bar/orange/fly/gnat.py (glob) + b/bar/orange/fly/housefly.txt (glob) + b/foo/apple/bees/flower.py (glob) + $ mv oldmf .hg/store/meta/a + +Test files with just includes and excludes. + + $ mv .hg/store/meta/a oldmf + $ mv .hg/store/meta/b/bar/orange/fly oldmf2 + $ mv .hg/store/meta/b/foo/apple/bees oldmf3 + $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees + b/bar/fruits.txt (glob) + $ mv oldmf .hg/store/meta/a + $ mv oldmf2 .hg/store/meta/b/bar/orange/fly + $ mv oldmf3 .hg/store/meta/b/foo/apple/bees + +Test files for a subdirectory, excluding a directory within it. + + $ mv .hg/store/meta/a oldmf + $ mv .hg/store/meta/b/foo oldmf2 + $ hg files -r . -X path:b/foo b + b/bar/fruits.txt (glob) + b/bar/orange/fly/gnat.py (glob) + b/bar/orange/fly/housefly.txt (glob) + $ mv oldmf .hg/store/meta/a + $ mv oldmf2 .hg/store/meta/b/foo + +Test files for a sub directory, including only a directory within it, and +including an unrelated directory. + + $ mv .hg/store/meta/a oldmf + $ mv .hg/store/meta/b/foo oldmf2 + $ hg files -r . -I path:b/bar/orange -I path:a b + b/bar/orange/fly/gnat.py (glob) + b/bar/orange/fly/housefly.txt (glob) + $ mv oldmf .hg/store/meta/a + $ mv oldmf2 .hg/store/meta/b/foo + +Test files for a pattern, including a directory, and excluding a directory +within that. + + $ mv .hg/store/meta/a oldmf + $ mv .hg/store/meta/b/foo oldmf2 + $ mv .hg/store/meta/b/bar/orange oldmf3 + $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange + b/bar/fruits.txt (glob) + $ mv oldmf .hg/store/meta/a + $ mv oldmf2 .hg/store/meta/b/foo + $ mv oldmf3 .hg/store/meta/b/bar/orange + diff -r 501c51d60792 -r 96a38d44ba09 tests/test-trusted.py --- a/tests/test-trusted.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-trusted.py Sat Jul 18 17:32:38 2015 -0500 @@ -169,7 +169,7 @@ def assertraises(f, exc=util.Abort): try: f() - except exc, inst: + except exc as inst: print 'raised', inst.__class__.__name__ else: print 'no exception?!' @@ -188,10 +188,10 @@ try: testui(user='abc', group='def', silent=True) -except error.ParseError, inst: +except error.ParseError as inst: print inst try: testui(debug=True, silent=True) -except error.ParseError, inst: +except error.ParseError as inst: print inst diff -r 501c51d60792 -r 96a38d44ba09 tests/test-ui-config.py --- a/tests/test-ui-config.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-ui-config.py Sat Jul 18 17:32:38 2015 -0500 @@ -39,7 +39,7 @@ print "---" try: print repr(testui.configbool('values', 'string')) -except error.ConfigError, inst: +except error.ConfigError as inst: print inst print repr(testui.configbool('values', 'bool1')) print repr(testui.configbool('values', 'bool2')) diff -r 501c51d60792 -r 96a38d44ba09 tests/test-unbundlehash.t --- a/tests/test-unbundlehash.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-unbundlehash.t Sat Jul 18 17:32:38 2015 -0500 @@ -2,6 +2,14 @@ Test wire protocol unbundle with hashed heads (capability: unbundlehash) + $ cat << EOF >> $HGRCPATH + > [experimental] + > # This tests is intended for bundle1 only. + > # bundle2 carries the head information inside the bundle itself and + > # always uses 'force' as the heads value. + > bundle2-exp = False + > EOF + Create a remote repository. $ hg init remote @@ -33,5 +41,5 @@ Explicitly kill daemons to let the test exit on Windows - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/test-unified-test.t --- a/tests/test-unified-test.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-unified-test.t Sat Jul 18 17:32:38 2015 -0500 @@ -88,14 +88,14 @@ testing hghave - $ "$TESTDIR/hghave" true - $ "$TESTDIR/hghave" false + $ hghave true + $ hghave false skipped: missing feature: nail clipper [1] - $ "$TESTDIR/hghave" no-true + $ hghave no-true skipped: system supports yak shaving [1] - $ "$TESTDIR/hghave" no-false + $ hghave no-false Conditional sections based on hghave: diff -r 501c51d60792 -r 96a38d44ba09 tests/test-up-local-change.t --- a/tests/test-up-local-change.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-up-local-change.t Sat Jul 18 17:32:38 2015 -0500 @@ -49,9 +49,7 @@ preserving a for resolve of a b: remote created -> g getting b - updating: b 1/2 files (50.00%) a: versions differ -> m - updating: a 2/2 files (100.00%) picked tool 'true' for a (binary False symlink False) merging a my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a @@ -72,9 +70,7 @@ preserving a for resolve of a b: other deleted -> r removing b - updating: b 1/2 files (50.00%) a: versions differ -> m - updating: a 2/2 files (100.00%) picked tool 'true' for a (binary False symlink False) merging a my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb @@ -103,9 +99,7 @@ preserving a for resolve of a b: remote created -> g getting b - updating: b 1/2 files (50.00%) a: versions differ -> m - updating: a 2/2 files (100.00%) picked tool 'true' for a (binary False symlink False) merging a my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a diff -r 501c51d60792 -r 96a38d44ba09 tests/test-update-reverse.t --- a/tests/test-update-reverse.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-update-reverse.t Sat Jul 18 17:32:38 2015 -0500 @@ -72,10 +72,8 @@ removing side1 side2: other deleted -> r removing side2 - updating: side2 2/3 files (66.67%) main: remote created -> g getting main - updating: main 3/3 files (100.00%) 1 files updated, 0 files merged, 2 files removed, 0 files unresolved $ ls diff -r 501c51d60792 -r 96a38d44ba09 tests/test-url-rev.t --- a/tests/test-url-rev.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-url-rev.t Sat Jul 18 17:32:38 2015 -0500 @@ -101,6 +101,7 @@ branch: default commit: (clean) update: (current) + phases: 4 draft remote: 2 outgoing $ hg -q outgoing '../clone#foo' 2:faba9097cad4 @@ -110,6 +111,7 @@ branch: default commit: (clean) update: (current) + phases: 4 draft remote: 1 outgoing $ hg -q --cwd ../clone incoming '../repo#foo' @@ -282,6 +284,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft remote: 1 outgoing $ hg summary --remote --config paths.default='../clone#foo' --config paths.default-push='../clone' @@ -290,6 +293,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft remote: 2 outgoing $ hg summary --remote --config paths.default='../clone' --config paths.default-push='../clone#foo' @@ -298,6 +302,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft remote: 1 outgoing $ hg clone -q -r 0 . ../another @@ -311,6 +316,7 @@ branch: default commit: (clean) update: (current) + phases: 1 draft remote: 1 outgoing $ cd .. diff -r 501c51d60792 -r 96a38d44ba09 tests/test-verify.t --- a/tests/test-verify.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-verify.t Sat Jul 18 17:32:38 2015 -0500 @@ -44,17 +44,19 @@ checking manifests crosschecking files in changesets and manifests checking files - data/FOO.txt.i@0: missing revlog! + warning: revlog 'data/FOO.txt.i' not in fncache! 0: empty or missing FOO.txt FOO.txt@0: f62022d3d590 in manifests not found - data/QUICK.txt.i@0: missing revlog! + warning: revlog 'data/QUICK.txt.i' not in fncache! 0: empty or missing QUICK.txt QUICK.txt@0: 88b857db8eba in manifests not found - data/bar.txt.i@0: missing revlog! + warning: revlog 'data/bar.txt.i' not in fncache! 0: empty or missing bar.txt bar.txt@0: 256559129457 in manifests not found 3 files, 1 changesets, 0 total revisions - 9 integrity errors encountered! + 3 warnings encountered! + hint: run "hg debugrebuildfncache" to recover from corrupt fncache + 6 integrity errors encountered! (first damaged changeset appears to be 0) [1] @@ -103,7 +105,7 @@ test revlog format 0 - $ "$TESTDIR/revlog-formatv0.py" + $ revlog-formatv0.py $ cd formatv0 $ hg verify repository uses revlog format 0 diff -r 501c51d60792 -r 96a38d44ba09 tests/test-walk.t --- a/tests/test-walk.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-walk.t Sat Jul 18 17:32:38 2015 -0500 @@ -170,6 +170,32 @@ f beans/black beans/black f fenugreek fenugreek f mammals/skunk mammals/skunk + $ hg debugwalk -Ibeans mammals + $ hg debugwalk -Inon-existent + $ hg debugwalk -Inon-existent -Ibeans/black + f beans/black beans/black + $ hg debugwalk -Ibeans beans/black + f beans/black beans/black exact + $ hg debugwalk -Ibeans/black beans + f beans/black beans/black + $ hg debugwalk -Xbeans/black beans + f beans/borlotti beans/borlotti + f beans/kidney beans/kidney + f beans/navy beans/navy + f beans/pinto beans/pinto + f beans/turtle beans/turtle + $ hg debugwalk -Xbeans/black -Ibeans + f beans/borlotti beans/borlotti + f beans/kidney beans/kidney + f beans/navy beans/navy + f beans/pinto beans/pinto + f beans/turtle beans/turtle + $ hg debugwalk -Xbeans/black beans/black + f beans/black beans/black exact + $ hg debugwalk -Xbeans/black -Ibeans/black + $ hg debugwalk -Xbeans beans/black + f beans/black beans/black exact + $ hg debugwalk -Xbeans -Ibeans/black $ hg debugwalk 'glob:mammals/../beans/b*' f beans/black beans/black f beans/borlotti beans/borlotti diff -r 501c51d60792 -r 96a38d44ba09 tests/test-websub.t --- a/tests/test-websub.t Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-websub.t Sat Jul 18 17:32:38 2015 -0500 @@ -27,7 +27,7 @@ log - $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "rev/tip" | grep bts + $ get-with-headers.py localhost:$HGPORT "rev/tip" | grep bts
                                                          Issue123: fixed the bug!
                                                          errors diff -r 501c51d60792 -r 96a38d44ba09 tests/test-wireproto.py --- a/tests/test-wireproto.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/test-wireproto.py Sat Jul 18 17:32:38 2015 -0500 @@ -43,6 +43,6 @@ print clt.greet("Foobar") b = clt.batch() -fs = [b.greet(s) for s in ["Fo, =;o", "Bar"]] +fs = [b.greet(s) for s in ["Fo, =;: dummyssh - > import sys - > import os - > os.chdir(os.path.dirname(sys.argv[0])) - > if sys.argv[1] != "user@dummy": - > sys.exit(-1) - > if not os.path.exists("dummyssh"): - > sys.exit(-1) - > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2" - > r = os.system(sys.argv[2]) - > sys.exit(bool(r)) - > EOF - - $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo uno due tre quattro + $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo uno due tre quattro uno due tre quattro None - $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --four vier + $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --four vier eins zwei None vier None - $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei + $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei eins zwei None None None - $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --five fuenf + $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --five fuenf eins zwei None None None Explicitly kill daemons to let the test exit on Windows - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + $ killdaemons.py diff -r 501c51d60792 -r 96a38d44ba09 tests/tinyproxy.py --- a/tests/tinyproxy.py Fri Jul 03 18:10:58 2015 +0100 +++ b/tests/tinyproxy.py Sat Jul 18 17:32:38 2015 -0500 @@ -45,7 +45,7 @@ host_port = netloc, 80 print "\t" "connect to %s:%d" % host_port try: soc.connect(host_port) - except socket.error, arg: + except socket.error as arg: try: msg = arg[1] except (IndexError, TypeError): msg = arg self.send_error(404, msg)
                                                      changeset01de2d66a28d3f41bc784e7e
                                                      brancha-branch
                                                      bookmark
                                                      bookmarka-bookmark
                                                      taga-tag
                                                      user [up]43c799df6e75 9d8c40cba617foo [up]43c799df6e75 9d8c40cba617 43c799df6e75 9d8c40cba617 43c799df6e75 9d8c40cba617 43c799df6e75 9d8c40cba617 [up]43c799df6e75 9d8c40cba617foo [up]43c799df6e75 9d8c40cba617 43c799df6e75 9d8c40cba617 43c799df6e75 9d8c40cba617 43c799df6e75 9d8c40cba617 xyzzy9d8c40cba617 tipxyzzy9d8c40cba617[up]a7c1559b7bbafoo [up]a7c1559b7bba a7c1559b7bbaa7c1559b7bbaa7c1559b7bbaxyzzy9d8c40cba617 tipxyzzy9d8c40cba617[up]filesfoofoo [up]filesfoothirdsecondfirst9d8c40cba617files:foo a7c1559b7bbafiles:foo 43c799df6e75files:dir/bar foo [up] + dir/ + + foo + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=spartan&rev=all()' | egrep $REVLINKS + zip + 9d8c40cba617files:foo a7c1559b7bba9d8c40cba617files:foo 43c799df6e75a7c1559b7bbafiles:dir/bar foo a7c1559b7bba43c799df6e759d8c40cba617foo secondfirsta7c1559b7bbafiles:foo 43c799df6e75files:dir/bar foo [up] + dir/ + + foo + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=spartan' | egrep $REVLINKS + changelog + shortlog + graph + changeset + files + revisions + annotate + raw + a7c1559b7bba9d8c40cba617secondfirsta7c1559b7bba9d8c40cba617a7c1559b7bba43c799df6e759d8c40cba617
                                                      [up][up] drwxr-xr-x
                                                      - + dir. da/ - +
                                                      - + file foo