Mercurial > hg-stable
changeset 27909:3203dfe341f9 stable
merge default into stable for 3.7 code freeze
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Sun, 17 Jan 2016 21:40:21 -0600 |
parents | bf86e3e87123 (current diff) d73a5ab18015 (diff) |
children | d2c5ad3deccb |
files | contrib/fixpax.py contrib/import-checker.py contrib/mercurial.spec doc/gendoc.py hgext/progress.py i18n/check-translation.py tests/dumbhttp.py tests/hghave.py tests/seq.py tests/test-check-code-hg.t tests/test-check-code.t tests/test-check-commit-hg.t tests/test-check-config-hg.t tests/test-merge-prompt.t |
diffstat | 366 files changed, 16083 insertions(+), 5813 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed Jan 06 11:01:55 2016 -0800 +++ b/.hgignore Sun Jan 17 21:40:21 2016 -0600 @@ -20,6 +20,7 @@ \#*\# .\#* tests/.coverage* +tests/.testtimes* tests/annotated tests/*.err tests/htmlcov
--- a/Makefile Wed Jan 06 11:01:55 2016 -0800 +++ b/Makefile Sun Jan 17 21:40:21 2016 -0600 @@ -161,12 +161,11 @@ rm -rf dist/mercurial-*.mpkg deb: - mkdir -p packages/debian-unknown - contrib/builddeb --release unknown + contrib/builddeb docker-debian-jessie: mkdir -p packages/debian-jessie - contrib/dockerdeb jessie + contrib/dockerdeb debian jessie fedora20: mkdir -p packages/fedora20
--- a/contrib/Makefile.python Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/Makefile.python Sun Jan 17 21:40:21 2016 -0600 @@ -47,8 +47,8 @@ [ -f $(PYTHON_SRCFILE) ] || wget http://www.python.org/ftp/python/$(PYTHONVER)/$(PYTHON_SRCFILE) || curl -OL http://www.python.org/ftp/python/$(PYTHONVER)/$(PYTHON_SRCFILE) || [ -f $(PYTHON_SRCFILE) ] rm -rf $(PYTHON_SRCDIR) tar xf $(PYTHON_SRCFILE) - # Ubuntu disables SSLv2 the hard way, disable it on old Pythons too - -sed -i 's,self.*SSLv2_method(),0;//\0,g' $(PYTHON_SRCDIR)/Modules/_ssl.c + # Debian/Ubuntu disables SSLv2,3 the hard way, disable it on old Pythons too + -sed -i 's,self.*SSLv[23]_method(),0;//\0,g' $(PYTHON_SRCDIR)/Modules/_ssl.c # Find multiarch system libraries on Ubuntu and disable fortify error when setting argv LDFLAGS="-L/usr/lib/`dpkg-architecture -qDEB_HOST_MULTIARCH`"; \ BASECFLAGS=-U_FORTIFY_SOURCE; \
--- a/contrib/bash_completion Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/bash_completion Sun Jan 17 21:40:21 2016 -0600 @@ -629,7 +629,7 @@ _hg_cmd_shelve() { - if [[ "$prev" = @(-d|--delete|-l|--list) ]]; then + if [[ "$prev" = @(-d|--delete|-l|--list|-p|--patch|--stat) ]]; then _hg_shelves else _hg_status "mard"
--- a/contrib/builddeb Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/builddeb Sun Jan 17 21:40:21 2016 -0600 @@ -8,12 +8,18 @@ BUILD=1 CLEANUP=1 -DEBVERSION=jessie +DISTID=`(lsb_release -is 2> /dev/null | tr '[:upper:]' '[:lower:]') || echo debian` +CODENAME=`lsb_release -cs 2> /dev/null || echo unknown` while [ "$1" ]; do case "$1" in - --release ) + --distid ) + shift + DISTID="$1" shift - DEBVERSION="$1" + ;; + --codename ) + shift + CODENAME="$1" shift ;; --cleanup ) @@ -24,11 +30,6 @@ shift CLEANUP= ;; - --debbuilddir ) - shift - DEBBUILDDIR="$1" - shift - ;; * ) echo "Invalid parameter $1!" 1>&2 exit 1 @@ -82,7 +83,8 @@ fi if [ "$CLEANUP" ] ; then echo - OUTPUTDIR=${OUTPUTDIR:=packages/debian-$DEBVERSION} + OUTPUTDIR=${OUTPUTDIR:=packages/$DISTID-$CODENAME} + mkdir -p "$OUTPUTDIR" find ../mercurial*.deb ../mercurial_*.build ../mercurial_*.changes \ -type f -newer $control -print0 | \ xargs -Inarf -0 mv narf "$OUTPUTDIR"
--- a/contrib/buildrpm Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/buildrpm Sun Jan 17 21:40:21 2016 -0600 @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/bash -e # # Build a Mercurial RPM from the current repo # @@ -68,7 +68,7 @@ RPMPYTHONVER=%{nil} fi -mkdir -p $RPMBUILDDIR/SOURCES +mkdir -p $RPMBUILDDIR/{SOURCES,BUILD,SRPMS,RPMS} $HG archive -t tgz $RPMBUILDDIR/SOURCES/mercurial-$version-$release.tar.gz if [ "$PYTHONVER" ]; then (
--- a/contrib/check-code.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/check-code.py Sun Jan 17 21:40:21 2016 -0600 @@ -107,7 +107,7 @@ "use egrep for extended grep syntax"), (r'/bin/', "don't use explicit paths for tools"), (r'[^\n]\Z', "no trailing newline"), - (r'export.*=', "don't export and assign at once"), + (r'export .*=', "don't export and assign at once"), (r'^source\b', "don't use 'source', use '.'"), (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"), (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"), @@ -122,10 +122,12 @@ (r'^( *)\t', "don't use tabs to indent"), (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)', "put a backslash-escaped newline after sed 'i' command"), - (r'^diff *-\w*u.*$\n(^ \$ |^$)', "prefix diff -u with cmp"), + (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"), + (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"), (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"), (r'\butil\.Abort\b', "directly use error.Abort"), (r'\|&', "don't use |&, use 2>&1"), + (r'\w = +\w', "only one space after = allowed"), ], # warnings [ @@ -146,7 +148,7 @@ uprefix = r"^ \$ " utestpats = [ [ - (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"), + (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"), (uprefix + r'.*\|\s*sed[^|>\n]*\n', "use regex test output patterns instead of sed"), (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), @@ -219,6 +221,7 @@ (r'(\w|\)),\w', "missing whitespace after ,"), (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"), + (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), (r'.{81}', "line too long"), (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'), (r'[^\n]\Z', "no trailing newline"), @@ -336,6 +339,7 @@ (r'\w+ (\+\+|--)', "use foo++, not foo ++"), (r'\w,\w', "missing whitespace after ,"), (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), + (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), (r'^#\s+\w', "use #foo, not # foo"), (r'[^\n]\Z', "no trailing newline"), (r'^\s*#import\b', "use only #include in standard C code"), @@ -477,7 +481,13 @@ name, match, f) continue if "no-" "check-code" in pre: - print "Skipping %s it has no-" "check-code" % f + # If you're looking at this line, it's because a file has: + # no- check- code + # but the reason to output skipping is to make life for + # tests easier. So, instead of writing it with a normal + # spelling, we write it with the expected spelling from + # tests/test-check-code.t + print "Skipping %s it has no-che?k-code (glob)" % f return "Skip" # skip checking this file for p, r in filters: post = re.sub(p, r, post)
--- a/contrib/check-commit Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/check-commit Sun Jan 17 21:40:21 2016 -0600 @@ -17,41 +17,82 @@ import re, sys, os +commitheader = r"^(?:# [^\n]*\n)*" +afterheader = commitheader + r"(?!#)" +beforepatch = afterheader + r"(?!\n(?!@@))" + errors = [ - (r"[(]bc[)]", "(BC) needs to be uppercase"), - (r"[(]issue \d\d\d", "no space allowed between issue and number"), - (r"[(]bug(\d|\s)", "use (issueDDDD) instead of bug"), - (r"^# User [^@\n]+$", "username is not an email address"), - (r"^# .*\n(?!merge with )[^#]\S+[^:] ", + (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"), + (beforepatch + r".*[(]issue \d\d\d", "no space allowed between issue and number"), + (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"), + (commitheader + r"# User [^@\n]+\n", "username is not an email address"), + (commitheader + r"(?!merge with )[^#]\S+[^:] ", "summary line doesn't start with 'topic: '"), - (r"^# .*\n[A-Z][a-z]\S+", "don't capitalize summary lines"), - (r"^# .*\n[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"), - (r"^# .*\n.*\.\s+$", "don't add trailing period on summary line"), - (r"^# .*\n.{78,}", "summary line too long (limit is 78)"), - (r"^\+\n \n", "adds double empty line"), - (r"^ \n\+\n", "adds double empty line"), - (r"^\+[ \t]+def [a-z]+_[a-z]", "adds a function with foo_bar naming"), + (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"), + (afterheader + r"[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"), + (afterheader + r"\S*[^A-Za-z0-9-]\S*: ", + "summary keyword should be most user-relevant one-word command or topic"), + (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"), + (afterheader + r".{79,}", "summary line too long (limit is 78)"), + (r"\n\+\n \n", "adds double empty line"), + (r"\n \n\+\n", "adds double empty line"), + (r"\n\+[ \t]+def [a-z]+_[a-z]", "adds a function with foo_bar naming"), ] -node = os.environ.get("HG_NODE") +word = re.compile('\S') +def nonempty(first, second): + if word.search(first): + return first + return second -if node: - commit = os.popen("hg export %s" % node).read() -else: - commit = sys.stdin.read() - -exitcode = 0 -for exp, msg in errors: - m = re.search(exp, commit, re.MULTILINE) - if m: +def checkcommit(commit, node = None): + exitcode = 0 + printed = node is None + hits = [] + for exp, msg in errors: + m = re.search(exp, commit) + if m: + end = m.end() + trailing = re.search(r'(\\n)+$', exp) + if trailing: + end -= len(trailing.group()) / 2 + hits.append((end, exp, msg)) + if hits: + hits.sort() pos = 0 + last = '' for n, l in enumerate(commit.splitlines(True)): pos += len(l) - if pos >= m.end(): + while len(hits): + end, exp, msg = hits[0] + if pos < end: + break + if not printed: + printed = True + print "node: %s" % node print "%d: %s" % (n, msg) - print " %s" % l[:-1] + print " %s" % nonempty(l, last)[:-1] if "BYPASS" not in os.environ: exitcode = 1 - break + del hits[0] + last = nonempty(l, last) + + return exitcode + +def readcommit(node): + return os.popen("hg export %s" % node).read() -sys.exit(exitcode) +if __name__ == "__main__": + exitcode = 0 + node = os.environ.get("HG_NODE") + + if node: + commit = readcommit(node) + exitcode = checkcommit(commit) + elif sys.argv[1:]: + for node in sys.argv[1:]: + exitcode |= checkcommit(readcommit(node), node) + else: + commit = sys.stdin.read() + exitcode = checkcommit(commit) + sys.exit(exitcode)
--- a/contrib/check-config.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/check-config.py Sun Jan 17 21:40:21 2016 -0600 @@ -13,14 +13,16 @@ foundopts = {} documented = {} -configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"], ?""" - r"""['"](\S+)['"](,\s(?:default=)?(\S+?))?\)""") +configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"],\s*""" + r"""['"](\S+)['"](,\s+(?:default=)?(\S+?))?\)""") +configpartialre = (r"""ui\.config""") def main(args): for f in args: sect = '' prevname = '' confsect = '' + carryover = '' for l in open(f): # check topic-like bits @@ -40,29 +42,35 @@ if m: confsect = m.group(1) continue - m = re.match(r'^\s+(?:#\s*)?([a-z._]+) = ', l) + m = re.match(r'^\s+(?:#\s*)?(\S+) = ', 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) + m = re.match(r'^\s*(\S+\.\S+)$', l) + if m: + documented[m.group(1)] = 1 + + # like convert + m = re.match(r'^\s*:(\S+\.\S+):\s+', l) if m: documented[m.group(1)] = 1 # quoted in help or docstrings - m = re.match(r'.*?``([-a-z_]+\.[-a-z_]+)``', l) + m = re.match(r'.*?``(\S+\.\S+)``', l) if m: documented[m.group(1)] = 1 # look for ignore markers m = re.search(r'# (?:internal|experimental|deprecated|developer)' - ' config: (\S+.\S+)$', l) + ' config: (\S+\.\S+)$', l) if m: documented[m.group(1)] = 1 # look for code-like bits - m = re.search(configre, l) + line = carryover + l + m = re.search(configre, line, re.MULTILINE) if m: ctype = m.group(1) if not ctype: @@ -78,6 +86,13 @@ print "conflict on %s: %r != %r" % (name, (ctype, default), foundopts[name]) foundopts[name] = (ctype, default) + carryover = '' + else: + m = re.search(configpartialre, line) + if m: + carryover = line + else: + carryover = '' for name in sorted(foundopts): if name not in documented:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/check-py3-compat.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# +# check-py3-compat - check Python 3 compatibility of Mercurial files +# +# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import, print_function + +import ast +import sys + +def check_compat(f): + """Check Python 3 compatibility for a file.""" + with open(f, 'rb') as fh: + content = fh.read() + + # Ignore empty files. + if not content.strip(): + return + + root = ast.parse(content) + futures = set() + haveprint = False + for node in ast.walk(root): + if isinstance(node, ast.ImportFrom): + if node.module == '__future__': + futures |= set(n.name for n in node.names) + elif isinstance(node, ast.Print): + haveprint = True + + if 'absolute_import' not in futures: + print('%s not using absolute_import' % f) + if haveprint and 'print_function' not in futures: + print('%s requires print_function' % f) + +if __name__ == '__main__': + for f in sys.argv[1:]: + check_compat(f) + + sys.exit(0)
--- a/contrib/debugshell.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/debugshell.py Sun Jan 17 21:40:21 2016 -0600 @@ -4,7 +4,10 @@ import sys import mercurial import code -from mercurial import cmdutil +from mercurial import ( + cmdutil, + demandimport, +) cmdtable = {} command = cmdutil.command(cmdtable) @@ -45,7 +48,8 @@ # if IPython doesn't exist, fallback to code.interact try: - __import__(pdbmap[debugger]) + with demandimport.deactivated(): + __import__(pdbmap[debugger]) except ImportError: ui.warn("%s debugger specified but %s module was not found\n" % (debugger, pdbmap[debugger]))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dirstatenonnormalcheck.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,57 @@ +# dirstatenonnormalcheck.py - extension to check the consistency of the +# dirstate's non-normal map +# +# For most operations on dirstate, this extensions checks that the nonnormalset +# contains the right entries. +# It compares the nonnormal file to a nonnormalset built from the map of all +# the files in the dirstate to check that they contain the same files. + +from __future__ import absolute_import + +from mercurial import ( + dirstate, + extensions, +) + +def nonnormalentries(dmap): + """Compute nonnormal entries from dirstate's dmap""" + res = set() + for f, e in dmap.iteritems(): + if e[0] != 'n' or e[3] == -1: + res.add(f) + return res + +def checkconsistency(ui, orig, dmap, _nonnormalset, label): + """Compute nonnormalset from dmap, check that it matches _nonnormalset""" + nonnormalcomputedmap = nonnormalentries(dmap) + if _nonnormalset != nonnormalcomputedmap: + ui.develwarn("%s call to %s\n" % (label, orig)) + ui.develwarn("inconsistency in nonnormalset\n") + ui.develwarn("[nonnormalset] %s\n" % _nonnormalset) + ui.develwarn("[map] %s\n" % nonnormalcomputedmap) + +def _checkdirstate(orig, self, arg): + """Check nonnormal set consistency before and after the call to orig""" + checkconsistency(self._ui, orig, self._map, self._nonnormalset, "before") + r = orig(self, arg) + checkconsistency(self._ui, orig, self._map, self._nonnormalset, "after") + return r + +def extsetup(ui): + """Wrap functions modifying dirstate to check nonnormalset consistency""" + dirstatecl = dirstate.dirstate + devel = ui.configbool('devel', 'all-warnings') + paranoid = ui.configbool('experimental', 'nonnormalparanoidcheck') + if devel: + extensions.wrapfunction(dirstatecl, '_writedirstate', _checkdirstate) + if paranoid: + # We don't do all these checks when paranoid is disable as it would + # make the extension run very slowly on large repos + extensions.wrapfunction(dirstatecl, 'normallookup', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'otherparent', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'normal', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'write', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'add', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'remove', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'merge', _checkdirstate) + extensions.wrapfunction(dirstatecl, 'drop', _checkdirstate)
--- a/contrib/dockerdeb Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/dockerdeb Sun Jan 17 21:40:21 2016 -0600 @@ -8,8 +8,9 @@ checkdocker -DEBPLATFORM="$1" -PLATFORM="debian-$1" +DISTID="$1" +CODENAME="$2" +PLATFORM="$1-$2" shift # extra params are passed to build process OUTPUTDIR=${OUTPUTDIR:=$ROOTDIR/packages/$PLATFORM} @@ -26,8 +27,8 @@ sh -c "cd /mnt/$dn && make clean && make local" fi $DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \ - sh -c "cd /mnt/$dn && DEB_BUILD_OPTIONS='${DEB_BUILD_OPTIONS:=}' contrib/builddeb --build --release $DEBPLATFORM" -contrib/builddeb --cleanup --release $DEBPLATFORM + sh -c "cd /mnt/$dn && DEB_BUILD_OPTIONS='${DEB_BUILD_OPTIONS:=}' contrib/builddeb --build --distid $DISTID --codename $CODENAME" +contrib/builddeb --cleanup --distid $DISTID --codename $CODENAME if [ $(uname) = "Darwin" ] ; then $DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \ sh -c "cd /mnt/$dn && make clean"
--- a/contrib/dockerrpm Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/dockerrpm Sun Jan 17 21:40:21 2016 -0600 @@ -15,8 +15,6 @@ RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $* -mkdir -p $RPMBUILDDIR/{BUILD,SRPMS,RPMS} - DSHARED=/mnt/shared $DOCKER run -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \ rpmbuild --define "_topdir $DSHARED" -ba $DSHARED/SPECS/mercurial.spec --clean
--- a/contrib/fixpax.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/fixpax.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,4 @@ +#!/usr/bin/env python # fixpax - fix ownership in bdist_mpkg output # # Copyright 2015 Matt Mackall <mpm@selenic.com>
--- a/contrib/import-checker.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/import-checker.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,4 +1,7 @@ +#!/usr/bin/env python + import ast +import collections import os import sys @@ -11,6 +14,8 @@ # Whitelist of modules that symbols can be directly imported from. allowsymbolimports = ( '__future__', + 'mercurial.hgweb.common', + 'mercurial.hgweb.request', 'mercurial.i18n', 'mercurial.node', ) @@ -35,6 +40,17 @@ return False +def walklocal(root): + """Recursively yield all descendant nodes but not in a different scope""" + todo = collections.deque(ast.iter_child_nodes(root)) + yield root, False + while todo: + node = todo.popleft() + newscope = isinstance(node, ast.FunctionDef) + if not newscope: + todo.extend(ast.iter_child_nodes(node)) + yield node, newscope + def dotted_name_of_path(path, trimpure=False): """Given a relative path to a source file, return its dotted module name. @@ -45,7 +61,7 @@ >>> dotted_name_of_path('zlibmodule.so') 'zlib' """ - parts = path.split('/') + parts = path.replace(os.sep, '/').split('/') parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so if parts[-1].endswith('module'): parts[-1] = parts[-1][:-6] @@ -163,9 +179,6 @@ # consider them stdlib. for m in ['msvcrt', '_winreg']: yield m - # These get missed too - for m in 'ctypes', 'email', 'multiprocessing': - yield m yield 'builtins' # python3 only for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only yield m @@ -198,11 +211,12 @@ or top == libpath and d in ('hgext', 'mercurial')): del dirs[i] for name in files: - if name == '__init__.py': - continue if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')): continue - full_path = os.path.join(top, name) + if name.startswith('__init__.py'): + full_path = top + else: + full_path = os.path.join(top, name) rel_path = full_path[len(libpath) + 1:] mod = dotted_name_of_path(rel_path) yield mod @@ -237,7 +251,7 @@ >>> sorted(imported_modules( ... 'import foo1; from bar import bar1', ... modulename, localmods)) - ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1'] + ['foo.bar.bar1', 'foo.foo1'] >>> sorted(imported_modules( ... 'from bar.bar1 import name1, name2, name3', ... modulename, localmods)) @@ -284,21 +298,28 @@ continue absname, dottedpath, hassubmod = found - yield dottedpath if not hassubmod: + # "dottedpath" is not a package; must be imported + yield dottedpath # examination of "node.names" should be redundant # e.g.: from mercurial.node import nullid, nullrev continue + modnotfound = False prefix = absname + '.' for n in node.names: found = fromlocal(prefix + n.name) if not found: # this should be a function or a property of "node.module" + modnotfound = True continue yield found[1] + if modnotfound: + # "dottedpath" is a package, but imported because of non-module + # lookup + yield dottedpath -def verify_import_convention(module, source): +def verify_import_convention(module, source, localmods): """Verify imports match our established coding convention. We have 2 conventions: legacy and modern. The modern convention is in @@ -311,11 +332,11 @@ absolute = usingabsolute(root) if absolute: - return verify_modern_convention(module, root) + return verify_modern_convention(module, root, localmods) else: return verify_stdlib_on_own_line(root) -def verify_modern_convention(module, root): +def verify_modern_convention(module, root, localmods, root_col_offset=0): """Verify a file conforms to the modern import convention rules. The rules of the modern convention are: @@ -342,6 +363,7 @@ and readability problems. See `requirealias`. """ topmodule = module.split('.')[0] + fromlocal = fromlocalfunc(module, localmods) # Whether a local/non-stdlib import has been performed. seenlocal = False @@ -352,29 +374,36 @@ # Relative import levels encountered so far. seenlevels = set() - for node in ast.walk(root): - if isinstance(node, ast.Import): + for node, newscope in walklocal(root): + def msg(fmt, *args): + return (fmt % args, node.lineno) + if newscope: + # Check for local imports in function + for r in verify_modern_convention(module, node, localmods, + node.col_offset + 4): + yield r + elif 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) + yield msg('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 node.col_offset == root_col_offset: if lastname and name < lastname: - yield 'imports not lexically sorted: %s < %s' % ( - name, lastname) + yield msg('imports not lexically sorted: %s < %s', + name, lastname) lastname = name # 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 stdlib and seenlocal and node.col_offset == root_col_offset: + yield msg('stdlib import follows local import: %s', name) if not stdlib: seenlocal = True @@ -382,11 +411,11 @@ # Import of sibling modules should use relative imports. topname = name.split('.')[0] if topname == topmodule: - yield 'import should be relative: %s' % name + yield msg('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]) + yield msg('%s module must be "as" aliased to %s', + name, requirealias[name]) elif isinstance(node, ast.ImportFrom): # Resolve the full imported module name. @@ -400,39 +429,49 @@ topname = fullname.split('.')[0] if topname == topmodule: - yield 'import should be relative: %s' % fullname + yield msg('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' + yield msg('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 node.module and node.col_offset == root_col_offset: + found = fromlocal(node.module, node.level) + if found and found[2]: # node.module is a package + prefix = found[0] + '.' + symbols = [n.name for n in node.names + if not fromlocal(prefix + n.name)] + else: + symbols = [n.name for n in node.names] - if seennonsymbolrelative: - yield ('symbol import follows non-symbol import: %s' % - fullname) + if symbols and fullname not in allowsymbolimports: + yield msg('direct symbol import %s from %s', + ', '.join(symbols), fullname) + + if symbols and seennonsymbolrelative: + yield msg('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) + if (node.level in seenlevels + and node.col_offset == root_col_offset): + yield msg('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) + yield msg('higher-level import should come first: %s', + fullname) seenlevels.add(node.level) @@ -442,14 +481,14 @@ for n in node.names: if lastentryname and n.name < lastentryname: - yield 'imports from %s not lexically sorted: %s < %s' % ( - fullname, n.name, lastentryname) + yield msg('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]) + yield msg('%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 @@ -460,7 +499,7 @@ http://bugs.python.org/issue19510. >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo'))) - ['mixed imports\\n stdlib: sys\\n relative: foo'] + [('mixed imports\\n stdlib: sys\\n relative: foo', 1)] >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os'))) [] >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar'))) @@ -474,7 +513,7 @@ if from_stdlib[True] and from_stdlib[False]: yield ('mixed imports\n stdlib: %s\n relative: %s' % (', '.join(sorted(from_stdlib[True])), - ', '.join(sorted(from_stdlib[False])))) + ', '.join(sorted(from_stdlib[False]))), node.lineno) class CircularImport(Exception): pass @@ -546,9 +585,9 @@ src = f.read() used_imports[modname] = sorted( imported_modules(src, modname, localmods, ignore_nested=True)) - for error in verify_import_convention(modname, src): + for error, lineno in verify_import_convention(modname, src, localmods): any_errors = True - print source_path, error + print '%s:%d: %s' % (source_path, lineno, error) f.close() cycles = find_cycles(used_imports) if cycles:
--- a/contrib/memory.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/memory.py Sun Jan 17 21:40:21 2016 -0600 @@ -15,20 +15,15 @@ def memusage(ui): """Report memory usage of the current process.""" - status = None result = {'peak': 0, 'rss': 0} - try: + with open('/proc/self/status', 'r') as status: # This will only work on systems with a /proc file system # (like Linux). - status = open('/proc/self/status', 'r') for line in status: parts = line.split() key = parts[0][2:-1].lower() if key in result: result[key] = int(parts[1]) - finally: - if status is not None: - status.close() ui.write_err(", ".join(["%s: %.1f MiB" % (key, value / 1024.0) for key, value in result.iteritems()]) + "\n")
--- a/contrib/perf.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/perf.py Sun Jan 17 21:40:21 2016 -0600 @@ -2,20 +2,28 @@ '''helper extension to measure performance''' from mercurial import cmdutil, scmutil, util, commands, obsolete -from mercurial import repoview, branchmap, merge, copies +from mercurial import repoview, branchmap, merge, copies, error, revlog +from mercurial import mdiff import time, os, sys +import random import functools formatteropts = commands.formatteropts +revlogopts = commands.debugrevlogopts cmdtable = {} command = cmdutil.command(cmdtable) +def getlen(ui): + if ui.configbool("perf", "stub"): + return lambda x: 1 + return len + def gettimer(ui, opts=None): """return a timer function and formatter: (timer, formatter) - This functions exist to gather the creation of formatter in a single - place instead of duplicating it in all performance command.""" + This function exists to gather the creation of formatter in a single + place instead of duplicating it in all performance commands.""" # enforce an idle period before execution to counteract power management # experimental config: perf.presleep @@ -28,8 +36,15 @@ ui.fout = ui.ferr # get a formatter fm = ui.formatter('perf', opts) + # stub function, runs code only once instead of in a loop + # experimental config: perf.stub + if ui.configbool("perf", "stub"): + return functools.partial(stub_timer, fm), fm return functools.partial(_timer, fm), fm +def stub_timer(fm, func, title=None): + func() + def _timer(fm, func, title=None): results = [] begin = time.time() @@ -91,7 +106,7 @@ #m = match.always(repo.root, repo.getcwd()) #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, # False)))) - timer, fm = gettimer(ui, **opts) + timer, fm = gettimer(ui, opts) timer(lambda: sum(map(len, repo.status(unknown=opts['unknown'])))) fm.end() @@ -193,7 +208,7 @@ fm.end() @command('perfdirstatefoldmap', formatteropts) -def perffilefoldmap(ui, repo, **opts): +def perfdirstatefoldmap(ui, repo, **opts): timer, fm = gettimer(ui, opts) dirstate = repo.dirstate 'a' in dirstate @@ -239,8 +254,8 @@ def d(): # acceptremote is True because we don't want prompts in the middle of # our benchmark - merge.calculateupdates(repo, wctx, rctx, ancestor, False, False, False, - acceptremote=True) + merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False, + acceptremote=True, followcopies=True) timer(d) fm.end() @@ -260,8 +275,7 @@ ctx = scmutil.revsingle(repo, rev, rev) t = ctx.manifestnode() def d(): - repo.manifest._mancache.clear() - repo.manifest._cache = None + repo.manifest.clearcaches() repo.manifest.read(t) timer(d) fm.end() @@ -293,14 +307,24 @@ timer, fm = gettimer(ui, opts) cmd = sys.argv[0] def d(): - os.system("HGRCPATH= %s version -q > /dev/null" % cmd) + if os.name != 'nt': + os.system("HGRCPATH= %s version -q > /dev/null" % cmd) + else: + os.environ['HGRCPATH'] = '' + os.system("%s version -q > NUL" % cmd) timer(d) fm.end() @command('perfparents', formatteropts) def perfparents(ui, repo, **opts): timer, fm = gettimer(ui, opts) - nl = [repo.changelog.node(i) for i in xrange(1000)] + # control the number of commits perfparents iterates over + # experimental config: perf.parentscount + count = ui.configint("perf", "parentscount", 1000) + if len(repo.changelog) < count: + raise error.Abort("repo needs %d commits for this test" % count) + repo = repo.unfiltered() + nl = [repo.changelog.node(i) for i in xrange(count)] def d(): for n in nl: repo.changelog.parents(n) @@ -308,7 +332,7 @@ fm.end() @command('perfctxfiles', formatteropts) -def perfparents(ui, repo, x, **opts): +def perfctxfiles(ui, repo, x, **opts): x = int(x) timer, fm = gettimer(ui, opts) def d(): @@ -317,7 +341,7 @@ fm.end() @command('perfrawfiles', formatteropts) -def perfparents(ui, repo, x, **opts): +def perfrawfiles(ui, repo, x, **opts): x = int(x) timer, fm = gettimer(ui, opts) cl = repo.changelog @@ -329,10 +353,6 @@ @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() @@ -358,10 +378,12 @@ @command('perflog', [('', 'rename', False, 'ask log to follow renames')] + formatteropts) -def perflog(ui, repo, **opts): +def perflog(ui, repo, rev=None, **opts): + if rev is None: + rev=[] timer, fm = gettimer(ui, opts) ui.pushbuffer() - timer(lambda: commands.log(ui, repo, rev=[], date='', user='', + timer(lambda: commands.log(ui, repo, rev=rev, date='', user='', copies=opts.get('rename'))) ui.popbuffer() fm.end() @@ -381,10 +403,12 @@ fm.end() @command('perftemplating', formatteropts) -def perftemplating(ui, repo, **opts): +def perftemplating(ui, repo, rev=None, **opts): + if rev is None: + rev=[] timer, fm = gettimer(ui, opts) ui.pushbuffer() - timer(lambda: commands.log(ui, repo, rev=[], date='', user='', + timer(lambda: commands.log(ui, repo, rev=rev, date='', user='', template='{date|shortdate} [{rev}:{node|short}]' ' {author|person}: {desc|firstline}\n')) ui.popbuffer() @@ -410,10 +434,14 @@ timer, fm = gettimer(ui, opts) s = repo.store s.fncache._load() + lock = repo.lock() + tr = repo.transaction('perffncachewrite') def d(): s.fncache._dirty = True - s.fncache.write() + s.fncache.write(tr) timer(d) + lock.release() + tr.close() fm.end() @command('perffncacheencode', formatteropts) @@ -447,25 +475,124 @@ timer(d, title) fm.end() -@command('perfrevlog', - [('d', 'dist', 100, 'distance between the revisions')] + formatteropts, - "[INDEXFILE]") -def perfrevlog(ui, repo, file_, **opts): +@command('perfrevlog', revlogopts + formatteropts + + [('d', 'dist', 100, 'distance between the revisions'), + ('s', 'startrev', 0, 'revision to start reading at')], + '-c|-m|FILE') +def perfrevlog(ui, repo, file_=None, startrev=0, **opts): + """Benchmark reading a series of revisions from a revlog. + + By default, we read every ``-d/--dist`` revision from 0 to tip of + the specified revlog. + + The start revision can be defined via ``-s/--startrev``. + """ timer, fm = gettimer(ui, opts) - from mercurial import revlog dist = opts['dist'] + _len = getlen(ui) def d(): - r = revlog.revlog(lambda fn: open(fn, 'rb'), file_) - for x in xrange(0, len(r), dist): + r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts) + for x in xrange(startrev, _len(r), dist): r.revision(r.node(x)) timer(d) fm.end() +@command('perfrevlogrevision', revlogopts + formatteropts + + [('', 'cache', False, 'use caches instead of clearing')], + '-c|-m|FILE REV') +def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts): + """Benchmark obtaining a revlog revision. + + Obtaining a revlog revision consists of roughly the following steps: + + 1. Compute the delta chain + 2. Obtain the raw chunks for that delta chain + 3. Decompress each raw chunk + 4. Apply binary patches to obtain fulltext + 5. Verify hash of fulltext + + This command measures the time spent in each of these phases. + """ + if opts.get('changelog') or opts.get('manifest'): + file_, rev = None, file_ + elif rev is None: + raise error.CommandError('perfrevlogrevision', 'invalid arguments') + + r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts) + node = r.lookup(rev) + rev = r.rev(node) + + def dodeltachain(rev): + if not cache: + r.clearcaches() + r._deltachain(rev) + + def doread(chain): + if not cache: + r.clearcaches() + r._chunkraw(chain[0], chain[-1]) + + def dodecompress(data, chain): + if not cache: + r.clearcaches() + + start = r.start + length = r.length + inline = r._inline + iosize = r._io.size + buffer = util.buffer + offset = start(chain[0]) + + for rev in chain: + chunkstart = start(rev) + if inline: + chunkstart += (rev + 1) * iosize + chunklength = length(rev) + b = buffer(data, chunkstart - offset, chunklength) + revlog.decompress(b) + + def dopatch(text, bins): + if not cache: + r.clearcaches() + mdiff.patches(text, bins) + + def dohash(text): + if not cache: + r.clearcaches() + r._checkhash(text, node, rev) + + def dorevision(): + if not cache: + r.clearcaches() + r.revision(node) + + chain = r._deltachain(rev)[0] + data = r._chunkraw(chain[0], chain[-1])[1] + bins = r._chunks(chain) + text = str(bins[0]) + bins = bins[1:] + text = mdiff.patches(text, bins) + + benches = [ + (lambda: dorevision(), 'full'), + (lambda: dodeltachain(rev), 'deltachain'), + (lambda: doread(chain), 'read'), + (lambda: dodecompress(data, chain), 'decompress'), + (lambda: dopatch(text, bins), 'patch'), + (lambda: dohash(text), 'hash'), + ] + + for fn, title in benches: + timer, fm = gettimer(ui, opts) + timer(fn, title=title) + fm.end() + @command('perfrevset', - [('C', 'clear', False, 'clear volatile cache between each call.')] + [('C', 'clear', False, 'clear volatile cache between each call.'), + ('', 'contexts', False, 'obtain changectx for each revision')] + formatteropts, "REVSET") -def perfrevset(ui, repo, expr, clear=False, **opts): +def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts): """benchmark the execution time of a revset Use the --clean option if need to evaluate the impact of build volatile @@ -475,7 +602,10 @@ def d(): if clear: repo.invalidatevolatilesets() - for r in repo.revs(expr): pass + if contexts: + for ctx in repo.set(expr): pass + else: + for r in repo.revs(expr): pass timer(d) fm.end() @@ -576,3 +706,79 @@ timer, fm = gettimer(ui) timer(lambda: len(obsolete.obsstore(repo.svfs))) fm.end() + +@command('perflrucachedict', formatteropts + + [('', 'size', 4, 'size of cache'), + ('', 'gets', 10000, 'number of key lookups'), + ('', 'sets', 10000, 'number of key sets'), + ('', 'mixed', 10000, 'number of mixed mode operations'), + ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')], + norepo=True) +def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000, + mixedgetfreq=50, **opts): + def doinit(): + for i in xrange(10000): + util.lrucachedict(size) + + values = [] + for i in xrange(size): + values.append(random.randint(0, sys.maxint)) + + # Get mode fills the cache and tests raw lookup performance with no + # eviction. + getseq = [] + for i in xrange(gets): + getseq.append(random.choice(values)) + + def dogets(): + d = util.lrucachedict(size) + for v in values: + d[v] = v + for key in getseq: + value = d[key] + value # silence pyflakes warning + + # Set mode tests insertion speed with cache eviction. + setseq = [] + for i in xrange(sets): + setseq.append(random.randint(0, sys.maxint)) + + def dosets(): + d = util.lrucachedict(size) + for v in setseq: + d[v] = v + + # Mixed mode randomly performs gets and sets with eviction. + mixedops = [] + for i in xrange(mixed): + r = random.randint(0, 100) + if r < mixedgetfreq: + op = 0 + else: + op = 1 + + mixedops.append((op, random.randint(0, size * 2))) + + def domixed(): + d = util.lrucachedict(size) + + for op, v in mixedops: + if op == 0: + try: + d[v] + except KeyError: + pass + else: + d[v] = v + + benches = [ + (doinit, 'init'), + (dogets, 'gets'), + (dosets, 'sets'), + (domixed, 'mixed') + ] + + for fn, title in benches: + timer, fm = gettimer(ui, opts) + timer(fn, title=title) + fm.end()
--- a/contrib/revsetbenchmarks.py Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/revsetbenchmarks.py Sun Jan 17 21:40:21 2016 -0600 @@ -53,10 +53,13 @@ fullcmd += cmd return check_output(fullcmd, stderr=STDOUT) -def perf(revset, target=None): +def perf(revset, target=None, contexts=False): """run benchmark for this very revset""" try: - output = hg(['perfrevset', revset], repo=target) + args = ['perfrevset', revset] + if contexts: + args.append('--contexts') + output = hg(args, repo=target) return parseoutput(output) except CalledProcessError as exc: print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd @@ -238,6 +241,9 @@ default=','.join(DEFAULTVARIANTS), help="comma separated list of variant to test " "(eg: plain,min,sorted) (plain = no modification)") +parser.add_option('', '--contexts', + action='store_true', + help='obtain changectx from results instead of integer revs') (options, args) = parser.parse_args() @@ -283,7 +289,7 @@ varres = {} for var in variants: varrset = applyvariants(rset, var) - data = perf(varrset, target=options.repo) + data = perf(varrset, target=options.repo, contexts=options.contexts) varres[var] = data res.append(varres) printresult(variants, idx, varres, len(revsets),
--- a/contrib/win32/mercurial.iss Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/win32/mercurial.iss Sun Jan 17 21:40:21 2016 -0600 @@ -23,6 +23,7 @@ [Setup] AppCopyright=Copyright 2005-2015 Matt Mackall and others AppName=Mercurial +AppVersion={#VERSION} #if ARCH == "x64" AppVerName=Mercurial {#VERSION} (64-bit) OutputBaseFilename=Mercurial-{#VERSION}-x64 @@ -83,6 +84,7 @@ Source: doc\*.html; DestDir: {app}\Docs Source: doc\style.css; DestDir: {app}\Docs Source: mercurial\help\*.txt; DestDir: {app}\help +Source: mercurial\help\internals\*.txt; DestDir: {app}\help\internals Source: mercurial\default.d\*.rc; DestDir: {app}\default.d Source: mercurial\locale\*.*; DestDir: {app}\locale; Flags: recursesubdirs createallsubdirs skipifsourcedoesntexist Source: mercurial\templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
--- a/contrib/wix/guids.wxi Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/wix/guids.wxi Sun Jan 17 21:40:21 2016 -0600 @@ -19,7 +19,8 @@ <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?> <!-- help.wxs --> - <?define helpFolder.guid = {9FA957DB-6DFE-44f2-AD03-293B2791CF17} ?> + <?define help.root.guid = {9FA957DB-6DFE-44f2-AD03-293B2791CF17} ?> + <?define help.internals.guid = {2DD7669D-0DB8-4C39-9806-78E6475E7ACC} ?> <!-- i18n.wxs --> <?define i18nFolder.guid = {1BF8026D-CF7C-4174-AEE6-D6B7BF119248} ?>
--- a/contrib/wix/help.wxs Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/wix/help.wxs Sun Jan 17 21:40:21 2016 -0600 @@ -5,9 +5,16 @@ <?include defines.wxi ?> <Fragment> + <ComponentGroup Id='helpFolder'> + <ComponentRef Id='help.root' /> + <ComponentRef Id='help.internals' /> + </ComponentGroup> + </Fragment> + + <Fragment> <DirectoryRef Id="INSTALLDIR"> <Directory Id="helpdir" Name="help" FileSource="$(var.SourceDir)"> - <Component Id="helpFolder" Guid="$(var.helpFolder.guid)" Win64='$(var.IsX64)'> + <Component Id="help.root" Guid="$(var.help.root.guid)" Win64='$(var.IsX64)'> <File Name="config.txt" KeyPath="yes" /> <File Name="dates.txt" /> <File Name="diffs.txt" /> @@ -28,6 +35,15 @@ <File Name="templates.txt" /> <File Name="urls.txt" /> </Component> + + <Directory Id="help.internaldir" Name="internals"> + <Component Id="help.internals" Guid="$(var.help.internals.guid)" Win64='$(var.IsX64)'> + <File Id="internals.bundles.txt" Name="bundles.txt" KeyPath="yes" /> + <File Id="internals.changegroups.txt" Name="changegroups.txt" /> + <File Id="internals.revlogs.txt" Name="revlogs.txt" /> + </Component> + </Directory> + </Directory> </DirectoryRef> </Fragment>
--- a/contrib/wix/mercurial.wxs Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/wix/mercurial.wxs Sun Jan 17 21:40:21 2016 -0600 @@ -124,7 +124,7 @@ <ComponentRef Id='COPYING' /> <ComponentRef Id='mercurial.rc' /> <ComponentRef Id='mergetools.rc' /> - <ComponentRef Id='helpFolder' /> + <ComponentGroupRef Id='helpFolder' /> <ComponentGroupRef Id='templatesFolder' /> <MergeRef Id='VCRuntime' /> <MergeRef Id='VCRuntimePolicy' />
--- a/contrib/wix/templates.wxs Wed Jan 06 11:01:55 2016 -0800 +++ b/contrib/wix/templates.wxs Sun Jan 17 21:40:21 2016 -0600 @@ -34,6 +34,7 @@ <File Name="map-cmdline.default" /> <File Name="map-cmdline.bisect" /> <File Name="map-cmdline.xml" /> + <File Name="map-cmdline.status" /> <File Name="map-cmdline.phases" /> </Component>
--- a/doc/check-seclevel.py Wed Jan 06 11:01:55 2016 -0800 +++ b/doc/check-seclevel.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,9 +6,8 @@ import optparse # import from the live mercurial repo +os.environ['HGMODULEPOLICY'] = 'py' sys.path.insert(0, "..") -# fall back to pure modules if required C extensions are not available -sys.path.append(os.path.join('..', 'mercurial', 'pure')) from mercurial import demandimport; demandimport.enable() from mercurial.commands import table from mercurial.help import helptable @@ -88,7 +87,7 @@ for name in sorted(extensions.enabled().keys() + extensions.disabled().keys()): - mod = extensions.load(None, name, None) + mod = extensions.load(ui, name, None) if not mod.__doc__: ui.note(('skip checking %s extension: no help document\n') % name) continue @@ -108,11 +107,8 @@ filename = 'stdin' doc = sys.stdin.read() else: - fp = open(filename) - try: + with open(filename) as fp: doc = fp.read() - finally: - fp.close() ui.note(('checking input from %s with initlevel %d\n') % (filename, initlevel)) @@ -128,6 +124,9 @@ optparser.add_option("-v", "--verbose", help="enable additional output", action="store_true") + optparser.add_option("-d", "--debug", + help="debug mode", + action="store_true") optparser.add_option("-f", "--file", help="filename to read in (or '-' for stdin)", action="store", default="") @@ -153,6 +152,7 @@ ui = uimod.ui() ui.setconfig('ui', 'verbose', options.verbose, '--verbose') + ui.setconfig('ui', 'debug', options.debug, '--debug') if options.file: if checkfile(ui, options.file, options.initlevel):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/docchecker Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# docchecker - look for problematic markup +# +# Copyright 2016 timeless <timeless@mozdev.org> and others +# +# 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 re + +leadingline = re.compile(r'(^\s*)(\S.*)$') +hg_backtick = re.compile(r""":hg:`[^`]*'[^`]*`""") +hg_cramped = re.compile(r'\w:hg:`') + +def check(line): + if hg_backtick.search(line): + print(line) + print("""warning: please avoid nesting ' in :hg:`...`""") + if hg_cramped.search(line): + print(line) + print('warning: please have a space before :hg:') + +def work(file): + (llead, lline) = ('', '') + + for line in file: + # this section unwraps lines + match = leadingline.match(line) + if not match: + check(lline) + (llead, lline) = ('', '') + continue + + lead, line = match.group(1), match.group(2) + if (lead == llead): + if (lline != ''): + lline += ' ' + line + else: + lline = line + else: + check(lline) + (llead, lline) = (lead, line) + check(lline) + +def main(): + for f in sys.argv[1:]: + try: + with open(f) as file: + work(file) + except: + print("failed to process %s" % f) + +main()
--- a/doc/gendoc.py Wed Jan 06 11:01:55 2016 -0800 +++ b/doc/gendoc.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,13 +1,16 @@ +#!/usr/bin/env python """usage: %s DOC ... where DOC is the name of a document """ import os, sys, textwrap + +# This script is executed during installs and may not have C extensions +# available. Relax C module requirements. +os.environ['HGMODULEPOLICY'] = 'allow' # import from the live mercurial repo sys.path.insert(0, "..") -# fall back to pure modules if required C extensions are not available -sys.path.append(os.path.join('..', 'mercurial', 'pure')) from mercurial import demandimport; demandimport.enable() from mercurial import minirst from mercurial.commands import table, globalopts @@ -107,7 +110,7 @@ " :depth: 1\n\n") for extensionname in sorted(allextensionnames()): - mod = extensions.load(None, extensionname, None) + mod = extensions.load(ui, extensionname, None) ui.write(minirst.subsection(extensionname)) ui.write("%s\n\n" % gettext(mod.__doc__)) cmdtable = getattr(mod, 'cmdtable', None)
--- a/hgext/censor.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/censor.py Sun Jan 17 21:40:21 2016 -0600 @@ -28,6 +28,7 @@ from mercurial.node import short from mercurial import cmdutil, error, filelog, revlog, scmutil, util from mercurial.i18n import _ +from mercurial import lock as lockmod cmdtable = {} command = cmdutil.command(cmdtable) @@ -42,6 +43,15 @@ ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT'))], _('-r REV [-t TEXT] [FILE]')) def censor(ui, repo, path, rev='', tombstone='', **opts): + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + return _docensor(ui, repo, path, rev, tombstone, **opts) + finally: + lockmod.release(lock, wlock) + +def _docensor(ui, repo, path, rev='', tombstone='', **opts): if not path: raise error.Abort(_('must specify file path to censor')) if not rev:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/chgserver.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,396 @@ +# chgserver.py - command server extension for cHg +# +# Copyright 2011 Yuya Nishihara <yuya@tcha.org> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +"""command server extension for cHg (EXPERIMENTAL) + +'S' channel (read/write) + propagate ui.system() request to client + +'attachio' command + attach client's stdio passed by sendmsg() + +'chdir' command + change current directory + +'getpager' command + checks if pager is enabled and which pager should be executed + +'setenv' command + replace os.environ completely + +'SIGHUP' signal + reload configuration files +""" + +from __future__ import absolute_import + +import SocketServer +import errno +import os +import re +import signal +import struct +import traceback + +from mercurial.i18n import _ + +from mercurial import ( + cmdutil, + commands, + commandserver, + dispatch, + error, + osutil, + util, +) + +# 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' + +_log = commandserver.log + +# copied from hgext/pager.py:uisetup() +def _setuppagercmd(ui, options, cmd): + if not ui.formatted(): + return + + p = ui.config("pager", "pager", os.environ.get("PAGER")) + usepager = False + always = util.parsebool(options['pager']) + auto = options['pager'] == 'auto' + + if not p: + pass + elif always: + usepager = True + elif not auto: + usepager = False + else: + attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff'] + attend = ui.configlist('pager', 'attend', attended) + ignore = ui.configlist('pager', 'ignore') + cmds, _ = cmdutil.findcmd(cmd, commands.table) + + for cmd in cmds: + var = 'attend-%s' % cmd + if ui.config('pager', var): + usepager = ui.configbool('pager', var) + break + if (cmd in attend or + (cmd not in ignore and not attend)): + usepager = True + break + + if usepager: + ui.setconfig('ui', 'formatted', ui.formatted(), 'pager') + ui.setconfig('ui', 'interactive', False, 'pager') + return p + +_envvarre = re.compile(r'\$[a-zA-Z_]+') + +def _clearenvaliases(cmdtable): + """Remove stale command aliases referencing env vars; variable expansion + is done at dispatch.addaliases()""" + for name, tab in cmdtable.items(): + cmddef = tab[0] + if (isinstance(cmddef, dispatch.cmdalias) and + not cmddef.definition.startswith('!') and # shell alias + _envvarre.search(cmddef.definition)): + del cmdtable[name] + +def _newchgui(srcui, csystem): + class chgui(srcui.__class__): + def __init__(self, src=None): + super(chgui, self).__init__(src) + if src: + self._csystem = getattr(src, '_csystem', csystem) + else: + self._csystem = csystem + + def system(self, cmd, environ=None, cwd=None, onerr=None, + errprefix=None): + # copied from mercurial/util.py:system() + self.flush() + def py2shell(val): + if val is None or val is False: + return '0' + if val is True: + return '1' + return str(val) + env = os.environ.copy() + if environ: + env.update((k, py2shell(v)) for k, v in environ.iteritems()) + env['HG'] = util.hgexecutable() + rc = self._csystem(cmd, env, cwd) + if rc and onerr: + errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]), + util.explainexit(rc)[0]) + if errprefix: + errmsg = '%s: %s' % (errprefix, errmsg) + raise onerr(errmsg) + return rc + + return chgui(srcui) + +def _renewui(srcui): + newui = srcui.__class__() + for a in ['fin', 'fout', 'ferr', 'environ']: + setattr(newui, a, getattr(srcui, a)) + if util.safehasattr(srcui, '_csystem'): + newui._csystem = srcui._csystem + # stolen from tortoisehg.util.copydynamicconfig() + for section, name, value in srcui.walkconfig(): + source = srcui.configsource(section, name) + if ':' in source: + # path:line + continue + if source == 'none': + # ui.configsource returns 'none' by default + source = '' + newui.setconfig(section, name, value, source) + return newui + +class channeledsystem(object): + """Propagate ui.system() request in the following format: + + payload length (unsigned int), + cmd, '\0', + cwd, '\0', + envkey, '=', val, '\0', + ... + envkey, '=', val + + and waits: + + exitcode length (unsigned int), + exitcode (int) + """ + def __init__(self, in_, out, channel): + self.in_ = in_ + self.out = out + self.channel = channel + + def __call__(self, cmd, environ, cwd): + args = [util.quotecommand(cmd), cwd or '.'] + args.extend('%s=%s' % (k, v) for k, v in environ.iteritems()) + data = '\0'.join(args) + self.out.write(struct.pack('>cI', self.channel, len(data))) + self.out.write(data) + self.out.flush() + + length = self.in_.read(4) + length, = struct.unpack('>I', length) + if length != 4: + raise error.Abort(_('invalid response')) + rc, = struct.unpack('>i', self.in_.read(4)) + return rc + +_iochannels = [ + # server.ch, ui.fp, mode + ('cin', 'fin', 'rb'), + ('cout', 'fout', 'wb'), + ('cerr', 'ferr', 'wb'), +] + +class chgcmdserver(commandserver.server): + def __init__(self, ui, repo, fin, fout, sock): + super(chgcmdserver, self).__init__( + _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout) + self.clientsock = sock + self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio" + + def cleanup(self): + # dispatch._runcatch() does not flush outputs if exception is not + # handled by dispatch._dispatch() + self.ui.flush() + self._restoreio() + + def attachio(self): + """Attach to client's stdio passed via unix domain socket; all + channels except cresult will no longer be used + """ + # tell client to sendmsg() with 1-byte payload, which makes it + # distinctive from "attachio\n" command consumed by client.read() + self.clientsock.sendall(struct.pack('>cI', 'I', 1)) + clientfds = osutil.recvfds(self.clientsock.fileno()) + _log('received fds: %r\n' % clientfds) + + ui = self.ui + ui.flush() + first = self._saveio() + for fd, (cn, fn, mode) in zip(clientfds, _iochannels): + assert fd > 0 + fp = getattr(ui, fn) + os.dup2(fd, fp.fileno()) + os.close(fd) + if not first: + continue + # reset buffering mode when client is first attached. as we want + # to see output immediately on pager, the mode stays unchanged + # when client re-attached. ferr is unchanged because it should + # be unbuffered no matter if it is a tty or not. + if fn == 'ferr': + newfp = fp + else: + # make it line buffered explicitly because the default is + # decided on first write(), where fout could be a pager. + if fp.isatty(): + bufsize = 1 # line buffered + else: + bufsize = -1 # system default + newfp = os.fdopen(fp.fileno(), mode, bufsize) + setattr(ui, fn, newfp) + setattr(self, cn, newfp) + + self.cresult.write(struct.pack('>i', len(clientfds))) + + def _saveio(self): + if self._oldios: + return False + ui = self.ui + for cn, fn, _mode in _iochannels: + ch = getattr(self, cn) + fp = getattr(ui, fn) + fd = os.dup(fp.fileno()) + self._oldios.append((ch, fp, fd)) + return True + + def _restoreio(self): + ui = self.ui + for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels): + newfp = getattr(ui, fn) + # close newfp while it's associated with client; otherwise it + # would be closed when newfp is deleted + if newfp is not fp: + newfp.close() + # restore original fd: fp is open again + os.dup2(fd, fp.fileno()) + os.close(fd) + setattr(self, cn, ch) + setattr(ui, fn, fp) + del self._oldios[:] + + def chdir(self): + """Change current directory + + Note that the behavior of --cwd option is bit different from this. + It does not affect --config parameter. + """ + length = struct.unpack('>I', self._read(4))[0] + if not length: + return + path = self._read(length) + _log('chdir to %r\n' % path) + os.chdir(path) + + def getpager(self): + """Read cmdargs and write pager command to r-channel if enabled + + If pager isn't enabled, this writes '\0' because channeledoutput + does not allow to write empty data. + """ + length = struct.unpack('>I', self._read(4))[0] + if not length: + args = [] + else: + args = self._read(length).split('\0') + try: + cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui, + args) + except (error.Abort, error.AmbiguousCommand, error.CommandError, + error.UnknownCommand): + cmd = None + options = {} + if not cmd or 'pager' not in options: + self.cresult.write('\0') + return + + pagercmd = _setuppagercmd(self.ui, options, cmd) + if pagercmd: + self.cresult.write(pagercmd) + else: + self.cresult.write('\0') + + def setenv(self): + """Clear and update os.environ + + Note that not all variables can make an effect on the running process. + """ + length = struct.unpack('>I', self._read(4))[0] + if not length: + return + s = self._read(length) + try: + newenv = dict(l.split('=', 1) for l in s.split('\0')) + except ValueError: + raise ValueError('unexpected value in setenv request') + + diffkeys = set(k for k in set(os.environ.keys() + newenv.keys()) + if os.environ.get(k) != newenv.get(k)) + _log('change env: %r\n' % sorted(diffkeys)) + + os.environ.clear() + os.environ.update(newenv) + + if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys: + # reload config so that ui.plain() takes effect + self.ui = _renewui(self.ui) + + _clearenvaliases(commands.table) + + capabilities = commandserver.server.capabilities.copy() + capabilities.update({'attachio': attachio, + 'chdir': chdir, + 'getpager': getpager, + 'setenv': setenv}) + +# copied from mercurial/commandserver.py +class _requesthandler(SocketServer.StreamRequestHandler): + def handle(self): + ui = self.server.ui + repo = self.server.repo + sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection) + try: + try: + sv.serve() + # handle exceptions that may be raised by command server. most of + # known exceptions are caught by dispatch. + except error.Abort as inst: + ui.warn(_('abort: %s\n') % inst) + except IOError as inst: + if inst.errno != errno.EPIPE: + raise + except KeyboardInterrupt: + pass + finally: + sv.cleanup() + except: # re-raises + # also write traceback to error channel. otherwise client cannot + # see it because it is written to server's stderr by default. + traceback.print_exc(file=sv.cerr) + raise + +class chgunixservice(commandserver.unixservice): + def init(self): + # drop options set for "hg serve --cmdserver" command + self.ui.setconfig('progress', 'assume-tty', None) + signal.signal(signal.SIGHUP, self._reloadconfig) + class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer): + ui = self.ui + repo = self.repo + self.server = cls(self.address, _requesthandler) + # avoid writing "listening at" message to stdout before attachio + # request, which calls setvbuf() + + def _reloadconfig(self, signum, frame): + self.ui = self.server.ui = _renewui(self.ui) + +def uisetup(ui): + commandserver._servicemap['chgunix'] = chgunixservice
--- a/hgext/children.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/children.py Sun Jan 17 21:40:21 2016 -0600 @@ -40,6 +40,14 @@ be printed. If a file argument is given, revision in which the file was last changed (after the working directory revision or the argument to --rev if given) is printed. + + Please use :hg:`log` instead:: + + hg children => hg log -r 'children()' + hg children -r REV => hg log -r 'children(REV)' + + See :hg:`help log` and :hg:`help revsets.children`. + """ rev = opts.get('rev') if file_:
--- a/hgext/clonebundles.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/clonebundles.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -"""advertise pre-generated bundles to seed clones (experimental) +"""advertise pre-generated bundles to seed clones "clonebundles" is a server-side extension used to advertise the existence of pre-generated, externally hosted bundle files to clients that are @@ -47,7 +47,7 @@ * Generating bundle files of repository content (typically periodically, such as once per day). * A file server that clients have network access to and that Python knows - how to talk to through its normal URL handling facility (typically a + how to talk to through its normal URL handling facility (typically an HTTP server). * A process for keeping the bundles manifest in sync with available bundle files. @@ -70,11 +70,6 @@ operators need to be aware that newer versions of Mercurial may produce streaming clone bundles incompatible with older Mercurial versions.** -The list of requirements printed by :hg:`debugcreatestreamclonebundle` should -be specified in the ``requirements`` parameter of the *bundle specification -string* for the ``BUNDLESPEC`` manifest property described below. e.g. -``BUNDLESPEC=none-packed1;requirements%3Drevlogv1``. - A server operator is responsible for creating a ``.hg/clonebundles.manifest`` file containing the list of available bundle files suitable for seeding clones. If this file does not exist, the repository will not advertise the @@ -108,6 +103,10 @@ "<compression>-<type>" form. See mercurial.exchange.parsebundlespec() for more details. + :hg:`debugbundle --spec` can be used to print the bundle specification + string for a bundle file. The output of this command can be used verbatim + for the value of ``BUNDLESPEC`` (it is already escaped). + Clients will automatically filter out specifications that are unknown or unsupported so they won't attempt to download something that likely won't apply. @@ -117,7 +116,8 @@ files. **Use of this key is highly recommended**, as it allows clients to - easily skip unsupported bundles. + easily skip unsupported bundles. If this key is not defined, an old + client may attempt to apply a bundle that it is incapable of reading. REQUIRESNI Whether Server Name Indication (SNI) is required to connect to the URL. @@ -160,40 +160,9 @@ occurs. So server operators should prepare for some people to follow these instructions when a failure occurs, thus driving more load to the original Mercurial server when the bundle hosting service fails. - -The following config options influence the behavior of the clone bundles -feature: - -ui.clonebundleadvertise - Whether the server advertises the existence of the clone bundles feature - to compatible clients that aren't using it. - - When this is enabled (the default), a server will send a message to - compatible clients performing a traditional clone informing them of the - available clone bundles feature. Compatible clients are those that support - bundle2 and are advertising support for the clone bundles feature. - -ui.clonebundlefallback - Whether to automatically fall back to a traditional clone in case of - clone bundles failure. Defaults to false for reasons described above. - -experimental.clonebundles - Whether the clone bundles feature is enabled on clients. Defaults to true. - -experimental.clonebundleprefers - List of "key=value" properties the client prefers in bundles. Downloaded - bundle manifests will be sorted by the preferences in this list. e.g. - the value "BUNDLESPEC=gzip-v1, BUNDLESPEC=bzip2=v1" will prefer a gzipped - version 1 bundle type then bzip2 version 1 bundle type. - - If not defined, the order in the manifest will be used and the first - available bundle will be downloaded. """ -from mercurial.i18n import _ -from mercurial.node import nullid from mercurial import ( - exchange, extensions, wireproto, ) @@ -211,44 +180,5 @@ return caps -@exchange.getbundle2partsgenerator('clonebundlesadvertise', 0) -def advertiseclonebundlespart(bundler, repo, source, bundlecaps=None, - b2caps=None, heads=None, common=None, - cbattempted=None, **kwargs): - """Inserts an output part to advertise clone bundles availability.""" - # Allow server operators to disable this behavior. - # # experimental config: ui.clonebundleadvertise - if not repo.ui.configbool('ui', 'clonebundleadvertise', True): - return - - # Only advertise if a manifest is present. - if not repo.opener.exists('clonebundles.manifest'): - return - - # And when changegroup data is requested. - if not kwargs.get('cg', True): - return - - # And when the client supports clone bundles. - if cbattempted is None: - return - - # And when the client didn't attempt a clone bundle as part of this pull. - if cbattempted: - return - - # And when a full clone is requested. - # Note: client should not send "cbattempted" for regular pulls. This check - # is defense in depth. - if common and common != [nullid]: - return - - msg = _('this server supports the experimental "clone bundles" feature ' - 'that should enable faster and more reliable cloning\n' - 'help test it by setting the "experimental.clonebundles" config ' - 'flag to "true"') - - bundler.newpart('output', data=msg) - def extsetup(ui): extensions.wrapfunction(wireproto, '_capabilities', capabilities)
--- a/hgext/color.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/color.py Sun Jan 17 21:40:21 2016 -0600 @@ -419,16 +419,6 @@ _styles[status] = ' '.join(good) class colorui(uimod.ui): - def popbuffer(self, labeled=False): - if self._colormode is None: - return super(colorui, self).popbuffer(labeled) - - self._bufferstates.pop() - if labeled: - return ''.join(self.label(a, label) for a, label - in self._buffers.pop()) - return ''.join(a for a, label in self._buffers.pop()) - _colormode = 'ansi' def write(self, *args, **opts): if self._colormode is None: @@ -436,13 +426,16 @@ label = opts.get('label', '') if self._buffers: - self._buffers[-1].extend([(str(a), label) for a in args]) + if self._bufferapplylabels: + self._buffers[-1].extend(self.label(a, label) for a in args) + else: + self._buffers[-1].extend(args) elif self._colormode == 'win32': for a in args: win32print(a, super(colorui, self).write, **opts) else: return super(colorui, self).write( - *[self.label(str(a), label) for a in args], **opts) + *[self.label(a, label) for a in args], **opts) def write_err(self, *args, **opts): if self._colormode is None: @@ -456,7 +449,7 @@ win32print(a, super(colorui, self).write_err, **opts) else: return super(colorui, self).write_err( - *[self.label(str(a), label) for a in args], **opts) + *[self.label(a, label) for a in args], **opts) def showlabel(self, msg, label): if label and msg:
--- a/hgext/convert/hg.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/convert/hg.py Sun Jan 17 21:40:21 2016 -0600 @@ -23,6 +23,7 @@ from mercurial.node import bin, hex, nullid from mercurial import hg, util, context, bookmarks, error, scmutil, exchange from mercurial import phases +from mercurial import lock as lockmod from mercurial import merge as mergemod from common import NoRepo, commit, converter_source, converter_sink, mapfile @@ -191,7 +192,6 @@ self.repo, p1ctx, p2ctx, anc, True, # branchmerge True, # force - False, # partial False, # acceptremote False, # followcopies ) @@ -323,9 +323,7 @@ self.repo.ui.setconfig('phases', 'new-commit', phases.phasenames[commit.phase], 'convert') - tr = self.repo.transaction("convert") - - try: + with self.repo.transaction("convert") as tr: node = hex(self.repo.commitctx(ctx)) # If the node value has changed, but the phase is lower than @@ -336,9 +334,6 @@ 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 = node @@ -410,12 +405,19 @@ def putbookmarks(self, updatedbookmark): if not len(updatedbookmark): return - - self.ui.status(_("updating bookmarks\n")) - destmarks = self.repo._bookmarks - for bookmark in updatedbookmark: - destmarks[bookmark] = bin(updatedbookmark[bookmark]) - destmarks.write() + wlock = lock = tr = None + try: + wlock = self.repo.wlock() + lock = self.repo.lock() + tr = self.repo.transaction('bookmark') + self.ui.status(_("updating bookmarks\n")) + destmarks = self.repo._bookmarks + for bookmark in updatedbookmark: + destmarks[bookmark] = bin(updatedbookmark[bookmark]) + destmarks.recordchange(tr) + tr.close() + finally: + lockmod.release(lock, wlock, tr) def hascommitfrommap(self, rev): # the exact semantics of clonebranches is unclear so we can't say no @@ -481,13 +483,13 @@ self.keep = nodes.__contains__ self._heads = nodes - parents - def changectx(self, rev): + def _changectx(self, rev): if self.lastrev != rev: self.lastctx = self.repo[rev] self.lastrev = rev return self.lastctx - def parents(self, ctx): + def _parents(self, ctx): return [p for p in ctx.parents() if p and self.keep(p.node())] def getheads(self): @@ -495,36 +497,50 @@ def getfile(self, name, rev): try: - fctx = self.changectx(rev)[name] + fctx = self._changectx(rev)[name] return fctx.data(), fctx.flags() except error.LookupError: return None, None + def _changedfiles(self, ctx1, ctx2): + ma, r = [], [] + maappend = ma.append + rappend = r.append + d = ctx1.manifest().diff(ctx2.manifest()) + for f, ((node1, flag1), (node2, flag2)) in d.iteritems(): + if node2 is None: + rappend(f) + else: + maappend(f) + return ma, r + def getchanges(self, rev, full): - ctx = self.changectx(rev) - parents = self.parents(ctx) + ctx = self._changectx(rev) + parents = self._parents(ctx) if full or not parents: files = copyfiles = ctx.manifest() if parents: if self._changescache[0] == rev: - m, a, r = self._changescache[1] + ma, r = self._changescache[1] else: - m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3] + ma, r = self._changedfiles(parents[0], ctx) if not full: - files = m + a + r - copyfiles = m + a - # getcopies() is also run for roots and before filtering so missing + files = ma + r + copyfiles = ma + # _getcopies() is also run for roots and before filtering so missing # revlogs are detected early - copies = self.getcopies(ctx, parents, copyfiles) + copies = self._getcopies(ctx, parents, copyfiles) cleanp2 = set() if len(parents) == 2: - cleanp2.update(self.repo.status(parents[1].node(), ctx.node(), - clean=True).clean) + d = parents[1].manifest().diff(ctx.manifest(), clean=True) + for f, value in d.iteritems(): + if value is None: + cleanp2.add(f) changes = [(f, rev) for f in files if f not in self.ignored] changes.sort() return changes, copies, cleanp2 - def getcopies(self, ctx, parents, files): + def _getcopies(self, ctx, parents, files): copies = {} for name in files: if name in self.ignored: @@ -552,8 +568,8 @@ return copies def getcommit(self, rev): - ctx = self.changectx(rev) - parents = [p.hex() for p in self.parents(ctx)] + ctx = self._changectx(rev) + parents = [p.hex() for p in self._parents(ctx)] crev = rev return commit(author=ctx.user(), @@ -571,20 +587,20 @@ if self.keep(node)]) def getchangedfiles(self, rev, i): - ctx = self.changectx(rev) - parents = self.parents(ctx) + ctx = self._changectx(rev) + parents = self._parents(ctx) if not parents and i is None: i = 0 - changes = [], ctx.manifest().keys(), [] + ma, r = ctx.manifest().keys(), [] else: i = i or 0 - changes = self.repo.status(parents[i].node(), ctx.node())[:3] - changes = [[f for f in l if f not in self.ignored] for l in changes] + ma, r = self._changedfiles(parents[i], ctx) + ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)] if i == 0: - self._changescache = (rev, changes) + self._changescache = (rev, (ma, r)) - return changes[0] + changes[1] + changes[2] + return ma + r def converted(self, rev, destrev): if self.convertfp is None:
--- a/hgext/convert/subversion.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/convert/subversion.py Sun Jan 17 21:40:21 2016 -0600 @@ -1041,7 +1041,7 @@ relpaths.append(p.strip('/')) args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths, strict_node_history] - # undocumented feature: debugsvnlog can be disabled + # developer config: convert.svn.debugsvnlog if not self.ui.configbool('convert', 'svn.debugsvnlog', True): return directlogstream(*args) arg = encodeargs(args)
--- a/hgext/eol.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/eol.py Sun Jan 17 21:40:21 2016 -0600 @@ -201,7 +201,7 @@ data = ctx[f].data() if (target == "to-lf" and "\r\n" in data or target == "to-crlf" and singlelf.search(data)): - failed.append((str(ctx), target, f)) + failed.append((f, target, str(ctx))) break return failed @@ -244,7 +244,7 @@ if failed: eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'} msgs = [] - for node, target, f in failed: + for f, target, node in sorted(failed): msgs.append(_(" %s in %s should not have %s line endings") % (f, node, eols[target])) raise error.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
--- a/hgext/extdiff.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/extdiff.py Sun Jan 17 21:40:21 2016 -0600 @@ -270,15 +270,17 @@ ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot) -@command('extdiff', - [('p', 'program', '', - _('comparison program to run'), _('CMD')), +extdiffopts = [ ('o', 'option', [], _('pass option to comparison program'), _('OPT')), ('r', 'rev', [], _('revision'), _('REV')), ('c', 'change', '', _('change made by revision'), _('REV')), ('', 'patch', None, _('compare patches for two revisions')) - ] + commands.walkopts + commands.subrepoopts, + ] + commands.walkopts + commands.subrepoopts + +@command('extdiff', + [('p', 'program', '', _('comparison program to run'), _('CMD')), + ] + extdiffopts, _('hg extdiff [OPT]... [FILE]...'), inferrepo=True) def extdiff(ui, repo, *pats, **opts): @@ -367,6 +369,5 @@ # right encoding) prevents that. mydiff.__doc__ = doc.decode(encoding.encoding) return mydiff - cmdtable[cmd] = (save(cmdline), - cmdtable['extdiff'][1][1:], - _('hg %s [OPTION]... [FILE]...') % cmd) + command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd, + inferrepo=True)(save(cmdline))
--- a/hgext/gpg.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/gpg.py Sun Jan 17 21:40:21 2016 -0600 @@ -168,7 +168,7 @@ ui.write("%-30s %s\n" % (keystr(ui, k), r)) @command("sigcheck", [], _('hg sigcheck REV')) -def check(ui, repo, rev): +def sigcheck(ui, repo, rev): """verify all the signatures there may be for a particular revision""" mygpg = newgpg(ui) rev = repo.lookup(rev) @@ -222,7 +222,10 @@ See :hg:`help dates` for a list of formats valid for -d/--date. """ + with repo.wlock(): + return _dosign(ui, repo, *revs, **opts) +def _dosign(ui, repo, *revs, **opts): mygpg = newgpg(ui, **opts) sigver = "0" sigmessage = ""
--- a/hgext/graphlog.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/graphlog.py Sun Jan 17 21:40:21 2016 -0600 @@ -8,7 +8,7 @@ '''command to view revision graphs from a shell (DEPRECATED) The functionality of this extension has been include in core Mercurial -since version 2.3. +since version 2.3. Please use :hg:`log -G ...` instead. This extension adds a --graph option to the incoming, outgoing and log commands. When this options is given, an ASCII representation of the @@ -49,7 +49,7 @@ ] + commands.logopts + commands.walkopts, _('[OPTION]... [FILE]'), inferrepo=True) -def graphlog(ui, repo, *pats, **opts): +def glog(ui, repo, *pats, **opts): """show revision history alongside an ASCII revision graph Print a revision history alongside a revision graph drawn with @@ -57,6 +57,8 @@ Nodes printed as an @ character are parents of the working directory. + + This is an alias to :hg:`log -G`. """ opts['graph'] = True return commands.log(ui, repo, *pats, **opts)
--- a/hgext/highlight/highlight.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/highlight/highlight.py Sun Jan 17 21:40:21 2016 -0600 @@ -25,7 +25,7 @@ # append a <link ...> to the syntax highlighting css old_header = tmpl.load('header') if SYNTAX_CSS not in old_header: - new_header = old_header + SYNTAX_CSS + new_header = old_header + SYNTAX_CSS tmpl.cache['header'] = new_header text = fctx.data()
--- a/hgext/histedit.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/histedit.py Sun Jan 17 21:40:21 2016 -0600 @@ -143,19 +143,33 @@ repository that Mercurial does not detect to be related to the source repo, you can add a ``--force`` option. +Config +------ + Histedit rule lines are truncated to 80 characters by default. You can customize this behavior by setting a different length in your configuration file:: [histedit] linelen = 120 # truncate rule lines at 120 characters + +``hg histedit`` attempts to automatically choose an appropriate base +revision to use. To change which base revision is used, define a +revset in your configuration file:: + + [histedit] + defaultrev = only(.) & draft() + +By default each edited revision needs to be present in histedit commands. +To remove revision you need to use ``drop`` operation. You can configure +the drop to be implicit for missing commits by adding: + + [histedit] + dropmissing = True + """ -try: - import cPickle as pickle - pickle.dump # import now -except ImportError: - import pickle +import pickle import errno import os import sys @@ -166,6 +180,7 @@ from mercurial import error from mercurial import copies from mercurial import context +from mercurial import destutil from mercurial import exchange from mercurial import extensions from mercurial import hg @@ -181,32 +196,71 @@ cmdtable = {} command = cmdutil.command(cmdtable) +class _constraints(object): + # aborts if there are multiple rules for one node + noduplicates = 'noduplicates' + # abort if the node does belong to edited stack + forceother = 'forceother' + # abort if the node doesn't belong to edited stack + noother = 'noother' + + @classmethod + def known(cls): + return set([v for k, v in cls.__dict__.items() if k[0] != '_']) + # 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 -editcomment = _("""# Edit history between %s and %s -# -# Commits are listed from least to most recent -# -# Commands: -# p, pick = use commit -# e, edit = use commit, but stop for amending -# f, fold = use commit, but combine it with the one above -# r, roll = like fold, but discard this commit's description -# d, drop = remove commit from history -# m, mess = edit commit message without changing commit content -# +actiontable = {} +primaryactions = set() +secondaryactions = set() +tertiaryactions = set() +internalactions = set() + +def geteditcomment(first, last): + """ construct the editor comment + The comment includes:: + - an intro + - sorted primary commands + - sorted short commands + - sorted long commands + + Commands are only included once. + """ + intro = _("""Edit history between %s and %s + +Commits are listed from least to most recent + +Commands: """) + actions = [] + def addverb(v): + a = actiontable[v] + lines = a.message.split("\n") + if len(a.verbs): + v = ', '.join(sorted(a.verbs, key=lambda v: len(v))) + actions.append(" %s = %s" % (v, lines[0])) + actions.extend([' %s' for l in lines[1:]]) + + for v in ( + sorted(primaryactions) + + sorted(secondaryactions) + + sorted(tertiaryactions) + ): + addverb(v) + actions.append('') + + return ''.join(['# %s\n' % l if l else '#\n' + for l in ((intro % (first, last)).split('\n')) + actions]) class histeditstate(object): - def __init__(self, repo, parentctxnode=None, rules=None, keep=None, + def __init__(self, repo, parentctxnode=None, actions=None, keep=None, topmost=None, replacements=None, lock=None, wlock=None): self.repo = repo - self.rules = rules + self.actions = actions self.keep = keep self.topmost = topmost self.parentctxnode = parentctxnode @@ -221,22 +275,24 @@ def read(self): """Load histedit state from disk and set fields appropriately.""" try: - fp = self.repo.vfs('histedit-state', 'r') + state = self.repo.vfs.read('histedit-state') except IOError as err: if err.errno != errno.ENOENT: raise raise error.Abort(_('no histedit in progress')) - try: - data = pickle.load(fp) + if state.startswith('v1\n'): + data = self._load() + parentctxnode, rules, keep, topmost, replacements, backupfile = data + else: + data = pickle.loads(state) parentctxnode, rules, keep, topmost, replacements = data backupfile = None - except pickle.UnpicklingError: - data = self._load() - parentctxnode, rules, keep, topmost, replacements, backupfile = data self.parentctxnode = parentctxnode - self.rules = rules + rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules]) + actions = parserules(rules, self) + self.actions = actions self.keep = keep self.topmost = topmost self.replacements = replacements @@ -248,10 +304,9 @@ fp.write('%s\n' % node.hex(self.parentctxnode)) fp.write('%s\n' % node.hex(self.topmost)) fp.write('%s\n' % self.keep) - fp.write('%d\n' % len(self.rules)) - for rule in self.rules: - fp.write('%s\n' % rule[0]) # action - fp.write('%s\n' % rule[1]) # remainder + fp.write('%d\n' % len(self.actions)) + for action in self.actions: + fp.write('%s\n' % action.tostate()) fp.write('%d\n' % len(self.replacements)) for replacement in self.replacements: fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r) @@ -316,6 +371,7 @@ def inprogress(self): return self.repo.vfs.exists('histedit-state') + class histeditaction(object): def __init__(self, state, node): self.state = state @@ -326,13 +382,58 @@ def fromrule(cls, state, rule): """Parses the given rule, returning an instance of the histeditaction. """ - repo = state.repo rulehash = rule.strip().split(' ', 1)[0] try: - node = repo[rulehash].node() + rev = node.bin(rulehash) + except TypeError: + raise error.ParseError("invalid changeset %s" % rulehash) + return cls(state, rev) + + def verify(self, prev): + """ Verifies semantic correctness of the rule""" + repo = self.repo + ha = node.hex(self.node) + try: + self.node = repo[ha].node() except error.RepoError: - raise error.Abort(_('unknown changeset %s listed') % rulehash[:12]) - return cls(state, node) + raise error.ParseError(_('unknown changeset %s listed') + % ha[:12]) + + def torule(self): + """build a histedit rule line for an action + + by default lines are in the form: + <hash> <rev> <summary> + """ + ctx = self.repo[self.node] + summary = '' + if ctx.description(): + summary = ctx.description().splitlines()[0] + line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary) + # trim to 75 columns by default so it's not stupidly wide in my editor + # (the 5 more are left for verb) + maxlen = self.repo.ui.configint('histedit', 'linelen', default=80) + maxlen = max(maxlen, 22) # avoid truncating hash + return util.ellipsis(line, maxlen) + + def tostate(self): + """Print an action in format used by histedit state files + (the first line is a verb, the remainder is the second) + """ + return "%s\n%s" % (self.verb, node.hex(self.node)) + + def constraints(self): + """Return a set of constrains that this action should be verified for + """ + return set([_constraints.noduplicates, _constraints.noother]) + + def nodetoverify(self): + """Returns a node associated with the action that will be used for + verification purposes. + + If the action doesn't correspond to node it should return None + """ + return self.node def run(self): """Runs the action. The default behavior is simply apply the action's @@ -346,11 +447,13 @@ parentctx, but does not commit them.""" repo = self.repo rulectx = repo[self.node] - hg.update(repo, self.state.parentctxnode) + hg.update(repo, self.state.parentctxnode, quietempty=True) stats = applychanges(repo.ui, repo, rulectx, {}) if stats and stats[3] > 0: - raise error.InterventionRequired(_('Fix up the change and run ' - 'hg histedit --continue')) + raise error.InterventionRequired( + _('Fix up the change (%s %s)') % + (self.verb, node.short(self.node)), + hint=_('hg histedit --continue to resume')) def continuedirty(self): """Continues the action when changes have been applied to the working @@ -411,7 +514,7 @@ wcpar = repo.dirstate.parents()[0] if ctx.p1().node() == wcpar: # edits are "in place" we do not need to make any merge, - # just applies changes on parent for edition + # just applies changes on parent for editing cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True) stats = None else: @@ -439,7 +542,7 @@ return None for c in ctxs: if not c.mutable(): - raise error.Abort( + raise error.ParseError( _("cannot fold into public change %s") % node.short(c.node())) base = first.parents()[0] @@ -502,6 +605,38 @@ editor=editor) return repo.commitctx(new) +def _isdirtywc(repo): + return repo[None].dirty(missing=True) + +def abortdirty(): + raise error.Abort(_('working copy has pending changes'), + hint=_('amend, commit, or revert them and run histedit ' + '--continue, or abort with histedit --abort')) + +def action(verbs, message, priority=False, internal=False): + def wrap(cls): + assert not priority or not internal + verb = verbs[0] + if priority: + primaryactions.add(verb) + elif internal: + internalactions.add(verb) + elif len(verbs) > 1: + secondaryactions.add(verb) + else: + tertiaryactions.add(verb) + + cls.verb = verb + cls.verbs = verbs + cls.message = message + for verb in verbs: + actiontable[verb] = cls + return cls + return wrap + +@action(['pick', 'p'], + _('use commit'), + priority=True) class pick(histeditaction): def run(self): rulectx = self.repo[self.node] @@ -511,21 +646,41 @@ return super(pick, self).run() +@action(['edit', 'e'], + _('use commit, but stop for amending'), + priority=True) class edit(histeditaction): def run(self): repo = self.repo rulectx = repo[self.node] - hg.update(repo, self.state.parentctxnode) + hg.update(repo, self.state.parentctxnode, quietempty=True) applychanges(repo.ui, repo, rulectx, {}) raise error.InterventionRequired( - _('Make changes as needed, you may commit or record as needed ' - 'now.\nWhen you are finished, run hg histedit --continue to ' - 'resume.')) + _('Editing (%s), you may commit or record as needed now.') + % node.short(self.node), + hint=_('hg histedit --continue to resume')) def commiteditor(self): return cmdutil.getcommiteditor(edit=True, editform='histedit.edit') +@action(['fold', 'f'], + _('use commit, but combine it with the one above')) class fold(histeditaction): + def verify(self, prev): + """ Verifies semantic correctness of the fold rule""" + super(fold, self).verify(prev) + repo = self.repo + if not prev: + c = repo[self.node].parents()[0] + elif not prev.verb in ('pick', 'base'): + return + else: + c = repo[prev.node] + if not c.mutable(): + raise error.ParseError( + _("cannot fold into public change %s") % node.short(c.node())) + + def continuedirty(self): repo = self.repo rulectx = repo[self.node] @@ -618,7 +773,25 @@ replacements.append((ich, (n,))) return repo[n], replacements -class _multifold(fold): +class base(histeditaction): + def constraints(self): + return set([_constraints.forceother]) + + def run(self): + if self.repo['.'].node() != self.node: + mergemod.update(self.repo, self.node, False, True) + # branchmerge, force) + return self.continueclean() + + def continuedirty(self): + abortdirty() + + def continueclean(self): + basectx = self.repo['.'] + return basectx, [] + +@action(['_multifold'], + _( """fold subclass used for when multiple folds happen in a row We only want to fire the editor for the folded message once when @@ -626,10 +799,14 @@ similar to rollup, but we should preserve both messages so that when the last fold operation runs we can show the user all the commit messages in their editor. - """ + """), + internal=True) +class _multifold(fold): def skipprompt(self): return True +@action(["roll", "r"], + _("like fold, but discard this commit's description")) class rollup(fold): def mergedescs(self): return False @@ -637,11 +814,16 @@ def skipprompt(self): return True +@action(["drop", "d"], + _('remove commit from history')) class drop(histeditaction): def run(self): parentctx = self.repo[self.state.parentctxnode] return parentctx, [(self.node, tuple())] +@action(["mess", "m"], + _('edit commit message without changing commit content'), + priority=True) class message(histeditaction): def commiteditor(self): return cmdutil.getcommiteditor(edit=True, editform='histedit.mess') @@ -672,20 +854,6 @@ raise error.Abort(msg, hint=hint) return repo.lookup(roots[0]) -actiontable = {'p': pick, - 'pick': pick, - 'e': edit, - 'edit': edit, - 'f': fold, - 'fold': fold, - '_multifold': _multifold, - 'r': rollup, - 'roll': rollup, - 'd': drop, - 'drop': drop, - 'm': message, - 'mess': message, - } @command('histedit', [('', 'commands', '', @@ -699,25 +867,99 @@ ('f', 'force', False, _('force outgoing even for unrelated repositories')), ('r', 'rev', [], _('first revision to be edited'), _('REV'))], - _("ANCESTOR | --outgoing [URL]")) + _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])")) def histedit(ui, repo, *freeargs, **opts): """interactively edit changeset history - This command edits changesets between ANCESTOR and the parent of - the working directory. + This command lets you edit a linear series of changesets (up to + and including the working directory, which should be clean). + You can:: + + - `pick` to [re]order a changeset + + - `drop` to omit changeset + + - `mess` to reword the changeset commit message + + - `fold` to combine it with the preceding changeset + + - `roll` like fold, but discarding this commit's description + + - `edit` to edit this changeset + + There are a number of ways to select the root changset:: + + - Specify ANCESTOR directly - With --outgoing, this edits changesets not found in the - destination repository. If URL of the destination is omitted, the - 'default-push' (or 'default') path will be used. + - Use --outgoing -- it will be the first linear changeset not + included in destination. (See :hg:"help default-push") + + - Otherwise, the value from the "histedit.defaultrev" config option + is used as a revset to select the base revision when ANCESTOR is not + specified. The first revision returned by the revset is used. By + default, this selects the editable history that is unique to the + ancestry of the working directory. + + .. container:: verbose + + If you use --outgoing, this command will abort if there are ambiguous + outgoing revisions. For example, if there are multiple branches + containing outgoing revisions. + + Use "min(outgoing() and ::.)" or similar revset specification + instead of --outgoing to specify edit target revision exactly in + such ambiguous situation. See :hg:`help revsets` for detail about + selecting revisions. + + .. container:: verbose + + Examples: - For safety, this command is also aborted if there are ambiguous - outgoing revisions which may confuse users: for example, if there - are multiple branches containing outgoing revisions. + - A number of changes have been made. + Revision 3 is no longer needed. + + Start history editing from revision 3:: + + hg histedit -r 3 + + An editor opens, containing the list of revisions, + with specific actions specified:: + + pick 5339bf82f0ca 3 Zworgle the foobar + pick 8ef592ce7cc4 4 Bedazzle the zerlog + pick 0a9639fcda9d 5 Morgify the cromulancy + + Additional information about the possible actions + to take appears below the list of revisions. + + To remove revision 3 from the history, + its action (at the beginning of the relevant line) + is changed to 'drop':: - Use "min(outgoing() and ::.)" or similar revset specification - instead of --outgoing to specify edit target revision exactly in - such ambiguous situation. See :hg:`help revsets` for detail about - selecting revisions. + drop 5339bf82f0ca 3 Zworgle the foobar + pick 8ef592ce7cc4 4 Bedazzle the zerlog + pick 0a9639fcda9d 5 Morgify the cromulancy + + - A number of changes have been made. + Revision 2 and 4 need to be swapped. + + Start history editing from revision 2:: + + hg histedit -r 2 + + An editor opens, containing the list of revisions, + with specific actions specified:: + + pick 252a1af424ad 2 Blorb a morgwazzle + pick 5339bf82f0ca 3 Zworgle the foobar + pick 8ef592ce7cc4 4 Bedazzle the zerlog + + To swap revision 2 and 4, its lines are swapped + in the editor:: + + pick 8ef592ce7cc4 4 Bedazzle the zerlog + pick 5339bf82f0ca 3 Zworgle the foobar + pick 252a1af424ad 2 Blorb a morgwazzle Returns 0 on success, 1 if user intervention is required (not only for intentional "edit" command, but also for resolving unexpected @@ -732,7 +974,7 @@ release(state.lock, state.wlock) def _histedit(ui, repo, state, *freeargs, **opts): - # TODO only abort if we try and histedit mq patches, not just + # TODO only abort if we try to histedit mq patches, not just # blanket if mq patches are applied somewhere mq = getattr(repo, 'mq', None) if mq and mq.applied: @@ -775,10 +1017,10 @@ else: revs.extend(freeargs) if len(revs) == 0: - # experimental config: histedit.defaultrev - histeditdefault = ui.config('histedit', 'defaultrev') - if histeditdefault: - revs.append(histeditdefault) + defaultrev = destutil.desthistedit(ui, repo) + if defaultrev is not None: + revs.append(defaultrev) + if len(revs) != 1: raise error.Abort( _('histedit requires exactly one ancestor revision')) @@ -795,9 +1037,9 @@ elif goal == 'edit-plan': state.read() if not rules: - comment = editcomment % (node.short(state.parentctxnode), + comment = geteditcomment(node.short(state.parentctxnode), node.short(state.topmost)) - rules = ruleeditor(repo, ui, state.rules, comment) + rules = ruleeditor(repo, ui, state.actions, comment) else: if rules == '-': f = sys.stdin @@ -805,10 +1047,11 @@ f = open(rules) rules = f.read() f.close() - rules = [l for l in (r.strip() for r in rules.splitlines()) - if l and not l.startswith('#')] - rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules]) - state.rules = rules + actions = parserules(rules, state) + ctxs = [repo[act.nodetoverify()] \ + for act in state.actions if act.nodetoverify()] + warnverifyactions(ui, repo, actions, state, ctxs) + state.actions = actions state.write() return elif goal == 'abort': @@ -823,24 +1066,20 @@ backupfile = repo.join(state.backupfile) f = hg.openpath(ui, backupfile) gen = exchange.readbundle(ui, f, backupfile) - tr = repo.transaction('histedit.abort') - try: + with repo.transaction('histedit.abort') as tr: if not isinstance(gen, bundle2.unbundle20): gen.apply(repo, 'histedit', 'bundle:' + backupfile) if isinstance(gen, bundle2.unbundle20): bundle2.applybundle(repo, gen, tr, source='histedit', url='bundle:' + backupfile) - tr.close() - finally: - tr.release() os.remove(backupfile) # check whether we should update away if repo.unfiltered().revs('parents() and (%n or %ln::)', state.parentctxnode, leafs | tmpnodes): - hg.clean(repo, state.topmost) + hg.clean(repo, state.topmost, show_stats=True, quietempty=True) cleanupnode(ui, repo, 'created', tmpnodes) cleanupnode(ui, repo, 'temp', leafs) except Exception: @@ -877,8 +1116,9 @@ ctxs = [repo[r] for r in revs] if not rules: - comment = editcomment % (node.short(root), node.short(topmost)) - rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment) + comment = geteditcomment(node.short(root), node.short(topmost)) + actions = [pick(state, r) for r in revs] + rules = ruleeditor(repo, ui, actions, comment) else: if rules == '-': f = sys.stdin @@ -886,14 +1126,13 @@ f = open(rules) rules = f.read() f.close() - rules = [l for l in (r.strip() for r in rules.splitlines()) - if l and not l.startswith('#')] - rules = verifyrules(rules, repo, ctxs) + actions = parserules(rules, state) + warnverifyactions(ui, repo, actions, state, ctxs) parentctxnode = repo[root].parents()[0].node() state.parentctxnode = parentctxnode - state.rules = rules + state.actions = actions state.topmost = topmost state.replacements = replacements @@ -906,23 +1145,29 @@ # preprocess rules so that we can hide inner folds from the user # and only show one editor - rules = state.rules[:] - for idx, ((action, ha), (nextact, unused)) in enumerate( - zip(rules, rules[1:] + [(None, None)])): - if action == 'fold' and nextact == 'fold': - state.rules[idx] = '_multifold', ha + actions = state.actions[:] + for idx, (action, nextact) in enumerate( + zip(actions, actions[1:] + [None])): + if action.verb == 'fold' and nextact and nextact.verb == 'fold': + state.actions[idx].__class__ = _multifold - while state.rules: + total = len(state.actions) + pos = 0 + while state.actions: state.write() - action, ha = state.rules.pop(0) - ui.debug('histedit: processing %s %s\n' % (action, ha[:12])) - actobj = actiontable[action].fromrule(state, ha) + actobj = state.actions.pop(0) + pos += 1 + ui.progress(_("editing"), pos, actobj.torule(), + _('changes'), total) + ui.debug('histedit: processing %s %s\n' % (actobj.verb,\ + actobj.torule())) parentctx, replacement_ = actobj.run() state.parentctxnode = parentctx.node() state.replacements.extend(replacement_) state.write() + ui.progress(_("editing"), None) - hg.update(repo, state.parentctxnode) + hg.update(repo, state.parentctxnode, quietempty=True) mapping, tmpnodes, created, ntm = processreplacement(state) if mapping: @@ -963,20 +1208,18 @@ state.clear() if os.path.exists(repo.sjoin('undo')): os.unlink(repo.sjoin('undo')) + if repo.vfs.exists('histedit-last-edit.txt'): + repo.vfs.unlink('histedit-last-edit.txt') def bootstrapcontinue(ui, state, opts): repo = state.repo - if state.rules: - action, currentnode = state.rules.pop(0) - - actobj = actiontable[action].fromrule(state, currentnode) + if state.actions: + actobj = state.actions.pop(0) - s = repo.status() - if s.modified or s.added or s.removed or s.deleted: + if _isdirtywc(repo): actobj.continuedirty() - s = repo.status() - if s.modified or s.added or s.removed or s.deleted: - raise error.Abort(_("working copy still dirty")) + if _isdirtywc(repo): + abortdirty() parentctx, replacements = actobj.continueclean() @@ -1002,32 +1245,15 @@ hint=_('see "hg help phases" for details')) return [c.node() for c in ctxs] -def makedesc(repo, action, rev): - """build a initial action line for a ctx - - line are in the form: - - <action> <hash> <rev> <summary> - """ - ctx = repo[rev] - summary = '' - if ctx.description(): - summary = ctx.description().splitlines()[0] - line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary) - # trim to 80 columns so it's not stupidly wide in my editor - maxlen = repo.ui.configint('histedit', 'linelen', default=80) - maxlen = max(maxlen, 22) # avoid truncating hash - return util.ellipsis(line, maxlen) - -def ruleeditor(repo, ui, rules, editcomment=""): +def ruleeditor(repo, ui, actions, editcomment=""): """open an editor to edit rules rules are in the format [ [act, ctx], ...] like in state.rules """ - rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules]) + rules = '\n'.join([act.torule() for act in actions]) rules += '\n\n' rules += editcomment - rules = ui.edit(rules, ui.username()) + rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'}) # Save edit rules in .hg/histedit-last-edit.txt in case # the user needs to ask for help after something @@ -1038,40 +1264,81 @@ return rules -def verifyrules(rules, repo, ctxs): - """Verify that there exists exactly one edit rule per given changeset. +def parserules(rules, state): + """Read the histedit rules string and return list of action objects """ + rules = [l for l in (r.strip() for r in rules.splitlines()) + if l and not l.startswith('#')] + actions = [] + for r in rules: + if ' ' not in r: + raise error.ParseError(_('malformed line "%s"') % r) + verb, rest = r.split(' ', 1) + + if verb not in actiontable: + raise error.ParseError(_('unknown action "%s"') % verb) + + action = actiontable[verb].fromrule(state, rest) + actions.append(action) + return actions + +def warnverifyactions(ui, repo, actions, state, ctxs): + try: + verifyactions(actions, state, ctxs) + except error.ParseError: + if repo.vfs.exists('histedit-last-edit.txt'): + ui.warn(_('warning: histedit rules saved ' + 'to: .hg/histedit-last-edit.txt\n')) + raise + +def verifyactions(actions, state, ctxs): + """Verify that there exists exactly one action per given changeset and + other constraints. Will abort if there are to many or too few rules, a malformed rule, or a rule on a changeset outside of the user-given range. """ - parsed = [] expected = set(c.hex() for c in ctxs) seen = set() - for r in rules: - if ' ' not in r: - raise error.Abort(_('malformed line "%s"') % r) - action, rest = r.split(' ', 1) - ha = rest.strip().split(' ', 1)[0] - try: - ha = repo[ha].hex() - except error.RepoError: - raise error.Abort(_('unknown changeset %s listed') % ha[:12]) - if ha not in expected: - raise error.Abort( - _('may not use changesets other than the ones listed')) - if ha in seen: - raise error.Abort(_('duplicated command for changeset %s') % - ha[:12]) - seen.add(ha) - if action not in actiontable or action.startswith('_'): - raise error.Abort(_('unknown action "%s"') % action) - parsed.append([action, ha]) + prev = None + for action in actions: + action.verify(prev) + prev = action + constraints = action.constraints() + for constraint in constraints: + if constraint not in _constraints.known(): + raise error.ParseError(_('unknown constraint "%s"') % + constraint) + + nodetoverify = action.nodetoverify() + if nodetoverify is not None: + ha = node.hex(nodetoverify) + if _constraints.noother in constraints and ha not in expected: + raise error.ParseError( + _('%s "%s" changeset was not a candidate') + % (action.verb, node.short(ha)), + hint=_('only use listed changesets')) + if _constraints.forceother in constraints and ha in expected: + raise error.ParseError( + _('%s "%s" changeset was not an edited list candidate') + % (action.verb, node.short(ha)), + hint=_('only use listed changesets')) + if _constraints.noduplicates in constraints and ha in seen: + raise error.ParseError(_( + 'duplicated command for changeset %s') % + ha[:12]) + seen.add(ha) missing = sorted(expected - seen) # sort to stabilize output - if missing: - raise error.Abort(_('missing rules for changeset %s') % + + if state.repo.ui.configbool('histedit', 'dropmissing'): + drops = [drop(state, node.bin(n)) for n in missing] + # put the in the beginning so they execute immediately and + # don't show in the edit-plan in the future + actions[:0] = drops + elif missing: + raise error.ParseError(_('missing rules for changeset %s') % missing[0][:12], - hint=_('do you want to use the drop action?')) - return parsed + hint=_('use "drop %s" to discard, see also: ' + '"hg help -e histedit.config"') % missing[0][:12]) def newnodestoabort(state): """process the list of replacements to return @@ -1079,7 +1346,7 @@ 1) the list of final node 2) the list of temporary node - This meant to be used on abort as less data are required in this case. + This is meant to be used on abort as less data are required in this case. """ replacements = state.replacements allsuccs = set() @@ -1179,13 +1446,20 @@ # nothing to move moves.append((bk, new[-1])) if moves: - marks = repo._bookmarks - for mark, new in moves: - old = marks[mark] - ui.note(_('histedit: moving bookmarks %s from %s to %s\n') - % (mark, node.short(old), node.short(new))) - marks[mark] = new - marks.write() + lock = tr = None + try: + lock = repo.lock() + tr = repo.transaction('histedit') + marks = repo._bookmarks + for mark, new in moves: + old = marks[mark] + ui.note(_('histedit: moving bookmarks %s from %s to %s\n') + % (mark, node.short(old), node.short(new))) + marks[mark] = new + marks.recordchange(tr) + tr.close() + finally: + release(tr, lock) def cleanupnode(ui, repo, name, nodes): """strip a group of nodes from the repository @@ -1193,9 +1467,7 @@ The set of node to strip may contains unknown nodes.""" ui.debug('should strip %s nodes %s\n' % (name, ', '.join([node.short(n) for n in nodes]))) - lock = None - try: - lock = repo.lock() + with repo.lock(): # do not let filtering get in the way of the cleanse # we should probably get rid of obsolescence marker created during the # histedit, but we currently do not have such information. @@ -1210,8 +1482,6 @@ # but this trigger a bug in changegroup hook. # This would reduce bundle overhead repair.strip(ui, repo, c) - finally: - release(lock) def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs): if isinstance(nodelist, str): @@ -1219,8 +1489,8 @@ if os.path.exists(os.path.join(repo.path, 'histedit-state')): state = histeditstate(repo) state.read() - histedit_nodes = set([repo[rulehash].node() for (action, rulehash) - in state.rules if rulehash in repo]) + histedit_nodes = set([action.nodetoverify() for action + in state.actions if action.nodetoverify()]) strip_nodes = set([repo[n].node() for n in nodelist]) common_nodes = histedit_nodes & strip_nodes if common_nodes: @@ -1235,14 +1505,20 @@ return state = histeditstate(repo) state.read() - if state.rules: + if state.actions: # i18n: column positioning for "hg summary" ui.write(_('hist: %s (histedit --continue)\n') % (ui.label(_('%d remaining'), 'histedit.remaining') % - len(state.rules))) + len(state.actions))) def extsetup(ui): cmdutil.summaryhooks.add('histedit', summaryhook) cmdutil.unfinishedstates.append( ['histedit-state', False, True, _('histedit in progress'), _("use 'hg histedit --continue' or 'hg histedit --abort'")]) + cmdutil.afterresolvedstates.append( + ['histedit-state', _('hg histedit --continue')]) + if ui.configbool("experimental", "histeditng"): + globals()['base'] = action(['base', 'b'], + _('checkout changeset and apply further changesets from there') + )(base)
--- a/hgext/keyword.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/keyword.py Sun Jan 17 21:40:21 2016 -0600 @@ -357,14 +357,11 @@ if len(wctx.parents()) > 1: raise error.Abort(_('outstanding uncommitted merge')) kwt = kwtools['templater'] - wlock = repo.wlock() - try: + with repo.wlock(): status = _status(ui, repo, wctx, kwt, *pats, **opts) if status.modified or status.added or status.removed or status.deleted: raise error.Abort(_('outstanding uncommitted changes')) kwt.overwrite(wctx, status.clean, True, expand) - finally: - wlock.release() @command('kwdemo', [('d', 'default', None, _('show default keyword template maps')), @@ -447,11 +444,8 @@ repo[None].add([fn]) ui.note(_('\nkeywords written to %s:\n') % fn) ui.note(keywords) - wlock = repo.wlock() - try: + with repo.wlock(): repo.dirstate.setbranch('demobranch') - finally: - wlock.release() for name, cmd in ui.configitems('hooks'): if name.split('.', 1)[0].find('commit') > -1: repo.ui.setconfig('hooks', name, '', 'keyword') @@ -659,8 +653,7 @@ def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts): '''Wraps cmdutil.amend expanding keywords after amend.''' - wlock = repo.wlock() - try: + with repo.wlock(): kwt.postcommit = True newid = orig(ui, repo, commitfunc, old, extra, pats, opts) if newid != old.node(): @@ -669,8 +662,6 @@ kwt.overwrite(ctx, ctx.files(), False, True) kwt.restrict = False return newid - finally: - wlock.release() def kw_copy(orig, ui, repo, pats, opts, rename=False): '''Wraps cmdutil.copy so that copy/rename destinations do not @@ -682,8 +673,7 @@ For the latter we have to follow the symlink to find out whether its target is configured for expansion and we therefore must unexpand the keywords in the destination.''' - wlock = repo.wlock() - try: + with repo.wlock(): orig(ui, repo, pats, opts, rename) if opts.get('dry_run'): return @@ -703,13 +693,10 @@ candidates = [f for f in repo.dirstate.copies() if 'l' not in wctx.flags(f) and haskwsource(f)] kwt.overwrite(wctx, candidates, False, False) - finally: - wlock.release() def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts): '''Wraps record.dorecord expanding keywords after recording.''' - wlock = repo.wlock() - try: + with repo.wlock(): # record returns 0 even when nothing has changed # therefore compare nodes before and after kwt.postcommit = True @@ -724,8 +711,6 @@ kwt.overwrite(recctx, added, False, True, True) kwt.restrict = True return ret - finally: - wlock.release() def kwfilectx_cmp(orig, self, fctx): # keyword affects data size, comparing wdir and filelog size does
--- a/hgext/largefiles/lfcommands.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/largefiles/lfcommands.py Sun Jan 17 21:40:21 2016 -0600 @@ -141,12 +141,7 @@ if path is None: raise error.Abort(_("missing largefile for '%s' in %s") % (realname, realrev)) - fp = open(path, 'rb') - - try: - return (fp.read(), f[1]) - finally: - fp.close() + return util.readfile(path), f[1] class converter(convcmd.converter): def __init__(self, ui, source, dest, revmapfile, opts): @@ -431,8 +426,7 @@ ignore, for false) message forcibly". ''' statuswriter = lfutil.getstatuswriter(ui, repo, printmessage) - wlock = repo.wlock() - try: + with repo.wlock(): lfdirstate = lfutil.openlfdirstate(ui, repo) lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate) @@ -444,12 +438,14 @@ updated, removed = 0, 0 for lfile in lfiles: abslfile = repo.wjoin(lfile) + abslfileorig = scmutil.origpath(ui, repo, abslfile) absstandin = repo.wjoin(lfutil.standin(lfile)) + absstandinorig = scmutil.origpath(ui, repo, absstandin) if os.path.exists(absstandin): - if (os.path.exists(absstandin + '.orig') and + if (os.path.exists(absstandinorig) and os.path.exists(abslfile)): - shutil.copyfile(abslfile, abslfile + '.orig') - util.unlinkpath(absstandin + '.orig') + shutil.copyfile(abslfile, abslfileorig) + util.unlinkpath(absstandinorig) expecthash = lfutil.readstandin(repo, lfile) if expecthash != '': if lfile not in repo[None]: # not switched to normal file @@ -507,8 +503,6 @@ if lfiles: statuswriter(_('%d largefiles updated, %d removed\n') % (updated, removed)) - finally: - wlock.release() @command('lfpull', [('r', 'rev', [], _('pull largefiles for these revisions'))
--- a/hgext/largefiles/lfutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/largefiles/lfutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -221,7 +221,12 @@ hash = readstandin(repo, file, rev) if instore(repo, hash): return - copytostoreabsolute(repo, repo.wjoin(file), hash) + absfile = repo.wjoin(file) + if os.path.exists(absfile): + copytostoreabsolute(repo, absfile, hash) + else: + repo.ui.warn(_("%s: largefile %s not available from local store\n") % + (file, hash)) def copyalltostore(repo, node): '''Copy all largefiles in a given revision to the store'''
--- a/hgext/largefiles/localstore.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/largefiles/localstore.py Sun Jan 17 21:40:21 2016 -0600 @@ -39,11 +39,8 @@ if not path: raise basestore.StoreError(filename, hash, self.url, _("can't get file locally")) - fd = open(path, 'rb') - try: + with open(path, 'rb') as fd: return lfutil.copyandhash(fd, tmpfile) - finally: - fd.close() def _verifyfile(self, cctx, cset, contents, standin, verified): filename = lfutil.splitstandin(standin)
--- a/hgext/largefiles/overrides.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/largefiles/overrides.py Sun Jan 17 21:40:21 2016 -0600 @@ -141,8 +141,7 @@ # Need to lock, otherwise there could be a race condition between # when standins are created and added to the repo. - wlock = repo.wlock() - try: + with repo.wlock(): if not opts.get('dry_run'): standins = [] lfdirstate = lfutil.openlfdirstate(ui, repo) @@ -161,8 +160,6 @@ if f in m.files()] added = [f for f in lfnames if f not in bad] - finally: - wlock.release() return added, bad def removelargefiles(ui, repo, isaddremove, matcher, **opts): @@ -199,8 +196,7 @@ # Need to lock because standin files are deleted then removed from the # repository and we could race in-between. - wlock = repo.wlock() - try: + with repo.wlock(): lfdirstate = lfutil.openlfdirstate(ui, repo) for f in sorted(remove): if ui.verbose or not m.exact(f): @@ -231,8 +227,6 @@ False) lfdirstate.write() - finally: - wlock.release() return result @@ -458,11 +452,11 @@ # writing the files into the working copy and lfcommands.updatelfiles # will update the largefiles. def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force, - partial, acceptremote, followcopies): + acceptremote, followcopies, matcher=None): overwrite = force and not branchmerge actions, diverge, renamedelete = origfn( - repo, p1, p2, pas, branchmerge, force, partial, acceptremote, - followcopies) + repo, p1, p2, pas, branchmerge, force, acceptremote, + followcopies, matcher=matcher) if overwrite: return actions, diverge, renamedelete @@ -470,17 +464,20 @@ # Convert to dictionary with filename as key and action as value. lfiles = set() for f in actions: - splitstandin = f and lfutil.splitstandin(f) + splitstandin = lfutil.splitstandin(f) if splitstandin in p1: lfiles.add(splitstandin) elif lfutil.standin(f) in p1: lfiles.add(f) - for lfile in lfiles: + for lfile in sorted(lfiles): standin = lfutil.standin(lfile) (lm, largs, lmsg) = actions.get(lfile, (None, None, None)) (sm, sargs, smsg) = actions.get(standin, (None, None, None)) if sm in ('g', 'dc') and lm != 'r': + if sm == 'dc': + f1, f2, fa, move, anc = sargs + sargs = (p2[f2].flags(), False) # Case 1: normal file in the working copy, largefile in # the second parent usermsg = _('remote turned local normal file %s into a largefile\n' @@ -496,6 +493,9 @@ else: actions[standin] = ('r', None, 'replaced by non-standin') elif lm in ('g', 'dc') and sm != 'r': + if lm == 'dc': + f1, f2, fa, move, anc = largs + largs = (p2[f2].flags(), False) # Case 2: largefile in the working copy, normal file in # the second parent usermsg = _('remote turned local largefile %s into a normal file\n' @@ -538,7 +538,7 @@ # largefiles. This will handle identical edits without prompting the user. def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca, labels=None): - if not lfutil.isstandin(orig): + if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent(): return origfn(premerge, repo, mynode, orig, fcd, fco, fca, labels=labels) @@ -555,7 +555,7 @@ (lfutil.splitstandin(orig), ahash, dhash, ohash), 0) == 1)): repo.wwrite(fcd.path(), fco.data(), fco.flags()) - return True, 0 + return True, 0, False def copiespathcopies(orig, ctx1, ctx2, match=None): copies = orig(ctx1, ctx2, match=match) @@ -717,8 +717,7 @@ # Because we put the standins in a bad state (by updating them) # and then return them to a correct state we need to lock to # prevent others from changing them in their incorrect state. - wlock = repo.wlock() - try: + with repo.wlock(): lfdirstate = lfutil.openlfdirstate(ui, repo) s = lfutil.lfdirstatestatus(lfdirstate, repo) lfdirstate.write() @@ -778,9 +777,6 @@ lfcommands.updatelfiles(ui, repo, filelist, printmessage=False, normallookup=True) - finally: - wlock.release() - # after pulling changesets, we need to take some extra care to get # largefiles updated remotely def overridepull(orig, ui, repo, source=None, **opts): @@ -806,9 +802,11 @@ ui.status(_("%d largefiles cached\n") % numcached) return result +revsetpredicate = revset.extpredicate() + +@revsetpredicate('pulled()') def pulledrevsetsymbol(repo, subset, x): - """``pulled()`` - Changesets that just has been pulled. + """Changesets that just has been pulled. Only available with largefiles from pull --lfrev expressions. @@ -959,16 +957,7 @@ f = lfutil.splitstandin(f) - def getdatafn(): - fd = None - try: - fd = open(path, 'rb') - return fd.read() - finally: - if fd: - fd.close() - - getdata = getdatafn + getdata = lambda: util.readfile(path) write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata) if subrepos: @@ -1016,16 +1005,7 @@ f = lfutil.splitstandin(f) - def getdatafn(): - fd = None - try: - fd = open(os.path.join(prefix, path), 'rb') - return fd.read() - finally: - if fd: - fd.close() - - getdata = getdatafn + getdata = lambda: util.readfile(os.path.join(prefix, path)) write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata) @@ -1073,8 +1053,7 @@ # Need to lock because standin files are deleted then removed from the # repository and we could race in-between. - wlock = repo.wlock() - try: + with repo.wlock(): lfdirstate = lfutil.openlfdirstate(ui, repo) for f in forget: if lfdirstate[f] == 'a': @@ -1086,8 +1065,6 @@ for f in standins: util.unlinkpath(repo.wjoin(f), ignoremissing=True) rejected = repo[None].forget(standins) - finally: - wlock.release() bad.extend(f for f in rejected if f in m.files()) forgot.extend(f for f in forget if f not in rejected) @@ -1246,8 +1223,7 @@ orig(ui, repo, *dirs, **opts) repo.status = oldstatus def overriderollback(orig, ui, repo, **opts): - wlock = repo.wlock() - try: + with repo.wlock(): before = repo.dirstate.parents() orphans = set(f for f in repo.dirstate if lfutil.isstandin(f) and repo.dirstate[f] != 'r') @@ -1281,8 +1257,6 @@ for lfile in orphans: lfdirstate.drop(lfile) lfdirstate.write() - finally: - wlock.release() return result def overridetransplant(orig, ui, repo, *revs, **opts): @@ -1358,10 +1332,12 @@ err = 0 return err -def mergeupdate(orig, repo, node, branchmerge, force, partial, +def mergeupdate(orig, repo, node, branchmerge, force, *args, **kwargs): - wlock = repo.wlock() - try: + matcher = kwargs.get('matcher', None) + # note if this is a partial update + partial = matcher and not matcher.always() + with repo.wlock(): # branch | | | # merge | force | partial | action # -------+-------+---------+-------------- @@ -1399,7 +1375,7 @@ oldstandins = lfutil.getstandinsstate(repo) - result = orig(repo, node, branchmerge, force, partial, *args, **kwargs) + result = orig(repo, node, branchmerge, force, *args, **kwargs) newstandins = lfutil.getstandinsstate(repo) filelist = lfutil.getlfilestoupdate(oldstandins, newstandins) @@ -1410,8 +1386,6 @@ normallookup=partial) return result - finally: - wlock.release() def scmutilmarktouched(orig, repo, files, *args, **kwargs): result = orig(repo, files, *args, **kwargs)
--- a/hgext/largefiles/reposetup.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/largefiles/reposetup.py Sun Jan 17 21:40:21 2016 -0600 @@ -263,15 +263,12 @@ force=False, editor=False, extra={}): orig = super(lfilesrepo, self).commit - wlock = self.wlock() - try: + with self.wlock(): lfcommithook = self._lfcommithooks[-1] match = lfcommithook(self, match) result = orig(text=text, user=user, date=date, match=match, force=force, editor=editor, extra=extra) return result - finally: - wlock.release() def push(self, remote, force=False, revs=None, newbranch=False): if remote.local():
--- a/hgext/largefiles/uisetup.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/largefiles/uisetup.py Sun Jan 17 21:40:21 2016 -0600 @@ -9,7 +9,7 @@ '''setup for largefiles extension: uisetup''' from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \ - httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo, copies + httppeer, merge, scmutil, sshpeer, wireproto, subrepo, copies from mercurial.i18n import _ from mercurial.hgweb import hgweb_mod, webcommands @@ -83,7 +83,6 @@ ('', 'lfrev', [], _('download largefiles for these revisions'), _('REV'))] entry[1].extend(pullopt) - revset.symbols['pulled'] = overrides.pulledrevsetsymbol entry = extensions.wrapcommand(commands.table, 'clone', overrides.overrideclone) @@ -170,3 +169,5 @@ if name == 'transplant': extensions.wrapcommand(getattr(module, 'cmdtable'), 'transplant', overrides.overridetransplant) + + overrides.revsetpredicate.setup()
--- a/hgext/mq.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/mq.py Sun Jan 17 21:40:21 2016 -0600 @@ -68,6 +68,7 @@ from mercurial import commands, cmdutil, hg, scmutil, util, revset from mercurial import extensions, error, phases from mercurial import patch as patchmod +from mercurial import lock as lockmod from mercurial import localrepo from mercurial import subrepo import os, re, errno, shutil @@ -699,11 +700,13 @@ absf = repo.wjoin(f) if os.path.lexists(absf): self.ui.note(_('saving current version of %s as %s\n') % - (f, f + '.orig')) + (f, scmutil.origpath(self.ui, repo, f))) + + absorig = scmutil.origpath(self.ui, repo, absf) if copy: - util.copyfile(absf, absf + '.orig') + util.copyfile(absf, absorig) else: - util.rename(absf, absf + '.orig') + util.rename(absf, absorig) def printdiff(self, repo, diffopts, node1, node2=None, files=None, fp=None, changes=None, opts={}): @@ -1038,12 +1041,8 @@ oldqbase = repo[qfinished[0]] tphase = repo.ui.config('phases', 'new-commit', phases.draft) if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase: - tr = repo.transaction('qfinish') - try: + with repo.transaction('qfinish') as tr: phases.advanceboundary(repo, tr, tphase, qfinished) - tr.close() - finally: - tr.release() def delete(self, repo, patches, opts): if not patches and not opts.get('rev'): @@ -1165,8 +1164,7 @@ raise error.Abort(_('cannot manage merge changesets')) self.checktoppatch(repo) insert = self.fullseriesend() - wlock = repo.wlock() - try: + with repo.wlock(): try: # if patch file write fails, abort early p = self.opener(patchfn, "w") @@ -1236,8 +1234,6 @@ self.ui.warn(_('error unlinking %s\n') % patchpath) raise self.removeundo(repo) - finally: - release(wlock) def isapplied(self, patch): """returns (index, rev, patch)""" @@ -1318,8 +1314,7 @@ keepchanges=False): self.checkkeepchanges(keepchanges, force) diffopts = self.diffopts() - wlock = repo.wlock() - try: + with repo.wlock(): heads = [] for hs in repo.branchmap().itervalues(): heads.extend(hs) @@ -1461,14 +1456,10 @@ self.ui.write(_("now at: %s\n") % top) return ret[0] - finally: - wlock.release() - def pop(self, repo, patch=None, force=False, update=True, all=False, nobackup=False, keepchanges=False): self.checkkeepchanges(keepchanges, force) - wlock = repo.wlock() - try: + with repo.wlock(): if patch: # index, rev, patch info = self.isapplied(patch) @@ -1572,8 +1563,6 @@ self.ui.write(_("now at: %s\n") % self.applied[-1].name) else: self.ui.write(_("patch queue now empty\n")) - finally: - wlock.release() def diff(self, repo, pats, opts): top, patch = self.checktoppatch(repo) @@ -1790,27 +1779,34 @@ # Ensure we create a new changeset in the same phase than # the old one. - n = newcommit(repo, oldphase, message, user, ph.date, + lock = tr = None + try: + lock = repo.lock() + tr = repo.transaction('mq') + n = newcommit(repo, oldphase, message, user, ph.date, match=match, force=True, editor=editor) - # only write patch after a successful commit - c = [list(x) for x in refreshchanges] - if inclsubs: - self.putsubstate2changes(substatestate, c) - chunks = patchmod.diff(repo, patchparent, - changes=c, opts=diffopts) - comments = str(ph) - if comments: - patchf.write(comments) - for chunk in chunks: - patchf.write(chunk) - patchf.close() - - marks = repo._bookmarks - for bm in bmlist: - marks[bm] = n - marks.write() - - self.applied.append(statusentry(n, patchfn)) + # only write patch after a successful commit + c = [list(x) for x in refreshchanges] + if inclsubs: + self.putsubstate2changes(substatestate, c) + chunks = patchmod.diff(repo, patchparent, + changes=c, opts=diffopts) + comments = str(ph) + if comments: + patchf.write(comments) + for chunk in chunks: + patchf.write(chunk) + patchf.close() + + marks = repo._bookmarks + for bm in bmlist: + marks[bm] = n + marks.recordchange(tr) + tr.close() + + self.applied.append(statusentry(n, patchfn)) + finally: + lockmod.release(lock, tr) except: # re-raises ctx = repo[cparents[0]] repo.dirstate.rebuild(ctx.node(), ctx.manifest()) @@ -2083,8 +2079,7 @@ lastparent = None diffopts = self.diffopts({'git': git}) - tr = repo.transaction('qimport') - try: + with repo.transaction('qimport') as tr: for r in rev: if not repo[r].mutable(): raise error.Abort(_('revision %d is not mutable') % r, @@ -2125,9 +2120,6 @@ self.parseseries() self.applieddirty = True self.seriesdirty = True - tr.close() - finally: - tr.release() for i, filename in enumerate(files): if existing: @@ -2316,8 +2308,7 @@ Returns 0 if import succeeded. """ - lock = repo.lock() # cause this may move phase - try: + with repo.lock(): # cause this may move phase q = repo.mq try: imported = q.qimport( @@ -2326,8 +2317,6 @@ rev=opts.get('rev'), git=opts.get('git')) finally: q.savedirty() - finally: - lock.release() if imported and opts.get('push') and not opts.get('rev'): return q.push(repo, imported[-1]) @@ -2627,13 +2616,10 @@ q = repo.mq message = cmdutil.logmessage(ui, opts) setupheaderopts(ui, opts) - wlock = repo.wlock() - try: + with repo.wlock(): ret = q.refresh(repo, pats, msg=message, **opts) q.savedirty() return ret - finally: - wlock.release() @command("^qdiff", commands.diffopts + commands.diffopts2 + commands.walkopts, @@ -2718,14 +2704,11 @@ message = '\n'.join(message) diffopts = q.patchopts(q.diffopts(), *patches) - wlock = repo.wlock() - try: + with repo.wlock(): q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'), editform='mq.qfold') q.delete(repo, patches, opts) q.savedirty() - finally: - wlock.release() @command("qgoto", [('', 'keep-changes', None, @@ -2995,16 +2978,13 @@ r = q.qrepo() if r and patch in r.dirstate: wctx = r[None] - wlock = r.wlock() - try: + with r.wlock(): if r.dirstate[patch] == 'a': r.dirstate.drop(patch) r.dirstate.add(name) else: wctx.copy(patch, name) wctx.forget([patch]) - finally: - wlock.release() q.savedirty() @@ -3208,12 +3188,9 @@ # queue.finish may changes phases but leave the responsibility to lock the # repo to the caller to avoid deadlock with wlock. This command code is # responsibility for this locking. - lock = repo.lock() - try: + with repo.lock(): q.finish(repo, revs) q.savedirty() - finally: - lock.release() return 0 @command("qqueue", @@ -3548,9 +3525,11 @@ # i18n: column positioning for "hg summary" ui.note(_("mq: (empty queue)\n")) +revsetpredicate = revset.extpredicate() + +@revsetpredicate('mq()') def revsetmq(repo, subset, x): - """``mq()`` - Changesets managed by MQ. + """Changesets managed by MQ. """ revset.getargs(x, 0, 0, _("mq takes no arguments")) applied = set([repo[r.node].rev() for r in repo.mq.applied]) @@ -3586,7 +3565,7 @@ if extmodule.__file__ != __file__: dotable(getattr(extmodule, 'cmdtable', {})) - revset.symbols['mq'] = revsetmq + revsetpredicate.setup() colortable = {'qguard.negative': 'red', 'qguard.positive': 'yellow',
--- a/hgext/pager.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/pager.py Sun Jan 17 21:40:21 2016 -0600 @@ -49,9 +49,13 @@ To ignore global commands like :hg:`version` or :hg:`help`, you have to specify them in your user configuration file. -The --pager=... option can also be used to control when the pager is -used. Use a boolean value like yes, no, on, off, or use auto for -normal behavior. +To control whether the pager is used at all for an individual command, +you can use --pager=<value>:: + + - use as needed: `auto`. + - require the pager: `yes` or `on`. + - suppress the pager: `no` or `off` (any unrecognized value + will also work). '''
--- a/hgext/patchbomb.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/patchbomb.py Sun Jan 17 21:40:21 2016 -0600 @@ -44,6 +44,13 @@ directly from the commandline. See the [email] and [smtp] sections in hgrc(5) for details. +By default, :hg:`email` will prompt for a ``To`` or ``CC`` header if +you do not supply one via configuration or the command line. You can +override this to never prompt by configuring an empty value:: + + [email] + cc = + You can control the default inclusion of an introduction message with the ``patchbomb.intro`` configuration option. The configuration is always overwritten by command line flags like --intro and --desc:: @@ -58,7 +65,7 @@ ''' import os, errno, socket, tempfile, cStringIO -import email +import email as emailmod from mercurial import cmdutil, commands, hg, mail, patch, util, error from mercurial import scmutil @@ -155,7 +162,7 @@ body += '\n'.join(patchlines) if addattachment: - msg = email.MIMEMultipart.MIMEMultipart() + msg = emailmod.MIMEMultipart.MIMEMultipart() if body: msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', @@ -228,10 +235,7 @@ opts['type'] = btype try: commands.bundle(ui, repo, tmpfn, dest, **opts) - fp = open(tmpfn, 'rb') - data = fp.read() - fp.close() - return data + return util.readfile(tmpfn) finally: try: os.unlink(tmpfn) @@ -272,15 +276,15 @@ or prompt(ui, 'Subject:', 'A bundle for your repository')) body = _getdescription(repo, '', sender, **opts) - msg = email.MIMEMultipart.MIMEMultipart() + msg = emailmod.MIMEMultipart.MIMEMultipart() if body: msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) - datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle') + datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle') datapart.set_payload(bundle) bundlename = '%s.hg' % opts.get('bundlename', 'bundle') datapart.add_header('Content-Disposition', 'attachment', filename=bundlename) - email.Encoders.encode_base64(datapart) + emailmod.Encoders.encode_base64(datapart) msg.attach(datapart) msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) return [(msg, subj, None)] @@ -403,7 +407,7 @@ ('', 'intro', None, _('send an introduction email for a single patch')), ] + emailopts + commands.remoteopts, _('hg email [OPTION]... [DEST]...')) -def patchbomb(ui, repo, *revs, **opts): +def email(ui, repo, *revs, **opts): '''send changesets by email By default, diffs are sent in the format generated by @@ -596,10 +600,12 @@ # not on the command line: fallback to config and then maybe ask addr = (ui.config('email', configkey) or - ui.config('patchbomb', configkey) or - '') - if not addr and ask: - addr = prompt(ui, header, default=default) + ui.config('patchbomb', configkey)) + if not addr: + specified = (ui.hasconfig('email', configkey) or + ui.hasconfig('patchbomb', configkey)) + if not specified and ask: + addr = prompt(ui, header, default=default) if addr: showaddrs.append('%s: %s' % (header, addr)) return mail.addrlistencode(ui, [addr], _charsets, opts.get('test')) @@ -641,7 +647,7 @@ if not parent.endswith('>'): parent += '>' - sender_addr = email.Utils.parseaddr(sender)[1] + sender_addr = emailmod.Utils.parseaddr(sender)[1] sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) sendmail = None firstpatch = None @@ -660,7 +666,7 @@ parent = m['Message-Id'] m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version() - m['Date'] = email.Utils.formatdate(start_time[0], localtime=True) + m['Date'] = emailmod.Utils.formatdate(start_time[0], localtime=True) start_time = (start_time[0] + 1, start_time[1]) m['From'] = sender @@ -678,7 +684,7 @@ fp = util.popen(os.environ['PAGER'], 'w') else: fp = ui - generator = email.Generator.Generator(fp, mangle_from_=False) + generator = emailmod.Generator.Generator(fp, mangle_from_=False) try: generator.flatten(m, 0) fp.write('\n') @@ -702,7 +708,7 @@ # Exim does not remove the Bcc field del m['Bcc'] fp = cStringIO.StringIO() - generator = email.Generator.Generator(fp, mangle_from_=False) + generator = emailmod.Generator.Generator(fp, mangle_from_=False) generator.flatten(m, 0) sendmail(sender_addr, to + bcc + cc, fp.getvalue())
--- a/hgext/progress.py Wed Jan 06 11:01:55 2016 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# progress.py show progress bars for some actions -# -# Copyright (C) 2010 Augie Fackler <durin42@gmail.com> -# -# 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 (DEPRECATED) - -This extension has been merged into core, you can remove it from your config. -See hg help config.progress for configuration options. -""" -# 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'
--- a/hgext/rebase.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/rebase.py Sun Jan 17 21:40:21 2016 -0600 @@ -30,8 +30,11 @@ revtodo = -1 nullmerge = -2 revignored = -3 -# To do with obsolescence +# successor in rebase destination revprecursor = -4 +# plain prune (no successor) +revpruned = -5 +revskipped = (revignored, revprecursor, revpruned) cmdtable = {} command = cmdutil.command(cmdtable) @@ -44,14 +47,6 @@ def _nothingtorebase(): return 1 -def _savegraft(ctx, extra): - s = ctx.extra().get('source', None) - if s is not None: - extra['source'] = s - -def _savebranch(ctx, extra): - extra['branch'] = ctx.branch() - def _makeextrafn(copiers): """make an extrafn out of the given copy-functions. @@ -69,6 +64,9 @@ branch = repo[None].branch() return repo[branch].rev() +revsetpredicate = revset.extpredicate() + +@revsetpredicate('_destrebase') def _revsetdestrebase(repo, subset, x): # ``_rebasedefaultdest()`` @@ -115,50 +113,35 @@ useful for linearizing *local* changes relative to a master development tree. - You should not rebase changesets that have already been shared - with others. Doing so will force everybody else to perform the - same rebase or they will end up with duplicated changesets after - pulling in your rebased changesets. - - In its default configuration, Mercurial will prevent you from - rebasing published changes. See :hg:`help phases` for details. + Published commits cannot be rebased (see :hg:`help phases`). + To copy commits, see :hg:`help graft`. If you don't specify a destination changeset (``-d/--dest``), rebase uses the current branch tip as the destination. (The destination changeset is not modified by rebasing, but new changesets are added as its descendants.) - You can specify which changesets to rebase in two ways: as a - "source" changeset or as a "base" changeset. Both are shorthand - for a topologically related set of changesets (the "source - branch"). If you specify source (``-s/--source``), rebase will - rebase that changeset and all of its descendants onto dest. If you - specify base (``-b/--base``), rebase will select ancestors of base - back to but not including the common ancestor with dest. Thus, - ``-b`` is less precise but more convenient than ``-s``: you can - specify any changeset in the source branch, and rebase will select - the whole branch. If you specify neither ``-s`` nor ``-b``, rebase - uses the parent of the working directory as the base. + There are three ways to select changesets:: + + 1. Explicitly select them using ``--rev``. + + 2. Use ``--source`` to select a root changeset and include all of its + descendants. + + 3. Use ``--base`` to select a changeset; rebase will find ancestors + and their descendants which are not also ancestors of the destination. - For advanced usage, a third way is available through the ``--rev`` - option. It allows you to specify an arbitrary set of changesets to - rebase. Descendants of revs you specify with this option are not - automatically included in the rebase. + Rebase will destroy original changesets unless you use ``--keep``. + It will also move your bookmarks (even if you do). + + Some changesets may be dropped if they do not contribute changes + (e.g. merges from the destination branch). - By default, rebase recreates the changesets in the source branch - as descendants of dest and then destroys the originals. Use - ``--keep`` to preserve the original source changesets. Some - changesets in the source branch (e.g. merges from the destination - branch) may be dropped if they no longer contribute any change. + Unlike ``merge``, rebase will do nothing if you are at the branch tip of + a named branch with two heads. You will need to explicitly specify source + and/or destination. - One result of the rules for selecting the destination changeset - and source branch is that, unlike ``merge``, rebase will do - nothing if you are at the branch tip of a named branch - with two heads. You need to explicitly specify source and/or - destination (or ``update`` to the other head, if it's the head of - the intended source branch). - - If a rebase is interrupted to manually resolve a merge, it can be + If a rebase is interrupted to manually resolve a conflict, it can be continued with --continue/-c or aborted with --abort/-a. .. container:: verbose @@ -220,8 +203,9 @@ abortf = opts.get('abort') collapsef = opts.get('collapse', False) collapsemsg = cmdutil.logmessage(ui, opts) + date = opts.get('date', None) e = opts.get('extrafn') # internal, used by e.g. hgsubversion - extrafns = [_savegraft] + extrafns = [] if e: extrafns = [e] keepf = opts.get('keep', False) @@ -359,14 +343,34 @@ obsoletenotrebased = {} if ui.configbool('experimental', 'rebaseskipobsolete'): rebasesetrevs = set(rebaseset) + rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs) obsoletenotrebased = _computeobsoletenotrebased(repo, - rebasesetrevs, + rebaseobsrevs, dest) + rebaseobsskipped = set(obsoletenotrebased) + + # Obsolete node with successors not in dest leads to divergence + divergenceok = ui.configbool('rebase', + 'allowdivergence') + divergencebasecandidates = rebaseobsrevs - rebaseobsskipped + + if divergencebasecandidates and not divergenceok: + msg = _("this rebase will cause divergence") + h = _("to force the rebase please set " + "rebase.allowdivergence=True") + raise error.Abort(msg, hint=h) # - plain prune (no successor) changesets are rebased # - split changesets are not rebased if at least one of the # changeset resulting from the split is an ancestor of dest - rebaseset = rebasesetrevs - set(obsoletenotrebased) + rebaseset = rebasesetrevs - rebaseobsskipped + if rebasesetrevs and not rebaseset: + msg = _('all requested changesets have equivalents ' + 'or were marked as obsolete') + hint = _('to force the rebase, set the config ' + 'experimental.rebaseskipobsolete to False') + raise error.Abort(msg, hint=hint) + result = buildstate(repo, dest, rebaseset, collapsef, obsoletenotrebased) @@ -390,18 +394,13 @@ if dest.closesbranch() and not keepbranchesf: ui.status(_('reopening closed branch head %s\n') % dest) - if keepbranchesf: - # insert _savebranch at the start of extrafns so if - # there's a user-provided extrafn it can clobber branch if - # desired - extrafns.insert(0, _savebranch) - if collapsef: - branches = set() - for rev in state: - branches.add(repo[rev].branch()) - if len(branches) > 1: - raise error.Abort(_('cannot collapse multiple named ' - 'branches')) + if keepbranchesf and collapsef: + branches = set() + for rev in state: + branches.add(repo[rev].branch()) + if len(branches) > 1: + raise error.Abort(_('cannot collapse multiple named ' + 'branches')) # Rebase if not targetancestors: @@ -434,7 +433,7 @@ targetancestors) storestatus(repo, originalwd, target, state, collapsef, keepf, keepbranchesf, external, activebookmark) - if len(repo.parents()) == 2: + if len(repo[None].parents()) == 2: repo.ui.debug('resuming interrupted rebase\n') else: try: @@ -454,7 +453,8 @@ editor = cmdutil.getcommiteditor(editform=editform, **opts) newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn, editor=editor, - keepbranches=keepbranchesf) + keepbranches=keepbranchesf, + date=date) else: # Skip commit if we are collapsing repo.dirstate.beginparentchange() @@ -482,6 +482,9 @@ targetctx.description().split('\n', 1)[0]) msg = _('note: not rebasing %s, already in destination as %s\n') ui.status(msg % (desc, desctarget)) + elif state[rev] == revpruned: + msg = _('note: not rebasing %s, it has no successor\n') + ui.status(msg % desc) else: ui.status(_('already rebased %s as %s\n') % (desc, repo[state[rev]])) @@ -505,7 +508,8 @@ editor = cmdutil.getcommiteditor(edit=editopt, editform=editform) newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extrafn=extrafn, editor=editor, - keepbranches=keepbranchesf) + keepbranches=keepbranchesf, + date=date) if newnode is None: newrev = target else: @@ -543,13 +547,14 @@ collapsedas = newnode clearrebased(ui, repo, state, skipped, collapsedas) - if currentbookmarks: - updatebookmarks(repo, targetnode, nstate, currentbookmarks) - if activebookmark not in repo._bookmarks: - # active bookmark was divergent one and has been deleted - activebookmark = None + with repo.transaction('bookmark') as tr: + if currentbookmarks: + updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr) + if activebookmark not in repo._bookmarks: + # active bookmark was divergent one and has been deleted + activebookmark = None + clearstatus(repo) - clearstatus(repo) ui.note(_("rebase completed\n")) util.unlinkpath(repo.sjoin('undo'), ignoremissing=True) if skipped: @@ -586,7 +591,7 @@ ', '.join(str(p) for p in sorted(parents)))) def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None, - keepbranches=False): + keepbranches=False, date=None): '''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.''' @@ -597,7 +602,10 @@ if commitmsg is None: commitmsg = ctx.description() keepbranch = keepbranches and repo[p1].branch() != ctx.branch() - extra = {'rebase_source': ctx.hex()} + extra = ctx.extra().copy() + if not keepbranches: + del extra['branch'] + extra['rebase_source'] = ctx.hex() if extrafn: extrafn(ctx, extra) @@ -608,8 +616,10 @@ if keepbranch: repo.ui.setconfig('ui', 'allowemptycommit', True) # Commit might fail if unresolved files exist + if date is None: + date = ctx.date() newnode = repo.commit(text=commitmsg, user=ctx.user(), - date=ctx.date(), extra=extra, editor=editor) + date=date, extra=extra, editor=editor) finally: repo.ui.restoreconfig(backup) @@ -625,7 +635,7 @@ # Update to target and merge it with local if repo['.'].rev() != p1: repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1])) - merge.update(repo, p1, False, True, False) + merge.update(repo, p1, False, True) else: repo.ui.debug(" already in target\n") repo.dirstate.write(repo.currenttransaction()) @@ -634,7 +644,7 @@ repo.ui.debug(" detach base %d:%s\n" % (base, repo[base])) # When collapsing in-place, the parent is the common ancestor, we # have to allow merging with it. - stats = merge.update(repo, rev, True, True, False, base, collapse, + stats = merge.update(repo, rev, True, True, base, collapse, labels=['dest', 'source']) if collapse: copies.duplicatecopies(repo, rev, target) @@ -668,7 +678,7 @@ elif p1n in state: if state[p1n] == nullmerge: p1 = target - elif state[p1n] in (revignored, revprecursor): + elif state[p1n] in revskipped: p1 = nearestrebased(repo, p1n, state) if p1 is None: p1 = target @@ -684,7 +694,7 @@ if p2n in state: if p1 == target: # p1n in targetancestors or external p1 = state[p2n] - elif state[p2n] in (revignored, revprecursor): + elif state[p2n] in revskipped: p2 = nearestrebased(repo, p2n, state) if p2 is None: # no ancestors rebased yet, detach @@ -802,7 +812,7 @@ mq.seriesdirty = True mq.savedirty() -def updatebookmarks(repo, targetnode, nstate, originalbookmarks): +def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr): 'Move bookmarks to their correct changesets, and delete divergent ones' marks = repo._bookmarks for k, v in originalbookmarks.iteritems(): @@ -810,8 +820,7 @@ # update the bookmarks for revs that have moved marks[k] = nstate[v] bookmarks.deletedivergent(repo, [targetnode], k) - - marks.write() + marks.recordchange(tr) def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches, external, activebookmark): @@ -874,7 +883,7 @@ else: oldrev, newrev = l.split(':') if newrev in (str(nullmerge), str(revignored), - str(revprecursor)): + str(revprecursor), str(revpruned)): state[repo[oldrev].rev()] = int(newrev) elif newrev == nullid: state[repo[oldrev].rev()] = revtodo @@ -908,7 +917,7 @@ def needupdate(repo, state): '''check whether we should `update --clean` away from a merge, or if somehow the working dir got forcibly updated, e.g. by older hg''' - parents = [p.rev() for p in repo.parents()] + parents = [p.rev() for p in repo[None].parents()] # Are we in a merge state at all? if len(parents) < 2: @@ -952,7 +961,7 @@ if cleanup: # Update away from the rebase if necessary if needupdate(repo, state): - merge.update(repo, originalwd, False, True, False) + merge.update(repo, originalwd, False, True) # Strip from the first rebased revision rebased = filter(lambda x: x >= 0 and x != target, state.values()) @@ -1058,7 +1067,10 @@ for ignored in set(rebasedomain) - set(rebaseset): state[ignored] = revignored for r in obsoletenotrebased: - state[r] = revprecursor + if obsoletenotrebased[r] is None: + state[r] = revpruned + else: + state[r] = revprecursor return repo['.'].rev(), dest.rev(), state def clearrebased(ui, repo, state, skipped, collapsedas=None): @@ -1170,24 +1182,28 @@ blockers.update(getattr(repo, '_rebaseset', ())) return blockers -def _computeobsoletenotrebased(repo, rebasesetrevs, dest): +def _filterobsoleterevs(repo, revs): + """returns a set of the obsolete revisions in revs""" + return set(r for r in revs if repo[r].obsolete()) + +def _computeobsoletenotrebased(repo, rebaseobsrevs, dest): """return a mapping obsolete => successor for all obsolete nodes to be - rebased that have a successors in the destination""" + rebased that have a successors in the destination + + obsolete => None entries in the mapping indicate nodes with no succesor""" obsoletenotrebased = {} # Build a mapping successor => obsolete nodes for the obsolete # nodes to be rebased allsuccessors = {} cl = repo.changelog - for r in rebasesetrevs: - n = repo[r] - if n.obsolete(): - node = cl.node(r) - for s in obsolete.allsuccessors(repo.obsstore, [node]): - try: - allsuccessors[cl.rev(s)] = cl.rev(node) - except LookupError: - pass + for r in rebaseobsrevs: + node = cl.node(r) + for s in obsolete.allsuccessors(repo.obsstore, [node]): + try: + allsuccessors[cl.rev(s)] = cl.rev(node) + except LookupError: + pass if allsuccessors: # Look for successors of obsolete nodes to be rebased among @@ -1198,6 +1214,11 @@ for s in allsuccessors: if s in ancs: obsoletenotrebased[allsuccessors[s]] = s + elif (s == allsuccessors[s] and + allsuccessors.values().count(s) == 1): + # plain prune + obsoletenotrebased[s] = None + return obsoletenotrebased def summaryhook(ui, repo): @@ -1228,6 +1249,8 @@ cmdutil.unfinishedstates.append( ['rebasestate', False, False, _('rebase in progress'), _("use 'hg rebase --continue' or 'hg rebase --abort'")]) + cmdutil.afterresolvedstates.append( + ['rebasestate', _('hg rebase --continue')]) # ensure rebased rev are not hidden extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible) - revset.symbols['_destrebase'] = _revsetdestrebase + revsetpredicate.setup()
--- a/hgext/share.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/share.py Sun Jan 17 21:40:21 2016 -0600 @@ -73,7 +73,8 @@ the broken clone to reset it to a changeset that still exists. """ - return hg.share(ui, source, dest, not noupdate, bookmarks) + return hg.share(ui, source, dest=dest, update=not noupdate, + bookmarks=bookmarks) @command('unshare', [], '') def unshare(ui, repo): @@ -121,7 +122,7 @@ return orig(ui, source, *args, **opts) def extsetup(ui): - extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile) + extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile) extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange) extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo) extensions.wrapcommand(commands.table, 'clone', clone) @@ -149,12 +150,12 @@ srcurl, branches = parseurl(source) return repository(repo.ui, srcurl) -def getbkfile(orig, self, repo): +def getbkfile(orig, repo): if _hassharedbookmarks(repo): srcrepo = _getsrcrepo(repo) if srcrepo is not None: repo = srcrepo - return orig(self, repo) + return orig(repo) def recordchange(orig, self, tr): # Continue with write to local bookmarks file as usual
--- a/hgext/shelve.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/shelve.py Sun Jan 17 21:40:21 2016 -0600 @@ -224,7 +224,11 @@ def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" + with repo.wlock(): + cmdutil.checkunfinished(repo) + return _docreatecmd(ui, repo, pats, opts) +def _docreatecmd(ui, repo, pats, opts): def mutableancestors(ctx): """return all mutable ancestors for ctx (included) @@ -260,23 +264,8 @@ for i in xrange(1, 100): yield '%s-%02d' % (label, i) - def commitfunc(ui, repo, message, match, opts): - hasmq = util.safehasattr(repo, 'mq') - if hasmq: - saved, repo.mq.checkapplied = repo.mq.checkapplied, False - backup = repo.ui.backupconfig('phases', 'new-commit') - try: - repo.ui. setconfig('phases', 'new-commit', phases.secret) - editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) - return repo.commit(message, user, opts.get('date'), match, - editor=editor) - finally: - repo.ui.restoreconfig(backup) - if hasmq: - repo.mq.checkapplied = saved - if parent.node() != nullid: - desc = "changes to '%s'" % parent.description().split('\n', 1)[0] + desc = "changes to: %s" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' @@ -285,9 +274,8 @@ name = opts['name'] - wlock = lock = tr = None + lock = tr = None try: - wlock = repo.wlock() lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid @@ -313,6 +301,32 @@ if name.startswith('.'): raise error.Abort(_("shelved change names may not start with '.'")) interactive = opts.get('interactive', False) + includeunknown = (opts.get('unknown', False) and + not opts.get('addremove', False)) + + extra={} + if includeunknown: + s = repo.status(match=scmutil.match(repo[None], pats, opts), + unknown=True) + if s.unknown: + extra['shelve_unknown'] = '\0'.join(s.unknown) + repo[None].add(s.unknown) + + def commitfunc(ui, repo, message, match, opts): + hasmq = util.safehasattr(repo, 'mq') + if hasmq: + saved, repo.mq.checkapplied = repo.mq.checkapplied, False + backup = repo.ui.backupconfig('phases', 'new-commit') + try: + repo.ui. setconfig('phases', 'new-commit', phases.secret) + editor = cmdutil.getcommiteditor(editform='shelve.shelve', + **opts) + return repo.commit(message, user, opts.get('date'), match, + editor=editor, extra=extra) + finally: + repo.ui.restoreconfig(backup) + if hasmq: + repo.mq.checkapplied = saved def interactivecommitfunc(ui, repo, *pats, **opts): match = scmutil.match(repo['.'], pats, {}) @@ -346,38 +360,32 @@ _aborttransaction(repo) finally: - lockmod.release(tr, lock, wlock) + lockmod.release(tr, lock) def cleanupcmd(ui, repo): """subcommand that deletes all shelves""" - wlock = None - try: - wlock = repo.wlock() + with repo.wlock(): for (name, _type) in repo.vfs.readdir('shelved'): suffix = name.rsplit('.', 1)[-1] if suffix in ('hg', 'patch'): shelvedfile(repo, name).movetobackup() cleanupoldbackups(repo) - finally: - lockmod.release(wlock) def deletecmd(ui, repo, pats): """subcommand that deletes a specific shelve""" if not pats: raise error.Abort(_('no shelved changes specified!')) - wlock = repo.wlock() - try: - 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 error.Abort(_("shelved change '%s' not found") % name) - finally: - lockmod.release(wlock) + with repo.wlock(): + try: + 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 error.Abort(_("shelved change '%s' not found") % name) def listshelves(repo): """return all shelves in repo as list of (time, filename)""" @@ -418,8 +426,7 @@ ui.write(age, label='shelve.age') ui.write(' ' * (12 - len(age))) used += 12 - fp = open(name + '.patch', 'rb') - try: + with open(name + '.patch', 'rb') as fp: while True: line = fp.readline() if not line: @@ -441,8 +448,6 @@ for chunk, label in patch.diffstatui(difflines, width=width, git=True): ui.write(chunk, label=label) - finally: - fp.close() def singlepatchcmds(ui, repo, pats, opts, subcommand): """subcommand that displays a single shelf""" @@ -467,31 +472,27 @@ def unshelveabort(ui, repo, state, opts): """subcommand that abort an in-progress unshelve""" - wlock = repo.wlock() - lock = None - try: - checkparents(repo, state) - - util.rename(repo.join('unshelverebasestate'), - repo.join('rebasestate')) + with repo.lock(): try: - rebase.rebase(ui, repo, **{ - 'abort' : True - }) - except Exception: - util.rename(repo.join('rebasestate'), - repo.join('unshelverebasestate')) - raise + checkparents(repo, state) - lock = repo.lock() - - mergefiles(ui, repo, state.wctx, state.pendingctx) + util.rename(repo.join('unshelverebasestate'), + repo.join('rebasestate')) + try: + rebase.rebase(ui, repo, **{ + 'abort' : True + }) + except Exception: + util.rename(repo.join('rebasestate'), + repo.join('unshelverebasestate')) + raise - repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') - finally: - shelvedstate.clear(repo) - ui.warn(_("unshelve of '%s' aborted\n") % state.name) - lockmod.release(lock, wlock) + mergefiles(ui, repo, state.wctx, state.pendingctx) + repair.strip(ui, repo, state.stripnodes, backup=False, + topic='shelve') + finally: + shelvedstate.clear(repo) + ui.warn(_("unshelve of '%s' aborted\n") % state.name) def mergefiles(ui, repo, wctx, shelvectx): """updates to wctx and merges the changes from shelvectx into the @@ -507,7 +508,7 @@ # revert will overwrite unknown files, so move them out of the way for file in repo.status(unknown=True).unknown: if file in files: - util.rename(file, file + ".orig") + util.rename(file, scmutil.origpath(ui, repo, file)) ui.pushbuffer(True) cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), *pathtofiles(repo, files), @@ -527,18 +528,14 @@ """subcommand to continue an in-progress unshelve""" # We're finishing off a merge. First parent is our original # parent, second is the temporary "fake" commit we're unshelving. - wlock = repo.wlock() - lock = None - try: + with repo.lock(): checkparents(repo, state) - ms = merge.mergestate(repo) + ms = merge.mergestate.read(repo) if [f for f in ms if ms[f] == 'u']: raise error.Abort( _("unresolved conflicts, can't continue"), hint=_("see 'hg resolve', then 'hg unshelve --continue'")) - lock = repo.lock() - util.rename(repo.join('unshelverebasestate'), repo.join('rebasestate')) try: @@ -564,16 +561,15 @@ shelvedstate.clear(repo) unshelvecleanup(ui, repo, state.name, opts) ui.status(_("unshelve of '%s' complete\n") % state.name) - finally: - lockmod.release(lock, wlock) @command('unshelve', [('a', 'abort', None, _('abort an incomplete unshelve operation')), ('c', 'continue', None, _('continue an incomplete unshelve operation')), - ('', 'keep', None, + ('k', 'keep', None, _('keep shelve after unshelving')), + ('t', 'tool', '', _('specify merge tool')), ('', 'date', '', _('set date for temporary commits (DEPRECATED)'), _('DATE'))], _('hg unshelve [SHELVED]')) @@ -609,6 +605,10 @@ than ``maxbackups`` backups are kept, if same timestamp prevents from deciding exact order of them, for safety. """ + with repo.wlock(): + return _dounshelve(ui, repo, *shelved, **opts) + +def _dounshelve(ui, repo, *shelved, **opts): abortf = opts['abort'] continuef = opts['continue'] if not abortf and not continuef: @@ -620,6 +620,8 @@ if shelved: raise error.Abort(_('cannot combine abort/continue with ' 'naming a shelved change')) + if abortf and opts.get('tool', False): + ui.warn(_('tool option will be ignored\n')) try: state = shelvedstate.load(repo) @@ -647,9 +649,10 @@ raise error.Abort(_("shelved change '%s' not found") % basename) oldquiet = ui.quiet - wlock = lock = tr = None + lock = tr = None + forcemerge = ui.backupconfig('ui', 'forcemerge') try: - wlock = repo.wlock() + ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve') lock = repo.lock() tr = repo.transaction('unshelve', report=lambda x: None) @@ -663,8 +666,10 @@ # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. - # Store pending changes in a commit + # Store pending changes in a commit and remember added in case a shelve + # contains unknown files that are part of the pending change s = repo.status() + addedbefore = frozenset(s.added) if s.modified or s.added or s.removed or s.deleted: ui.status(_("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) @@ -675,7 +680,7 @@ backup = repo.ui.backupconfig('phases', 'new-commit') try: - repo.ui. setconfig('phases', 'new-commit', phases.secret) + repo.ui.setconfig('phases', 'new-commit', phases.secret) return repo.commit(message, 'shelve@localhost', opts.get('date'), match) finally: @@ -706,6 +711,7 @@ 'rev' : [shelvectx.rev()], 'dest' : str(tmpwctx.rev()), 'keep' : True, + 'tool' : opts.get('tool', ''), }) except error.InterventionRequired: tr.close() @@ -728,6 +734,16 @@ shelvectx = tmpwctx mergefiles(ui, repo, pctx, shelvectx) + + # Forget any files that were unknown before the shelve, unknown before + # unshelve started, but are now added. + shelveunknown = shelvectx.extra().get('shelve_unknown') + if shelveunknown: + shelveunknown = frozenset(shelveunknown.split('\0')) + addedafter = frozenset(repo.status().added) + toforget = (addedafter & shelveunknown) - addedbefore + repo[None].forget(toforget) + shelvedstate.clear(repo) # The transaction aborting will strip all the commits for us, @@ -743,11 +759,14 @@ ui.quiet = oldquiet if tr: tr.release() - lockmod.release(lock, wlock) + lockmod.release(lock) + ui.restoreconfig(forcemerge) @command('shelve', [('A', 'addremove', None, _('mark new/missing files as added/removed before shelving')), + ('u', 'unknown', None, + _('Store unknown files in the shelve')), ('', 'cleanup', None, _('delete all shelved changes')), ('', 'date', '', @@ -796,10 +815,9 @@ To delete specific shelved changes, use ``--delete``. To delete all shelved changes, use ``--cleanup``. ''' - cmdutil.checkunfinished(repo) - allowables = [ ('addremove', set(['create'])), # 'create' is pseudo action + ('unknown', set(['create'])), ('cleanup', set(['cleanup'])), # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests ('delete', set(['delete'])), @@ -837,3 +855,5 @@ [shelvedstate._filename, False, False, _('unshelve already in progress'), _("use 'hg unshelve --continue' or 'hg unshelve --abort'")]) + cmdutil.afterresolvedstates.append( + [shelvedstate._filename, _('hg unshelve --continue')])
--- a/hgext/strip.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/strip.py Sun Jan 17 21:40:21 2016 -0600 @@ -7,7 +7,7 @@ from mercurial.node import nullid from mercurial.lock import release from mercurial import cmdutil, hg, scmutil, util, error -from mercurial import repair, bookmarks, merge +from mercurial import repair, bookmarks as bookmarksmod , merge cmdtable = {} command = cmdutil.command(cmdtable) @@ -44,7 +44,7 @@ raise error.Abort(_("local changed subrepos found" + excsuffix)) return s -def strip(ui, repo, revs, update=True, backup=True, force=None, bookmark=None): +def strip(ui, repo, revs, update=True, backup=True, force=None, bookmarks=None): wlock = lock = None try: wlock = repo.wlock() @@ -62,13 +62,16 @@ repair.strip(ui, repo, revs, backup) - marks = repo._bookmarks - if bookmark: - if bookmark == repo._activebookmark: - bookmarks.deactivate(repo) - del marks[bookmark] - marks.write() - ui.write(_("bookmark '%s' deleted\n") % bookmark) + repomarks = repo._bookmarks + if bookmarks: + with repo.transaction('strip') as tr: + if repo._activebookmark in bookmarks: + bookmarksmod.deactivate(repo) + for bookmark in bookmarks: + del repomarks[bookmark] + repomarks.recordchange(tr) + for bookmark in sorted(bookmarks): + ui.write(_("bookmark '%s' deleted\n") % bookmark) finally: release(lock, wlock) @@ -85,7 +88,7 @@ ('n', '', None, _('ignored (DEPRECATED)')), ('k', 'keep', None, _("do not modify working directory during " "strip")), - ('B', 'bookmark', '', _("remove revs only reachable from given" + ('B', 'bookmark', [], _("remove revs only reachable from given" " bookmark"))], _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...')) def stripcmd(ui, repo, *revs, **opts): @@ -125,29 +128,37 @@ revs = list(revs) + opts.get('rev') revs = set(scmutil.revrange(repo, revs)) - wlock = repo.wlock() - try: - if opts.get('bookmark'): - mark = opts.get('bookmark') - marks = repo._bookmarks - if mark not in marks: - raise error.Abort(_("bookmark '%s' not found") % mark) + with repo.wlock(): + bookmarks = set(opts.get('bookmark')) + if bookmarks: + repomarks = repo._bookmarks + if not bookmarks.issubset(repomarks): + raise error.Abort(_("bookmark '%s' not found") % + ','.join(sorted(bookmarks - set(repomarks.keys())))) # If the requested bookmark is not the only one pointing to a # a revision we have to only delete the bookmark and not strip # anything. revsets cannot detect that case. - uniquebm = True - for m, n in marks.iteritems(): - if m != mark and n == repo[mark].node(): - uniquebm = False - break - if uniquebm: - rsrevs = repair.stripbmrevset(repo, mark) - revs.update(set(rsrevs)) + nodetobookmarks = {} + for mark, node in repomarks.iteritems(): + nodetobookmarks.setdefault(node, []).append(mark) + for marks in nodetobookmarks.values(): + if bookmarks.issuperset(marks): + rsrevs = repair.stripbmrevset(repo, marks[0]) + revs.update(set(rsrevs)) if not revs: - del marks[mark] - marks.write() - ui.write(_("bookmark '%s' deleted\n") % mark) + lock = tr = None + try: + lock = repo.lock() + tr = repo.transaction('bookmark') + for bookmark in bookmarks: + del repomarks[bookmark] + repomarks.recordchange(tr) + tr.close() + for bookmark in sorted(bookmarks): + ui.write(_("bookmark '%s' deleted\n") % bookmark) + finally: + release(lock, tr) if not revs: raise error.Abort(_('empty revision set')) @@ -208,15 +219,12 @@ repo.dirstate.write(repo.currenttransaction()) # clear resolve state - ms = merge.mergestate(repo) - ms.reset(repo['.'].node()) + merge.mergestate.clean(repo, repo['.'].node()) update = False strip(ui, repo, revs, backup=backup, update=update, - force=opts.get('force'), bookmark=opts.get('bookmark')) - finally: - wlock.release() + force=opts.get('force'), bookmarks=bookmarks) return 0
--- a/hgext/transplant.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/transplant.py Sun Jan 17 21:40:21 2016 -0600 @@ -127,9 +127,8 @@ diffopts = patch.difffeatureopts(self.ui, opts) diffopts.git = True - lock = wlock = tr = None + lock = tr = None try: - wlock = repo.wlock() lock = repo.lock() tr = repo.transaction('transplant') for rev in revs: @@ -152,7 +151,7 @@ if pulls: if source != repo: exchange.pull(repo, source.peer(), heads=pulls) - merge.update(repo, pulls[-1], False, False, None) + merge.update(repo, pulls[-1], False, False) p1, p2 = repo.dirstate.parents() pulls = [] @@ -216,7 +215,7 @@ tr.close() if pulls: exchange.pull(repo, source.peer(), heads=pulls) - merge.update(repo, pulls[-1], False, False, None) + merge.update(repo, pulls[-1], False, False) finally: self.saveseries(revmap, merges) self.transplants.write() @@ -224,7 +223,6 @@ tr.release() if lock: lock.release() - wlock.release() def filter(self, filter, node, changelog, patchfile): '''arbitrarily rewrite changeset before applying it''' @@ -283,7 +281,7 @@ p2 = node self.log(user, date, message, p1, p2, merge=merge) self.ui.write(str(inst) + '\n') - raise TransplantError(_('fix up the merge and run ' + raise TransplantError(_('fix up the working directory and run ' 'hg transplant --continue')) else: files = None @@ -304,6 +302,9 @@ return n + def canresume(self): + return os.path.exists(os.path.join(self.path, 'journal')) + def resume(self, repo, source, opts): '''recover last transaction and apply remaining changesets''' if os.path.exists(os.path.join(self.path, 'journal')): @@ -345,7 +346,6 @@ merge = True extra = {'transplant_source': node} - wlock = repo.wlock() try: p1, p2 = repo.dirstate.parents() if p1 != parent: @@ -367,7 +367,9 @@ return n, node finally: - wlock.release() + # TODO: get rid of this meaningless try/finally enclosing. + # this is kept only to reduce changes in a patch. + pass def readseries(self): nodes = [] @@ -572,6 +574,10 @@ and then resume where you left off by calling :hg:`transplant --continue/-c`. ''' + with repo.wlock(): + return _dotransplant(ui, repo, *revs, **opts) + +def _dotransplant(ui, repo, *revs, **opts): def incwalk(repo, csets, match=util.always): for node in csets: if match(node): @@ -599,7 +605,7 @@ return if not (opts.get('source') or revs or opts.get('merge') or opts.get('branch')): - raise error.Abort(_('no source URL, branch revision or revision ' + raise error.Abort(_('no source URL, branch revision, or revision ' 'list provided')) if opts.get('all'): if not opts.get('branch'): @@ -619,11 +625,14 @@ tp = transplanter(ui, repo, opts) - cmdutil.checkunfinished(repo) p1, p2 = repo.dirstate.parents() if len(repo) > 0 and p1 == revlog.nullid: raise error.Abort(_('no revision checked out')) - if not opts.get('continue'): + if opts.get('continue'): + if not tp.canresume(): + raise error.Abort(_('no transplant to continue')) + else: + cmdutil.checkunfinished(repo) if p2 != revlog.nullid: raise error.Abort(_('outstanding uncommitted merges')) m, a, r, d = repo.status()[:4] @@ -685,9 +694,11 @@ if cleanupfn: cleanupfn() +revsetpredicate = revset.extpredicate() + +@revsetpredicate('transplanted([set])') def revsettransplanted(repo, subset, x): - """``transplanted([set])`` - Transplanted changesets in set, or all transplanted changesets. + """Transplanted changesets in set, or all transplanted changesets. """ if x: s = revset.getset(repo, subset, x) @@ -703,10 +714,10 @@ return n and revlog.hex(n) or '' def extsetup(ui): - revset.symbols['transplanted'] = revsettransplanted + revsetpredicate.setup() templatekw.keywords['transplanted'] = kwtransplanted cmdutil.unfinishedstates.append( - ['series', True, False, _('transplant in progress'), + ['transplant/journal', True, False, _('transplant in progress'), _("use 'hg transplant --continue' or 'hg update' to abort")]) # tell hggettext to extract docstrings from these functions:
--- a/hgext/zeroconf/Zeroconf.py Wed Jan 06 11:01:55 2016 -0800 +++ b/hgext/zeroconf/Zeroconf.py Sun Jan 17 21:40:21 2016 -0600 @@ -150,7 +150,7 @@ _TYPE_TXT = 16 _TYPE_AAAA = 28 _TYPE_SRV = 33 -_TYPE_ANY = 255 +_TYPE_ANY = 255 # Mapping constants to names @@ -522,7 +522,7 @@ def readString(self, len): """Reads a string of a given length from the packet""" format = '!' + str(len) + 's' - length = struct.calcsize(format) + length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0]
--- a/i18n/de.po Wed Jan 06 11:01:55 2016 -0800 +++ b/i18n/de.po Sun Jan 17 21:40:21 2016 -0600 @@ -1187,7 +1187,7 @@ "This extension is deprecated. You should use :hg:`log -r\n" "\"children(REV)\"` instead.\n" msgstr "" -"Diese Erweiterung ist veraltet. Benutzen Sie stattdessen:hg:`log -r " +"Diese Erweiterung ist veraltet. Benutzen Sie stattdessen :hg:`log -r " "\"children(REV)\"`.\n" msgid "show the children of the given or working directory revision"
--- a/mercurial/__init__.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/__init__.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,144 @@ +# __init__.py - Startup and module loading logic for Mercurial. +# +# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import imp +import os +import sys +import zipimport + +__all__ = [] + +# Rules for how modules can be loaded. Values are: +# +# c - require C extensions +# allow - allow pure Python implementation when C loading fails +# py - only load pure Python modules +modulepolicy = '@MODULELOADPOLICY@' + +# By default, require the C extensions for performance reasons. +if modulepolicy == '@' 'MODULELOADPOLICY' '@': + modulepolicy = 'c' + +# PyPy doesn't load C extensions. +# +# The canonical way to do this is to test platform.python_implementation(). +# But we don't import platform and don't bloat for it here. +if '__pypy__' in sys.builtin_module_names: + modulepolicy = 'py' + +# Environment variable can always force settings. +modulepolicy = os.environ.get('HGMODULEPOLICY', modulepolicy) + +# Modules that have both Python and C implementations. See also the +# set of .py files under mercurial/pure/. +_dualmodules = set([ + 'mercurial.base85', + 'mercurial.bdiff', + 'mercurial.diffhelpers', + 'mercurial.mpatch', + 'mercurial.osutil', + 'mercurial.parsers', +]) + +class hgimporter(object): + """Object that conforms to import hook interface defined in PEP-302.""" + def find_module(self, name, path=None): + # We only care about modules that have both C and pure implementations. + if name in _dualmodules: + return self + return None + + def load_module(self, name): + mod = sys.modules.get(name, None) + if mod: + return mod + + mercurial = sys.modules['mercurial'] + + # The zip importer behaves sufficiently differently from the default + # importer to warrant its own code path. + loader = getattr(mercurial, '__loader__', None) + if isinstance(loader, zipimport.zipimporter): + def ziploader(*paths): + """Obtain a zipimporter for a directory under the main zip.""" + path = os.path.join(loader.archive, *paths) + zl = sys.path_importer_cache.get(path) + if not zl: + zl = zipimport.zipimporter(path) + return zl + + try: + if modulepolicy == 'py': + raise ImportError() + + zl = ziploader('mercurial') + mod = zl.load_module(name) + # Unlike imp, ziploader doesn't expose module metadata that + # indicates the type of module. So just assume what we found + # is OK (even though it could be a pure Python module). + except ImportError: + if modulepolicy == 'c': + raise + zl = ziploader('mercurial', 'pure') + mod = zl.load_module(name) + + sys.modules[name] = mod + return mod + + # Unlike the default importer which searches special locations and + # sys.path, we only look in the directory where "mercurial" was + # imported from. + + # imp.find_module doesn't support submodules (modules with "."). + # Instead you have to pass the parent package's __path__ attribute + # as the path argument. + stem = name.split('.')[-1] + + try: + if modulepolicy == 'py': + raise ImportError() + + modinfo = imp.find_module(stem, mercurial.__path__) + + # The Mercurial installer used to copy files from + # mercurial/pure/*.py to mercurial/*.py. Therefore, it's possible + # for some installations to have .py files under mercurial/*. + # Loading Python modules when we expected C versions could result + # in a) poor performance b) loading a version from a previous + # Mercurial version, potentially leading to incompatibility. Either + # scenario is bad. So we verify that modules loaded from + # mercurial/* are C extensions. If the current policy allows the + # loading of .py modules, the module will be re-imported from + # mercurial/pure/* below. + if modinfo[2][2] != imp.C_EXTENSION: + raise ImportError('.py version of %s found where C ' + 'version should exist' % name) + + except ImportError: + if modulepolicy == 'c': + raise + + # Could not load the C extension and pure Python is allowed. So + # try to load them. + from . import pure + modinfo = imp.find_module(stem, pure.__path__) + if not modinfo: + raise ImportError('could not find mercurial module %s' % + name) + + mod = imp.load_module(name, *modinfo) + sys.modules[name] = mod + return mod + +# We automagically register our custom importer as a side-effect of loading. +# This is necessary to ensure that any entry points are able to import +# mercurial.* modules without having to perform this registration themselves. +if not any(isinstance(x, hgimporter) for x in sys.meta_path): + # meta_path is used before any implicit finders and before sys.path. + sys.meta_path.insert(0, hgimporter())
--- a/mercurial/base85.c Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/base85.c Sun Jan 17 21:40:21 2016 -0600 @@ -18,8 +18,7 @@ "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; static char b85dec[256]; -static void -b85prep(void) +static void b85prep(void) { unsigned i; @@ -28,8 +27,7 @@ b85dec[(int)(b85chars[i])] = i + 1; } -static PyObject * -b85encode(PyObject *self, PyObject *args) +static PyObject *b85encode(PyObject *self, PyObject *args) { const unsigned char *text; PyObject *out; @@ -76,8 +74,7 @@ return out; } -static PyObject * -b85decode(PyObject *self, PyObject *args) +static PyObject *b85decode(PyObject *self, PyObject *args) { PyObject *out; const char *text;
--- a/mercurial/bookmarks.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/bookmarks.py Sun Jan 17 21:40:21 2016 -0600 @@ -22,26 +22,44 @@ util, ) +def _getbkfile(repo): + """Hook so that extensions that mess with the store can hook bm storage. + + For core, this just handles wether we should see pending + bookmarks or the committed ones. Other extensions (like share) + may need to tweak this behavior further. + """ + bkfile = None + if 'HG_PENDING' in os.environ: + try: + bkfile = repo.vfs('bookmarks.pending') + except IOError as inst: + if inst.errno != errno.ENOENT: + raise + if bkfile is None: + bkfile = repo.vfs('bookmarks') + return bkfile + + class bmstore(dict): """Storage for bookmarks. - This object should do all bookmark reads and writes, so that it's - fairly simple to replace the storage underlying bookmarks without - having to clone the logic surrounding bookmarks. + This object should do all bookmark-related reads and writes, so + that it's fairly simple to replace the storage underlying + bookmarks without having to clone the logic surrounding + bookmarks. This type also should manage the active bookmark, if + any. This particular bmstore implementation stores bookmarks as {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 "active" bookmark state at this - time. """ def __init__(self, repo): dict.__init__(self) self._repo = repo try: - bkfile = self.getbkfile(repo) + bkfile = _getbkfile(repo) for line in bkfile: line = line.strip() if not line: @@ -59,18 +77,29 @@ except IOError as inst: if inst.errno != errno.ENOENT: raise + self._clean = True + self._active = _readactive(repo, self) + self._aclean = True - def getbkfile(self, repo): - bkfile = None - if 'HG_PENDING' in os.environ: - try: - bkfile = repo.vfs('bookmarks.pending') - except IOError as inst: - if inst.errno != errno.ENOENT: - raise - if bkfile is None: - bkfile = repo.vfs('bookmarks') - return bkfile + @property + def active(self): + return self._active + + @active.setter + def active(self, mark): + if mark is not None and mark not in self: + raise AssertionError('bookmark %s does not exist!' % mark) + + self._active = mark + self._aclean = False + + def __setitem__(self, *args, **kwargs): + self._clean = False + return dict.__setitem__(self, *args, **kwargs) + + def __delitem__(self, key): + self._clean = False + return dict.__delitem__(self, key) def recordchange(self, tr): """record that bookmarks have been changed in a transaction @@ -89,6 +118,13 @@ We also store a backup of the previous state in undo.bookmarks that can be copied back on rollback. ''' + msg = 'bm.write() is deprecated, use bm.recordchange(transaction)' + self._repo.ui.deprecwarn(msg, '3.7') + # TODO: writing the active bookmark should probably also use a + # transaction. + self._writeactive() + if self._clean: + return repo = self._repo if (repo.ui.configbool('devel', 'all-warnings') or repo.ui.configbool('devel', 'check-locks')): @@ -108,24 +144,45 @@ def _writerepo(self, repo): """Factored out for extensibility""" - if repo._activebookmark not in self: - deactivate(repo) + rbm = repo._bookmarks + if rbm.active not in self: + rbm.active = None + rbm._writeactive() - wlock = repo.wlock() - try: + with repo.wlock(): + file_ = repo.vfs('bookmarks', 'w', atomictemp=True) + try: + self._write(file_) + except: # re-raises + file_.discard() + raise + finally: + file_.close() - file = repo.vfs('bookmarks', 'w', atomictemp=True) - self._write(file) - file.close() - - finally: - wlock.release() + def _writeactive(self): + if self._aclean: + return + with self._repo.wlock(): + if self._active is not None: + f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True) + try: + f.write(encoding.fromlocal(self._active)) + finally: + f.close() + else: + try: + self._repo.vfs.unlink('bookmarks.current') + except OSError as inst: + if inst.errno != errno.ENOENT: + raise + self._aclean = True def _write(self, fp): for name, node in self.iteritems(): fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name))) + self._clean = True -def readactive(repo): +def _readactive(repo, marks): """ Get the active bookmark. We can have an active bookmark that updates itself as we commit. This function returns the name of that bookmark. @@ -139,10 +196,19 @@ raise return None try: - # No readline() in osutil.posixfile, reading everything is cheap + # No readline() in osutil.posixfile, reading everything is + # cheap. + # Note that it's possible for readlines() here to raise + # IOError, since we might be reading the active mark over + # static-http which only tries to load the file when we try + # to read from it. mark = encoding.tolocal((file.readlines() or [''])[0]) - if mark == '' or mark not in repo._bookmarks: + if mark == '' or mark not in marks: mark = None + except IOError as inst: + if inst.errno != errno.ENOENT: + raise + return None finally: file.close() return mark @@ -153,35 +219,15 @@ 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) - - active = repo._activebookmark - if active == mark: - return - - wlock = repo.wlock() - try: - file = repo.vfs('bookmarks.current', 'w', atomictemp=True) - file.write(encoding.fromlocal(mark)) - file.close() - finally: - wlock.release() - repo._activebookmark = mark + repo._bookmarks.active = mark + repo._bookmarks._writeactive() def deactivate(repo): """ Unset the active bookmark in this repository. """ - wlock = repo.wlock() - try: - repo.vfs.unlink('bookmarks.current') - repo._activebookmark = None - except OSError as inst: - if inst.errno != errno.ENOENT: - raise - finally: - wlock.release() + repo._bookmarks.active = None + repo._bookmarks._writeactive() def isactivewdirparent(repo): """ @@ -231,7 +277,7 @@ deletefrom = parents marks = repo._bookmarks update = False - active = repo._activebookmark + active = marks.active if not active: return False @@ -249,7 +295,14 @@ update = True if update: - marks.write() + lock = tr = None + try: + lock = repo.lock() + tr = repo.transaction('bookmark') + marks.recordchange(tr) + tr.close() + finally: + lockmod.release(tr, lock) return update def listbookmarks(repo):
--- a/mercurial/bundle2.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/bundle2.py Sun Jan 17 21:40:21 2016 -0600 @@ -851,13 +851,15 @@ self._advisoryparams, self._data, self.mandatory) # methods used to defines the part content - def __setdata(self, data): + @property + def data(self): + return self._data + + @data.setter + def data(self, data): if self._generated is not None: raise error.ReadOnlyPartError('part is being generated') self._data = data - def __getdata(self): - return self._data - data = property(__getdata, __setdata) @property def mandatoryparams(self): @@ -1240,7 +1242,7 @@ Exists to allow extensions (like evolution) to mutate the capabilities. """ caps = capabilities.copy() - caps['changegroup'] = tuple(sorted(changegroup.packermap.keys())) + caps['changegroup'] = tuple(sorted(changegroup.supportedversions(repo))) if obsolete.isenabled(repo, obsolete.exchangeopt): supportedformat = tuple('V%i' % v for v in obsolete.formats) caps['obsmarkers'] = supportedformat @@ -1262,7 +1264,7 @@ obscaps = caps.get('obsmarkers', ()) return [int(c[1:]) for c in obscaps if c.startswith('V')] -@parthandler('changegroup', ('version', 'nbchanges')) +@parthandler('changegroup', ('version', 'nbchanges', 'treemanifest')) def handlechangegroup(op, inpart): """apply a changegroup part on the repo @@ -1277,13 +1279,21 @@ op.gettransaction() unpackerversion = inpart.params.get('version', '01') # We should raise an appropriate exception here - unpacker = changegroup.packermap[unpackerversion][1] - cg = unpacker(inpart, None) + cg = changegroup.getunbundler(unpackerversion, inpart, None) # the source and url passed here are overwritten by the one contained in # the transaction.hookargs argument. So 'bundle2' is a placeholder nbchangesets = None if 'nbchanges' in inpart.params: nbchangesets = int(inpart.params.get('nbchanges')) + if ('treemanifest' in inpart.params and + 'treemanifest' not in op.repo.requirements): + if len(op.repo.changelog) != 0: + raise error.Abort(_( + "bundle contains tree manifests, but local repo is " + "non-empty and does not use tree manifests")) + op.repo.requirements.add('treemanifest') + op.repo._applyopenerreqs() + op.repo._writerequirements() ret = cg.apply(op.repo, 'bundle2', 'bundle2', expectedtotal=nbchangesets) op.records.add('changegroup', {'return': ret}) if op.reply is not None:
--- a/mercurial/bundlerepo.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/bundlerepo.py Sun Jan 17 21:40:21 2016 -0600 @@ -245,17 +245,14 @@ fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-", suffix=".hg10un") self.tempfile = temp - fptemp = os.fdopen(fdtemp, 'wb') - try: + with os.fdopen(fdtemp, 'wb') as fptemp: fptemp.write(header) while True: chunk = read(2**18) if not chunk: break fptemp.write(chunk) - finally: - fptemp.close() return self.vfs.open(self.tempfile, mode="rb") self._tempparent = None @@ -285,7 +282,7 @@ "multiple changegroups") cgstream = part version = part.params.get('version', '01') - if version not in changegroup.packermap: + if version not in changegroup.supportedversions(self): msg = _('Unsupported changegroup version: %s') raise error.Abort(msg % version) if self.bundle.compressed(): @@ -296,7 +293,7 @@ raise error.Abort('No changegroups found') cgstream.seek(0) - self.bundle = changegroup.packermap[version][1](cgstream, 'UN') + self.bundle = changegroup.getunbundler(version, cgstream, 'UN') elif self.bundle.compressed(): f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN') @@ -329,6 +326,10 @@ # consume the header if it exists self.bundle.manifestheader() m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev) + # XXX: hack to work with changegroup3, but we still don't handle + # tree manifests correctly + if self.bundle.version == "03": + self.bundle.filelogheader() self.filestart = self.bundle.tell() return m
--- a/mercurial/byterange.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/byterange.py Sun Jan 17 21:40:21 2016 -0600 @@ -17,11 +17,25 @@ # $Id: byterange.py,v 1.9 2005/02/14 21:55:07 mstenner Exp $ +from __future__ import absolute_import + +import email +import ftplib +import mimetypes import os +import re +import socket import stat import urllib import urllib2 -import email.Utils + +addclosehook = urllib.addclosehook +addinfourl = urllib.addinfourl +splitattr = urllib.splitattr +splitpasswd = urllib.splitpasswd +splitport = urllib.splitport +splituser = urllib.splituser +unquote = urllib.unquote class RangeError(IOError): """Error raised when an unsatisfiable range is requested.""" @@ -196,8 +210,6 @@ server would. """ def open_local_file(self, req): - import mimetypes - import email host = req.get_host() file = req.get_selector() localfile = urllib.url2pathname(file) @@ -234,13 +246,6 @@ # follows: # -- range support modifications start/end here -from urllib import splitport, splituser, splitpasswd, splitattr, \ - unquote, addclosehook, addinfourl -import ftplib -import socket -import mimetypes -import email - class FTPRangeHandler(urllib2.FTPHandler): def ftp_open(self, req): host = req.get_host() @@ -406,7 +411,6 @@ if range_header is None: return None if _rangere is None: - import re _rangere = re.compile(r'^bytes=(\d{1,})-(\d*)') match = _rangere.match(range_header) if match:
--- a/mercurial/changegroup.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/changegroup.py Sun Jan 17 21:40:21 2016 -0600 @@ -32,6 +32,7 @@ _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s" _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s" +_CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH" def readexactly(stream, n): '''read n bytes from stream.read and abort if less was available''' @@ -246,7 +247,8 @@ deltabase = p1 else: deltabase = prevnode - return node, p1, p2, deltabase, cs + flags = 0 + return node, p1, p2, deltabase, cs, flags def deltachunk(self, prevnode): l = self._chunklength() @@ -255,9 +257,9 @@ headerdata = readexactly(self._stream, self.deltaheadersize) header = struct.unpack(self.deltaheader, headerdata) delta = readexactly(self._stream, l - self.deltaheadersize) - node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode) + node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode) return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs, - 'deltabase': deltabase, 'delta': delta} + 'deltabase': deltabase, 'delta': delta, 'flags': flags} def getchunks(self): """returns all the chunks contains in the bundle @@ -321,159 +323,165 @@ changesets = files = revisions = 0 - tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) try: - # The transaction could have been created before and already - # carries source information. In this case we use the top - # level data. We overwrite the argument because we need to use - # the top level value (if they exist) in this function. - srctype = tr.hookargs.setdefault('source', srctype) - url = tr.hookargs.setdefault('url', url) - repo.hook('prechangegroup', throw=True, **tr.hookargs) + with repo.transaction("\n".join([srctype, + util.hidepassword(url)])) as tr: + # The transaction could have been created before and already + # carries source information. In this case we use the top + # level data. We overwrite the argument because we need to use + # the top level value (if they exist) in this function. + srctype = tr.hookargs.setdefault('source', srctype) + url = tr.hookargs.setdefault('url', url) + repo.hook('prechangegroup', throw=True, **tr.hookargs) - # write changelog data to temp files so concurrent readers - # will not see an inconsistent view - cl = repo.changelog - cl.delayupdate(tr) - oldheads = cl.heads() + # write changelog data to temp files so concurrent readers + # will not see an inconsistent view + cl = repo.changelog + cl.delayupdate(tr) + oldheads = cl.heads() - trp = weakref.proxy(tr) - # pull off the changeset group - repo.ui.status(_("adding changesets\n")) - clstart = len(cl) - class prog(object): - 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 - self.callback = prog(_('changesets'), expectedtotal) + trp = weakref.proxy(tr) + # pull off the changeset group + repo.ui.status(_("adding changesets\n")) + clstart = len(cl) + class prog(object): + 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 + self.callback = prog(_('changesets'), expectedtotal) - efiles = set() - def onchangelog(cl, node): - efiles.update(cl.read(node)[3]) + efiles = set() + def onchangelog(cl, node): + efiles.update(cl.read(node)[3]) - self.changelogheader() - srccontent = cl.addgroup(self, csmap, trp, - addrevisioncb=onchangelog) - efiles = len(efiles) + self.changelogheader() + srccontent = cl.addgroup(self, csmap, trp, + addrevisioncb=onchangelog) + efiles = len(efiles) - if not (srccontent or emptyok): - raise error.Abort(_("received changelog group is empty")) - clend = len(cl) - changesets = clend - clstart - repo.ui.progress(_('changesets'), None) + if not (srccontent or emptyok): + raise error.Abort(_("received changelog group is empty")) + clend = len(cl) + changesets = clend - clstart + repo.ui.progress(_('changesets'), None) - # pull off the manifest group - repo.ui.status(_("adding manifests\n")) - self._unpackmanifests(repo, revmap, trp, prog, changesets) + # pull off the manifest group + repo.ui.status(_("adding manifests\n")) + self._unpackmanifests(repo, revmap, trp, prog, changesets) - needfiles = {} - if repo.ui.configbool('server', 'validate', default=False): - # validate incoming csets have their manifests - for cset in xrange(clstart, clend): - 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) + needfiles = {} + if repo.ui.configbool('server', 'validate', default=False): + # validate incoming csets have their manifests + for cset in xrange(clstart, clend): + 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")) - self.callback = None - pr = prog(_('files'), efiles) - newrevs, newfiles = _addchangegroupfiles( - repo, self, revmap, trp, pr, needfiles) - revisions += newrevs - files += newfiles + # process the files + repo.ui.status(_("adding file changes\n")) + self.callback = None + pr = prog(_('files'), efiles) + newrevs, newfiles = _addchangegroupfiles( + repo, self, revmap, trp, pr, needfiles) + revisions += newrevs + files += newfiles - dh = 0 - if oldheads: - heads = cl.heads() - dh = len(heads) - len(oldheads) - for h in heads: - if h not in oldheads and repo[h].closesbranch(): - dh -= 1 - htext = "" - if dh: - htext = _(" (%+d heads)") % dh + dh = 0 + if oldheads: + heads = cl.heads() + dh = len(heads) - len(oldheads) + for h in heads: + if h not in oldheads and repo[h].closesbranch(): + dh -= 1 + htext = "" + if dh: + htext = _(" (%+d heads)") % dh - repo.ui.status(_("added %d changesets" - " with %d changes to %d files%s\n") - % (changesets, revisions, files, htext)) - repo.invalidatevolatilesets() + repo.ui.status(_("added %d changesets" + " with %d changes to %d files%s\n") + % (changesets, revisions, files, htext)) + repo.invalidatevolatilesets() - if changesets > 0: - if 'node' not in tr.hookargs: - tr.hookargs['node'] = hex(cl.node(clstart)) - hookargs = dict(tr.hookargs) - else: - hookargs = dict(tr.hookargs) - hookargs['node'] = hex(cl.node(clstart)) - repo.hook('pretxnchangegroup', throw=True, **hookargs) + if changesets > 0: + if 'node' not in tr.hookargs: + tr.hookargs['node'] = hex(cl.node(clstart)) + tr.hookargs['node_last'] = hex(cl.node(clend - 1)) + hookargs = dict(tr.hookargs) + else: + hookargs = dict(tr.hookargs) + hookargs['node'] = hex(cl.node(clstart)) + hookargs['node_last'] = hex(cl.node(clend - 1)) + repo.hook('pretxnchangegroup', throw=True, **hookargs) - added = [cl.node(r) for r in xrange(clstart, clend)] - 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 - # exists locally as secret - # - # We should not use added here but the list of all change in - # the bundle - if publishing: - phases.advanceboundary(repo, tr, phases.public, srccontent) - else: - # Those changesets have been pushed from the outside, their - # phases are going to be pushed alongside. Therefor - # `targetphase` is ignored. - phases.advanceboundary(repo, tr, phases.draft, srccontent) - phases.retractboundary(repo, tr, phases.draft, added) - elif srctype != 'strip': - # publishing only alter behavior during push - # - # strip should not touch boundary at all - phases.retractboundary(repo, tr, targetphase, added) - - if changesets > 0: - if srctype != 'strip': - # During strip, branchcache is invalid but coming call to - # `destroyed` will repair it. - # In other case we can safely update cache on disk. - branchmap.updatecache(repo.filtered('served')) + added = [cl.node(r) for r in xrange(clstart, clend)] + 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 + # exists locally as secret + # + # We should not use added here but the list of all change in + # the bundle + if publishing: + phases.advanceboundary(repo, tr, phases.public, + srccontent) + else: + # Those changesets have been pushed from the + # outside, their phases are going to be pushed + # alongside. Therefor `targetphase` is + # ignored. + phases.advanceboundary(repo, tr, phases.draft, + srccontent) + phases.retractboundary(repo, tr, phases.draft, added) + elif srctype != 'strip': + # publishing only alter behavior during push + # + # strip should not touch boundary at all + phases.retractboundary(repo, tr, targetphase, added) - def runhooks(): - # These hooks run when the lock releases, not when the - # transaction closes. So it's possible for the changelog - # to have changed since we last saw it. - if clstart >= len(repo): - return + if changesets > 0: + if srctype != 'strip': + # During strip, branchcache is invalid but + # coming call to `destroyed` will repair it. + # In other case we can safely update cache on + # disk. + branchmap.updatecache(repo.filtered('served')) - # forcefully update the on-disk branch cache - repo.ui.debug("updating the branch cache\n") - repo.hook("changegroup", **hookargs) + def runhooks(): + # These hooks run when the lock releases, not when the + # transaction closes. So it's possible for the changelog + # to have changed since we last saw it. + if clstart >= len(repo): + return - for n in added: - args = hookargs.copy() - args['node'] = hex(n) - repo.hook("incoming", **args) + # forcefully update the on-disk branch cache + repo.ui.debug("updating the branch cache\n") + repo.hook("changegroup", **hookargs) + + for n in added: + args = hookargs.copy() + args['node'] = hex(n) + del args['node_last'] + repo.hook("incoming", **args) - newheads = [h for h in repo.heads() if h not in oldheads] - repo.ui.log("incoming", - "%s incoming changes - new heads: %s\n", - len(added), - ', '.join([hex(c[:6]) for c in newheads])) + newheads = [h for h in repo.heads() + if h not in oldheads] + repo.ui.log("incoming", + "%s incoming changes - new heads: %s\n", + len(added), + ', '.join([hex(c[:6]) for c in newheads])) - tr.addpostclose('changegroup-runhooks-%020i' % clstart, - lambda tr: repo._afterlock(runhooks)) - - tr.close() - + tr.addpostclose('changegroup-runhooks-%020i' % clstart, + lambda tr: repo._afterlock(runhooks)) finally: - tr.release() repo.ui.flush() # never return 0 here: if dh < 0: @@ -494,7 +502,37 @@ def _deltaheader(self, headertuple, prevnode): node, p1, p2, deltabase, cs = headertuple - return node, p1, p2, deltabase, cs + flags = 0 + return node, p1, p2, deltabase, cs, flags + +class cg3unpacker(cg2unpacker): + """Unpacker for cg3 streams. + + cg3 streams add support for exchanging treemanifests and revlog + flags. It adds the revlog flags to the delta header and an empty chunk + separating manifests and files. + """ + deltaheader = _CHANGEGROUPV3_DELTA_HEADER + deltaheadersize = struct.calcsize(deltaheader) + version = '03' + + def _deltaheader(self, headertuple, prevnode): + node, p1, p2, deltabase, cs, flags = headertuple + return node, p1, p2, deltabase, cs, flags + + def _unpackmanifests(self, repo, revmap, trp, prog, numchanges): + super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog, + numchanges) + while True: + chunkdata = self.filelogheader() + if not chunkdata: + break + # If we get here, there are directory manifests in the changegroup + d = chunkdata["filename"] + repo.ui.debug("adding %s revisions\n" % d) + dirlog = repo.manifest.dirlog(d) + if not dirlog.addgroup(self, revmap, trp): + raise error.Abort(_("received dir revlog group is empty")) class headerlessfixup(object): def __init__(self, fh, h): @@ -508,6 +546,27 @@ return d return readexactly(self._fh, n) +def _moddirs(files): + """Given a set of modified files, find the list of modified directories. + + This returns a list of (path to changed dir, changed dir) tuples, + as that's what the one client needs anyway. + + >>> _moddirs(['a/b/c.py', 'a/b/c.txt', 'a/d/e/f/g.txt', 'i.txt', ]) + [('/', 'a/'), ('a/', 'b/'), ('a/', 'd/'), ('a/d/', 'e/'), ('a/d/e/', 'f/')] + + """ + alldirs = set() + for f in files: + path = f.split('/')[:-1] + for i in xrange(len(path) - 1, -1, -1): + dn = '/'.join(path[:i]) + current = dn + '/', path[i] + '/' + if current in alldirs: + break + alldirs.add(current) + return sorted(alldirs) + class cg1packer(object): deltaheader = _CHANGEGROUPV1_DELTA_HEADER version = '01' @@ -593,7 +652,7 @@ rr, rl = revlog.rev, revlog.linkrev return [n for n in missing if rl(rr(n)) not in commonrevs] - def _packmanifests(self, mfnodes, lookuplinknode): + def _packmanifests(self, mfnodes, tmfnodes, lookuplinknode): """Pack flat manifests into a changegroup stream.""" ml = self._repo.manifest size = 0 @@ -602,6 +661,11 @@ size += len(chunk) yield chunk self._verbosenote(_('%8.i (manifests)\n') % size) + # It looks odd to assert this here, but tmfnodes doesn't get + # filled in until after we've called lookuplinknode for + # sending root manifests, so the only way to tell the streams + # got crossed is to check after we've done all the work. + assert not tmfnodes def generate(self, commonrevs, clnodes, fastpathlinkrev, source): '''yield a sequence of changegroup chunks (strings)''' @@ -611,8 +675,10 @@ clrevorder = {} mfs = {} # needed manifests + tmfnodes = {} fnodes = {} # needed file nodes - changedfiles = set() + # maps manifest node id -> set(changed files) + mfchangedfiles = {} # Callback for the changelog, used to collect changed files and manifest # nodes. @@ -620,9 +686,12 @@ def lookupcl(x): c = cl.read(x) clrevorder[x] = len(clrevorder) - changedfiles.update(c[3]) + n = c[0] # record the first changeset introducing this manifest version - mfs.setdefault(c[0], x) + mfs.setdefault(n, x) + # Record a complete list of potentially-changed files in + # this manifest. + mfchangedfiles.setdefault(n, set()).update(c[3]) return x self._verbosenote(_('uncompressed size of bundle content:\n')) @@ -648,41 +717,91 @@ # simply take the slowpath, which already has the 'clrevorder' logic. # This was also fixed in cc0ff93d0c0c. fastpathlinkrev = fastpathlinkrev and not self._reorder + # Treemanifests don't work correctly with fastpathlinkrev + # either, because we don't discover which directory nodes to + # send along with files. This could probably be fixed. + fastpathlinkrev = fastpathlinkrev and ( + 'treemanifest' not in repo.requirements) # Callback for the manifest, used to collect linkrevs for filelog # revisions. # Returns the linkrev node (collected in lookupcl). - def lookupmflinknode(x): - clnode = mfs[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 - # version - fclnodes = fnodes.setdefault(f, {}) - fclnode = fclnodes.setdefault(n, clnode) + if fastpathlinkrev: + lookupmflinknode = mfs.__getitem__ + else: + def lookupmflinknode(x): + """Callback for looking up the linknode for manifests. + + Returns the linkrev node for the specified manifest. + + SIDE EFFECT: + + 1) fclnodes gets populated with the list of relevant + file nodes if we're not using fastpathlinkrev + 2) When treemanifests are in use, collects treemanifest nodes + to send + + Note that this means manifests must be completely sent to + the client before you can trust the list of files and + treemanifests to send. + """ + clnode = mfs[x] + # We no longer actually care about reading deltas of + # the manifest here, because we already know the list + # of changed files, so for treemanifests (which + # lazily-load anyway to *generate* a readdelta) we can + # just load them with read() and then we'll actually + # be able to correctly load node IDs from the + # submanifest entries. + if 'treemanifest' in repo.requirements: + mdata = ml.read(x) + else: + mdata = ml.readfast(x) + for f in mfchangedfiles[x]: + try: + n = mdata[f] + except KeyError: + continue + # record the first changeset introducing this filelog + # version + fclnodes = fnodes.setdefault(f, {}) + fclnode = fclnodes.setdefault(n, clnode) + if clrevorder[clnode] < clrevorder[fclnode]: + fclnodes[n] = clnode + # gather list of changed treemanifest nodes + if 'treemanifest' in repo.requirements: + submfs = {'/': mdata} + for dn, bn in _moddirs(mfchangedfiles[x]): + submf = submfs[dn] + submf = submf._dirs[bn] + submfs[submf.dir()] = submf + tmfclnodes = tmfnodes.setdefault(submf.dir(), {}) + tmfclnodes.setdefault(submf._node, clnode) if clrevorder[clnode] < clrevorder[fclnode]: - fclnodes[n] = clnode - return clnode + tmfclnodes[n] = clnode + return clnode mfnodes = self.prune(ml, mfs, commonrevs) - for x in self._packmanifests(mfnodes, lookupmflinknode): + for x in self._packmanifests( + mfnodes, tmfnodes, lookupmflinknode): yield x mfs.clear() clrevs = set(cl.rev(x) for x in clnodes) - def linknodes(filerevlog, fname): - if fastpathlinkrev: + if not fastpathlinkrev: + def linknodes(unused, fname): + return fnodes.get(fname, {}) + else: + cln = cl.node + def linknodes(filerevlog, fname): llr = filerevlog.linkrev - def genfilenodes(): - for r in filerevlog: - linkrev = llr(r) - if linkrev in clrevs: - yield filerevlog.node(r), cl.node(linkrev) - return dict(genfilenodes()) - return fnodes.get(fname, {}) + fln = filerevlog.node + revs = ((r, llr(r)) for r in filerevlog) + return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs) + changedfiles = set() + for x in mfchangedfiles.itervalues(): + changedfiles.update(x) for chunk in self.generatefiles(changedfiles, linknodes, commonrevs, source): yield chunk @@ -751,14 +870,16 @@ delta = revlog.revdiff(base, rev) p1n, p2n = revlog.parents(node) basenode = revlog.node(base) - meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode) + flags = revlog.flags(rev) + meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags) meta += prefix l = len(meta) + len(delta) yield chunkheader(l) yield meta yield delta - def builddeltaheader(self, node, p1n, p2n, basenode, linknode): + def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags): # do nothing with basenode, it is implicitly the previous one in HG10 + # do nothing with flags, it is implicitly 0 for cg1 and cg2 return struct.pack(self.deltaheader, node, p1n, p2n, linknode) class cg2packer(cg1packer): @@ -781,14 +902,59 @@ return prev return dp - def builddeltaheader(self, node, p1n, p2n, basenode, linknode): + def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags): + # Do nothing with flags, it is implicitly 0 in cg1 and cg2 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode) -packermap = {'01': (cg1packer, cg1unpacker), +class cg3packer(cg2packer): + version = '03' + deltaheader = _CHANGEGROUPV3_DELTA_HEADER + + def _packmanifests(self, mfnodes, tmfnodes, lookuplinknode): + # Note that debug prints are super confusing in this code, as + # tmfnodes gets populated by the calls to lookuplinknode in + # the superclass's manifest packer. In the future we should + # probably see if we can refactor this somehow to be less + # confusing. + for x in super(cg3packer, self)._packmanifests( + mfnodes, {}, lookuplinknode): + yield x + dirlog = self._repo.manifest.dirlog + for name, nodes in tmfnodes.iteritems(): + # For now, directory headers are simply file headers with + # a trailing '/' on the path (already in the name). + yield self.fileheader(name) + for chunk in self.group(nodes, dirlog(name), nodes.get): + yield chunk + yield self.close() + + def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags): + return struct.pack( + self.deltaheader, node, p1n, p2n, basenode, linknode, flags) + +_packermap = {'01': (cg1packer, cg1unpacker), # cg2 adds support for exchanging generaldelta '02': (cg2packer, cg2unpacker), + # cg3 adds support for exchanging revlog flags and treemanifests + '03': (cg3packer, cg3unpacker), } +def supportedversions(repo): + versions = _packermap.keys() + cg3 = ('treemanifest' in repo.requirements or + repo.ui.configbool('experimental', 'changegroup3') or + repo.ui.configbool('experimental', 'treemanifest')) + if not cg3: + versions.remove('03') + return versions + +def getbundler(version, repo, bundlecaps=None): + assert version in supportedversions(repo) + return _packermap[version][0](repo, bundlecaps) + +def getunbundler(version, fh, alg): + return _packermap[version][1](fh, alg) + def _changegroupinfo(repo, nodes, source): if repo.ui.verbose or source == 'bundle': repo.ui.status(_("%d changesets found\n") % len(nodes)) @@ -815,7 +981,7 @@ def getsubset(repo, outgoing, bundler, source, fastpath=False): gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath) - return packermap[bundler.version][1](util.chunkbuffer(gengroup), None) + return getunbundler(bundler.version, util.chunkbuffer(gengroup), None) def changegroupsubset(repo, roots, heads, source, version='01'): """Compute a changegroup consisting of all the nodes that are @@ -841,7 +1007,7 @@ included = set(csets) discbases = [n for n in discbases if n not in included] outgoing = discovery.outgoing(cl, discbases, heads) - bundler = packermap[version][0](repo) + bundler = getbundler(version, repo) return getsubset(repo, outgoing, bundler, source) def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None, @@ -852,7 +1018,7 @@ precomputed sets in outgoing. Returns a raw changegroup generator.""" if not outgoing.missing: return None - bundler = packermap[version][0](repo, bundlecaps) + bundler = getbundler(version, repo, bundlecaps) return getsubsetraw(repo, outgoing, bundler, source) def getlocalchangegroup(repo, source, outgoing, bundlecaps=None, @@ -863,7 +1029,7 @@ precomputed sets in outgoing.""" if not outgoing.missing: return None - bundler = packermap[version][0](repo, bundlecaps) + bundler = getbundler(version, repo, bundlecaps) return getsubset(repo, outgoing, bundler, source) def computeoutgoing(repo, heads, common):
--- a/mercurial/changelog.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/changelog.py Sun Jan 17 21:40:21 2016 -0600 @@ -360,6 +360,17 @@ files = l[3:] return (manifest, user, (time, timezone), files, desc, extra) + def readfiles(self, node): + """ + short version of read that only returns the files modified by the cset + """ + text = self.revision(node) + if not text: + return [] + last = text.index("\n\n") + l = text[:last].split('\n') + return l[3:] + def add(self, manifest, files, desc, transaction, p1, p2, user, date=None, extra=None): # Convert to UTF-8 encoded bytestrings as the very first
--- a/mercurial/cmdutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/cmdutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -70,11 +70,11 @@ testfile = ui.config('experimental', 'crecordtest', None) oldwrite = setupwrapcolorwrite(ui) try: - newchunks = filterchunks(ui, originalhunks, usecurses, testfile, - operation) + newchunks, newopts = filterchunks(ui, originalhunks, usecurses, + testfile, operation) finally: ui.write = oldwrite - return newchunks + return newchunks, newopts def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts): @@ -116,14 +116,16 @@ diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True) diffopts.nodates = True diffopts.git = True - originaldiff = patch.diff(repo, changes=status, opts=diffopts) + diffopts.showfunc = True + originaldiff = patch.diff(repo, changes=status, opts=diffopts) originalchunks = patch.parsepatch(originaldiff) # 1. filter patch, so we have intending-to apply subset of it try: - chunks = filterfn(ui, originalchunks) + chunks, newopts = filterfn(ui, originalchunks) except patch.PatchError as err: raise error.Abort(_('error parsing patch: %s') % err) + opts.update(newopts) # We need to keep a backup of files that have been newly added and # modified during the recording process because there is a previous @@ -181,9 +183,9 @@ # 3a. apply filtered patch to clean repo (clean) if backups: # Equivalent to hg.revert - choices = lambda key: key in backups + m = scmutil.matchfiles(repo, backups.keys()) mergemod.update(repo, repo.dirstate.p1(), - False, True, choices) + False, True, matcher=m) # 3b. (apply) if dopatch: @@ -228,11 +230,8 @@ pass def recordinwlock(ui, repo, message, match, opts): - wlock = repo.wlock() - try: + with repo.wlock(): return recordfunc(ui, repo, message, match, opts) - finally: - wlock.release() return commit(ui, repo, recordinwlock, pats, opts) @@ -436,6 +435,19 @@ raise error.Abort(_("invalid format spec '%%%s' in output filename") % inst.args[0]) +class _unclosablefile(object): + def __init__(self, fp): + self._fp = fp + + def close(self): + pass + + def __iter__(self): + return iter(self._fp) + + def __getattr__(self, attr): + return getattr(self._fp, attr) + def makefileobj(repo, pat, node=None, desc=None, total=None, seqno=None, revwidth=None, mode='wb', modemap=None, pathname=None): @@ -447,22 +459,7 @@ fp = repo.ui.fout else: fp = repo.ui.fin - if util.safehasattr(fp, 'fileno'): - return os.fdopen(os.dup(fp.fileno()), mode) - else: - # if this fp can't be duped properly, return - # a dummy object that can be closed - class wrappedfileobj(object): - noop = lambda x: None - def __init__(self, f): - self.f = f - def __getattr__(self, attr): - if attr == 'close': - return self.noop - else: - return getattr(self.f, attr) - - return wrappedfileobj(fp) + return _unclosablefile(fp) if util.safehasattr(pat, 'write') and writable: return pat if util.safehasattr(pat, 'read') and 'r' in mode: @@ -870,20 +867,21 @@ extractdata = patch.extract(ui, hunk) tmpname = extractdata.get('filename') message = extractdata.get('message') - user = extractdata.get('user') - date = extractdata.get('date') + user = opts.get('user') or extractdata.get('user') + date = opts.get('date') or extractdata.get('date') branch = extractdata.get('branch') nodeid = extractdata.get('nodeid') p1 = extractdata.get('p1') p2 = extractdata.get('p2') + nocommit = opts.get('no_commit') + importbranch = opts.get('import_branch') update = not opts.get('bypass') strip = opts["strip"] prefix = opts["prefix"] sim = float(opts.get('similarity') or 0) if not tmpname: return (None, None, False) - msg = _('applied to working directory') rejects = False @@ -932,7 +930,7 @@ if p2 != parents[1]: repo.setparents(p1.node(), p2.node()) - if opts.get('exact') or opts.get('import_branch'): + if opts.get('exact') or importbranch: repo.dirstate.setbranch(branch or 'default') partial = opts.get('partial', False) @@ -947,7 +945,7 @@ rejects = True files = list(files) - if opts.get('no_commit'): + if nocommit: if message: msgs.append(message) else: @@ -970,15 +968,15 @@ 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, + n = repo.commit(message, user, + date, match=m, editor=editor, extra=extra) for idfunc in extrapostimport: extrapostimportmap[idfunc](repo[n]) finally: repo.ui.restoreconfig(allowemptyback) else: - if opts.get('exact') or opts.get('import_branch'): + if opts.get('exact') or importbranch: branch = branch or 'default' else: branch = p1.branch() @@ -996,19 +994,20 @@ editor = getcommiteditor(editform='import.bypass') memctx = context.makememctx(repo, (p1.node(), p2.node()), message, - opts.get('user') or user, - opts.get('date') or date, + user, + date, branch, files, store, editor=editor) n = memctx.commit() finally: store.close() - if opts.get('exact') and opts.get('no_commit'): + if opts.get('exact') and nocommit: # --exact with --no-commit is still useful in that it does merge # and branch bits ui.warn(_("warning: can't check exact import with --no-commit\n")) elif opts.get('exact') and hex(n) != nodeid: raise error.Abort(_('patch is damaged or loses information')) + msg = _('applied to working directory') if n: # i18n: refers to a short changeset id msg = _('created %s') % short(n) @@ -1052,9 +1051,8 @@ fp = makefileobj(repo, template, node, desc=desc, total=total, seqno=seqno, revwidth=revwidth, mode='wb', modemap=filemode) - if fp != template: - shouldclose = True - if fp and fp != sys.stdout and util.safehasattr(fp, 'name'): + shouldclose = True + if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'): repo.ui.note("%s\n" % fp.name) if not fp: @@ -1182,9 +1180,9 @@ def show(self, ctx, copies=None, matchfn=None, **props): if self.buffered: - self.ui.pushbuffer() + self.ui.pushbuffer(labeled=True) self._show(ctx, copies, matchfn, props) - self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True) + self.hunk[ctx.rev()] = self.ui.popbuffer() else: self._show(ctx, copies, matchfn, props) @@ -1297,16 +1295,17 @@ label='log.summary') self.ui.write("\n") - self.showpatch(changenode, matchfn) - - def showpatch(self, node, matchfn): + self.showpatch(ctx, matchfn) + + def showpatch(self, ctx, matchfn): if not matchfn: matchfn = self.matchfn if matchfn: stat = self.diffopts.get('stat') diff = self.diffopts.get('patch') diffopts = patch.diffallopts(self.ui, self.diffopts) - prev = self.repo.changelog.parents(node)[0] + node = ctx.node() + prev = ctx.p1().node() if stat: diffordiffstat(self.ui, self.repo, diffopts, prev, node, match=matchfn, stat=True) @@ -1488,7 +1487,7 @@ # write changeset metadata, then patch if requested key = self._parts['changeset'] self.ui.write(templater.stringify(self.t(key, **props))) - self.showpatch(ctx.node(), matchfn) + self.showpatch(ctx, matchfn) if self._parts['footer']: if not self.footer: @@ -2153,17 +2152,31 @@ return revs, expr, filematcher -def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None, +def _graphnodeformatter(ui, displayer): + spec = ui.config('ui', 'graphnodetemplate') + if not spec: + return templatekw.showgraphnode # fast path for "{graphnode}" + + templ = formatter.gettemplater(ui, 'graphnode', spec) + cache = {} + if isinstance(displayer, changeset_templater): + cache = displayer.cache # reuse cache of slow templates + props = templatekw.keywords.copy() + props['templ'] = templ + props['cache'] = cache + def formatnode(repo, ctx): + props['ctx'] = ctx + props['repo'] = repo + props['revcache'] = {} + return templater.stringify(templ('graphnode', **props)) + return formatnode + +def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None, filematcher=None): + formatnode = _graphnodeformatter(ui, displayer) seen, state = [], graphmod.asciistate() for rev, type, ctx, parents in dag: - char = 'o' - if ctx.node() in showparents: - char = '@' - elif ctx.obsolete(): - char = 'x' - elif ctx.closesbranch(): - char = '_' + char = formatnode(repo, ctx) copies = None if getrenamed and ctx.rev(): copies = [] @@ -2196,9 +2209,8 @@ endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) displayer = show_changeset(ui, repo, opts, buffered=True) - showparents = [ctx.node() for ctx in repo[None].parents()] - displaygraph(ui, revdag, displayer, showparents, - graphmod.asciiedges, getrenamed, filematcher) + displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed, + filematcher) def checkunsupportedgraphflags(pats, opts): for op in ["newest_first"]: @@ -2409,16 +2421,13 @@ if ui.verbose or not m.exact(f): ui.status(_('removing %s\n') % m.rel(f)) - wlock = repo.wlock() - try: + with repo.wlock(): if not after: for f in list: if f in added: continue # we never unlink added files on remove util.unlinkpath(repo.wjoin(f), ignoremissing=True) repo[None].forget(list) - finally: - wlock.release() return ret @@ -2503,8 +2512,7 @@ try: wlock = repo.wlock() lock = repo.lock() - tr = repo.transaction('amend') - try: + with repo.transaction('amend') as tr: # See if we got a message from -m or -l, if not, open the editor # with the message of the changeset to amend message = logmessage(ui, opts) @@ -2515,13 +2523,14 @@ # First, do a regular commit to record all changes in the working # directory (if there are any) ui.callhooks = False - activebookmark = repo._activebookmark + activebookmark = repo._bookmarks.active try: - repo._activebookmark = None + repo._bookmarks.active = None opts['message'] = 'temporary amend commit for %s' % old node = commit(ui, repo, commitfunc, pats, opts) finally: - repo._activebookmark = activebookmark + repo._bookmarks.active = activebookmark + repo._bookmarks.recordchange(tr) ui.callhooks = True ctx = repo[node] @@ -2614,6 +2623,11 @@ message = old.description() pureextra = extra.copy() + if 'amend_source' in pureextra: + del pureextra['amend_source'] + pureoldextra = old.extra() + if 'amend_source' in pureoldextra: + del pureoldextra['amend_source'] extra['amend_source'] = old.hex() new = context.memctx(repo, @@ -2626,12 +2640,12 @@ extra=extra, editor=editor) - newdesc = changelog.stripdesc(new.description()) + newdesc = changelog.stripdesc(new.description()) if ((not node) and newdesc == old.description() and user == old.user() and date == old.date() - and pureextra == old.extra()): + and pureextra == pureoldextra): # nothing changed. continuing here would create a new node # anyway because of the amend_source noise. # @@ -2670,9 +2684,6 @@ obs.append((ctx, ())) obsolete.createmarkers(repo, obs) - tr.close() - finally: - tr.release() if not createmarkers and newid != old.node(): # Strip the intermediate commit (if there was one) and the amended # commit @@ -2853,8 +2864,7 @@ # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>) names = {} - wlock = repo.wlock() - try: + with repo.wlock(): ## filling of the `names` mapping # walk dirstate to fill `names` @@ -3078,7 +3088,7 @@ xlist.append(abs) if dobackup and (backup <= dobackup or wctx[abs].cmp(ctx[abs])): - bakname = "%s.orig" % rel + bakname = scmutil.origpath(ui, repo, rel) ui.note(_('saving current version of %s as %s\n') % (rel, bakname)) if not opts.get('dry_run'): @@ -3107,8 +3117,6 @@ except KeyError: raise error.Abort("subrepository '%s' does not exist in %s!" % (sub, short(ctx.node()))) - finally: - wlock.release() def _revertprefetch(repo, ctx, *files): """Let extension changing the storage layer prefetch content""" @@ -3160,9 +3168,9 @@ diffopts = patch.difffeatureopts(repo.ui, whitespace=True) diffopts.nodates = True diffopts.git = True - reversehunks = repo.ui.configbool('experimental', - 'revertalternateinteractivemode', - True) + reversehunks = repo.ui.configbool('experimental', + 'revertalternateinteractivemode', + True) if reversehunks: diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) else: @@ -3171,7 +3179,7 @@ try: - chunks = recordfilter(repo.ui, originalchunks) + chunks, opts = recordfilter(repo.ui, originalchunks) if reversehunks: chunks = patch.reversehunks(chunks) @@ -3323,6 +3331,19 @@ if clearable and repo.vfs.exists(f): util.unlink(repo.join(f)) +afterresolvedstates = [ + ('graftstate', + _('hg graft --continue')), + ] + +def checkafterresolved(repo): + contmsg = _("continue: %s\n") + for f, msg in afterresolvedstates: + if repo.vfs.exists(f): + repo.ui.warn(contmsg % msg) + return + repo.ui.note(contmsg % _("hg commit")) + class dirstateguard(object): '''Restore dirstate at unexpected failure.
--- a/mercurial/commands.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/commands.py Sun Jan 17 21:40:21 2016 -0600 @@ -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, bin, nullid, nullrev, short +from node import hex, bin, nullhex, nullid, nullrev, short from lock import release from i18n import _ import os, re, difflib, time, tempfile, errno, shlex @@ -15,7 +15,6 @@ import archival, changegroup, cmdutil, hbisect import sshserver, hgweb import extensions -from hgweb import server as hgweb_server import merge as mergemod import minirst, revset, fileset import dagparser, context, simplemerge, graphmod, copies @@ -24,6 +23,7 @@ import phases, obsolete, exchange, bundle2, repair, lock as lockmod import ui as uimod import streamclone +import commandserver table = {} @@ -172,6 +172,12 @@ _('recurse into subrepositories')) ] +debugrevlogopts = [ + ('c', 'changelog', False, _('open changelog')), + ('m', 'manifest', False, _('open manifest')), + ('', 'dir', False, _('open directory manifest')), +] + # Commands start here, listed alphabetically @command('^add', @@ -187,21 +193,36 @@ The files will be added to the repository at the next commit. To undo an add before that, see :hg:`forget`. - If no names are given, add all files to the repository. + If no names are given, add all files to the repository (except + files matching ``.hgignore``). .. container:: verbose - An example showing how new (unknown) files are added - automatically by :hg:`add`:: - - $ ls - foo.c - $ hg status - ? foo.c - $ hg add - adding foo.c - $ hg status - A foo.c + Examples: + + - New (unknown) files are added + automatically by :hg:`add`:: + + $ ls + foo.c + $ hg status + ? foo.c + $ hg add + adding foo.c + $ hg status + A foo.c + + - Specific files to be added can be specified:: + + $ ls + bar.c foo.c + $ hg status + ? bar.c + ? foo.c + $ hg add bar.c + $ hg status + A bar.c + ? foo.c Returns 0 if all files are successfully added. """ @@ -220,9 +241,9 @@ Add all new files and remove all missing files from the repository. - New files are ignored if they match any of the patterns in - ``.hgignore``. As with add, these changes take effect at the next - commit. + Unless names are given, new files are ignored if they match any of + the patterns in ``.hgignore``. As with add, these changes take + effect at the next commit. Use the -s/--similarity option to detect renamed files. This option takes a percentage between 0 (disabled) and 100 (files must @@ -234,6 +255,46 @@ not specified, -s/--similarity defaults to 100 and only renames of identical files are detected. + .. container:: verbose + + Examples: + + - A number of files (bar.c and foo.c) are new, + while foobar.c has been removed (without using :hg:`remove`) + from the repository:: + + $ ls + bar.c foo.c + $ hg status + ! foobar.c + ? bar.c + ? foo.c + $ hg addremove + adding bar.c + adding foo.c + removing foobar.c + $ hg status + A bar.c + A foo.c + R foobar.c + + - A file foobar.c was moved to foo.c without using :hg:`rename`. + Afterwards, it was edited slightly:: + + $ ls + foo.c + $ hg status + ! foobar.c + ? foo.c + $ hg addremove --similarity 90 + removing foobar.c + adding foo.c + recording removal of foobar.c as rename to foo.c (94% similar) + $ hg status -C + A foo.c + foobar.c + R foobar.c + Returns 0 if all files are successfully added. """ try: @@ -264,11 +325,14 @@ """show changeset information by line for each file List changes in files, showing the revision id responsible for - each line + each line. This command is useful for discovering when a change was made and by whom. + If you include --file, --user, or --date, the revision number is + suppressed unless you also include --number. + Without the -a/--text option, annotate will avoid processing files it detects as binary. With -a, annotate will annotate the file anyway, although the results will probably be neither useful @@ -403,7 +467,7 @@ directory; use -r/--rev to specify a different revision. The archive type is automatically detected based on file - extension (or override using -t/--type). + extension (to override, use -t/--type). .. container:: verbose @@ -462,30 +526,49 @@ @command('backout', [('', 'merge', None, _('merge with old dirstate parent after backout')), - ('', 'commit', None, _('commit if no conflicts were encountered')), + ('', 'commit', None, + _('commit if no conflicts were encountered (DEPRECATED)')), + ('', 'no-commit', None, _('do not commit')), ('', 'parent', '', _('parent to choose when backing out merge (DEPRECATED)'), _('REV')), ('r', 'rev', '', _('revision to backout'), _('REV')), ('e', 'edit', False, _('invoke editor on commit messages')), ] + mergetoolopts + walkopts + commitopts + commitopts2, _('[OPTION]... [-r] REV')) -def backout(ui, repo, node=None, rev=None, commit=False, **opts): +def backout(ui, repo, node=None, rev=None, **opts): '''reverse effect of earlier changeset Prepare a new changeset with the effect of REV undone in the - current working directory. + current working directory. If no conflicts were encountered, + it will be committed immediately. If REV is the parent of the working directory, then this new changeset - is committed automatically. Otherwise, hg needs to merge the - changes and the merged result is left uncommitted. + is committed automatically (unless --no-commit is specified). .. note:: - backout cannot be used to fix either an unwanted or - incorrect merge. + :hg:`backout` cannot be used to fix either an unwanted or + incorrect merge. .. container:: verbose + Examples: + + - Reverse the effect of the parent of the working directory. + This backout will be committed immediately:: + + hg backout -r . + + - Reverse the effect of previous bad revision 23:: + + hg backout -r 23 + + - Reverse the effect of previous bad revision 23 and + leave changes uncommitted:: + + hg backout -r 23 --no-commit + hg commit -m "Backout revision 23" + By default, the pending changeset will have one parent, maintaining a linear history. With --merge, the pending changeset will instead have two parents: the old parent of the @@ -504,6 +587,18 @@ Returns 0 on success, 1 if nothing to backout or there are unresolved files. ''' + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + return _dobackout(ui, repo, node, rev, **opts) + finally: + release(lock, wlock) + +def _dobackout(ui, repo, node=None, rev=None, **opts): + if opts.get('commit') and opts.get('no_commit'): + raise error.Abort(_("cannot use --commit with --no-commit")) + if rev and node: raise error.Abort(_("please specify just one revision")) @@ -542,70 +637,65 @@ parent = p1 # the backout should appear on the same branch - wlock = repo.wlock() - try: - branch = repo.dirstate.branch() - bheads = repo.branchheads(branch) - rctx = scmutil.revsingle(repo, hex(parent)) - if not opts.get('merge') and op1 != node: - dsguard = cmdutil.dirstateguard(repo, 'backout') - try: - ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), - 'backout') - stats = mergemod.update(repo, parent, True, True, False, - node, False) - repo.setparents(op1, op2) - dsguard.close() - hg._showstats(repo, stats) - if stats[3]: - repo.ui.status(_("use 'hg resolve' to retry unresolved " - "file merges\n")) - return 1 - elif not commit: - msg = _("changeset %s backed out, " - "don't forget to commit.\n") - ui.status(msg % short(node)) - return 0 - finally: - ui.setconfig('ui', 'forcemerge', '', '') - lockmod.release(dsguard) - else: - hg.clean(repo, node, show_stats=False) - repo.dirstate.setbranch(branch) - cmdutil.revert(ui, repo, rctx, repo.dirstate.parents()) - - - def commitfunc(ui, repo, message, match, opts): - editform = 'backout' - e = cmdutil.getcommiteditor(editform=editform, **opts) - if not message: - # we don't translate commit messages - message = "Backed out changeset %s" % short(node) - e = cmdutil.getcommiteditor(edit=True, editform=editform) - return repo.commit(message, opts.get('user'), opts.get('date'), - match, editor=e) - newnode = cmdutil.commit(ui, repo, commitfunc, [], opts) - if not newnode: - ui.status(_("nothing changed\n")) - return 1 - cmdutil.commitstatus(repo, newnode, branch, bheads) - - def nice(node): - return '%d:%s' % (repo.changelog.rev(node), short(node)) - ui.status(_('changeset %s backs out changeset %s\n') % - (nice(repo.changelog.tip()), nice(node))) - if opts.get('merge') and op1 != node: - hg.clean(repo, op1, show_stats=False) - ui.status(_('merging with changeset %s\n') - % nice(repo.changelog.tip())) - try: - ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), - 'backout') - return hg.merge(repo, hex(repo.changelog.tip())) - finally: - ui.setconfig('ui', 'forcemerge', '', '') - finally: - wlock.release() + branch = repo.dirstate.branch() + bheads = repo.branchheads(branch) + rctx = scmutil.revsingle(repo, hex(parent)) + if not opts.get('merge') and op1 != node: + dsguard = cmdutil.dirstateguard(repo, 'backout') + try: + ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), + 'backout') + stats = mergemod.update(repo, parent, True, True, node, False) + repo.setparents(op1, op2) + dsguard.close() + hg._showstats(repo, stats) + if stats[3]: + repo.ui.status(_("use 'hg resolve' to retry unresolved " + "file merges\n")) + return 1 + elif opts.get('no_commit'): + msg = _("changeset %s backed out, " + "don't forget to commit.\n") + ui.status(msg % short(node)) + return 0 + finally: + ui.setconfig('ui', 'forcemerge', '', '') + lockmod.release(dsguard) + else: + hg.clean(repo, node, show_stats=False) + repo.dirstate.setbranch(branch) + cmdutil.revert(ui, repo, rctx, repo.dirstate.parents()) + + + def commitfunc(ui, repo, message, match, opts): + editform = 'backout' + e = cmdutil.getcommiteditor(editform=editform, **opts) + if not message: + # we don't translate commit messages + message = "Backed out changeset %s" % short(node) + e = cmdutil.getcommiteditor(edit=True, editform=editform) + return repo.commit(message, opts.get('user'), opts.get('date'), + match, editor=e) + newnode = cmdutil.commit(ui, repo, commitfunc, [], opts) + if not newnode: + ui.status(_("nothing changed\n")) + return 1 + cmdutil.commitstatus(repo, newnode, branch, bheads) + + def nice(node): + return '%d:%s' % (repo.changelog.rev(node), short(node)) + ui.status(_('changeset %s backs out changeset %s\n') % + (nice(repo.changelog.tip()), nice(node))) + if opts.get('merge') and op1 != node: + hg.clean(repo, op1, show_stats=False) + ui.status(_('merging with changeset %s\n') + % nice(repo.changelog.tip())) + try: + ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), + 'backout') + return hg.merge(repo, hex(repo.changelog.tip())) + finally: + ui.setconfig('ui', 'forcemerge', '', '') return 0 @command('bisect', @@ -1100,7 +1190,7 @@ Use the command :hg:`update` to switch to an existing branch. Use :hg:`commit --close-branch` to mark this branch head as closed. - When all heads of the branch are closed, the branch will be + When all heads of a branch are closed, the branch will be considered closed. Returns 0 on success. @@ -1112,15 +1202,14 @@ ui.write("%s\n" % repo.dirstate.branch()) return - wlock = repo.wlock() - try: + with repo.wlock(): if opts.get('clean'): label = repo[None].p1().branch() repo.dirstate.setbranch(label) ui.status(_('reset working directory to branch %s\n') % label) elif label: if not opts.get('force') and label in repo.branchmap(): - if label not in [p.branch() for p in repo.parents()]: + if label not in [p.branch() for p in repo[None].parents()]: raise error.Abort(_('a branch of the same name already' ' exists'), # i18n: "it" refers to an existing branch @@ -1135,8 +1224,6 @@ if not others: ui.status(_('(branches are permanent and global, ' 'did you want a bookmark?)\n')) - finally: - wlock.release() @command('branches', [('a', 'active', False, @@ -1214,19 +1301,20 @@ def bundle(ui, repo, fname, dest=None, **opts): """create a changegroup file - Generate a compressed changegroup file collecting changesets not - known to be in another repository. - - If you omit the destination repository, then hg assumes the - destination will have all the nodes you specify with --base - parameters. To create a bundle containing all changesets, use - -a/--all (or --base null). + Generate a changegroup file collecting changesets to be added + to a repository. + + To create a bundle containing all changesets, use -a/--all + (or --base null). Otherwise, hg assumes the destination will have + all the nodes you specify with --base parameters. Otherwise, hg + will assume the repository has all the nodes in destination, or + default-push/default if no destination is specified. You can change bundle format with the -t/--type option. You can specify a compression, a bundle version or both using a dash (comp-version). The available compression methods are: none, bzip2, and gzip (by default, bundles are compressed using bzip2). The - available format are: v1, v2 (default to most suitable). + available formats are: v1, v2 (default to most suitable). The bundle file can then be transferred using conventional means and applied to another repository with the unbundle or pull @@ -1257,6 +1345,11 @@ hint=_('use "hg debugcreatestreamclonebundle"')) if opts.get('all'): + if dest: + raise error.Abort(_("--all is incompatible with specifying " + "a destination")) + if opts.get('base'): + ui.warn(_("ignoring --base because --all was specified\n")) base = ['null'] else: base = scmutil.revrange(repo, opts.get('base')) @@ -1337,7 +1430,8 @@ @command('^clone', [('U', 'noupdate', None, _('the clone will include an empty working ' 'directory (only a repository)')), - ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')), + ('u', 'updaterev', '', _('revision, tag, or branch to check out'), + _('REV')), ('r', 'rev', [], _('include the specified changeset'), _('REV')), ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')), ('', 'pull', None, _('use pull protocol to copy metadata')), @@ -1360,20 +1454,23 @@ destinations. For ``ssh://`` destinations, no working directory or ``.hg/hgrc`` will be created on the remote side. - To pull only a subset of changesets, specify one or more revisions - identifiers with -r/--rev or branches with -b/--branch. The - resulting clone will contain only the specified changesets and - their ancestors. These options (or 'clone src#rev dest') imply - --pull, even for local source repositories. Note that specifying a - tag will include the tagged changeset but not the changeset - containing the tag. - If the source repository has a bookmark called '@' set, that revision will be checked out in the new repository by default. To check out a particular version, use -u/--update, or -U/--noupdate to create a clone with no working directory. + To pull only a subset of changesets, specify one or more revisions + identifiers with -r/--rev or branches with -b/--branch. The + resulting clone will contain only the specified changesets and + their ancestors. These options (or 'clone src#rev dest') imply + --pull, even for local source repositories. + + .. note:: + + Specifying a tag will include the tagged changeset but not the + changeset containing the tag. + .. container:: verbose For efficiency, hardlinks are used for cloning whenever the @@ -1410,6 +1507,14 @@ h) the tipmost head of the default branch i) tip + When cloning from servers that support it, Mercurial may fetch + pre-generated data from a server-advertised URL. When this is done, + hooks operating on incoming changesets and changegroups may fire twice, + once for the bundle fetched from the URL and another for any additional + data not fetched from this URL. In addition, if an error occurs, the + repository may be rolled back to a partial clone. This behavior may + change in future releases. See :hg:`help -e clonebundles` for more. + Examples: - clone a remote repository to a new directory named hg/:: @@ -1505,7 +1610,32 @@ See :hg:`help dates` for a list of formats valid for -d/--date. Returns 0 on success, 1 if nothing changed. + + .. container:: verbose + + Examples: + + - commit all files ending in .py:: + + hg commit --include "set:**.py" + + - commit all non-binary files:: + + hg commit --exclude "set:binary()" + + - amend the current commit and set the date to now:: + + hg commit --amend --date now """ + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + return _docommit(ui, repo, *pats, **opts) + finally: + release(lock, wlock) + +def _docommit(ui, repo, *pats, **opts): if opts.get('interactive'): opts.pop('interactive') cmdutil.dorecord(ui, repo, commit, None, False, @@ -1530,8 +1660,8 @@ if not bheads: raise error.Abort(_('can only close branch heads')) elif opts.get('amend'): - if repo.parents()[0].p1().branch() != branch and \ - repo.parents()[0].p2().branch() != branch: + if repo[None].parents()[0].p1().branch() != branch and \ + repo[None].parents()[0].p2().branch() != branch: raise error.Abort(_('can only close branch heads')) if opts.get('amend'): @@ -1547,6 +1677,9 @@ if not allowunstable and old.children(): raise error.Abort(_('cannot amend changeset with children')) + newextra = extra.copy() + newextra['branch'] = branch + extra = newextra # commitfunc is used only for temporary amend commit by cmdutil.amend def commitfunc(ui, repo, message, match, opts): return repo.commit(message, @@ -1713,11 +1846,8 @@ Returns 0 on success, 1 if errors are encountered. """ - wlock = repo.wlock(False) - try: + with repo.wlock(False): return cmdutil.copy(ui, repo, pats, opts) - finally: - wlock.release() @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True) def debugancestor(ui, repo, *args): @@ -1890,13 +2020,18 @@ release(tr, lock) @command('debugbundle', - [('a', 'all', None, _('show all details'))], + [('a', 'all', None, _('show all details')), + ('', 'spec', None, _('print the bundlespec of the bundle'))], _('FILE'), norepo=True) -def debugbundle(ui, bundlepath, all=None, **opts): +def debugbundle(ui, bundlepath, all=None, spec=None, **opts): """lists the contents of a bundle""" - f = hg.openpath(ui, bundlepath) - try: + with hg.openpath(ui, bundlepath) as f: + if spec: + spec = exchange.getbundlespec(ui, f) + ui.write('%s\n' % spec) + return + gen = exchange.readbundle(ui, f, bundlepath) if isinstance(gen, bundle2.unbundle20): return _debugbundle2(ui, gen, all=all, **opts) @@ -1943,8 +2078,6 @@ node = chunkdata['node'] ui.write("%s\n" % hex(node)) chain = node - finally: - f.close() def _debugbundle2(ui, gen, **opts): """lists the contents of a bundle2""" @@ -1955,7 +2088,7 @@ ui.write('%s -- %r\n' % (part.type, repr(part.params))) if part.type == 'changegroup': version = part.params.get('version', '01') - cg = changegroup.packermap[version][1](part, 'UN') + cg = changegroup.getunbundler(version, part, 'UN') chunkdata = cg.changelogheader() chain = None while True: @@ -2111,11 +2244,7 @@ ui.write(line) ui.write("\n") -@command('debugdata', - [('c', 'changelog', False, _('open changelog')), - ('m', 'manifest', False, _('open manifest')), - ('', 'dir', False, _('open directory manifest'))], - _('-c|-m|FILE REV')) +@command('debugdata', debugrevlogopts, _('-c|-m|FILE REV')) def debugdata(ui, repo, file_, rev=None, **opts): """dump the contents of a data file revision""" if opts.get('changelog') or opts.get('manifest'): @@ -2192,8 +2321,7 @@ serverlogs = opts.get('serverlog') if serverlogs: for filename in serverlogs: - logfile = open(filename, 'r') - try: + with open(filename, 'r') as logfile: line = logfile.readline() while line: parts = line.strip().split(';') @@ -2205,9 +2333,6 @@ elif op == 'unb': doit(parts[3].split(' '), parts[2].split(' ')) line = logfile.readline() - finally: - logfile.close() - else: remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, opts.get('remote_head')) @@ -2311,21 +2436,51 @@ raise error.Abort(_('unknown bundle type specified with --type')) changegroup.writebundle(ui, bundle, bundlepath, bundletype) -@command('debugignore', [], '') -def debugignore(ui, repo, *values, **opts): - """display the combined ignore pattern""" +@command('debugignore', [], '[FILE]') +def debugignore(ui, repo, *files, **opts): + """display the combined ignore pattern and information about ignored files + + With no argument display the combined ignore pattern. + + Given space separated file names, shows if the given file is ignored and + if so, show the ignore rule (file and line number) that matched it. + """ ignore = repo.dirstate._ignore - includepat = getattr(ignore, 'includepat', None) - if includepat is not None: - ui.write("%s\n" % includepat) + if not files: + # Show all the patterns + includepat = getattr(ignore, 'includepat', None) + if includepat is not None: + ui.write("%s\n" % includepat) + else: + raise error.Abort(_("no ignore patterns found")) else: - raise error.Abort(_("no ignore patterns found")) - -@command('debugindex', - [('c', 'changelog', False, _('open changelog')), - ('m', 'manifest', False, _('open manifest')), - ('', 'dir', False, _('open directory manifest')), - ('f', 'format', 0, _('revlog format'), _('FORMAT'))], + for f in files: + ignored = None + ignoredata = None + if f != '.': + if ignore(f): + ignored = f + ignoredata = repo.dirstate._ignorefileandline(f) + else: + for p in util.finddirs(f): + if ignore(p): + ignored = p + ignoredata = repo.dirstate._ignorefileandline(p) + break + if ignored: + if ignored == f: + ui.write("%s is ignored\n" % f) + else: + ui.write("%s is ignored because of containing folder %s\n" + % (f, ignored)) + ignorefile, lineno, line = ignoredata + ui.write("(ignore rule in %s, line %d: '%s')\n" + % (ignorefile, lineno, line)) + else: + ui.write("%s is not ignored\n" % f) + +@command('debugindex', debugrevlogopts + + [('f', 'format', 0, _('revlog format'), _('FORMAT'))], _('[-f FORMAT] -c|-m|FILE'), optionalrepo=True) def debugindex(ui, repo, file_=None, **opts): @@ -2380,16 +2535,11 @@ i, r.flags(i), r.start(i), r.length(i), r.rawsize(i), base, r.linkrev(i), pr[0], pr[1], shortfn(node))) -@command('debugindexdot', [], _('FILE'), optionalrepo=True) -def debugindexdot(ui, repo, file_): +@command('debugindexdot', debugrevlogopts, + _('-c|-m|FILE'), optionalrepo=True) +def debugindexdot(ui, repo, file_=None, **opts): """dump an index DAG as a graphviz dot file""" - r = None - if repo: - filelog = repo.file(file_) - if len(filelog): - r = filelog - if not r: - r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_) + r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts) ui.write(("digraph G {\n")) for i in r: node = r.node(i) @@ -2399,6 +2549,107 @@ ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i)) ui.write("}\n") +@command('debugdeltachain', + debugrevlogopts + formatteropts, + _('-c|-m|FILE'), + optionalrepo=True) +def debugdeltachain(ui, repo, file_=None, **opts): + """dump information about delta chains in a revlog + + Output can be templatized. Available template keywords are: + + rev revision number + chainid delta chain identifier (numbered by unique base) + chainlen delta chain length to this revision + prevrev previous revision in delta chain + deltatype role of delta / how it was computed + compsize compressed size of revision + uncompsize uncompressed size of revision + chainsize total size of compressed revisions in chain + chainratio total chain size divided by uncompressed revision size + (new delta chains typically start at ratio 2.00) + lindist linear distance from base revision in delta chain to end + of this revision + extradist total size of revisions not part of this delta chain from + base of delta chain to end of this revision; a measurement + of how much extra data we need to read/seek across to read + the delta chain for this revision + extraratio extradist divided by chainsize; another representation of + how much unrelated data is needed to load this delta chain + """ + r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts) + index = r.index + generaldelta = r.version & revlog.REVLOGGENERALDELTA + + def revinfo(rev): + e = index[rev] + compsize = e[1] + uncompsize = e[2] + chainsize = 0 + + if generaldelta: + if e[3] == e[5]: + deltatype = 'p1' + elif e[3] == e[6]: + deltatype = 'p2' + elif e[3] == rev - 1: + deltatype = 'prev' + elif e[3] == rev: + deltatype = 'base' + else: + deltatype = 'other' + else: + if e[3] == rev: + deltatype = 'base' + else: + deltatype = 'prev' + + chain = r._deltachain(rev)[0] + for iterrev in chain: + e = index[iterrev] + chainsize += e[1] + + return compsize, uncompsize, deltatype, chain, chainsize + + fm = ui.formatter('debugdeltachain', opts) + + fm.plain(' rev chain# chainlen prev delta ' + 'size rawsize chainsize ratio lindist extradist ' + 'extraratio\n') + + chainbases = {} + for rev in r: + comp, uncomp, deltatype, chain, chainsize = revinfo(rev) + chainbase = chain[0] + chainid = chainbases.setdefault(chainbase, len(chainbases) + 1) + basestart = r.start(chainbase) + revstart = r.start(rev) + lineardist = revstart + comp - basestart + extradist = lineardist - chainsize + try: + prevrev = chain[-2] + except IndexError: + prevrev = -1 + + chainratio = float(chainsize) / float(uncomp) + extraratio = float(extradist) / float(chainsize) + + fm.startitem() + fm.write('rev chainid chainlen prevrev deltatype compsize ' + 'uncompsize chainsize chainratio lindist extradist ' + 'extraratio', + '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n', + rev, chainid, len(chain), prevrev, deltatype, comp, + uncomp, chainsize, chainratio, lineardist, extradist, + extraratio, + rev=rev, chainid=chainid, chainlen=len(chain), + prevrev=prevrev, deltatype=deltatype, compsize=comp, + uncompsize=uncomp, chainsize=chainsize, + chainratio=chainratio, lindist=lineardist, + extradist=extradist, extraratio=extraratio) + + fm.end() + @command('debuginstall', [], '', norepo=True) def debuginstall(ui): '''test Mercurial installation @@ -2522,6 +2773,12 @@ Use --verbose to print out information about whether v1 or v2 merge state was chosen.""" + def _hashornull(h): + if h == nullhex: + return 'null' + else: + return h + def printrecords(version): ui.write(('* version %s records\n') % version) if version == 1: @@ -2539,7 +2796,7 @@ driver, mdstate = record.split('\0', 1) ui.write(('merge driver: %s (state "%s")\n') % (driver, mdstate)) - elif rtype in 'FD': + elif rtype in 'FDC': r = record.split('\0') f, state, hash, lfile, afile, anode, ofile = r[0:7] if version == 1: @@ -2547,15 +2804,20 @@ flags = r[7] else: onode, flags = r[7:9] - ui.write(('file: %s (state "%s", hash %s)\n') - % (f, state, hash)) + ui.write(('file: %s (record type "%s", state "%s", hash %s)\n') + % (f, rtype, state, _hashornull(hash))) ui.write((' local path: %s (flags "%s")\n') % (lfile, flags)) - ui.write((' ancestor path: %s (node %s)\n') % (afile, anode)) - ui.write((' other path: %s (node %s)\n') % (ofile, onode)) + ui.write((' ancestor path: %s (node %s)\n') + % (afile, _hashornull(anode))) + ui.write((' other path: %s (node %s)\n') + % (ofile, _hashornull(onode))) else: ui.write(('unrecognized entry: %s\t%s\n') % (rtype, record.replace('\0', '\t'))) + # Avoid mergestate.read() since it may raise an exception for unsupported + # merge state records. We shouldn't be doing this, but this is OK since this + # command is pretty low-level. ms = mergemod.mergestate(repo) # sort so that reasonable information is on top @@ -2878,26 +3140,19 @@ check the actual file content. """ ctx = scmutil.revsingle(repo, rev) - wlock = repo.wlock() - try: + with repo.wlock(): dirstate = repo.dirstate - + changedfiles = None # See command doc for what minimal does. if opts.get('minimal'): + manifestfiles = set(ctx.manifest().keys()) dirstatefiles = set(dirstate) - ctxfiles = set(ctx.manifest().keys()) - for file in (dirstatefiles | ctxfiles): - indirstate = file in dirstatefiles - inctx = file in ctxfiles - - if indirstate and not inctx and dirstate[file] != 'a': - dirstate.drop(file) - elif inctx and not indirstate: - dirstate.normallookup(file) - else: - dirstate.rebuild(ctx.node(), ctx.manifest()) - finally: - wlock.release() + manifestonly = manifestfiles - dirstatefiles + dsonly = dirstatefiles - manifestfiles + dsnotadded = set(f for f in dsonly if dirstate[f] != 'a') + changedfiles = manifestonly | dsnotadded + + dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles) @command('debugrebuildfncache', [], '') def debugrebuildfncache(ui, repo): @@ -2921,11 +3176,8 @@ else: ui.write(_("%s not renamed\n") % rel) -@command('debugrevlog', - [('c', 'changelog', False, _('open changelog')), - ('m', 'manifest', False, _('open manifest')), - ('', 'dir', False, _('open directory manifest')), - ('d', 'dump', False, _('dump index data'))], +@command('debugrevlog', debugrevlogopts + + [('d', 'dump', False, _('dump index data'))], _('-c|-m|FILE'), optionalrepo=True) def debugrevlog(ui, repo, file_=None, **opts): @@ -3150,20 +3402,21 @@ r1 = scmutil.revsingle(repo, rev1).node() r2 = scmutil.revsingle(repo, rev2, 'null').node() - wlock = repo.wlock() - try: + with repo.wlock(): repo.dirstate.beginparentchange() repo.setparents(r1, r2) repo.dirstate.endparentchange() - finally: - wlock.release() @command('debugdirstate|debugstate', [('', 'nodates', None, _('do not display the saved mtime')), ('', 'datesort', None, _('sort by saved mtime'))], _('[OPTION]...')) -def debugstate(ui, repo, nodates=None, datesort=None): +def debugstate(ui, repo, **opts): """show the contents of the current dirstate""" + + nodates = opts.get('nodates') + datesort = opts.get('datesort') + timestr = "" if datesort: keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename @@ -3304,7 +3557,7 @@ .. note:: - diff may generate unexpected results for merges, as it will + :hg:`diff` may generate unexpected results for merges, as it will default to comparing against the working directory's first parent changeset if no revisions are specified. @@ -3312,7 +3565,7 @@ between those revisions. If only one revision is specified then that revision is compared to the working directory, and, when no revisions are specified, the working directory files are compared - to its parent. + to its first parent. Alternatively you can specify -c/--change with a revision to see the changes in that changeset relative to its first parent. @@ -3395,7 +3648,7 @@ .. note:: - export may generate unexpected diff output for merge + :hg:`export` may generate unexpected diff output for merge changesets, as it will compare the merge changeset against its first parent only. @@ -3570,7 +3823,7 @@ ('U', 'currentuser', False, _('record the current user as committer'), _('DATE'))] + commitopts2 + mergetoolopts + dryrunopts, - _('[OPTION]... [-r] REV...')) + _('[OPTION]... [-r REV]... REV...')) def graft(ui, repo, *revs, **opts): '''copy changes from other branches onto the current branch @@ -3599,8 +3852,8 @@ .. note:: - The -c/--continue option does not reapply earlier options, except - for --force. + The -c/--continue option does not reapply earlier options, except + for --force. .. container:: verbose @@ -3623,11 +3876,22 @@ hg log --debug -r . + - show revisions sorted by date:: + + hg log -r 'sort(all(), date)' + See :hg:`help revisions` and :hg:`help revsets` for more about specifying revisions. Returns 0 on successful completion. ''' + with repo.wlock(): + return _dograft(ui, repo, *revs, **opts) + +def _dograft(ui, repo, *revs, **opts): + if revs and opts['rev']: + ui.warn(_('warning: inconsistent use of --rev might give unexpected ' + 'revision ordering!\n')) revs = list(revs) revs.extend(opts['rev']) @@ -3734,66 +3998,70 @@ if not revs: return -1 - wlock = repo.wlock() - try: - for pos, ctx in enumerate(repo.set("%ld", revs)): - desc = '%d:%s "%s"' % (ctx.rev(), ctx, - ctx.description().split('\n', 1)[0]) - names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) - if names: - desc += ' (%s)' % ' '.join(names) - ui.status(_('grafting %s\n') % desc) - if opts.get('dry_run'): - continue - - source = ctx.extra().get('source') - extra = {} - if source: - extra['source'] = source - extra['intermediate-source'] = ctx.hex() - else: - extra['source'] = ctx.hex() - user = ctx.user() - if opts.get('user'): - user = opts['user'] - date = ctx.date() - if opts.get('date'): - date = opts['date'] - message = ctx.description() - if opts.get('log'): - message += '\n(grafted from %s)' % ctx.hex() - - # we don't merge the first commit when continuing - if not cont: - # perform the graft merge with p1(rev) as 'ancestor' - try: - # ui.forcemerge is an internal variable, do not document - repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), - 'graft') - stats = mergemod.graft(repo, ctx, ctx.p1(), - ['local', 'graft']) - finally: - repo.ui.setconfig('ui', 'forcemerge', '', 'graft') - # report any conflicts - if stats and stats[3] > 0: - # write out state for --continue - nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]] - repo.vfs.write('graftstate', ''.join(nodelines)) - raise error.Abort( - _("unresolved conflicts, can't continue"), - hint=_('use hg resolve and hg graft --continue')) - else: - cont = False - - # commit - node = repo.commit(text=message, user=user, - date=date, extra=extra, editor=editor) - if node is None: - ui.warn( - _('note: graft of %d:%s created no changes to commit\n') % - (ctx.rev(), ctx)) - finally: - wlock.release() + for pos, ctx in enumerate(repo.set("%ld", revs)): + desc = '%d:%s "%s"' % (ctx.rev(), ctx, + ctx.description().split('\n', 1)[0]) + names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) + if names: + desc += ' (%s)' % ' '.join(names) + ui.status(_('grafting %s\n') % desc) + if opts.get('dry_run'): + continue + + extra = ctx.extra().copy() + del extra['branch'] + source = extra.get('source') + if source: + extra['intermediate-source'] = ctx.hex() + else: + extra['source'] = ctx.hex() + user = ctx.user() + if opts.get('user'): + user = opts['user'] + date = ctx.date() + if opts.get('date'): + date = opts['date'] + message = ctx.description() + if opts.get('log'): + message += '\n(grafted from %s)' % ctx.hex() + + # we don't merge the first commit when continuing + if not cont: + # perform the graft merge with p1(rev) as 'ancestor' + try: + # ui.forcemerge is an internal variable, do not document + repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), + 'graft') + stats = mergemod.graft(repo, ctx, ctx.p1(), + ['local', 'graft']) + finally: + repo.ui.setconfig('ui', 'forcemerge', '', 'graft') + # report any conflicts + if stats and stats[3] > 0: + # write out state for --continue + nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]] + repo.vfs.write('graftstate', ''.join(nodelines)) + extra = '' + if opts.get('user'): + extra += ' --user %s' % opts['user'] + if opts.get('date'): + extra += ' --date %s' % opts['date'] + if opts.get('log'): + extra += ' --log' + hint=_('use hg resolve and hg graft --continue%s') % extra + raise error.Abort( + _("unresolved conflicts, can't continue"), + hint=hint) + else: + cont = False + + # commit + node = repo.commit(text=message, user=user, + date=date, extra=extra, editor=editor) + if node is None: + ui.warn( + _('note: graft of %d:%s created no changes to commit\n') % + (ctx.rev(), ctx)) # remove state when we complete successfully if not opts.get('dry_run'): @@ -4099,8 +4367,9 @@ [('e', 'extension', None, _('show only help for extensions')), ('c', 'command', None, _('show only help for commands')), ('k', 'keyword', None, _('show topics matching keyword')), + ('s', 'system', [], _('show help for specific platform(s)')), ], - _('[-eck] [TOPIC]'), + _('[-ecks] [TOPIC]'), norepo=True) def help_(ui, name=None, **opts): """show help for a given topic or a help overview @@ -4115,25 +4384,31 @@ textwidth = min(ui.termwidth(), 80) - 2 - keep = [] + keep = opts.get('system') or [] + if len(keep) == 0: + if sys.platform.startswith('win'): + keep.append('windows') + elif sys.platform == 'OpenVMS': + keep.append('vms') + elif sys.platform == 'plan9': + keep.append('plan9') + else: + keep.append('unix') + keep.append(sys.platform.lower()) if ui.verbose: keep.append('verbose') - if sys.platform.startswith('win'): - keep.append('windows') - elif sys.platform == 'OpenVMS': - keep.append('vms') - elif sys.platform == 'plan9': - keep.append('plan9') - else: - keep.append('unix') - keep.append(sys.platform.lower()) section = None + subtopic = None if name and '.' in name: name, section = name.split('.', 1) section = section.lower() - - text = help.help_(ui, name, **opts) + if '.' in section: + subtopic, section = section.split('.', 1) + else: + subtopic = section + + text = help.help_(ui, name, subtopic=subtopic, **opts) formatted, pruned = minirst.format(text, textwidth, keep=keep, section=section) @@ -4195,6 +4470,9 @@ hg id -r tip http://selenic.com/hg/ + See :hg:`log` for generating more information about specific revisions, + including full hash identifiers. + Returns 0 if successful. """ @@ -4328,14 +4606,23 @@ Import a list of patches and commit them individually (unless --no-commit is specified). - Because import first applies changes to the working directory, - import will abort if there are outstanding changes. + To read a patch from standard input, use "-" as the patch name. If + a URL is specified, the patch will be downloaded from there. + + Import first applies changes to the working directory (unless + --bypass is specified), import will abort if there are outstanding + changes. + + Use --bypass to apply and commit patches directly to the + repository, without affecting the working directory. Without + --exact, patches will be applied on top of the working directory + parent revision. You can import a patch straight from a mail message. Even patches as attachments work (to use the body part, it must have type text/plain or text/x-patch). From and Subject headers of email message are used as default committer and commit message. All - text/plain body parts before first diff are added to commit + text/plain body parts before first diff are added to the commit message. If the imported patch was generated by :hg:`export`, user and @@ -4349,23 +4636,21 @@ the patch. This may happen due to character set problems or other deficiencies in the text patch format. - Use --bypass to apply and commit patches directly to the - repository, not touching the working directory. Without --exact, - patches will be applied on top of the working directory parent - revision. - - With -s/--similarity, hg will attempt to discover renames and - copies in the patch in the same way as :hg:`addremove`. - Use --partial to ensure a changeset will be created from the patch even if some hunks fail to apply. Hunks that fail to apply will be written to a <target-file>.rej file. Conflicts can then be resolved by hand before :hg:`commit --amend` is run to update the created changeset. This flag exists to let people import patches that partially apply without losing the associated metadata (author, - date, description, ...). Note that when none of the hunk applies - cleanly, :hg:`import --partial` will create an empty changeset, - importing only the patch metadata. + date, description, ...). + + .. note:: + + When no hunks apply cleanly, :hg:`import --partial` will create + an empty changeset, importing only the patch metadata. + + With -s/--similarity, hg will attempt to discover renames and + copies in the patch in the same way as :hg:`addremove`. It is possible to use external patch programs to perform the patch by setting the ``ui.patch`` configuration option. For the default @@ -4373,8 +4658,6 @@ 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. .. container:: verbose @@ -4419,6 +4702,7 @@ if date: opts['date'] = util.parsedate(date) + exact = opts.get('exact') update = not opts.get('bypass') if not update and opts.get('no_commit'): raise error.Abort(_('cannot use --no-commit with --bypass')) @@ -4430,15 +4714,11 @@ raise error.Abort(_('similarity must be between 0 and 100')) if sim and not update: raise error.Abort(_('cannot use --similarity with --bypass')) - if opts.get('exact') and opts.get('edit'): - raise error.Abort(_('cannot use --exact with --edit')) - if opts.get('exact') and opts.get('prefix'): - raise error.Abort(_('cannot use --exact with --prefix')) - - if update: - cmdutil.checkunfinished(repo) - if (opts.get('exact') or not opts.get('force')) and update: - cmdutil.bailifchanged(repo) + if exact: + if opts.get('edit'): + raise error.Abort(_('cannot use --exact with --edit')) + if opts.get('prefix'): + raise error.Abort(_('cannot use --exact with --prefix')) base = opts["base"] wlock = dsguard = lock = tr = None @@ -4447,57 +4727,58 @@ try: - try: - wlock = repo.wlock() - if not opts.get('no_commit'): - lock = repo.lock() - tr = repo.transaction('import') + wlock = repo.wlock() + + if update: + cmdutil.checkunfinished(repo) + if (exact or not opts.get('force')): + cmdutil.bailifchanged(repo) + + if not opts.get('no_commit'): + lock = repo.lock() + tr = repo.transaction('import') + else: + dsguard = cmdutil.dirstateguard(repo, 'import') + parents = repo[None].parents() + for patchurl in patches: + if patchurl == '-': + ui.status(_('applying patch from stdin\n')) + patchfile = ui.fin + patchurl = 'stdin' # for error message else: - dsguard = cmdutil.dirstateguard(repo, 'import') - parents = repo.parents() - for patchurl in patches: - if patchurl == '-': - ui.status(_('applying patch from stdin\n')) - patchfile = ui.fin - patchurl = 'stdin' # for error message + patchurl = os.path.join(base, patchurl) + ui.status(_('applying %s\n') % patchurl) + patchfile = hg.openpath(ui, patchurl) + + haspatch = False + for hunk in patch.split(patchfile): + (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk, + parents, opts, + msgs, hg.clean) + if msg: + haspatch = True + ui.note(msg + '\n') + if update or exact: + parents = repo[None].parents() else: - patchurl = os.path.join(base, patchurl) - ui.status(_('applying %s\n') % patchurl) - patchfile = hg.openpath(ui, patchurl) - - haspatch = False - for hunk in patch.split(patchfile): - (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk, - parents, opts, - msgs, hg.clean) - if msg: - haspatch = True - ui.note(msg + '\n') - if update or opts.get('exact'): - parents = repo.parents() - else: - parents = [repo[node]] - if rej: - ui.write_err(_("patch applied partially\n")) - ui.write_err(_("(fix the .rej files and run " - "`hg commit --amend`)\n")) - ret = 1 - break - - if not haspatch: - raise error.Abort(_('%s: no diffs found') % patchurl) - - if tr: - tr.close() - if msgs: - repo.savecommitmessage('\n* * *\n'.join(msgs)) - if dsguard: - dsguard.close() - return ret - finally: - # TODO: get rid of this meaningless try/finally enclosing. - # this is kept only to reduce changes in a patch. - pass + parents = [repo[node]] + if rej: + ui.write_err(_("patch applied partially\n")) + ui.write_err(_("(fix the .rej files and run " + "`hg commit --amend`)\n")) + ret = 1 + break + + if not haspatch: + raise error.Abort(_('%s: no diffs found') % patchurl) + + if tr: + tr.close() + if msgs: + repo.savecommitmessage('\n* * *\n'.join(msgs)) + if dsguard: + dsguard.close() + return ret finally: if tr: tr.release() @@ -4573,8 +4854,7 @@ cmdutil.checkunsupportedgraphflags([], opts) def display(other, chlist, displayer): revdag = cmdutil.graphrevs(other, chlist, opts) - showparents = [ctx.node() for ctx in repo[None].parents()] - cmdutil.displaygraph(ui, revdag, displayer, showparents, + cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True) @@ -4716,14 +4996,14 @@ .. note:: - log -p/--patch may generate unexpected diff output for merge + :hg:`log --patch` may generate unexpected diff output for merge changesets, as it will only compare the merge changeset against its first parent. Also, only files different from BOTH parents will appear in files:. .. note:: - for performance reasons, log FILE may omit duplicate changes + For performance reasons, :hg:`log FILE` may omit duplicate changes made on branches and will not show removals or mode changes. To see all such changes, use the --removed switch. @@ -4755,6 +5035,10 @@ hg log -k bug --template "{rev}\\n" + - the full hash identifier of the working directory parent:: + + hg log -r . --template "{node}\\n" + - list available log templates:: hg log -T list @@ -4774,7 +5058,7 @@ See :hg:`help dates` for a list of formats valid for -d/--date. See :hg:`help revisions` and :hg:`help revsets` for more about - specifying revisions. + specifying and ordering revisions. See :hg:`help templates` for more about pre-packaged styles and specifying custom templates. @@ -4854,13 +5138,10 @@ suffix = ".i" plen = len(prefix) slen = len(suffix) - lock = repo.lock() - try: + with repo.lock(): for fn, b, size in repo.store.datafiles(): if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix: res.append(fn[plen:-slen]) - finally: - lock.release() for f in res: fm.startitem() fm.write("path", '%s\n', f) @@ -4913,7 +5194,7 @@ head, the other head is merged with by default. Otherwise, an explicit revision with which to merge with must be provided. - :hg:`resolve` must be used to resolve unresolved files. + See :hg:`help resolve` for information on handling file conflicts. To undo an uncommitted merge, use :hg:`update --clean .` which will check out a clean copy of the original merge parent, losing @@ -5007,9 +5288,7 @@ revdag = cmdutil.graphrevs(repo, o, opts) displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True) - showparents = [ctx.node() for ctx in repo[None].parents()] - cmdutil.displaygraph(ui, revdag, displayer, showparents, - graphmod.asciiedges) + cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) cmdutil.outgoinghooks(ui, repo, other, opts, o) return 0 @@ -5043,6 +5322,13 @@ last changed (before the working directory revision or the argument to --rev if given) is printed. + This command is equivalent to:: + + hg log -r "p1()+p2()" or + hg log -r "p1(REV)+p2(REV)" or + hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or + hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))" + See :hg:`summary` and :hg:`help revsets` for related information. Returns 0 on success. @@ -5078,8 +5364,8 @@ displayer.show(repo[n]) displayer.close() -@command('paths', [], _('[NAME]'), optionalrepo=True) -def paths(ui, repo, search=None): +@command('paths', formatteropts, _('[NAME]'), optionalrepo=True) +def paths(ui, repo, search=None, **opts): """show aliases for remote repositories Show definition of symbolic path name NAME. If no name is given, @@ -5098,30 +5384,53 @@ When ``default-push`` is set, it will be used for push and ``default`` will be used for pull; otherwise ``default`` is used as the fallback for both. When cloning a repository, the clone - source is written as ``default`` in ``.hg/hgrc``. Note that - ``default`` and ``default-push`` apply to all inbound (e.g. - :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and - :hg:`bundle`) operations. + source is written as ``default`` in ``.hg/hgrc``. + + .. note:: + + ``default`` and ``default-push`` apply to all inbound (e.g. + :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` + and :hg:`bundle`) operations. See :hg:`help urls` for more information. Returns 0 on success. """ if search: - for name, path in sorted(ui.paths.iteritems()): - if name == search: - ui.status("%s\n" % util.hidepassword(path.rawloc)) - return + pathitems = [(name, path) for name, path in ui.paths.iteritems() + if name == search] + else: + pathitems = sorted(ui.paths.iteritems()) + + fm = ui.formatter('paths', opts) + if fm: + hidepassword = str + else: + hidepassword = util.hidepassword + if ui.quiet: + namefmt = '%s\n' + else: + namefmt = '%s = ' + showsubopts = not search and not ui.quiet + + for name, path in pathitems: + fm.startitem() + fm.condwrite(not search, 'name', namefmt, name) + fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc)) + for subopt, value in sorted(path.suboptions.items()): + assert subopt not in ('name', 'url') + if showsubopts: + fm.plain('%s:%s = ' % (name, subopt)) + fm.condwrite(showsubopts, subopt, '%s\n', value) + + fm.end() + + if search and not pathitems: if not ui.quiet: ui.warn(_("not found!\n")) return 1 else: - for name, path in sorted(ui.paths.iteritems()): - if ui.quiet: - ui.write("%s\n" % name) - else: - ui.write("%s = %s\n" % (name, - util.hidepassword(path.rawloc))) + return 0 @command('phase', [('p', 'public', False, _('set changeset phase to public')), @@ -5223,14 +5532,13 @@ brev = checkout movemarkfrom = None if not checkout: - updata = destutil.destupdate(repo) + updata = destutil.destupdate(repo) checkout, movemarkfrom, brev = updata ret = hg.update(repo, checkout) except error.UpdateAbort as inst: - ui.warn(_("not updating: %s\n") % str(inst)) - if inst.hint: - ui.warn(_("(%s)\n") % inst.hint) - return 0 + msg = _("not updating: %s") % str(inst) + hint = inst.hint + raise error.UpdateAbort(msg, hint=hint) if not ret and not checkout: if bookmarks.update(repo, [movemarkfrom], repo['.'].node()): ui.status(_("updating bookmark %s\n") % repo._activebookmark) @@ -5366,9 +5674,9 @@ .. note:: - Extra care should be taken with the -f/--force option, - which will push all new heads on all branches, an action which will - almost always cause confusion for collaborators. + Extra care should be taken with the -f/--force option, + which will push all new heads on all branches, an action which will + almost always cause confusion for collaborators. If -r/--rev is used, the specified revision and all its ancestors will be pushed to the remote repository. @@ -5394,13 +5702,12 @@ # this lets simultaneous -r, -b options continue working opts.setdefault('rev', []).append("null") - path = ui.paths.getpath(dest, default='default-push') - if not path: - path = ui.paths.getpath(dest, default='default') + path = ui.paths.getpath(dest, default=('default-push', 'default')) if not path: raise error.Abort(_('default repository not configured!'), hint=_('see the "path" section in "hg help config"')) - dest, branches = path.loc, (path.branch, opts.get('branch') or []) + dest = path.pushloc or path.loc + branches = (path.branch, opts.get('branch') or []) ui.status(_('pushing to %s\n') % util.hidepassword(dest)) revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev')) other = hg.peer(repo, opts, dest) @@ -5491,8 +5798,10 @@ -Af R R R R ========= == == == == - Note that remove never deletes files in Added [A] state from the - working directory, not even if option --force is specified. + .. note:: + + :hg:`remove` never deletes files in Added [A] state from the + working directory, not even if ``--force`` is specified. Returns 0 on success, 1 if any warnings encountered. """ @@ -5526,11 +5835,8 @@ Returns 0 on success, 1 if errors are encountered. """ - wlock = repo.wlock(False) - try: + with repo.wlock(False): return cmdutil.copy(ui, repo, pats, opts, rename=True) - finally: - wlock.release() @command('resolve', [('a', 'all', None, _('select all unresolved files')), @@ -5572,9 +5878,11 @@ - :hg:`resolve -l`: list files which had or still have conflicts. In the printed list, ``U`` = unresolved and ``R`` = resolved. - Note that Mercurial will not let you commit files with unresolved - merge conflicts. You must use :hg:`resolve -m ...` before you can - commit after a conflicting merge. + .. note:: + + Mercurial will not let you commit files with unresolved merge + conflicts. You must use :hg:`resolve -m ...` before you can + commit after a conflicting merge. Returns 0 on success, 1 if any files fail a resolve attempt. """ @@ -5592,7 +5900,7 @@ if show: fm = ui.formatter('resolve', opts) - ms = mergemod.mergestate(repo) + ms = mergemod.mergestate.read(repo) m = scmutil.match(repo[None], pats, opts) for f in ms: if not m(f): @@ -5605,9 +5913,8 @@ fm.end() return 0 - wlock = repo.wlock() - try: - ms = mergemod.mergestate(repo) + with repo.wlock(): + ms = mergemod.mergestate.read(repo) if not (ms.active() or repo.dirstate.p2() != nullid): raise error.Abort( @@ -5657,7 +5964,11 @@ else: # backup pre-resolve (merge uses .orig for its own purposes) a = repo.wjoin(f) - util.copyfile(a, a + ".resolve") + try: + util.copyfile(a, a + ".resolve") + except (IOError, OSError) as inst: + if inst.errno != errno.ENOENT: + raise try: # preresolve file @@ -5675,7 +5986,12 @@ # replace filemerge's .orig file with our resolve file, but only # for merges that are complete if complete: - util.rename(a + ".resolve", a + ".orig") + try: + util.rename(a + ".resolve", + scmutil.origpath(ui, repo, a)) + except OSError as inst: + if inst.errno != errno.ENOENT: + raise for f in tocomplete: try: @@ -5691,9 +6007,14 @@ # replace filemerge's .orig file with our resolve file a = repo.wjoin(f) - util.rename(a + ".resolve", a + ".orig") + try: + util.rename(a + ".resolve", scmutil.origpath(ui, repo, a)) + except OSError as inst: + if inst.errno != errno.ENOENT: + raise ms.commit() + ms.recordactions() if not didwork and pats: ui.warn(_("arguments do not match paths that need resolving\n")) @@ -5709,14 +6030,12 @@ if not proceed: return 1 - finally: - wlock.release() - # Nudge users into finishing an unfinished operation unresolvedf = list(ms.unresolved()) driverresolvedf = list(ms.driverresolved()) if not unresolvedf and not driverresolvedf: ui.status(_('(no more unresolved files)\n')) + cmdutil.checkafterresolved(repo) elif not unresolvedf: ui.status(_('(no more unresolved files -- ' 'run "hg resolve --all" to conclude)\n')) @@ -5921,81 +6240,10 @@ s.serve_forever() if opts["cmdserver"]: - import commandserver service = commandserver.createservice(ui, repo, opts) - return cmdutil.service(opts, initfn=service.init, runfn=service.run) - - # this way we can check if something was given in the command-line - if opts.get('port'): - opts['port'] = util.getport(opts.get('port')) - - if repo: - baseui = repo.baseui else: - baseui = ui - optlist = ("name templates style address port prefix ipv6" - " accesslog errorlog certificate encoding") - for o in optlist.split(): - val = opts.get(o, '') - if val in (None, ''): # should check against default options instead - continue - baseui.setconfig("web", o, val, 'serve') - if repo and repo.ui != baseui: - repo.ui.setconfig("web", o, val, 'serve') - - o = opts.get('web_conf') or opts.get('webdir_conf') - if not o: - if not repo: - raise error.RepoError(_("there is no Mercurial repository" - " here (.hg not found)")) - o = repo - - app = hgweb.hgweb(o, baseui=baseui) - service = httpservice(ui, app, opts) - cmdutil.service(opts, initfn=service.init, runfn=service.run) - -class httpservice(object): - def __init__(self, ui, app, opts): - self.ui = ui - self.app = app - self.opts = opts - - def init(self): - util.setsignalhandler() - self.httpd = hgweb_server.create_server(self.ui, self.app) - - if self.opts['port'] and not self.ui.verbose: - return - - if self.httpd.prefix: - prefix = self.httpd.prefix.strip('/') + '/' - else: - prefix = '' - - port = ':%d' % self.httpd.port - if port == ':80': - port = '' - - bindaddr = self.httpd.addr - if bindaddr == '0.0.0.0': - bindaddr = '*' - elif ':' in bindaddr: # IPv6 - bindaddr = '[%s]' % bindaddr - - fqaddr = self.httpd.fqaddr - if ':' in fqaddr: - fqaddr = '[%s]' % fqaddr - if self.opts['port']: - write = self.ui.status - else: - write = self.ui.write - write(_('listening at http://%s%s/%s (bound to %s:%d)\n') % - (fqaddr, port, prefix, bindaddr, self.httpd.port)) - self.ui.flush() # avoid buffering of status message - - def run(self): - self.httpd.serve_forever() - + service = hgweb.createservice(ui, repo, opts) + return cmdutil.service(opts, initfn=service.init, runfn=service.run) @command('^status|st', [('A', 'all', None, _('show status of all files')), @@ -6029,7 +6277,7 @@ .. note:: - status may appear to disagree with diff if permissions have + :hg:`status` may appear to disagree with diff if permissions have changed or a merge has occurred. The standard diff format does not report permission changes and diff only reports changes relative to one merge parent. @@ -6207,8 +6455,15 @@ if d in status.added: status.added.remove(d) - ms = mergemod.mergestate(repo) - unresolved = [f for f in ms if ms[f] == 'u'] + try: + ms = mergemod.mergestate.read(repo) + except error.UnsupportedMergeRecords as e: + s = ' '.join(e.recordtypes) + ui.warn( + _('warning: merge state has unsupported record types: %s\n') % s) + unresolved = 0 + else: + unresolved = [f for f in ms if ms[f] == 'u'] subs = [s for s in ctx.substate if ctx.sub(s).dirty()] @@ -6229,6 +6484,8 @@ t = ', '.join(t) cleanworkdir = False + if repo.vfs.exists('graftstate'): + t += _(' (graft in progress)') if repo.vfs.exists('updatestate'): t += _(' (interrupted update)') elif len(parents) > 1: @@ -6286,6 +6543,18 @@ if draft or secret: ui.status(_('phases: %s\n') % ', '.join(t)) + if obsolete.isenabled(repo, obsolete.createmarkersopt): + for trouble in ("unstable", "divergent", "bumped"): + numtrouble = len(repo.revs(trouble + "()")) + # We write all the possibilities to ease translation + troublemsg = { + "unstable": _("unstable: %d changesets"), + "divergent": _("divergent: %d changesets"), + "bumped": _("bumped: %d changesets"), + } + if numtrouble > 0: + ui.status(troublemsg[trouble] % numtrouble + "\n") + cmdutil.summaryhooks(ui, repo) if opts.get('remote'): @@ -6506,6 +6775,7 @@ This lists both regular and local tags. When the -v/--verbose switch is used, a third column "local" is printed for local tags. + When the -q/--quiet switch is used, only the tag name is printed. Returns 0 on success. """ @@ -6571,8 +6841,7 @@ """ fnames = (fname1,) + fnames - lock = repo.lock() - try: + with repo.lock(): for fname in fnames: f = hg.openpath(ui, fname) gen = exchange.readbundle(ui, f, fname) @@ -6584,10 +6853,10 @@ tr.close() except error.BundleUnknownFeatureError as exc: raise error.Abort(_('%s: unknown bundle feature, %s') - % (fname, exc), - hint=_("see https://mercurial-scm.org/" - "wiki/BundleFeature for more " - "information")) + % (fname, exc), + hint=_("see https://mercurial-scm.org/" + "wiki/BundleFeature for more " + "information")) finally: if tr: tr.release() @@ -6601,8 +6870,6 @@ hint=_('use "hg debugapplystreamclonebundle"')) else: modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname) - finally: - lock.release() return postincoming(ui, repo, modheads, opts.get('update'), None) @@ -6672,8 +6939,7 @@ if rev is None or rev == '': rev = node - wlock = repo.wlock() - try: + with repo.wlock(): cmdutil.clearunfinished(repo) if date: @@ -6692,7 +6958,7 @@ if check: cmdutil.bailifchanged(repo, merge=False) if rev is None: - updata = destutil.destupdate(repo, clean=clean, check=check) + updata = destutil.destupdate(repo, clean=clean, check=check) rev, movemarkfrom, brev = updata repo.ui.setconfig('ui', 'forcemerge', tool, 'update') @@ -6720,8 +6986,6 @@ ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark) bookmarks.deactivate(repo) - finally: - wlock.release() return ret
--- a/mercurial/commandserver.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/commandserver.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,10 +5,21 @@ # 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 _ +from __future__ import absolute_import + +import SocketServer +import errno +import os import struct -import sys, os, errno, traceback, SocketServer -import dispatch, encoding, util, error +import sys +import traceback + +from .i18n import _ +from . import ( + encoding, + error, + util, +) logfile = None @@ -32,6 +43,10 @@ self.out = out self.channel = channel + @property + def name(self): + return '<%c-channel>' % self.channel + def write(self, data): if not data: return @@ -64,6 +79,10 @@ self.out = out self.channel = channel + @property + def name(self): + return '<%c-channel>' % self.channel + def read(self, size=-1): if size < 0: # if we need to consume all the clients input, ask for 4k chunks @@ -174,6 +193,7 @@ def runcommand(self): """ reads a list of \0 terminated arguments, executes and writes the return code to the result channel """ + from . import dispatch # avoid cycle length = struct.unpack('>I', self._read(4))[0] if not length: @@ -194,9 +214,17 @@ self.repo.ui = self.repo.dirstate._ui = repoui self.repo.invalidateall() + # reset last-print time of progress bar per command + # (progbar is singleton, we don't have to do for all uis) + if copiedui._progbar: + copiedui._progbar.resetstate() + for ui in uis: - # any kind of interaction must use server channels - ui.setconfig('ui', 'nontty', 'true', 'commandserver') + # any kind of interaction must use server channels, but chg may + # replace channels by fully functional tty files. so nontty is + # enforced only if cin is a channel. + if not util.safehasattr(self.cin, 'fileno'): + ui.setconfig('ui', 'nontty', 'true', 'commandserver') req = dispatch.request(args[:], copiedui, self.repo, self.cin, self.cout, self.cerr)
--- a/mercurial/config.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/config.py Sun Jan 17 21:40:21 2016 -0600 @@ -30,6 +30,8 @@ return config(self) def __contains__(self, section): return section in self._data + def hasitem(self, section, item): + return item in self._data.get(section, {}) def __getitem__(self, section): return self._data.get(section, {}) def __iter__(self):
--- a/mercurial/context.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/context.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,17 +5,37 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +from __future__ import absolute_import + +import errno +import os import re +import stat -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 os, errno, stat -import obsolete as obsmod -import repoview -import fileset -import revlog +from .i18n import _ +from .node import ( + bin, + hex, + nullid, + nullrev, + short, + wdirid, +) +from . import ( + encoding, + error, + fileset, + match as matchmod, + mdiff, + obsolete as obsmod, + patch, + phases, + repoview, + revlog, + scmutil, + subrepo, + util, +) propertycache = util.propertycache @@ -120,13 +140,15 @@ added.append(fn) elif node2 is None: removed.append(fn) - elif node2 != _newnode: - # The file was not a new file in mf2, so an entry - # from diff is really a difference. + elif flag1 != flag2: + modified.append(fn) + elif self.rev() is not None: + # When comparing files between two commits, we save time by + # not comparing the file contents when the nodeids differ. + # Note that this means we incorrectly report a reverted change + # to a file as a modification. modified.append(fn) elif self[fn].cmp(other[fn]): - # node2 was newnode, but the working file doesn't - # match the one in mf1. modified.append(fn) else: clean.append(fn) @@ -221,9 +243,10 @@ return self._parents[0] def p2(self): - if len(self._parents) == 2: - return self._parents[1] - return changectx(self._repo, -1) + parents = self._parents + if len(parents) == 2: + return parents[1] + return changectx(self._repo, nullrev) def _fileinfo(self, path): if '_manifest' in self.__dict__: @@ -270,7 +293,7 @@ r = self._repo return matchmod.match(r.root, r.getcwd(), pats, include, exclude, default, - auditor=r.auditor, ctx=self, + auditor=r.nofsauditor, ctx=self, listsubrepos=listsubrepos, badfn=badfn) def diff(self, ctx2=None, match=None, **opts): @@ -335,17 +358,19 @@ if listsubrepos: for subpath, sub in scmutil.itersubrepos(ctx1, ctx2): - rev2 = ctx2.subrev(subpath) try: - submatch = matchmod.narrowmatcher(subpath, match) - s = sub.status(rev2, match=submatch, ignored=listignored, - clean=listclean, unknown=listunknown, - listsubrepos=True) - for rfiles, sfiles in zip(r, s): - rfiles.extend("%s/%s" % (subpath, f) for f in sfiles) - except error.LookupError: - self._repo.ui.status(_("skipping missing " - "subrepository: %s\n") % subpath) + rev2 = ctx2.subrev(subpath) + except KeyError: + # A subrepo that existed in node1 was deleted between + # node1 and node2 (inclusive). Thus, ctx2's substate + # won't contain that subpath. The best we can do ignore it. + rev2 = None + submatch = matchmod.narrowmatcher(subpath, match) + s = sub.status(rev2, match=submatch, ignored=listignored, + clean=listclean, unknown=listunknown, + listsubrepos=True) + for rfiles, sfiles in zip(r, s): + rfiles.extend("%s/%s" % (subpath, f) for f in sfiles) for l in r: l.sort() @@ -366,8 +391,8 @@ extra = {} if branch: extra['branch'] = encoding.fromlocal(branch) - ctx = memctx(repo, parents, text, files, getfilectx, user, - date, extra, editor) + ctx = memctx(repo, parents, text, files, getfilectx, user, + date, extra, editor) return ctx class changectx(basectx): @@ -511,10 +536,11 @@ @propertycache def _parents(self): - p = self._repo.changelog.parentrevs(self._rev) - if p[1] == nullrev: - p = p[:-1] - return [changectx(self._repo, x) for x in p] + repo = self._repo + p1, p2 = repo.changelog.parentrevs(self._rev) + if p2 == nullrev: + return [changectx(repo, p1)] + return [changectx(repo, p1), changectx(repo, p2)] def changeset(self): return self._changeset @@ -747,11 +773,22 @@ def islink(self): return 'l' in self.flags() + def isabsent(self): + """whether this filectx represents a file not in self._changectx + + This is mainly for merge code to detect change/delete conflicts. This is + expected to be True for all subclasses of basectx.""" + return False + + _customcmp = False def cmp(self, fctx): """compare with other file context returns True if different than fctx. """ + if fctx._customcmp: + return fctx.cmp(self) + if (fctx._filerev is None and (self._repo._encodefilterpats # if file data starts with '\1\n', empty metadata block is @@ -1140,17 +1177,17 @@ # filesystem doesn't support them copiesget = self._repo.dirstate.copies().get - - if len(self._parents) < 2: + parents = self.parents() + if len(parents) < 2: # when we have one parent, it's easy: copy from parent - man = self._parents[0].manifest() + man = parents[0].manifest() def func(f): f = copiesget(f, f) return man.flags(f) else: # merges are tricky: we try to reconstruct the unstored # result from the merge (issue1802) - p1, p2 = self._parents + p1, p2 = parents pa = p1.ancestor(p2) m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() @@ -1180,10 +1217,11 @@ an extra 'a'. This is used by manifests merge to see that files are different and by update logic to avoid deleting newly added files. """ + parents = self.parents() - man1 = self._parents[0].manifest() + man1 = parents[0].manifest() man = man1.copy() - if len(self._parents) > 1: + if len(parents) > 1: man2 = self.p2().manifest() def getman(f): if f in man1: @@ -1377,9 +1415,8 @@ def add(self, list, prefix=""): join = lambda f: os.path.join(prefix, f) - wlock = self._repo.wlock() - ui, ds = self._repo.ui, self._repo.dirstate - try: + with self._repo.wlock(): + ui, ds = self._repo.ui, self._repo.dirstate rejected = [] lstat = self._repo.wvfs.lstat for f in list: @@ -1407,13 +1444,10 @@ else: ds.add(f) return rejected - finally: - wlock.release() def forget(self, files, prefix=""): join = lambda f: os.path.join(prefix, f) - wlock = self._repo.wlock() - try: + with self._repo.wlock(): rejected = [] for f in files: if f not in self._repo.dirstate: @@ -1424,13 +1458,10 @@ else: self._repo.dirstate.drop(f) return rejected - finally: - wlock.release() def undelete(self, list): pctxs = self.parents() - wlock = self._repo.wlock() - try: + with self._repo.wlock(): for f in list: if self._repo.dirstate[f] != 'r': self._repo.ui.warn(_("%s not removed!\n") % f) @@ -1439,8 +1470,6 @@ t = fctx.data() self._repo.wwrite(f, t, fctx.flags()) self._repo.dirstate.normal(f) - finally: - wlock.release() def copy(self, source, dest): try: @@ -1454,15 +1483,12 @@ self._repo.ui.warn(_("copy failed: %s is not a file or a " "symbolic link\n") % dest) else: - wlock = self._repo.wlock() - try: + with self._repo.wlock(): if self._repo.dirstate[dest] in '?': self._repo.dirstate.add(dest) elif self._repo.dirstate[dest] in 'r': self._repo.dirstate.normallookup(dest) self._repo.dirstate.copy(source, dest) - finally: - wlock.release() def match(self, pats=[], include=None, exclude=None, default='glob', listsubrepos=False, badfn=None): @@ -1522,17 +1548,15 @@ # so we don't wait on the lock # wlock can invalidate the dirstate, so cache normal _after_ # taking the lock - wlock = self._repo.wlock(False) - normal = self._repo.dirstate.normal - try: + with self._repo.wlock(False): + normal = self._repo.dirstate.normal 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 + # after this block from doing so for subsequent + # changing files self._repo.dirstate.write(self._repo.currenttransaction()) - finally: - wlock.release() except error.LockError: pass return modified, fixup @@ -1694,7 +1718,7 @@ def date(self): t, tz = self._changectx.date() try: - return (util.statmtimesec(self._repo.wvfs.lstat(self._path)), tz) + return (self._repo.wvfs.lstat(self._path).st_mtime, tz) except OSError as err: if err.errno != errno.ENOENT: raise @@ -1755,6 +1779,22 @@ changed.update(self._status.removed) return changed +def makecachingfilectxfn(func): + """Create a filectxfn that caches based on the path. + + We can't use util.cachefunc because it uses all arguments as the cache + key and this creates a cycle since the arguments include the repo and + memctx. + """ + cache = {} + + def getfilectx(repo, memctx, path): + if path not in cache: + cache[path] = func(repo, memctx, path) + return cache[path] + + return getfilectx + class memctx(committablectx): """Use memctx to perform in-memory commits via localrepo.commitctx(). @@ -1814,9 +1854,8 @@ copied=copied, memctx=memctx) self._filectxfn = getfilectx else: - # "util.cachefunc" reduces invocation of possibly expensive - # "filectxfn" for performance (e.g. converting from another VCS) - self._filectxfn = util.cachefunc(filectxfn) + # memoizing increases performance for e.g. vcs convert scenarios. + self._filectxfn = makecachingfilectxfn(filectxfn) if extra: self._extra = extra.copy()
--- a/mercurial/copies.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/copies.py Sun Jan 17 21:40:21 2016 -0600 @@ -401,13 +401,13 @@ continue elif dsrc in d1 and ddst in d1: # directory wasn't entirely moved locally - invalid.add(dsrc) + invalid.add(dsrc + "/") elif dsrc in d2 and ddst in d2: # directory wasn't entirely moved remotely - invalid.add(dsrc) - elif dsrc in dirmove and dirmove[dsrc] != ddst: + invalid.add(dsrc + "/") + elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/": # files from the same directory moved to two different places - invalid.add(dsrc) + invalid.add(dsrc + "/") else: # looks good so far dirmove[dsrc + "/"] = ddst + "/"
--- a/mercurial/crecord.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/crecord.py Sun Jan 17 21:40:21 2016 -0600 @@ -24,6 +24,7 @@ encoding, error, patch as patchmod, + util, ) # This is required for ncurses to display non-ASCII characters in default user @@ -455,11 +456,11 @@ # if there are no changed files if len(headers) == 0: - return [] + return [], {} uiheaders = [uiheader(h) for h in headers] # let user choose headers/hunks/lines, and mark their applied flags # accordingly - chunkselector(ui, uiheaders) + ret = chunkselector(ui, uiheaders) appliedhunklist = [] for hdr in uiheaders: if (hdr.applied and @@ -477,7 +478,7 @@ else: fixoffset += hnk.removed - hnk.added - return appliedhunklist + return (appliedhunklist, ret) def gethw(): """ @@ -506,6 +507,7 @@ raise error.Abort(chunkselector.initerr) # ncurses does not restore signal handler for SIGTSTP signal.signal(signal.SIGTSTP, f) + return chunkselector.opts def testdecorator(testfn, f): def u(*args, **kwargs): @@ -526,6 +528,7 @@ while True: if chunkselector.handlekeypressed(testcommands.pop(0), test=True): break + return chunkselector.opts class curseschunkselector(object): def __init__(self, headerlist, ui): @@ -533,6 +536,7 @@ self.headerlist = patch(headerlist) self.ui = ui + self.opts = {} self.errorstr = None # list of all chunks @@ -1012,7 +1016,7 @@ pairname="legend") printstring(self.statuswin, " (f)old/unfold; (c)onfirm applied; (q)uit; (?) help " - "| [X]=hunk applied **=folded", + "| [X]=hunk applied **=folded, toggle [a]mend mode", pairname="legend") except curses.error: pass @@ -1368,7 +1372,7 @@ F : fold / unfold parent item and all of its ancestors m : edit / resume editing the commit message e : edit the currently selected hunk - a : toggle amend mode (hg rev >= 2.2) + a : toggle amend mode (hg rev >= 2.2), only with commit -i c : confirm selected changes r : review/edit and confirm selected changes q : quit without confirming (no changes will be made) @@ -1435,6 +1439,35 @@ else: return False + def toggleamend(self, opts, test): + """Toggle the amend flag. + + When the amend flag is set, a commit will modify the most recently + committed changeset, instead of creating a new changeset. Otherwise, a + new changeset will be created (the normal commit behavior). + + """ + try: + ver = float(util.version()[:3]) + except ValueError: + ver = 1 + if ver < 2.19: + msg = ("The amend option is unavailable with hg versions < 2.2\n\n" + "Press any key to continue.") + elif opts.get('amend') is None: + opts['amend'] = True + msg = ("Amend option is turned on -- commiting the currently " + "selected changes will not create a new changeset, but " + "instead update the most recently committed changeset.\n\n" + "Press any key to continue.") + elif opts.get('amend') is True: + opts['amend'] = None + msg = ("Amend option is turned off -- commiting the currently " + "selected changes will create a new changeset.\n\n" + "Press any key to continue.") + if not test: + self.confirmationwindow(msg) + def recenterdisplayedarea(self): """ once we scrolled with pg up pg down we can be pointing outside of the @@ -1572,6 +1605,8 @@ self.leftarrowshiftevent() elif keypressed in ["q"]: raise error.Abort(_('user quit')) + elif keypressed in ['a']: + self.toggleamend(self.opts, test) elif keypressed in ["c"]: if self.confirmcommit(): return True
--- a/mercurial/demandimport.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/demandimport.py Sun Jan 17 21:40:21 2016 -0600 @@ -133,6 +133,8 @@ self._load() setattr(self._module, attr, val) +_pypy = '__pypy__' in sys.builtin_module_names + def _demandimport(name, globals=None, locals=None, fromlist=None, level=level): if not locals or name in ignore or fromlist == ('*',): # these cases we can't really delay @@ -182,16 +184,28 @@ symbol._addref(globalname) if level >= 0: - # Mercurial's enforced import style does not use - # "from a import b,c,d" or "from .a import b,c,d" syntax. In - # addition, this appears to be giving errors with some modules - # for unknown reasons. Since we shouldn't be using this syntax - # much, work around the problems. + # The "from a import b,c,d" or "from .a import b,c,d" + # syntax gives errors with some modules for unknown + # reasons. Work around the problem. if name: return _hgextimport(_origimport, name, globals, locals, fromlist, level) - mod = _hgextimport(_origimport, name, globals, locals, level=level) + if _pypy: + # PyPy's __import__ throws an exception if invoked + # with an empty name and no fromlist. Recreate the + # desired behaviour by hand. + mn = globalname + mod = sys.modules[mn] + if getattr(mod, '__path__', nothing) is nothing: + mn = mn.rsplit('.', 1)[0] + mod = sys.modules[mn] + if level > 1: + mn = mn.rsplit('.', level - 1)[0] + mod = sys.modules[mn] + else: + mod = _hgextimport(_origimport, name, globals, locals, + level=level) for x in fromlist: processfromitem(mod, x)
--- a/mercurial/destutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/destutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,6 +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. +from __future__ import absolute_import + from .i18n import _ from . import ( bookmarks, @@ -198,3 +200,21 @@ else: node = _destmergebranch(repo) return repo[node].rev() + +histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())' + +def desthistedit(ui, repo): + """Default base revision to edit for `hg histedit`.""" + # Avoid cycle: scmutil -> revset -> destutil + from . import scmutil + + default = ui.config('histedit', 'defaultrev', histeditdefaultrevset) + if default: + revs = scmutil.revrange(repo, [default]) + if revs: + # The revset supplied by the user may not be in ascending order nor + # take the first revision. So do this manually. + revs.sort() + return revs.first() + + return None
--- a/mercurial/dirstate.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/dirstate.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,11 +5,25 @@ # 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 -from i18n import _ -import scmutil, util, osutil, parsers, encoding, pathutil, error -import os, stat, errno -import match as matchmod +from __future__ import absolute_import + +import collections +import errno +import os +import stat + +from .i18n import _ +from .node import nullid +from . import ( + encoding, + error, + match as matchmod, + osutil, + parsers, + pathutil, + scmutil, + util, +) propertycache = util.propertycache filecache = scmutil.filecache @@ -31,11 +45,19 @@ '''Get "now" timestamp on filesystem''' tmpfd, tmpname = vfs.mkstemp() try: - return util.statmtimesec(os.fstat(tmpfd)) + return os.fstat(tmpfd).st_mtime finally: os.close(tmpfd) vfs.unlink(tmpname) +def nonnormalentries(dmap): + '''Compute the nonnormal dirstate entries from the dmap''' + try: + return parsers.nonnormalentries(dmap) + except AttributeError: + return set(fname for fname, e in dmap.iteritems() + if e[0] != 'n' or e[3] == -1) + def _trypending(root, vfs, filename): '''Open file to be read according to HG_PENDING environment variable @@ -119,6 +141,10 @@ return self._copymap @propertycache + def _nonnormalset(self): + return nonnormalentries(self._map) + + @propertycache def _filefoldmap(self): try: makefilefoldmap = parsers.make_file_foldmap @@ -178,15 +204,7 @@ @rootcache('.hgignore') def _ignore(self): - 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))) - + files = self._ignorefiles() if not files: return util.never @@ -418,7 +436,7 @@ def invalidate(self): for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch", - "_pl", "_dirs", "_ignore"): + "_pl", "_dirs", "_ignore", "_nonnormalset"): if a in self.__dict__: delattr(self, a) self._lastnormaltime = 0 @@ -467,15 +485,19 @@ self._dirs.addpath(f) self._dirty = True self._map[f] = dirstatetuple(state, mode, size, mtime) + if state != 'n' or mtime == -1: + self._nonnormalset.add(f) def normal(self, f): '''Mark a file normal and clean.''' s = os.lstat(self._join(f)) - mtime = util.statmtimesec(s) + mtime = s.st_mtime self._addpath(f, 'n', s.st_mode, s.st_size & _rangemask, mtime & _rangemask) if f in self._copymap: del self._copymap[f] + if f in self._nonnormalset: + self._nonnormalset.remove(f) if mtime > self._lastnormaltime: # Remember the most recent modification timeslot for status(), # to make sure we won't miss future size-preserving file content @@ -503,6 +525,8 @@ self._addpath(f, 'n', 0, -1, -1) if f in self._copymap: del self._copymap[f] + if f in self._nonnormalset: + self._nonnormalset.remove(f) def otherparent(self, f): '''Mark as coming from the other parent, always dirty.''' @@ -538,6 +562,7 @@ elif entry[0] == 'n' and entry[2] == -2: # other parent size = -2 self._map[f] = dirstatetuple('r', 0, size, 0) + self._nonnormalset.add(f) if size == 0 and f in self._copymap: del self._copymap[f] @@ -553,6 +578,8 @@ self._dirty = True self._droppath(f) del self._map[f] + if f in self._nonnormalset: + self._nonnormalset.remove(f) def _discoverpath(self, path, normed, ignoremissing, exists, storemap): if exists is None: @@ -630,6 +657,7 @@ def clear(self): self._map = {} + self._nonnormalset = set() if "_dirs" in self.__dict__: delattr(self, "_dirs") self._copymap = {} @@ -639,17 +667,24 @@ def rebuild(self, parent, allfiles, changedfiles=None): if changedfiles is None: + # Rebuild entire dirstate changedfiles = allfiles - oldmap = self._map - self.clear() - for f in allfiles: - if f not in changedfiles: - self._map[f] = oldmap[f] + lastnormaltime = self._lastnormaltime + self.clear() + self._lastnormaltime = lastnormaltime + + for f in changedfiles: + mode = 0o666 + if f in allfiles and 'x' in allfiles.flags(f): + mode = 0o777 + + if f in allfiles: + self._map[f] = dirstatetuple('n', mode, -1, 0) else: - if 'x' in allfiles.flags(f): - self._map[f] = dirstatetuple('n', 0o777, -1, 0) - else: - self._map[f] = dirstatetuple('n', 0o666, -1, 0) + self._map.pop(f, None) + if f in self._nonnormalset: + self._nonnormalset.remove(f) + self._pl = (parent, nullid) self._dirty = True @@ -657,13 +692,6 @@ if not self._dirty: return - # enough 'delaywrite' prevents 'pack_dirstate' from dropping - # timestamp of each entries in dirstate, because of 'now > mtime' - delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0) - if delaywrite > 0: - import time # to avoid useless import - time.sleep(delaywrite) - filename = self._filename if tr is False: # not explicitly specified if (self._ui.configbool('devel', 'all-warnings') @@ -689,6 +717,7 @@ for f, e in dmap.iteritems(): if e[0] == 'n' and e[3] == now: dmap[f] = dirstatetuple(e[0], e[1], e[2], -1) + self._nonnormalset.add(f) # emulate that all 'dirstate.normal' results are written out self._lastnormaltime = 0 @@ -704,8 +733,26 @@ def _writedirstate(self, st): # use the modification time of the newly created temporary file as the # filesystem's notion of 'now' - now = util.statmtimesec(util.fstat(st)) & _rangemask + now = util.fstat(st).st_mtime & _rangemask + + # enough 'delaywrite' prevents 'pack_dirstate' from dropping + # timestamp of each entries in dirstate, because of 'now > mtime' + delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0) + if delaywrite > 0: + # do we have any files to delay for? + for f, e in self._map.iteritems(): + if e[0] == 'n' and e[3] == now: + import time # to avoid useless import + # rather than sleep n seconds, sleep until the next + # multiple of n seconds + clock = time.time() + start = int(clock) - (int(clock) % delaywrite) + end = start + delaywrite + time.sleep(end - clock) + break + st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now)) + self._nonnormalset = nonnormalentries(self._map) st.close() self._lastnormaltime = 0 self._dirty = self._dirtypl = False @@ -720,6 +767,37 @@ return True return False + def _ignorefiles(self): + 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 files + + def _ignorefileandline(self, f): + files = collections.deque(self._ignorefiles()) + visited = set() + while files: + i = files.popleft() + patterns = matchmod.readpatternfile(i, self._ui.warn, + sourceinfo=True) + for pattern, lineno, line in patterns: + kind, p = matchmod._patsplit(pattern, 'glob') + if kind == "subinclude": + if p not in visited: + files.append(p) + continue + m = matchmod.match(self._root, '', [], [pattern], + warn=self._ui.warn) + if m(f): + return (i, lineno, line) + visited.add(i) + return (None, -1, "") + def _walkexplicit(self, match, subrepos): '''Get stat data about the files explicitly specified by match. @@ -1008,14 +1086,8 @@ # We may not have walked the full directory tree above, # so stat and check everything we missed. nf = iter(visit).next - pos = 0 - while pos < len(visit): - # visit in mid-sized batches so that we don't - # block signals indefinitely - xr = xrange(pos, min(len(visit), pos + 1000)) - for st in util.statfiles([join(visit[n]) for n in xr]): - results[nf()] = st - pos += 1000 + for st in util.statfiles([join(i) for i in visit]): + results[nf()] = st return results def status(self, match, subrepos, ignored, clean, unknown): @@ -1084,16 +1156,15 @@ if not st and state in "nma": dadd(fn) elif state == 'n': - mtime = util.statmtimesec(st) if (size >= 0 and ((size != st.st_size and size != st.st_size & _rangemask) or ((mode ^ st.st_mode) & 0o100 and checkexec)) or size == -2 # other parent or fn in copymap): madd(fn) - elif time != mtime and time != mtime & _rangemask: + elif time != st.st_mtime and time != st.st_mtime & _rangemask: ladd(fn) - elif mtime == lastnormaltime: + elif st.st_mtime == lastnormaltime: # fn may have just been marked as normal and it may have # changed in the same second without changing its size. # This can happen if we quickly do multiple commits.
--- a/mercurial/discovery.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/discovery.py Sun Jan 17 21:40:21 2016 -0600 @@ -238,12 +238,42 @@ unsynced = set() return {None: (oldheads, newheads, unsynced)} -def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False, - newbookmarks=[]): +def _nowarnheads(pushop): + # Compute newly pushed bookmarks. We don't warn about bookmarked heads. + + # internal config: bookmarks.pushing + newbookmarks = pushop.ui.configlist('bookmarks', 'pushing') + + repo = pushop.repo.unfiltered() + remote = pushop.remote + localbookmarks = repo._bookmarks + remotebookmarks = remote.listkeys('bookmarks') + bookmarkedheads = set() + for bm in localbookmarks: + rnode = remotebookmarks.get(bm) + if rnode and rnode in repo: + lctx, rctx = repo[bm], repo[rnode] + if bookmarks.validdest(repo, rctx, lctx): + bookmarkedheads.add(lctx.node()) + else: + if bm in newbookmarks and bm not in remotebookmarks: + bookmarkedheads.add(repo[bm].node()) + + return bookmarkedheads + +def checkheads(pushop): """Check that a push won't add any outgoing head raise Abort error and display ui message as needed. """ + + repo = pushop.repo.unfiltered() + remote = pushop.remote + outgoing = pushop.outgoing + remoteheads = pushop.remoteheads + newbranch = pushop.newbranch + inc = bool(pushop.incoming) + # Check for each named branch if we're creating new remote heads. # To be a remote head after push, node must be either: # - unknown locally @@ -268,19 +298,8 @@ hint=_("use 'hg push --new-branch' to create" " new remote branches")) - # 2. Compute newly pushed bookmarks. We don't warn about bookmarked heads. - localbookmarks = repo._bookmarks - remotebookmarks = remote.listkeys('bookmarks') - bookmarkedheads = set() - for bm in localbookmarks: - rnode = remotebookmarks.get(bm) - if rnode and rnode in repo: - lctx, rctx = repo[bm], repo[rnode] - if bookmarks.validdest(repo, rctx, lctx): - bookmarkedheads.add(lctx.node()) - else: - if bm in newbookmarks and bm not in remotebookmarks: - bookmarkedheads.add(repo[bm].node()) + # 2. Find heads that we need not warn about + nowarnheads = _nowarnheads(pushop) # 3. Check for new heads. # If there are more heads after the push than before, a suitable @@ -366,7 +385,7 @@ " pushing new heads") elif len(newhs) > len(oldhs): # remove bookmarked or existing remote heads from the new heads list - dhs = sorted(newhs - bookmarkedheads - oldhs) + dhs = sorted(newhs - nowarnheads - oldhs) if dhs: if errormsg is None: if branch not in ('default', None):
--- a/mercurial/dispatch.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/dispatch.py Sun Jan 17 21:40:21 2016 -0600 @@ -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 __future__ import absolute_import +from __future__ import absolute_import, print_function import atexit import difflib @@ -59,6 +59,13 @@ # probably be investigated and tweaked. return [s for s in symbols if sim(s) > 0.6] +def _reportsimilar(write, similar): + if len(similar) == 1: + write(_("(did you mean %s?)\n") % similar[0]) + elif similar: + ss = ", ".join(sorted(similar)) + write(_("(did you mean one of %s?)\n") % ss) + def _formatparse(write, inst): similar = [] if isinstance(inst, error.UnknownIdentifier): @@ -71,12 +78,7 @@ write(_("unexpected leading whitespace\n")) else: write(_("hg: parse error: %s\n") % inst.args[0]) - if similar: - if len(similar) == 1: - write(_("(did you mean %r?)\n") % similar[0]) - else: - ss = ", ".join(sorted(similar)) - write(_("(did you mean one of %s?)\n") % ss) + _reportsimilar(write, similar) def dispatch(req): "run the command specified in req.args" @@ -107,6 +109,8 @@ return -1 except error.ParseError as inst: _formatparse(ferr.write, inst) + if inst.hint: + ferr.write(_("(%s)\n") % inst.hint) return -1 msg = ' '.join(' ' in a and repr(a) or a for a in req.args) @@ -202,6 +206,8 @@ (inst.args[0], " ".join(inst.args[1]))) except error.ParseError as inst: _formatparse(ui.warn, inst) + if inst.hint: + ui.warn(_("(%s)\n") % inst.hint) return -1 except error.LockHeld as inst: if inst.errno == errno.ETIMEDOUT: @@ -258,13 +264,14 @@ if len(inst.args) == 2: sim = _getsimilar(inst.args[1], inst.args[0]) if sim: - ui.warn(_('(did you mean one of %s?)\n') % - ', '.join(sorted(sim))) + _reportsimilar(ui.warn, sim) suggested = True if not suggested: commands.help_(ui, 'shortlist') except error.InterventionRequired as inst: ui.warn("%s\n" % inst) + if inst.hint: + ui.warn(_("(%s)\n") % inst.hint) return 1 except error.Abort as inst: ui.warn(_("abort: %s\n") % inst) @@ -320,7 +327,6 @@ except socket.error as inst: ui.warn(_("abort: %s\n") % inst.args[-1]) except: # re-raises - myver = util.version() # For compatibility checking, we discard the portion of the hg # version after the + on the assumption that if a "normal # user" is running a build with a + in it the packager @@ -328,8 +334,7 @@ # 'make local' copy of hg (where the version number can be out # of date) will be clueful enough to notice the implausible # version number and try updating. - compare = myver.split('+')[0] - ct = tuplever(compare) + ct = util.versiontuple(n=2) worst = None, ct, '' if ui.config('ui', 'supportcontact', None) is None: for name, mod in extensions.extensions(): @@ -344,7 +349,7 @@ if testedwith == 'internal': continue - tested = [tuplever(t) for t in testedwith.split()] + tested = [util.versiontuple(t, 2) for t in testedwith.split()] if ct in tested: continue @@ -369,7 +374,8 @@ warning = (_("** unknown exception encountered, " "please report by visiting\n** ") + bugtracker + '\n') warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) + - (_("** Mercurial Distributed SCM (version %s)\n") % myver) + + (_("** Mercurial Distributed SCM (version %s)\n") % + util.version()) + (_("** Extensions loaded: %s\n") % ", ".join([x[0] for x in extensions.extensions()]))) ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc()) @@ -378,15 +384,6 @@ return -1 -def tuplever(v): - try: - # Assertion: tuplever is only used for extension compatibility - # checking. Otherwise, the discarding of extra version fields is - # incorrect. - return tuple([int(i) for i in v.split('.')[0:2]]) - except ValueError: - return tuple() - def aliasargs(fn, givenargs): args = getattr(fn, 'args', []) if args: @@ -437,6 +434,7 @@ self.help = '' self.norepo = True self.optionalrepo = False + self.inferrepo = False self.badalias = None self.unknowncmd = False @@ -502,6 +500,8 @@ self.norepo = False if cmd in commands.optionalrepo.split(' '): self.optionalrepo = True + if cmd in commands.inferrepo.split(' '): + self.inferrepo = True if self.help.startswith("hg " + cmd): # drop prefix in old-style help lines so hg shows the alias self.help = self.help[4 + len(cmd):] @@ -560,6 +560,8 @@ commands.norepo += ' %s' % alias if aliasdef.optionalrepo: commands.optionalrepo += ' %s' % alias + if aliasdef.inferrepo: + commands.inferrepo += ' %s' % alias def _parse(ui, args): options = {} @@ -726,9 +728,11 @@ strict = True norepo = commands.norepo optionalrepo = commands.optionalrepo + inferrepo = commands.inferrepo def restorecommands(): commands.norepo = norepo commands.optionalrepo = optionalrepo + commands.inferrepo = inferrepo cmdtable = commands.table.copy() addaliases(lui, cmdtable) else: @@ -864,7 +868,7 @@ if options['version']: return commands.version_(ui) if options['help']: - return commands.help_(ui, cmd, command=True) + return commands.help_(ui, cmd, command=cmd is not None) elif not cmd: return commands.help_(ui, 'shortlist') @@ -976,9 +980,9 @@ finally: thread.stop() thread.join() - print 'Collected %d stack frames (%d unique) in %2.2f seconds.' % ( + print('Collected %d stack frames (%d unique) in %2.2f seconds.' % ( time.clock() - start_time, thread.num_frames(), - thread.num_frames(unique=True)) + thread.num_frames(unique=True))) def statprofile(ui, func, fp):
--- a/mercurial/encoding.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/encoding.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,8 +5,15 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import error -import unicodedata, locale, os +from __future__ import absolute_import + +import locale +import os +import unicodedata + +from . import ( + error, +) # These unicode characters are ignored by HFS+ (Apple Technote 1150, # "Unicode Subtleties"), so we need to ignore them in some places for @@ -194,7 +201,7 @@ 'ellipsis' is always placed at trimmed side. >>> ellipsis = '+++' - >>> from mercurial import encoding + >>> from . import encoding >>> encoding.encoding = 'utf-8' >>> t= '1234567890' >>> print trim(t, 12, ellipsis=ellipsis) @@ -290,7 +297,7 @@ def asciilower(s): # delay importing avoids cyclic dependency around "parsers" in # pure Python build (util => i18n => encoding => parsers => util) - import parsers + from . import parsers impl = getattr(parsers, 'asciilower', _asciilower) global asciilower asciilower = impl @@ -306,7 +313,7 @@ def asciiupper(s): # delay importing avoids cyclic dependency around "parsers" in # pure Python build (util => i18n => encoding => parsers => util) - import parsers + from . import parsers impl = getattr(parsers, 'asciiupper', _asciiupper) global asciiupper asciiupper = impl @@ -388,8 +395,10 @@ >>> jsonescape('this is a test') 'this is a test' - >>> jsonescape('escape characters: \\0 \\x0b \\t \\n \\r \\" \\\\') - 'escape characters: \\\\u0000 \\\\u000b \\\\t \\\\n \\\\r \\\\" \\\\\\\\' + >>> jsonescape('escape characters: \\0 \\x0b \\x7f') + 'escape characters: \\\\u0000 \\\\u000b \\\\u007f' + >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\') + 'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\' >>> jsonescape('a weird byte: \\xdd') 'a weird byte: \\xed\\xb3\\x9d' >>> jsonescape('utf-8: caf\\xc3\\xa9') @@ -400,10 +409,11 @@ if not _jsonmap: for x in xrange(32): - _jsonmap[chr(x)] = "\u%04x" %x + _jsonmap[chr(x)] = "\\u%04x" % x for x in xrange(32, 256): c = chr(x) _jsonmap[c] = c + _jsonmap['\x7f'] = '\\u007f' _jsonmap['\t'] = '\\t' _jsonmap['\n'] = '\\n' _jsonmap['\"'] = '\\"' @@ -414,6 +424,25 @@ return ''.join(_jsonmap[c] for c in toutf8b(s)) +_utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4] + +def getutf8char(s, pos): + '''get the next full utf-8 character in the given string, starting at pos + + Raises a UnicodeError if the given location does not start a valid + utf-8 character. + ''' + + # find how many bytes to attempt decoding from first nibble + l = _utf8len[ord(s[pos]) >> 4] + if not l: # ascii + return s[pos] + + c = s[pos:pos + l] + # validate with attempted decode + c.decode("utf-8") + return c + def toutf8b(s): '''convert a local, possibly-binary string into UTF-8b @@ -444,24 +473,32 @@ internal surrogate encoding as a UTF-8 string.) ''' - if isinstance(s, localstr): - return s._utf8 + if "\xed" not in s: + if isinstance(s, localstr): + return s._utf8 + try: + s.decode('utf-8') + return s + except UnicodeDecodeError: + pass - try: - s.decode('utf-8') - return s - except UnicodeDecodeError: - # surrogate-encode any characters that don't round-trip - s2 = s.decode('utf-8', 'ignore').encode('utf-8') - r = "" - pos = 0 - for c in s: - if s2[pos:pos + 1] == c: - r += c + r = "" + pos = 0 + l = len(s) + while pos < l: + try: + c = getutf8char(s, pos) + if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf": + # have to re-escape existing U+DCxx characters + c = unichr(0xdc00 + ord(s[pos])).encode('utf-8') pos += 1 else: - r += unichr(0xdc00 + ord(c)).encode('utf-8') - return r + pos += len(c) + except UnicodeDecodeError: + c = unichr(0xdc00 + ord(s[pos])).encode('utf-8') + pos += 1 + r += c + return r def fromutf8b(s): '''Given a UTF-8b string, return a local, possibly-binary string. @@ -470,11 +507,19 @@ is a round-trip process for strings like filenames, but metadata that's was passed through tolocal will remain in UTF-8. + >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x >>> m = "\\xc3\\xa9\\x99abcd" - >>> n = toutf8b(m) - >>> n + >>> toutf8b(m) '\\xc3\\xa9\\xed\\xb2\\x99abcd' - >>> fromutf8b(n) == m + >>> roundtrip(m) + True + >>> roundtrip("\\xc2\\xc2\\x80") + True + >>> roundtrip("\\xef\\xbf\\xbd") + True + >>> roundtrip("\\xef\\xef\\xbf\\xbd") + True + >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80") True ''' @@ -482,11 +527,19 @@ if "\xed" not in s: return s - u = s.decode("utf-8") + # We could do this with the unicode type but some Python builds + # use UTF-16 internally (issue5031) which causes non-BMP code + # points to be escaped. Instead, we use our handy getutf8char + # helper again to walk the string without "decoding" it. + r = "" - for c in u: - if ord(c) & 0xff00 == 0xdc00: - r += chr(ord(c) & 0xff) - else: - r += c.encode("utf-8") + pos = 0 + l = len(s) + while pos < l: + c = getutf8char(s, pos) + pos += len(c) + # unescape U+DCxx characters + if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf": + c = chr(ord(c.decode("utf-8")) & 0xff) + r += c return r
--- a/mercurial/error.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/error.py Sun Jan 17 21:40:21 2016 -0600 @@ -50,7 +50,7 @@ class CommandError(Exception): """Exception raised on errors in parsing the command line.""" -class InterventionRequired(Exception): +class InterventionRequired(HintException): """Exception raised when a command requires human intervention.""" class Abort(HintException): @@ -72,14 +72,16 @@ class UpdateAbort(Abort): """Raised when an update is aborted for destination issue""" -class OutOfBandError(Exception): +class ResponseExpected(Abort): + """Raised when an EOF is received for a prompt""" + def __init__(self): + from .i18n import _ + Abort.__init__(self, _('response expected')) + +class OutOfBandError(HintException): """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): +class ParseError(HintException): """Raised when parsing config files and {rev,file}sets (msg[, pos])""" class UnknownIdentifier(ParseError): @@ -106,6 +108,16 @@ class RequirementError(RepoError): """Exception raised if .hg/requires has an unknown entry.""" +class UnsupportedMergeRecords(Abort): + def __init__(self, recordtypes): + from .i18n import _ + self.recordtypes = sorted(recordtypes) + s = ' '.join(self.recordtypes) + Abort.__init__( + self, _('unsupported merge state records: %s') % s, + hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for ' + 'more information')) + class LockError(IOError): def __init__(self, errno, strerror, filename, desc): IOError.__init__(self, errno, strerror, filename)
--- a/mercurial/exchange.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/exchange.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,16 +5,35 @@ # 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 _ -from node import hex, nullid -import errno, urllib, urllib2 -import util, scmutil, changegroup, base85, error -import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey -import lock as lockmod -import streamclone -import sslutil -import tags -import url as urlmod +from __future__ import absolute_import + +import errno +import urllib +import urllib2 + +from .i18n import _ +from .node import ( + hex, + nullid, +) +from . import ( + base85, + bookmarks as bookmod, + bundle2, + changegroup, + discovery, + error, + lock as lockmod, + obsolete, + phases, + pushkey, + scmutil, + sslutil, + streamclone, + tags, + url as urlmod, + util, +) # Maps bundle compression human names to internal representation. _bundlespeccompressions = {'none': None, @@ -168,6 +187,59 @@ else: raise error.Abort(_('%s: unknown bundle version %s') % (fname, version)) +def getbundlespec(ui, fh): + """Infer the bundlespec from a bundle file handle. + + The input file handle is seeked and the original seek position is not + restored. + """ + def speccompression(alg): + for k, v in _bundlespeccompressions.items(): + if v == alg: + return k + return None + + b = readbundle(ui, fh, None) + if isinstance(b, changegroup.cg1unpacker): + alg = b._type + if alg == '_truncatedBZ': + alg = 'BZ' + comp = speccompression(alg) + if not comp: + raise error.Abort(_('unknown compression algorithm: %s') % alg) + return '%s-v1' % comp + elif isinstance(b, bundle2.unbundle20): + if 'Compression' in b.params: + comp = speccompression(b.params['Compression']) + if not comp: + raise error.Abort(_('unknown compression algorithm: %s') % comp) + else: + comp = 'none' + + version = None + for part in b.iterparts(): + if part.type == 'changegroup': + version = part.params['version'] + if version in ('01', '02'): + version = 'v2' + else: + raise error.Abort(_('changegroup version %s does not have ' + 'a known bundlespec') % version, + hint=_('try upgrading your Mercurial ' + 'client')) + + if not version: + raise error.Abort(_('could not identify changegroup version in ' + 'bundle')) + + return '%s-%s' % (comp, version) + elif isinstance(b, streamclone.streamcloneapplier): + requirements = streamclone.readbundle1header(fh)[2] + params = 'requirements=%s' % ','.join(sorted(requirements)) + return 'none-packed1;%s' % urllib.quote(params) + else: + raise error.Abort(_('unknown bundle type: %s') % b) + def buildobsmarkerspart(bundler, markers): """add an obsmarker part to the bundler with <markers> @@ -571,13 +643,7 @@ elif ctx.troubled(): raise error.Abort(mst[ctx.troubles()[0]] % ctx) - # internal config: bookmarks.pushing - newbm = pushop.ui.configlist('bookmarks', 'pushing') - discovery.checkheads(unfi, pushop.remote, outgoing, - pushop.remoteheads, - pushop.newbranch, - bool(pushop.incoming), - newbm) + discovery.checkheads(pushop) return True # List of names of steps to perform for an outgoing bundle2, order matters. @@ -640,7 +706,8 @@ cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push', pushop.outgoing) else: - cgversions = [v for v in cgversions if v in changegroup.packermap] + cgversions = [v for v in cgversions + if v in changegroup.supportedversions(pushop.repo)] if not cgversions: raise ValueError(_('no common changegroup version')) version = max(cgversions) @@ -1386,10 +1453,14 @@ remoteobs = pullop.remote.listkeys('obsolete') if 'dump0' in remoteobs: tr = pullop.gettransaction() + markers = [] for key in sorted(remoteobs, reverse=True): if key.startswith('dump'): data = base85.b85decode(remoteobs[key]) - pullop.repo.obsstore.mergemarkers(tr, data) + version, newmarks = obsolete._readmarkers(data) + markers += newmarks + if markers: + pullop.repo.obsstore.add(tr, markers) pullop.repo.invalidatevolatilesets() return tr @@ -1427,6 +1498,11 @@ return func return dec +def bundle2requested(bundlecaps): + if bundlecaps is not None: + return any(cap.startswith('HG2') for cap in bundlecaps) + return False + def getbundle(repo, source, heads=None, common=None, bundlecaps=None, **kwargs): """return a full bundle (with potentially multiple kind of parts) @@ -1442,10 +1518,8 @@ The implementation is at a very early stage and will get massive rework when the API of bundle is refined. """ + usebundle2 = bundle2requested(bundlecaps) # bundle10 case - usebundle2 = False - if bundlecaps is not None: - 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')) @@ -1485,7 +1559,8 @@ cgversions = b2caps.get('changegroup') getcgkwargs = {} if cgversions: # 3.1 and 3.2 ship with an empty value - cgversions = [v for v in cgversions if v in changegroup.packermap] + cgversions = [v for v in cgversions + if v in changegroup.supportedversions(repo)] if not cgversions: raise ValueError(_('no common changegroup version')) version = getcgkwargs['version'] = max(cgversions) @@ -1499,6 +1574,8 @@ if version is not None: part.addparam('version', version) part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False) + if 'treemanifest' in repo.requirements: + part.addparam('treemanifest', '1') @getbundle2partsgenerator('listkeys') def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None, @@ -1655,7 +1732,7 @@ repo = pullop.repo remote = pullop.remote - if not repo.ui.configbool('experimental', 'clonebundles', False): + if not repo.ui.configbool('ui', 'clonebundles', True): return # Only run if local repo is empty. @@ -1711,7 +1788,7 @@ hint=_('if this error persists, consider contacting ' 'the server operator or disable clone ' 'bundles via ' - '"--config experimental.clonebundles=false"')) + '"--config ui.clonebundles=false"')) def parseclonebundlesmanifest(repo, s): """Parses the raw text of a clone bundles manifest. @@ -1783,8 +1860,7 @@ return newentries def sortclonebundleentries(ui, entries): - # experimental config: experimental.clonebundleprefers - prefers = ui.configlist('experimental', 'clonebundleprefers', default=[]) + prefers = ui.configlist('ui', 'clonebundleprefers', default=[]) if not prefers: return list(entries)
--- a/mercurial/extensions.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/extensions.py Sun Jan 17 21:40:21 2016 -0600 @@ -24,7 +24,8 @@ _extensions = {} _aftercallbacks = {} _order = [] -_ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify'] +_builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg', + 'inotify']) def extensions(ui=None): if ui: @@ -44,7 +45,7 @@ '''return module with given extension name''' mod = None try: - mod = _extensions[name] + mod = _extensions[name] except KeyError: for k, v in _extensions.iteritems(): if k.endswith('.' + name) or k.endswith('/' + name): @@ -75,7 +76,7 @@ shortname = name[6:] else: shortname = name - if shortname in _ignore: + if shortname in _builtin: return None if shortname in _extensions: return _extensions[shortname] @@ -100,6 +101,17 @@ if ui.debugflag: ui.traceback() mod = importh(name) + + # Before we do anything with the extension, check against minimum stated + # compatibility. This gives extension authors a mechanism to have their + # extensions short circuit when loaded with a known incompatible version + # of Mercurial. + minver = getattr(mod, 'minimumhgversion', None) + if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2): + ui.warn(_('(third party extension %s requires version %s or newer ' + 'of Mercurial; disabling)\n') % (shortname, minver)) + return + _extensions[shortname] = mod _order.append(shortname) for fn in _aftercallbacks.get(shortname, []):
--- a/mercurial/filemerge.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/filemerge.py Sun Jan 17 21:40:21 2016 -0600 @@ -13,11 +13,12 @@ import tempfile from .i18n import _ -from .node import short +from .node import nullid, short from . import ( error, match, + scmutil, simplemerge, tagmerge, templatekw, @@ -43,6 +44,50 @@ mergeonly = 'mergeonly' # just the full merge, no premerge fullmerge = 'fullmerge' # both premerge and merge +class absentfilectx(object): + """Represents a file that's ostensibly in a context but is actually not + present in it. + + This is here because it's very specific to the filemerge code for now -- + other code is likely going to break with the values this returns.""" + def __init__(self, ctx, f): + self._ctx = ctx + self._f = f + + def path(self): + return self._f + + def size(self): + return None + + def data(self): + return None + + def filenode(self): + return nullid + + _customcmp = True + def cmp(self, fctx): + """compare with other file context + + returns True if different from fctx. + """ + return not (fctx.isabsent() and + fctx.ctx() == self.ctx() and + fctx.path() == self.path()) + + def flags(self): + return '' + + def changectx(self): + return self._ctx + + def isbinary(self): + return False + + def isabsent(self): + return True + def internaltool(name, mergetype, onfailure=None, precheck=None): '''return a decorator for populating internal merge tool table''' def decorator(func): @@ -75,8 +120,11 @@ exe = _toolstr(ui, tool, "executable", tool) return util.findexe(util.expandpath(exe)) -def _picktool(repo, ui, path, binary, symlink): - def check(tool, pat, symlink, binary): +def _picktool(repo, ui, path, binary, symlink, changedelete): + def supportscd(tool): + return tool in internals and internals[tool].mergetype == nomerge + + def check(tool, pat, symlink, binary, changedelete): tmsg = tool if pat: tmsg += " specified for " + pat @@ -89,6 +137,10 @@ ui.warn(_("tool %s can't handle symlinks\n") % tmsg) elif binary and not _toolbool(ui, tool, "binary"): ui.warn(_("tool %s can't handle binary\n") % tmsg) + elif changedelete and not supportscd(tool): + # the nomerge tools are the only tools that support change/delete + # conflicts + pass elif not util.gui() and _toolbool(ui, tool, "gui"): ui.warn(_("tool %s requires a GUI\n") % tmsg) else: @@ -100,21 +152,27 @@ force = ui.config('ui', 'forcemerge') if force: toolpath = _findtool(ui, force) - if toolpath: - return (force, util.shellquote(toolpath)) + if changedelete and not supportscd(toolpath): + return ":prompt", None else: - # mimic HGMERGE if given tool not found - return (force, force) + if toolpath: + return (force, util.shellquote(toolpath)) + else: + # mimic HGMERGE if given tool not found + return (force, force) # HGMERGE takes next precedence hgmerge = os.environ.get("HGMERGE") if hgmerge: - return (hgmerge, hgmerge) + if changedelete and not supportscd(hgmerge): + return ":prompt", None + else: + return (hgmerge, hgmerge) # then patterns for pat, tool in ui.configitems("merge-patterns"): mf = match.match(repo.root, '', [pat]) - if mf(path) and check(tool, pat, symlink, False): + if mf(path) and check(tool, pat, symlink, False, changedelete): toolpath = _findtool(ui, tool) return (tool, util.shellquote(toolpath)) @@ -131,17 +189,19 @@ tools = sorted([(-p, t) for t, p in tools.items() if t not in disabled]) uimerge = ui.config("ui", "merge") if uimerge: - if uimerge not in names: + # external tools defined in uimerge won't be able to handle + # change/delete conflicts + if uimerge not in names and not changedelete: return (uimerge, uimerge) tools.insert(0, (None, uimerge)) # highest priority tools.append((None, "hgmerge")) # the old default, if found for p, t in tools: - if check(t, None, symlink, binary): + if check(t, None, symlink, binary, changedelete): toolpath = _findtool(ui, t) return (t, util.shellquote(toolpath)) # internal merge or prompt as last resort - if symlink or binary: + if symlink or binary or changedelete: return ":prompt", None return ":merge", None @@ -175,23 +235,53 @@ ui = repo.ui fd = fcd.path() - if ui.promptchoice(_(" no tool found to merge %s\n" - "keep (l)ocal or take (o)ther?" - "$$ &Local $$ &Other") % fd, 0): - return _iother(repo, mynode, orig, fcd, fco, fca, toolconf) - else: - return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf) + try: + if fco.isabsent(): + index = ui.promptchoice( + _("local changed %s which remote deleted\n" + "use (c)hanged version, (d)elete, or leave (u)nresolved?" + "$$ &Changed $$ &Delete $$ &Unresolved") % fd, 2) + choice = ['local', 'other', 'unresolved'][index] + elif fcd.isabsent(): + index = ui.promptchoice( + _("remote changed %s which local deleted\n" + "use (c)hanged version, leave (d)eleted, or " + "leave (u)nresolved?" + "$$ &Changed $$ &Deleted $$ &Unresolved") % fd, 2) + choice = ['other', 'local', 'unresolved'][index] + else: + index = ui.promptchoice( + _("no tool found to merge %s\n" + "keep (l)ocal, take (o)ther, or leave (u)nresolved?" + "$$ &Local $$ &Other $$ &Unresolved") % fd, 2) + choice = ['local', 'other', 'unresolved'][index] + + if choice == 'other': + return _iother(repo, mynode, orig, fcd, fco, fca, toolconf) + elif choice == 'local': + return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf) + elif choice == 'unresolved': + return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf) + except error.ResponseExpected: + ui.write("\n") + return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf) @internaltool('local', nomerge) def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf): """Uses the local version of files as the merged version.""" - return 0 + return 0, fcd.isabsent() @internaltool('other', nomerge) def _iother(repo, mynode, orig, fcd, fco, fca, toolconf): """Uses the other version of files as the merged version.""" - repo.wwrite(fcd.path(), fco.data(), fco.flags()) - return 0 + if fco.isabsent(): + # local changed, remote deleted -- 'deleted' picked + repo.wvfs.unlinkpath(fcd.path()) + deleted = True + else: + repo.wwrite(fcd.path(), fco.data(), fco.flags()) + deleted = False + return 0, deleted @internaltool('fail', nomerge) def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf): @@ -199,11 +289,14 @@ Rather than attempting to merge files that were modified on both branches, it marks them as unresolved. The resolve command must be used to resolve these conflicts.""" - return 1 + # for change/delete conflicts write out the changed version, then fail + if fcd.isabsent(): + repo.wwrite(fcd.path(), fco.data(), fco.flags()) + return 1, False -def _premerge(repo, toolconf, files, labels=None): +def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None): tool, toolpath, binary, symlink = toolconf - if symlink: + if symlink or fcd.isabsent() or fco.isabsent(): return 1 a, b, c, back = files @@ -236,12 +329,16 @@ util.copyfile(back, a) # restore from backup and try again return 1 # continue merging -def _symlinkcheck(repo, mynode, orig, fcd, fco, fca, toolconf): +def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf): tool, toolpath, binary, symlink = toolconf if symlink: repo.ui.warn(_('warning: internal %s cannot merge symlinks ' 'for %s\n') % (tool, fcd.path())) return False + if fcd.isabsent() or fco.isabsent(): + repo.ui.warn(_('warning: internal %s cannot merge change/delete ' + 'conflict for %s\n') % (tool, fcd.path())) + return False return True def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode): @@ -255,12 +352,12 @@ ui = repo.ui r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode) - return True, r + return True, r, False @internaltool('union', fullmerge, _("warning: conflicts while merging %s! " "(edit, then use 'hg resolve --mark')\n"), - precheck=_symlinkcheck) + precheck=_mergecheck) def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): """ Uses the internal non-interactive simple merge algorithm for merging @@ -272,7 +369,7 @@ @internaltool('merge', fullmerge, _("warning: conflicts while merging %s! " "(edit, then use 'hg resolve --mark')\n"), - precheck=_symlinkcheck) + precheck=_mergecheck) def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): """ Uses the internal non-interactive simple merge algorithm for merging @@ -285,7 +382,7 @@ @internaltool('merge3', fullmerge, _("warning: conflicts while merging %s! " "(edit, then use 'hg resolve --mark')\n"), - precheck=_symlinkcheck) + precheck=_mergecheck) def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): """ Uses the internal non-interactive simple merge algorithm for merging @@ -305,30 +402,26 @@ """ assert localorother is not None tool, toolpath, binary, symlink = toolconf - if symlink: - repo.ui.warn(_('warning: :merge-%s cannot merge symlinks ' - 'for %s\n') % (localorother, fcd.path())) - return False, 1 a, b, c, back = files r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels, localorother=localorother) return True, r -@internaltool('merge-local', mergeonly) +@internaltool('merge-local', mergeonly, precheck=_mergecheck) def _imergelocal(*args, **kwargs): """ Like :merge, but resolve all conflicts non-interactively in favor of the local changes.""" success, status = _imergeauto(localorother='local', *args, **kwargs) - return success, status + return success, status, False -@internaltool('merge-other', mergeonly) +@internaltool('merge-other', mergeonly, precheck=_mergecheck) def _imergeother(*args, **kwargs): """ Like :merge, but resolve all conflicts non-interactively in favor of the other changes.""" success, status = _imergeauto(localorother='other', *args, **kwargs) - return success, status + return success, status, False @internaltool('tagmerge', mergeonly, _("automatic tag merging of %s failed! " @@ -338,7 +431,8 @@ """ Uses the internal tag merge algorithm (experimental). """ - return tagmerge.merge(repo, fcd, fco, fca) + success, status = tagmerge.merge(repo, fcd, fco, fca) + return success, status, False @internaltool('dump', fullmerge) def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): @@ -356,10 +450,14 @@ util.copyfile(a, a + ".local") repo.wwrite(fd + ".other", fco.data(), fco.flags()) repo.wwrite(fd + ".base", fca.data(), fca.flags()) - return False, 1 + return False, 1, False def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): tool, toolpath, binary, symlink = toolconf + if fcd.isabsent() or fco.isabsent(): + repo.ui.warn(_('warning: %s cannot merge change/delete conflict ' + 'for %s\n') % (tool, fcd.path())) + return False, 1, None a, b, c, back = files out = "" env = {'HG_FILE': fcd.path(), @@ -383,7 +481,7 @@ repo.ui.debug('launching merge tool: %s\n' % cmd) r = ui.system(cmd, cwd=repo.root, environ=env) repo.ui.debug('merge tool returned: %s\n' % r) - return True, r + return True, r, False def _formatconflictmarker(repo, ctx, template, label, pad): """Applies the given template to the ctx, prefixed by the label. @@ -448,8 +546,8 @@ fca = ancestor file context fcd = local file context for current/destination file - Returns whether the merge is complete, and the return value of the merge. - """ + Returns whether the merge is complete, the return value of the merge, and + a boolean indicating whether the file was deleted from disk.""" def temp(prefix, ctx): pre = "%s~%s." % (os.path.basename(ctx.path()), prefix) @@ -461,18 +559,19 @@ return name if not fco.cmp(fcd): # files identical? - return True, None + return True, None, False ui = repo.ui fd = fcd.path() binary = fcd.isbinary() or fco.isbinary() or fca.isbinary() symlink = 'l' in fcd.flags() + fco.flags() - tool, toolpath = _picktool(repo, ui, fd, binary, symlink) + changedelete = fcd.isabsent() or fco.isabsent() + tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete) if tool in internals and tool.startswith('internal:'): # normalize to new-style names (':merge' etc) tool = tool[len('internal'):] - ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" % - (tool, fd, binary, symlink)) + ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n" + % (tool, fd, binary, symlink, changedelete)) if tool in internals: func = internals[tool] @@ -488,7 +587,8 @@ toolconf = tool, toolpath, binary, symlink if mergetype == nomerge: - return True, func(repo, mynode, orig, fcd, fco, fca, toolconf) + r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf) + return True, r, deleted if premerge: if orig != fco.path(): @@ -502,14 +602,17 @@ toolconf): if onfailure: ui.warn(onfailure % fd) - return True, 1 + return True, 1, False a = repo.wjoin(fd) b = temp("base", fca) c = temp("other", fco) - back = a + ".orig" - if premerge: - util.copyfile(a, back) + if not fcd.isabsent(): + back = scmutil.origpath(ui, repo, a) + if premerge: + util.copyfile(a, back) + else: + back = None files = (a, b, c, back) r = 1 @@ -521,12 +624,13 @@ labels = _formatlabels(repo, fcd, fco, fca, labels) if premerge and mergetype == fullmerge: - r = _premerge(repo, toolconf, files, labels=labels) + r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels) # complete if premerge successful (r is 0) - return not r, r + return not r, r, False - needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf, files, - labels=labels) + needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca, + toolconf, files, labels=labels) + if needcheck: r = _check(r, ui, tool, fcd, files) @@ -534,9 +638,9 @@ if onfailure: ui.warn(onfailure % fd) - return True, r + return True, r, deleted finally: - if not r: + if not r and back is not None: util.unlink(back) util.unlink(b) util.unlink(c) @@ -561,13 +665,13 @@ if not r and not checked and (_toolbool(ui, tool, "checkchanged") or 'changed' in _toollist(ui, tool, "check")): - if filecmp.cmp(a, back): + if back is not None and filecmp.cmp(a, back): if ui.promptchoice(_(" output file %s appears unchanged\n" "was merge successful (yn)?" "$$ &Yes $$ &No") % fd, 1): r = 1 - if _toolbool(ui, tool, "fixeol"): + if back is not None and _toolbool(ui, tool, "fixeol"): _matcheol(a, back) return r
--- a/mercurial/fileset.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/fileset.py Sun Jan 17 21:40:21 2016 -0600 @@ -128,47 +128,99 @@ return [f for f in xl if f not in yl] def listset(mctx, a, b): - raise error.ParseError(_("can't use a list in this context")) + raise error.ParseError(_("can't use a list in this context"), + hint=_('see hg help "filesets.x or y"')) + +# symbols are callable like: +# fun(mctx, x) +# with: +# mctx - current matchctx instance +# x - argument in tree form +symbols = {} + +# filesets using matchctx.status() +_statuscallers = set() + +# filesets using matchctx.existing() +_existingcallers = set() + +def predicate(decl, callstatus=False, callexisting=False): + """Return a decorator for fileset predicate function + + 'decl' argument is the declaration (including argument list like + 'adds(pattern)') or the name (for internal use only) of predicate. + Optional 'callstatus' argument indicates whether predicate implies + 'matchctx.status()' at runtime or not (False, by default). + + Optional 'callexisting' argument indicates whether predicate + implies 'matchctx.existing()' at runtime or not (False, by + default). + """ + def decorator(func): + i = decl.find('(') + if i > 0: + name = decl[:i] + else: + name = decl + symbols[name] = func + if callstatus: + _statuscallers.add(name) + if callexisting: + _existingcallers.add(name) + if func.__doc__: + func.__doc__ = "``%s``\n %s" % (decl, func.__doc__.strip()) + return func + return decorator + +@predicate('modified()', callstatus=True) def modified(mctx, x): - """``modified()`` - File that is modified according to :hg:`status`. + """File that is modified according to :hg:`status`. """ # i18n: "modified" is a keyword getargs(x, 0, 0, _("modified takes no arguments")) s = mctx.status().modified return [f for f in mctx.subset if f in s] +@predicate('added()', callstatus=True) def added(mctx, x): - """``added()`` - File that is added according to :hg:`status`. + """File that is added according to :hg:`status`. """ # i18n: "added" is a keyword getargs(x, 0, 0, _("added takes no arguments")) s = mctx.status().added return [f for f in mctx.subset if f in s] +@predicate('removed()', callstatus=True) def removed(mctx, x): - """``removed()`` - File that is removed according to :hg:`status`. + """File that is removed according to :hg:`status`. """ # i18n: "removed" is a keyword getargs(x, 0, 0, _("removed takes no arguments")) s = mctx.status().removed return [f for f in mctx.subset if f in s] +@predicate('deleted()', callstatus=True) def deleted(mctx, x): - """``deleted()`` - File that is deleted according to :hg:`status`. + """Alias for ``missing()``. """ # i18n: "deleted" is a keyword getargs(x, 0, 0, _("deleted takes no arguments")) s = mctx.status().deleted return [f for f in mctx.subset if f in s] +@predicate('missing()', callstatus=True) +def missing(mctx, x): + """File that is missing according to :hg:`status`. + """ + # i18n: "missing" is a keyword + getargs(x, 0, 0, _("missing takes no arguments")) + s = mctx.status().deleted + return [f for f in mctx.subset if f in s] + +@predicate('unknown()', callstatus=True) def unknown(mctx, x): - """``unknown()`` - File that is unknown according to :hg:`status`. These files will only be + """File that is unknown according to :hg:`status`. These files will only be considered if this predicate is used. """ # i18n: "unknown" is a keyword @@ -176,9 +228,9 @@ s = mctx.status().unknown return [f for f in mctx.subset if f in s] +@predicate('ignored()', callstatus=True) def ignored(mctx, x): - """``ignored()`` - File that is ignored according to :hg:`status`. These files will only be + """File that is ignored according to :hg:`status`. These files will only be considered if this predicate is used. """ # i18n: "ignored" is a keyword @@ -186,9 +238,9 @@ s = mctx.status().ignored return [f for f in mctx.subset if f in s] +@predicate('clean()', callstatus=True) def clean(mctx, x): - """``clean()`` - File that is clean according to :hg:`status`. + """File that is clean according to :hg:`status`. """ # i18n: "clean" is a keyword getargs(x, 0, 0, _("clean takes no arguments")) @@ -197,7 +249,13 @@ def func(mctx, a, b): if a[0] == 'symbol' and a[1] in symbols: - return symbols[a[1]](mctx, b) + funcname = a[1] + enabled = mctx._existingenabled + mctx._existingenabled = funcname in _existingcallers + try: + return symbols[funcname](mctx, b) + finally: + mctx._existingenabled = enabled keep = lambda fn: getattr(fn, '__doc__', None) is not None @@ -217,64 +275,64 @@ raise error.ParseError(err) return l +@predicate('binary()', callexisting=True) def binary(mctx, x): - """``binary()`` - File that appears to be binary (contains NUL bytes). + """File that appears to be binary (contains NUL bytes). """ # i18n: "binary" is a keyword getargs(x, 0, 0, _("binary takes no arguments")) return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())] +@predicate('exec()', callexisting=True) def exec_(mctx, x): - """``exec()`` - File that is marked as executable. + """File that is marked as executable. """ # i18n: "exec" is a keyword getargs(x, 0, 0, _("exec takes no arguments")) return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x'] +@predicate('symlink()', callexisting=True) def symlink(mctx, x): - """``symlink()`` - File that is marked as a symlink. + """File that is marked as a symlink. """ # i18n: "symlink" is a keyword getargs(x, 0, 0, _("symlink takes no arguments")) return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l'] +@predicate('resolved()') def resolved(mctx, x): - """``resolved()`` - File that is marked resolved according to :hg:`resolve -l`. + """File that is marked resolved according to :hg:`resolve -l`. """ # i18n: "resolved" is a keyword getargs(x, 0, 0, _("resolved takes no arguments")) if mctx.ctx.rev() is not None: return [] - ms = merge.mergestate(mctx.ctx.repo()) + ms = merge.mergestate.read(mctx.ctx.repo()) return [f for f in mctx.subset if f in ms and ms[f] == 'r'] +@predicate('unresolved()') def unresolved(mctx, x): - """``unresolved()`` - File that is marked unresolved according to :hg:`resolve -l`. + """File that is marked unresolved according to :hg:`resolve -l`. """ # i18n: "unresolved" is a keyword getargs(x, 0, 0, _("unresolved takes no arguments")) if mctx.ctx.rev() is not None: return [] - ms = merge.mergestate(mctx.ctx.repo()) + ms = merge.mergestate.read(mctx.ctx.repo()) return [f for f in mctx.subset if f in ms and ms[f] == 'u'] +@predicate('hgignore()') def hgignore(mctx, x): - """``hgignore()`` - File that matches the active .hgignore pattern. + """File that matches the active .hgignore pattern. """ # i18n: "hgignore" is a keyword getargs(x, 0, 0, _("hgignore takes no arguments")) ignore = mctx.ctx.repo().dirstate._ignore return [f for f in mctx.subset if ignore(f)] +@predicate('portable()') def portable(mctx, x): - """``portable()`` - File that has a portable name. (This doesn't include filenames with case + """File that has a portable name. (This doesn't include filenames with case collisions.) """ # i18n: "portable" is a keyword @@ -282,9 +340,9 @@ checkwinfilename = util.checkwinfilename return [f for f in mctx.subset if checkwinfilename(f) is None] +@predicate('grep(regex)', callexisting=True) def grep(mctx, x): - """``grep(regex)`` - File contains the given regular expression. + """File contains the given regular expression. """ try: # i18n: "grep" is a keyword @@ -309,9 +367,9 @@ except ValueError: raise error.ParseError(_("couldn't parse size: %s") % s) +@predicate('size(expression)', callexisting=True) def size(mctx, x): - """``size(expression)`` - File size matches the given expression. Examples: + """File size matches the given expression. Examples: - 1k (files from 1024 to 2047 bytes) - < 20k (files less than 20480 bytes) @@ -347,9 +405,9 @@ return [f for f in mctx.existing() if m(mctx.ctx[f].size())] +@predicate('encoding(name)', callexisting=True) def encoding(mctx, x): - """``encoding(name)`` - File can be successfully decoded with the given character + """File can be successfully decoded with the given character encoding. May not be useful for encodings other than ASCII and UTF-8. """ @@ -370,9 +428,9 @@ return s +@predicate('eol(style)', callexisting=True) def eol(mctx, x): - """``eol(style)`` - File contains newlines of the given style (dos, unix, mac). Binary + """File contains newlines of the given style (dos, unix, mac). Binary files are excluded, files with mixed line endings match multiple styles. """ @@ -393,9 +451,9 @@ s.append(f) return s +@predicate('copied()') def copied(mctx, x): - """``copied()`` - File that is recorded as being copied. + """File that is recorded as being copied. """ # i18n: "copied" is a keyword getargs(x, 0, 0, _("copied takes no arguments")) @@ -406,9 +464,9 @@ s.append(f) return s +@predicate('subrepo([pattern])') def subrepo(mctx, x): - """``subrepo([pattern])`` - Subrepositories whose paths match the given pattern. + """Subrepositories whose paths match the given pattern. """ # i18n: "subrepo" is a keyword getargs(x, 0, 1, _("subrepo takes at most one argument")) @@ -429,29 +487,6 @@ else: return [sub for sub in sstate] -symbols = { - 'added': added, - 'binary': binary, - 'clean': clean, - 'copied': copied, - 'deleted': deleted, - 'encoding': encoding, - 'eol': eol, - 'exec': exec_, - 'grep': grep, - 'ignored': ignored, - 'hgignore': hgignore, - 'modified': modified, - 'portable': portable, - 'removed': removed, - 'resolved': resolved, - 'size': size, - 'symlink': symlink, - 'unknown': unknown, - 'unresolved': unresolved, - 'subrepo': subrepo, -} - methods = { 'string': stringset, 'symbol': stringset, @@ -469,6 +504,7 @@ self.ctx = ctx self.subset = subset self._status = status + self._existingenabled = False def status(self): return self._status def matcher(self, patterns): @@ -476,6 +512,7 @@ def filter(self, files): return [f for f in files if f in self.subset] def existing(self): + assert self._existingenabled, 'unexpected existing() invocation' if self._status is not None: removed = set(self._status[3]) unknown = set(self._status[4] + self._status[5]) @@ -497,21 +534,11 @@ return True return False -# filesets using matchctx.existing() -_existingcallers = [ - 'binary', - 'exec', - 'grep', - 'size', - 'symlink', -] - def getfileset(ctx, expr): tree = parse(expr) # do we need status info? - if (_intree(['modified', 'added', 'removed', 'deleted', - 'unknown', 'ignored', 'clean'], tree) or + if (_intree(_statuscallers, tree) or # Using matchctx.existing() on a workingctx requires us to check # for deleted files. (ctx.rev() is None and _intree(_existingcallers, tree))):
--- a/mercurial/hbisect.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hbisect.py Sun Jan 17 21:40:21 2016 -0600 @@ -11,7 +11,6 @@ from __future__ import absolute_import import collections -import os from .i18n import _ from .node import ( @@ -143,26 +142,22 @@ def load_state(repo): state = {'current': [], 'good': [], 'bad': [], 'skip': []} - if os.path.exists(repo.join("bisect.state")): - for l in repo.vfs("bisect.state"): - kind, node = l[:-1].split() - node = repo.lookup(node) - if kind not in state: - raise error.Abort(_("unknown bisect kind %s") % kind) - state[kind].append(node) + for l in repo.vfs.tryreadlines("bisect.state"): + kind, node = l[:-1].split() + node = repo.lookup(node) + if kind not in state: + raise error.Abort(_("unknown bisect kind %s") % kind) + state[kind].append(node) return state def save_state(repo, state): f = repo.vfs("bisect.state", "w", atomictemp=True) - wlock = repo.wlock() - try: + with repo.wlock(): for kind in sorted(state): for node in state[kind]: f.write("%s %s\n" % (kind, hex(node))) f.close() - finally: - wlock.release() def get(repo, status): """
--- a/mercurial/help.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/help.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,14 +5,33 @@ # 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 gettext, _ -import itertools, os, textwrap -import error -import extensions, revset, fileset, templatekw, templatefilters, filemerge -import templater -import encoding, util, minirst -import cmdutil -import hgweb.webcommands as webcommands +from __future__ import absolute_import + +import itertools +import os +import textwrap + +from .i18n import ( + _, + gettext, +) +from . import ( + cmdutil, + encoding, + error, + extensions, + filemerge, + fileset, + minirst, + revset, + templatefilters, + templatekw, + templater, + util, +) +from .hgweb import ( + webcommands, +) _exclkeywords = [ "(DEPRECATED)", @@ -27,11 +46,12 @@ '''return a text listing of the given extensions''' rst = [] if exts: - rst.append('\n%s\n\n' % header) for name, desc in sorted(exts.iteritems()): if not showdeprecated and any(w in desc for w in _exclkeywords): continue rst.append('%s:%s: %s\n' % (' ' * indent, name, desc)) + if rst: + rst.insert(0, '\n%s\n\n' % header) return rst def extshelp(ui): @@ -83,6 +103,13 @@ if notomitted: rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted) +def filtercmd(ui, cmd, kw, doc): + if not ui.debugflag and cmd.startswith("debug") and kw != "debug": + return True + if not ui.verbose and doc and any(w in doc for w in _exclkeywords): + return True + return False + def topicmatch(ui, kw): """Return help topics matching kw. @@ -103,7 +130,7 @@ or lowercontains(header) or (callable(doc) and lowercontains(doc(ui)))): results['topics'].append((names[0], header)) - import commands # avoid cycle + from . import commands # avoid cycle for cmd, entry in commands.table.iteritems(): if len(entry) == 3: summary = entry[2] @@ -115,32 +142,37 @@ doclines = docs.splitlines() if doclines: summary = doclines[0] - cmdname = cmd.split('|')[0].lstrip('^') + cmdname = cmd.partition('|')[0].lstrip('^') + if filtercmd(ui, cmdname, kw, docs): + continue results['commands'].append((cmdname, summary)) for name, docs in itertools.chain( extensions.enabled(False).iteritems(), extensions.disabled().iteritems()): - # extensions.load ignores the UI argument - mod = extensions.load(None, name, '') - name = name.split('.')[-1] + mod = extensions.load(ui, name, '') + name = name.rpartition('.')[-1] if lowercontains(name) or lowercontains(docs): # extension docs are already translated results['extensions'].append((name, docs.splitlines()[0])) for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems(): if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])): - cmdname = cmd.split('|')[0].lstrip('^') + cmdname = cmd.partition('|')[0].lstrip('^') if entry[0].__doc__: cmddoc = gettext(entry[0].__doc__).splitlines()[0] else: cmddoc = _('(no help text available)') + if filtercmd(ui, cmdname, kw, cmddoc): + continue results['extensioncommands'].append((cmdname, cmddoc)) return results -def loaddoc(topic): +def loaddoc(topic, subdir=None): """Return a delayed loader for help/topic.txt.""" def loader(ui): docdir = os.path.join(util.datapath, 'help') + if subdir: + docdir = os.path.join(docdir, subdir) path = os.path.join(docdir, topic + ".txt") doc = gettext(util.readfile(path)) for rewriter in helphooks.get(topic, []): @@ -149,6 +181,23 @@ return loader +internalstable = sorted([ + (['bundles'], _('container for exchange of repository data'), + loaddoc('bundles', subdir='internals')), + (['changegroups'], _('representation of revlog data'), + loaddoc('changegroups', subdir='internals')), + (['revlogs'], _('revision storage mechanism'), + loaddoc('revlogs', subdir='internals')), +]) + +def internalshelp(ui): + """Generate the index for the "internals" topic.""" + lines = [] + for names, header, doc in internalstable: + lines.append(' :%s: %s\n' % (names[0], header)) + + return ''.join(lines) + helptable = sorted([ (["config", "hgrc"], _("Configuration Files"), loaddoc('config')), (["dates"], _("Date Formats"), loaddoc('dates')), @@ -175,8 +224,15 @@ (["phases"], _("Working with Phases"), loaddoc('phases')), (['scripting'], _('Using Mercurial from scripts and automation'), loaddoc('scripting')), + (['internals'], _("Technical implementation topics"), + internalshelp), ]) +# Maps topics with sub-topics to a list of their sub-topics. +subtopics = { + 'internals': internalstable, +} + # Map topics to lists of callable taking the current topic help and # returning the updated version helphooks = {} @@ -226,15 +282,15 @@ addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands, dedent=True) -def help_(ui, name, unknowncmd=False, full=True, **opts): +def help_(ui, name, unknowncmd=False, full=True, subtopic=None, **opts): ''' Generate the help for 'name' as unformatted restructured text. If 'name' is None, describe the commands available. ''' - import commands # avoid cycle + from . import commands # avoid cycle - def helpcmd(name): + def helpcmd(name, subtopic=None): try: aliases, entry = cmdutil.findcmd(name, commands.table, strict=unknowncmd) @@ -318,7 +374,7 @@ return rst - def helplist(select=None): + def helplist(select=None, **opts): # list of commands if name == "shortlist": header = _('basic commands:\n\n') @@ -330,7 +386,7 @@ h = {} cmds = {} for c, e in commands.table.iteritems(): - f = c.split("|", 1)[0] + f = c.partition("|")[0] if select and not select(f): continue if (not select and name != 'shortlist' and @@ -339,10 +395,8 @@ if name == "shortlist" and not f.startswith("^"): continue f = f.lstrip("^") - if not ui.debugflag and f.startswith("debug") and name != "debug": - continue doc = e[0].__doc__ - if not ui.verbose and doc and any(w in doc for w in _exclkeywords): + if filtercmd(ui, f, name, doc): continue doc = gettext(doc) if not doc: @@ -366,7 +420,9 @@ else: rst.append(' :%s: %s\n' % (f, h[f])) - if not name: + ex = opts.get + anyopts = (ex('keyword') or not (ex('command') or ex('extension'))) + if not name and anyopts: exts = listexts(_('enabled extensions:'), extensions.enabled()) if exts: rst.append('\n') @@ -403,12 +459,20 @@ % (name and " " + name or "")) return rst - def helptopic(name): - for names, header, doc in helptable: - if name in names: - break - else: - raise error.UnknownCommand(name) + def helptopic(name, subtopic=None): + # Look for sub-topic entry first. + header, doc = None, None + if subtopic and name in subtopics: + for names, header, doc in subtopics[name]: + if subtopic in names: + break + + if not header: + for names, header, doc in helptable: + if name in names: + break + else: + raise error.UnknownCommand(name) rst = [minirst.section(header)] @@ -431,7 +495,7 @@ pass return rst - def helpext(name): + def helpext(name, subtopic=None): try: mod = extensions.find(name) doc = gettext(mod.__doc__) or _('no help text available') @@ -445,7 +509,7 @@ head, tail = doc, "" else: head, tail = doc.split('\n', 1) - rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)] + rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)] if tail: rst.extend(tail.splitlines(True)) rst.append('\n') @@ -460,20 +524,21 @@ ct = mod.cmdtable except AttributeError: ct = {} - modcmds = set([c.split('|', 1)[0] for c in ct]) + modcmds = set([c.partition('|')[0] for c in ct]) rst.extend(helplist(modcmds.__contains__)) else: rst.append(_('(use "hg help extensions" for information on enabling' ' extensions)\n')) return rst - def helpextcmd(name): + def helpextcmd(name, subtopic=None): cmd, ext, mod = extensions.disabledcmd(ui, name, ui.configbool('ui', 'strict')) doc = gettext(mod.__doc__).splitlines()[0] rst = listexts(_("'%s' is provided by the following " - "extension:") % cmd, {ext: doc}, indent=4) + "extension:") % cmd, {ext: doc}, indent=4, + showdeprecated=True) rst.append('\n') rst.append(_('(use "hg help extensions" for information on enabling ' 'extensions)\n')) @@ -482,8 +547,8 @@ rst = [] kw = opts.get('keyword') - if kw: - matches = topicmatch(ui, name) + if kw or name is None and any(opts[o] for o in opts): + matches = topicmatch(ui, name or '') helpareas = [] if opts.get('extension'): helpareas += [('extensions', _('Extensions'))] @@ -515,7 +580,7 @@ queries = (helptopic, helpcmd, helpext, helpextcmd) for f in queries: try: - rst = f(name) + rst = f(name, subtopic) break except error.UnknownCommand: pass @@ -530,6 +595,6 @@ # program name if not ui.quiet: rst = [_("Mercurial Distributed SCM\n"), '\n'] - rst.extend(helplist()) + rst.extend(helplist(None, **opts)) return ''.join(rst)
--- a/mercurial/help/config.txt Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/help/config.txt Sun Jan 17 21:40:21 2016 -0600 @@ -11,8 +11,8 @@ See :hg:`help config.syntax` and :hg:`help config.files` for information about how and where to override things. -Format -====== +Structure +========= The configuration files use a simple ini-file format. A configuration file consists of sections, led by a ``[section]`` header and followed @@ -30,10 +30,19 @@ Mercurial reads configuration data from several files, if they exist. These files do not exist by default and you will have to create the -appropriate configuration files yourself: global configuration like -the username setting is typically put into -``%USERPROFILE%\mercurial.ini`` or ``$HOME/.hgrc`` and local -configuration is put into the per-repository ``<repo>/.hg/hgrc`` file. +appropriate configuration files yourself: + +Local configuration is put into the per-repository ``<repo>/.hg/hgrc`` file. + +Global configuration like the username setting is typically put into: + +.. container:: windows + + ``%USERPROFILE%\mercurial.ini`` + +.. container:: unix.plan9 + + ``$HOME/.hgrc`` The names of these files depend on the system on which Mercurial is installed. ``*.rc`` files from a single directory are read in @@ -72,6 +81,10 @@ The registry key ``HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mercurial`` is used when running 32-bit Python on 64-bit Windows. +.. container:: windows + + On Windows 9x, ``%HOME%`` is replaced by ``%APPDATA%``. + .. container:: verbose.plan9 On Plan9, the following files are consulted: @@ -87,23 +100,29 @@ Per-repository configuration options only apply in a particular repository. This file is not version-controlled, and will not get transferred during a "clone" operation. Options in -this file override options in all other configuration files. On -Plan 9 and Unix, most of this file will be ignored if it doesn't -belong to a trusted user or to a trusted group. See -:hg:`help config.trusted` for more details. - -Per-user configuration file(s) are for the user running Mercurial. On -Windows 9x, ``%HOME%`` is replaced by ``%APPDATA%``. Options in these -files apply to all Mercurial commands executed by this user in any +this file override options in all other configuration files. + +.. container:: unix.plan9 + + On Plan 9 and Unix, most of this file will be ignored if it doesn't + belong to a trusted user or to a trusted group. See + :hg:`help config.trusted` for more details. + +Per-user configuration file(s) are for the user running Mercurial. Options +in these files apply to all Mercurial commands executed by this user in any directory. Options in these files override per-system and per-installation options. Per-installation configuration files are searched for in the directory where Mercurial is installed. ``<install-root>`` is the -parent directory of the **hg** executable (or symlink) being run. For -example, if installed in ``/shared/tools/bin/hg``, Mercurial will look -in ``/shared/tools/etc/mercurial/hgrc``. Options in these files apply -to all Mercurial commands executed by any user in any directory. +parent directory of the **hg** executable (or symlink) being run. + +.. container:: unix.plan9 + + For example, if installed in ``/shared/tools/bin/hg``, Mercurial + will look in ``/shared/tools/etc/mercurial/hgrc``. Options in these + files apply to all Mercurial commands executed by any user in any + directory. Per-installation configuration files are for the system on which Mercurial is running. Options in these files apply to all @@ -516,11 +535,13 @@ of an empty temporary file, where the filtered data must be written by the command. -.. note:: - - The tempfile mechanism is recommended for Windows systems, - where the standard shell I/O redirection operators often have - strange effects and may corrupt the contents of your files. +.. container:: windows + + .. note:: + + The tempfile mechanism is recommended for Windows systems, + where the standard shell I/O redirection operators often have + strange effects and may corrupt the contents of your files. This filter mechanism is used internally by the ``eol`` extension to translate line ending characters between Windows (CRLF) and Unix (LF) @@ -666,29 +687,45 @@ ``format`` ---------- -``usestore`` - Enable or disable the "store" repository format which improves - compatibility with systems that fold case or otherwise mangle - filenames. Enabled by default. Disabling this option will allow - you to store longer filenames in some situations at the expense of - compatibility and ensures that the on-disk format of newly created - repositories will be compatible with Mercurial before version 0.9.4. +``usegeneraldelta`` + Enable or disable the "generaldelta" repository format which improves + repository compression by allowing "revlog" to store delta against arbitrary + revision instead of the previous stored one. This provides significant + improvement for repositories with branches. + + Repositories with this on-disk format require Mercurial version 1.9. + + Enabled by default. + +``dotencode`` + Enable or disable the "dotencode" repository format which enhances + the "fncache" repository format (which has to be enabled to use + dotencode) to avoid issues with filenames starting with ._ on + Mac OS X and spaces on Windows. + + Repositories with this on-disk format require Mercurial version 1.7. + + Enabled by default. ``usefncache`` Enable or disable the "fncache" repository format which enhances the "store" repository format (which has to be enabled to use fncache) to allow longer filenames and avoids using Windows - reserved names, e.g. "nul". Enabled by default. Disabling this - option ensures that the on-disk format of newly created - repositories will be compatible with Mercurial before version 1.1. - -``dotencode`` - Enable or disable the "dotencode" repository format which enhances - the "fncache" repository format (which has to be enabled to use - dotencode) to avoid issues with filenames starting with ._ on - Mac OS X and spaces on Windows. Enabled by default. Disabling this - option ensures that the on-disk format of newly created - repositories will be compatible with Mercurial before version 1.7. + reserved names, e.g. "nul". + + Repositories with this on-disk format require Mercurial version 1.1. + + Enabled by default. + +``usestore`` + Enable or disable the "store" repository format which improves + compatibility with systems that fold case or otherwise mangle + filenames. Disabling this option will allow you to store longer filenames + in some situations at the expense of compatibility. + + Repositories with this on-disk format require Mercurial version 0.9.4. + + Enabled by default. ``graph`` --------- @@ -726,7 +763,7 @@ hooks can be run for the same action by appending a suffix to the action. Overriding a site-wide hook can be done by changing its value or setting it to an empty string. Hooks can be prioritized -by adding a prefix of ``priority`` to the hook name on a new line +by adding a prefix of ``priority.`` to the hook name on a new line and setting the priority. The default priority is 0. Example ``.hg/hgrc``:: @@ -746,9 +783,9 @@ variables it is passed are listed with names of the form ``$HG_foo``. ``changegroup`` - Run after a changegroup has been added via push, pull or unbundle. - ID of the first new changeset is in ``$HG_NODE``. URL from which - changes came is in ``$HG_URL``. + Run after a changegroup has been added via push, pull or unbundle. ID of the + first new changeset is in ``$HG_NODE`` and last in ``$HG_NODE_LAST``. URL + from which changes came is in ``$HG_URL``. ``commit`` Run after a changeset has been created in the local repository. ID @@ -779,7 +816,7 @@ command line are passed as ``$HG_ARGS``. Parsed command line arguments are passed as ``$HG_PATS`` and ``$HG_OPTS``. These contain string representations of the data internally passed to <command>. ``$HG_OPTS`` - is a dictionary of options (with unspecified options set to their + is a dictionary of options (with unspecified options set to their defaults). ``$HG_PATS`` is a list of arguments. If the hook returns failure, the command doesn't execute and Mercurial returns the failure code. @@ -830,17 +867,16 @@ transaction from being opened. ``pretxnclose`` - Run right before the transaction is actually finalized. Any - repository change will be visible to the hook program. This lets you - validate the transaction content or change it. Exit status 0 allows - the commit to proceed. Non-zero status will cause the transaction to - be rolled back. The reason for the transaction opening will be in - ``$HG_TXNNAME`` and a unique identifier for the transaction will be in - ``HG_TXNID``. The rest of the available data will vary according the - transaction type. New changesets will add ``$HG_NODE`` (id of the - first added changeset), ``$HG_URL`` and ``$HG_SOURCE`` variables, - bookmarks and phases changes will set ``HG_BOOKMARK_MOVED`` and - ``HG_PHASES_MOVED`` to ``1``, etc. + Run right before the transaction is actually finalized. Any repository change + will be visible to the hook program. This lets you validate the transaction + content or change it. Exit status 0 allows the commit to proceed. Non-zero + status will cause the transaction to be rolled back. The reason for the + transaction opening will be in ``$HG_TXNNAME`` and a unique identifier for + the transaction will be in ``HG_TXNID``. The rest of the available data will + vary according the transaction type. New changesets will add ``$HG_NODE`` (id + of the first added changeset), ``$HG_NODE_LAST`` (id of the last added + changeset), ``$HG_URL`` and ``$HG_SOURCE`` variables, bookmarks and phases + changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``, etc. ``txnclose`` Run after any repository transaction has been committed. At this @@ -853,14 +889,13 @@ docs for details about available variables. ``pretxnchangegroup`` - Run after a changegroup has been added via push, pull or unbundle, - but before the transaction has been committed. Changegroup is - visible to hook program. This lets you validate incoming changes - before accepting them. Passed the ID of the first new changeset in - ``$HG_NODE``. Exit status 0 allows the transaction to commit. Non-zero - status will cause the transaction to be rolled back and the push, - pull or unbundle will fail. URL that was source of changes is in - ``$HG_URL``. + Run after a changegroup has been added via push, pull or unbundle, but before + the transaction has been committed. Changegroup is visible to hook program. + This lets you validate incoming changes before accepting them. Passed the ID + of the first new changeset in ``$HG_NODE`` and last in ``$HG_NODE_LAST``. + Exit status 0 allows the transaction to commit. Non-zero status will cause + the transaction to be rolled back and the push, pull or unbundle will fail. + URL that was source of changes is in ``$HG_URL``. ``pretxncommit`` Run after a changeset has been created but the transaction not yet @@ -971,6 +1006,25 @@ Optional. Always use the proxy, even for localhost and any entries in ``http_proxy.no``. (default: False) +``merge`` +--------- + +This section specifies behavior during merges and updates. + +``checkignored`` + Controls behavior when an ignored file on disk has the same name as a tracked + file in the changeset being merged or updated to, and has different + contents. Options are ``abort``, ``warn`` and ``ignore``. With ``abort``, + abort on such files. With ``warn``, warn on such files and back them up as + .orig. With ``ignore``, don't print a warning and back them up as + .orig. (default: ``abort``) + +``checkunknown`` + Controls behavior when an unknown file that isn't ignored has the same name + as a tracked file in the changeset being merged or updated to, and has + different contents. Similar to ``merge.checkignored``, except for files that + are not ignored. (default: ``abort``) + ``merge-patterns`` ------------------ @@ -1021,8 +1075,13 @@ (default: 0) ``executable`` - Either just the name of the executable or its pathname. On Windows, - the path can use environment variables with ${ProgramFiles} syntax. + Either just the name of the executable or its pathname. + + .. container:: windows + + On Windows, the path can use environment variables with ${ProgramFiles} + syntax. + (default: the tool name) ``args`` @@ -1070,27 +1129,29 @@ ``gui`` This tool requires a graphical interface to run. (default: False) -``regkey`` - Windows registry key which describes install location of this - tool. Mercurial will search for this key first under - ``HKEY_CURRENT_USER`` and then under ``HKEY_LOCAL_MACHINE``. - (default: None) - -``regkeyalt`` - An alternate Windows registry key to try if the first key is not - found. The alternate key uses the same ``regname`` and ``regappend`` - semantics of the primary key. The most common use for this key - is to search for 32bit applications on 64bit operating systems. - (default: None) - -``regname`` - Name of value to read from specified registry key. - (default: the unnamed (default) value) - -``regappend`` - String to append to the value read from the registry, typically - the executable name of the tool. - (default: None) +.. container:: windows + + ``regkey`` + Windows registry key which describes install location of this + tool. Mercurial will search for this key first under + ``HKEY_CURRENT_USER`` and then under ``HKEY_LOCAL_MACHINE``. + (default: None) + + ``regkeyalt`` + An alternate Windows registry key to try if the first key is not + found. The alternate key uses the same ``regname`` and ``regappend`` + semantics of the primary key. The most common use for this key + is to search for 32bit applications on 64bit operating systems. + (default: None) + + ``regname`` + Name of value to read from specified registry key. + (default: the unnamed (default) value) + + ``regappend`` + String to append to the value read from the registry, typically + the executable name of the tool. + (default: None) ``patch`` @@ -1119,29 +1180,43 @@ ``paths`` --------- -Assigns symbolic names to repositories. The left side is the -symbolic name, and the right gives the directory or URL that is the -location of the repository. Default paths can be declared by setting -the following entries. +Assigns symbolic names and behavior to repositories. + +Options are symbolic names defining the URL or directory that is the +location of the repository. Example:: + + [paths] + my_server = https://example.com/my_repo + local_path = /home/me/repo + +These symbolic names can be used from the command line. To pull +from ``my_server``: :hg:`pull my_server`. To push to ``local_path``: +:hg:`push local_path`. + +Options containing colons (``:``) denote sub-options that can influence +behavior for that specific path. Example:: + + [paths] + my_server = https://example.com/my_path + my_server:pushurl = ssh://example.com/my_path + +The following sub-options can be defined: + +``pushurl`` + The URL to use for push operations. If not defined, the location + defined by the path's main entry is used. + +The following special named paths exist: ``default`` - Directory or URL to use when pulling if no source is specified. - (default: repository from which the current repository was cloned) + The URL or directory to use when no source or remote is specified. + + :hg:`clone` will automatically define this path to the location the + repository was cloned from. ``default-push`` - Optional. Directory or URL to use when pushing if no destination - is specified. - -Custom paths can be defined by assigning the path to a name that later can be -used from the command line. Example:: - - [paths] - my_path = http://example.com/path - -To push to the path defined in ``my_path`` run the command:: - - hg push my_path - + (deprecated) The URL or directory for the default :hg:`push` location. + ``default:pushurl`` should be used instead. ``phases`` ---------- @@ -1256,12 +1331,12 @@ 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 ``-<num>`` - which would take the last num characters, or ``+<num>`` for the first num - characters. - - (default: Topic bar number estimate) + ``unit``, ``estimate``, ``speed``, and ``item``. ``item`` defaults to the + last 20 characters of the item, but this can be changed by adding either + ``-<num>`` which would take the last num characters, or ``+<num>`` for the + first num characters. + + (default: topic bar number estimate) ``width`` If set, the maximum width of the progress information (that is, min(width, @@ -1276,6 +1351,13 @@ ``assume-tty`` If true, ALWAYS show a progress bar, unless disable is given. +``rebase`` +---------- + +``allowdivergence`` + Default to False, when True allow creating divergence when performing + rebase of obsolete changesets. + ``revsetalias`` --------------- @@ -1311,6 +1393,35 @@ Instruct HTTP clients not to send request headers longer than this many bytes. (default: 1024) +``bundle1`` + Whether to allow clients to push and pull using the legacy bundle1 + exchange format. (default: True) + +``bundle1gd`` + Like ``bundle1`` but only used if the repository is using the + *generaldelta* storage format. (default: True) + +``bundle1.push`` + Whether to allow clients to push using the legacy bundle1 exchange + format. (default: True) + +``bundle1gd.push`` + Like ``bundle1.push`` but only used if the repository is using the + *generaldelta* storage format. (default: True) + +``bundle1.pull`` + Whether to allow clients to pull using the legacy bundle1 exchange + format. (default: True) + +``bundle1gd.pull`` + Like ``bundle1.pull`` but only used if the repository is using the + *generaldelta* storage format. (default: True) + + Large repositories using the *generaldelta* storage format should + consider setting this option because converting *generaldelta* + repositories to the exchange format required by the bundle1 data + format can consume a lot of CPU. + ``smtp`` -------- @@ -1415,6 +1526,16 @@ default ``USER@HOST`` is used instead. (default: False) +``clonebundles`` + Whether the "clone bundles" feature is enabled. + + When enabled, :hg:`clone` may download and apply a server-advertised + bundle file from a URL instead of using the normal exchange mechanism. + + This can likely result in faster and more reliable clones. + + (default: True) + ``clonebundlefallback`` Whether failure to apply an advertised "clone bundle" from a server should result in fallback to a regular clone. @@ -1430,6 +1551,30 @@ (default: False) +``clonebundleprefers`` + Defines preferences for which "clone bundles" to use. + + Servers advertising "clone bundles" may advertise multiple available + bundles. Each bundle may have different attributes, such as the bundle + type and compression format. This option is used to prefer a particular + bundle over another. + + The following keys are defined by Mercurial: + + BUNDLESPEC + A bundle type specifier. These are strings passed to :hg:`bundle -t`. + e.g. ``gzip-v2`` or ``bzip2-v1``. + + COMPRESSION + The compression format of the bundle. e.g. ``gzip`` and ``bzip2``. + + Server operators may define custom keys. + + Example values: ``COMPRESSION=bzip2``, + ``BUNDLESPEC=gzip-v2, COMPRESSION=gzip``. + + By default, the first bundle advertised by the server is used. + ``commitsubrepos`` Whether to commit modified subrepositories when committing the parent repository. If False and one subrepository has uncommitted @@ -1446,6 +1591,10 @@ Encoding to try if it's not possible to decode the changelog using UTF-8. (default: ISO-8859-1) +``graphnodetemplate`` + The template used to print changeset nodes in an ASCII revision graph. + (default: ``{graphnode}``) + ``ignore`` A file to read per-user ignore patterns from. This file should be in the same format as a repository-wide .hgignore file. Filenames @@ -1488,6 +1637,10 @@ markers is different from the encoding of the merged files, serious problems may occur. +``origbackuppath`` + The path to a directory used to store generated .orig files. If the path is + not a directory, one will be created. + ``patch`` An optional external tool that ``hg import`` and some extensions will use for applying patches. By default Mercurial uses an @@ -1504,24 +1657,42 @@ ``portablefilenames`` Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``. (default: ``warn``) - If set to ``warn`` (or ``true``), a warning message is printed on POSIX - platforms, if a file with a non-portable filename is added (e.g. a file - with a name that can't be created on Windows because it contains reserved - parts like ``AUX``, reserved characters like ``:``, or would cause a case - collision with an existing file). - If set to ``ignore`` (or ``false``), no warning is printed. - If set to ``abort``, the command is aborted. - On Windows, this configuration option is ignored and the command aborted. + + ``warn`` + Print a warning message on POSIX platforms, if a file with a non-portable + filename is added (e.g. a file with a name that can't be created on + Windows because it contains reserved parts like ``AUX``, reserved + characters like ``:``, or would cause a case collision with an existing + file). + + ``ignore`` + Don't print a warning. + + ``abort`` + The command is aborted. + + ``true`` + Alias for ``warn``. + + ``false`` + Alias for ``ignore``. + + .. container:: windows + + On Windows, this configuration option is ignored and the command aborted. ``quiet`` - Reduce the amount of output printed. (default: False) + Reduce the amount of output printed. + (default: False) ``remotecmd`` - Remote command to use for clone/push/pull operations. (default: ``hg``) + Remote command to use for clone/push/pull operations. + (default: ``hg``) ``report_untrusted`` Warn if a ``.hg/hgrc`` file is ignored due to not being owned by a - trusted user or group. (default: True) + trusted user or group. + (default: True) ``slash`` Display paths using a slash (``/``) as the path separator. This @@ -1565,7 +1736,7 @@ username are expanded. (default: ``$EMAIL`` or ``username@hostname``. If the username in - hgrc is empty, e.g. if the system admin set ``username =`` in the + hgrc is empty, e.g. if the system admin set ``username =`` in the system hgrc, it has to be specified manually or in a different hgrc file) @@ -1866,3 +2037,27 @@ Number of CPUs to use for parallel operations. A zero or negative value is treated as ``use the default``. (default: 4 or the number of CPUs on the system, whichever is larger) + +``backgroundclose`` + Whether to enable closing file handles on background threads during certain + operations. Some platforms aren't very efficient at closing file + handles that have been written or appened to. By performing file closing + on background threads, file write rate can increase substantially. + (default: true on Windows, false elsewhere) + +``backgroundcloseminfilecount`` + Minimum number of files required to trigger background file closing. + Operations not writing this many files won't start background close + threads. + (default: 2048) + +``backgroundclosemaxqueue`` + The maximum number of opened file handles waiting to be closed in the + background. This option only has an effect if ``backgroundclose`` is + enabled. + (default: 384) + +``backgroundclosethreadcount`` + Number of threads to process background file closes. Only relevant if + ``backgroundclose`` is enabled. + (default: 4)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/help/internals/bundles.txt Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,97 @@ +Bundles +======= + +A bundle is a container for repository data. + +Bundles are used as standalone files as well as the interchange format +over the wire protocol used when two Mercurial peers communicate with +each other. + +Headers +------- + +Bundles produced since Mercurial 0.7 (September 2005) have a 4 byte +header identifying the major bundle type. The header always begins with +``HG`` and the follow 2 bytes indicate the bundle type/version. Some +bundle types have additional data after this 4 byte header. + +The following sections describe each bundle header/type. + +HG10 +---- + +``HG10`` headers indicate a *changegroup bundle*. This is the original +bundle format, so it is sometimes referred to as *bundle1*. It has been +present since version 0.7 (released September 2005). + +This header is followed by 2 bytes indicating the compression algorithm +used for data that follows. All subsequent data following this +compression identifier is compressed according to the algorithm/method +specified. + +Supported algorithms include the following. + +``BZ`` + *bzip2* compression. + + Bzip2 compressors emit a leading ``BZ`` header. Mercurial uses this + leading ``BZ`` as part of the bundle header. Therefore consumers + of bzip2 bundles need to *seed* the bzip2 decompressor with ``BZ`` or + seek the input stream back to the beginning of the algorithm component + of the bundle header so that decompressor input is valid. This behavior + is unique among supported compression algorithms. + + Supported since version 0.7 (released December 2006). + +``GZ`` + *zlib* compression. + + Supported since version 0.9.2 (released December 2006). + +``UN`` + *Uncompressed* or no compression. Unmodified changegroup data follows. + + Supported since version 0.9.2 (released December 2006). + +3rd party extensions may implement their own compression. However, no +authority reserves values for their compression algorithm identifiers. + +HG2X +---- + +``HG2X`` headers (where ``X`` is any value) denote a *bundle2* bundle. +Bundle2 bundles are a container format for various kinds of repository +data and capabilities, beyond changegroup data (which was the only data +supported by ``HG10`` bundles. + +``HG20`` is currently the only defined bundle2 version. + +The ``HG20`` format is not yet documented here. See the inline comments +in ``mercurial/exchange.py`` for now. + +Initial ``HG20`` support was added in Mercurial 3.0 (released May +2014). However, bundle2 bundles were hidden behind an experimental flag +until version 3.5 (released August 2015), when they were enabled in the +wire protocol. Various commands (including ``hg bundle``) did not +support generating bundle2 files until Mercurial 3.6 (released November +2015). + +HGS1 +---- + +*Experimental* + +A ``HGS1`` header indicates a *streaming clone bundle*. This is a bundle +that contains raw revlog data from a repository store. (Typically revlog +data is exchanged in the form of changegroups.) + +The purpose of *streaming clone bundles* are to *clone* repository data +very efficiently. + +The ``HGS1`` header is always followed by 2 bytes indicating a +compression algorithm of the data that follows. Only ``UN`` +(uncompressed data) is currently allowed. + +``HGS1UN`` support was added as an experimental feature in version 3.6 +(released November 2015) as part of the initial offering of the *clone +bundles* feature.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/help/internals/changegroups.txt Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,157 @@ +Changegroups +============ + +Changegroups are representations of repository revlog data, specifically +the changelog, manifest, and filelogs. + +There are 3 versions of changegroups: ``1``, ``2``, and ``3``. From a +high-level, versions ``1`` and ``2`` are almost exactly the same, with +the only difference being a header on entries in the changeset +segment. Version ``3`` adds support for exchanging treemanifests and +includes revlog flags in the delta header. + +Changegroups consists of 3 logical segments:: + + +---------------------------------+ + | | | | + | changeset | manifest | filelogs | + | | | | + +---------------------------------+ + +The principle building block of each segment is a *chunk*. A *chunk* +is a framed piece of data:: + + +---------------------------------------+ + | | | + | length | data | + | (32 bits) | <length> bytes | + | | | + +---------------------------------------+ + +Each chunk starts with a 32-bit big-endian signed integer indicating +the length of the raw data that follows. + +There is a special case chunk that has 0 length (``0x00000000``). We +call this an *empty chunk*. + +Delta Groups +------------ + +A *delta group* expresses the content of a revlog as a series of deltas, +or patches against previous revisions. + +Delta groups consist of 0 or more *chunks* followed by the *empty chunk* +to signal the end of the delta group:: + + +------------------------------------------------------------------------+ + | | | | | | + | chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 | + | (32 bits) | (various) | (32 bits) | (various) | (32 bits) | + | | | | | | + +------------------------------------------------------------+-----------+ + +Each *chunk*'s data consists of the following:: + + +-----------------------------------------+ + | | | | + | delta header | mdiff header | delta | + | (various) | (12 bytes) | (various) | + | | | | + +-----------------------------------------+ + +The *length* field is the byte length of the remaining 3 logical pieces +of data. The *delta* is a diff from an existing entry in the changelog. + +The *delta header* is different between versions ``1``, ``2``, and +``3`` of the changegroup format. + +Version 1:: + + +------------------------------------------------------+ + | | | | | + | node | p1 node | p2 node | link node | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | + | | | | | + +------------------------------------------------------+ + +Version 2:: + + +------------------------------------------------------------------+ + | | | | | | + | node | p1 node | p2 node | base node | link node | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | + | | | | | | + +------------------------------------------------------------------+ + +Version 3:: + + +------------------------------------------------------------------------------+ + | | | | | | | + | node | p1 node | p2 node | base node | link node | flags | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) | + | | | | | | | + +------------------------------------------------------------------------------+ + +The *mdiff header* consists of 3 32-bit big-endian signed integers +describing offsets at which to apply the following delta content:: + + +-------------------------------------+ + | | | | + | offset | old length | new length | + | (32 bits) | (32 bits) | (32 bits) | + | | | | + +-------------------------------------+ + +In version 1, the delta is always applied against the previous node from +the changegroup or the first parent if this is the first entry in the +changegroup. + +In version 2, the delta base node is encoded in the entry in the +changegroup. This allows the delta to be expressed against any parent, +which can result in smaller deltas and more efficient encoding of data. + +Changeset Segment +----------------- + +The *changeset segment* consists of a single *delta group* holding +changelog data. It is followed by an *empty chunk* to denote the +boundary to the *manifests segment*. + +Manifest Segment +---------------- + +The *manifest segment* consists of a single *delta group* holding +manifest data. It is followed by an *empty chunk* to denote the boundary +to the *filelogs segment*. + +Filelogs Segment +---------------- + +The *filelogs* segment consists of multiple sub-segments, each +corresponding to an individual file whose data is being described:: + + +--------------------------------------+ + | | | | | + | filelog0 | filelog1 | filelog2 | ... | + | | | | | + +--------------------------------------+ + +In version ``3`` of the changegroup format, filelogs may include +directory logs when treemanifests are in use. directory logs are +identified by having a trailing '/' on their filename (see below). + +The final filelog sub-segment is followed by an *empty chunk* to denote +the end of the segment and the overall changegroup. + +Each filelog sub-segment consists of the following:: + + +------------------------------------------+ + | | | | + | filename size | filename | delta group | + | (32 bits) | (various) | (various) | + | | | | + +------------------------------------------+ + +That is, a *chunk* consisting of the filename (not terminated or padded) +followed by N chunks constituting the *delta group* for this file. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/help/internals/revlogs.txt Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,193 @@ +Revisions Logs +============== + +Revision logs - or *revlogs* - are an append only data structure for +storing discrete entries, or *revisions*. They are the primary storage +mechanism of repository data. + +Revlogs effectively model a directed acyclic graph (DAG). Each node +has edges to 1 or 2 *parent* nodes. Each node contains metadata and +the raw value for that node. + +Revlogs consist of entries which have metadata and revision data. +Metadata includes the hash of the revision's content, sizes, and +links to its *parent* entries. The collective metadata is referred +to as the *index* and the revision data is the *data*. + +Revision data is stored as a series of compressed deltas against previous +revisions. + +Revlogs are written in an append-only fashion. We never need to rewrite +a file to insert nor do we need to remove data. Rolling back in-progress +writes can be performed by truncating files. Read locks can be avoided +using simple techniques. This means that references to other data in +the same revlog *always* refer to a previous entry. + +Revlogs can be modeled as 0-indexed arrays. The first revision is +revision #0 and the second is revision #1. The revision -1 is typically +used to mean *does not exist* or *not defined*. + +File Format +----------- + +A revlog begins with a 32-bit big endian integer holding version info +and feature flags. + +This integer is logically divided into 2 16-bit shorts. The least +significant half of the integer is the format/version short. The other +short holds feature flags that dictate behavior of the revlog. + +Only 1 bit of the format/version short is currently used. Remaining +bits are reserved for future use. + +The following values for the format/version short are defined: + +0 + The original revlog version. +1 + RevlogNG (*next generation*). It replaced version 0 when it was + implemented in 2006. + +The feature flags short consists of bit flags. Where 0 is the least +significant bit, the following bit offsets define flags: + +0 + Store revision data inline. +1 + Generaldelta encoding. + +2-15 + Reserved for future use. + +The following header values are common: + +00 00 00 01 + RevlogNG +00 01 00 01 + RevlogNG + inline +00 02 00 01 + RevlogNG + generaldelta +00 03 00 01 + RevlogNG + inline + generaldelta + +Following the 32-bit header is *index* data. Inlined revision data is possibly +located between index entries. More on this layout is described below. + +RevlogNG Format +--------------- + +RevlogNG (version 1) begins with an index describing the revisions in +the revlog. If the ``inline`` flag is set, revision data is stored inline, +or between index entries (as opposed to in a separate container). + +Each index entry is 64 bytes. The byte layout of each entry is as +follows, with byte 0 being the first byte (all data stored as big endian): + +0-5 (6 bytes) + Absolute offset of revision data from beginning of revlog. +6-7 (2 bytes) + Bit flags impacting revision behavior. +8-11 (4 bytes) + Compressed length of revision data / chunk as stored in revlog. +12-15 (4 bytes) + Uncompressed length of revision data / chunk. +16-19 (4 bytes) + Base or previous revision this revision's delta was produced against. + -1 means this revision holds full text (as opposed to a delta). + For generaldelta repos, this is the previous revision in the delta + chain. For non-generaldelta repos, this is the base or first + revision in the delta chain. +20-23 (4 bytes) + A revision this revision is *linked* to. This allows a revision in + one revlog to be forever associated with a revision in another + revlog. For example, a file's revlog may point to the changelog + revision that introduced it. +24-27 (4 bytes) + Revision of 1st parent. -1 indicates no parent. +28-31 (4 bytes) + Revision of 2nd parent. -1 indicates no 2nd parent. +32-63 (32 bytes) + Hash of revision's full text. Currently, SHA-1 is used and only + the first 20 bytes of this field are used. The rest of the bytes + are ignored and should be stored as \0. + +If inline revision data is being stored, the compressed revision data +(of length from bytes offset 8-11 from the index entry) immediately +follows the index entry. There is no header on the revision data. There +is no padding between it and the index entries before and after. + +If revision data is not inline, then raw revision data is stored in a +separate byte container. The offsets from bytes 0-5 and the compressed +length from bytes 8-11 define how to access this data. + +Delta Chains +------------ + +Revision data is encoded as a chain of *chunks*. Each chain begins with +the compressed original full text for that revision. Each subsequent +*chunk* is a *delta* against the previous revision. We therefore call +these chains of chunks/deltas *delta chains*. + +The full text for a revision is reconstructed by loading the original +full text for the base revision of a *delta chain* and then applying +*deltas* until the target revision is reconstructed. + +*Delta chains* are limited in length so lookup time is bound. They are +limited to ~2x the length of the revision's data. The linear distance +between the base chunk and the final chunk is also limited so the +amount of read I/O to load all chunks in the delta chain is bound. + +Deltas and delta chains are either computed against the previous +revision in the revlog or another revision (almost certainly one of +the parents of the revision). Historically, deltas were computed against +the previous revision. The *generaldelta* revlog feature flag (enabled +by default in Mercurial 3.7) activates the mode where deltas are +computed against an arbitrary revision (almost certainly a parent revision). + +File Storage +------------ + +Revlogs logically consist of an index (metadata of entries) and +revision data. This data may be stored together in a single file or in +separate files. The mechanism used is indicated by the ``inline`` feature +flag on the revlog. + +Mercurial's behavior is to use inline storage until a revlog reaches a +certain size, at which point it will be converted to non-inline. The +reason there is a size limit on inline storage is to establish an upper +bound on how much data must be read to load the index. It would be a waste +to read tens or hundreds of extra megabytes of data just to access the +index data. + +The actual layout of revlog files on disk is governed by the repository's +*store format*. Typically, a ``.i`` file represents the index revlog +(possibly containing inline data) and a ``.d`` file holds the revision data. + +Revision Entries +---------------- + +Revision entries consist of an optional 1 byte header followed by an +encoding of the revision data. The headers are as follows: + +\0 (0x00) + Revision data is the entirety of the entry, including this header. +u (0x75) + Raw revision data follows. +x (0x78) + zlib (RFC 1950) data. + + The 0x78 value is actually the first byte of the zlib header (CMF byte). + +Hash Computation +---------------- + +The hash of the revision is stored in the index and is used both as a primary +key and for data integrity verification. + +Currently, SHA-1 is the only supported hashing algorithm. To obtain the SHA-1 +hash of a revision: + +1. Hash the parent nodes +2. Hash the fulltext of the revision + +The 20 byte node ids of the parents are fed into the hasher in ascending order. \ No newline at end of file
--- a/mercurial/help/phases.txt Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/help/phases.txt Sun Jan 17 21:40:21 2016 -0600 @@ -28,6 +28,12 @@ Phases can also be manually manipulated with the :hg:`phase` command if needed. See :hg:`help -v phase` for examples. +To make yours commits secret by default, put this in your +configuration file:: + + [phases] + new-commit = secret + Phases and servers ==================
--- a/mercurial/hg.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hg.py Sun Jan 17 21:40:21 2016 -0600 @@ -52,7 +52,7 @@ if not hashbranch and not branches: x = revs or None if util.safehasattr(revs, 'first'): - y = revs.first() + y = revs.first() elif revs: y = revs[0] else: @@ -235,13 +235,7 @@ destvfs.write('sharedpath', sharedpath) r = repository(ui, destwvfs.base) - - default = srcrepo.ui.config('paths', 'default') - if default: - fp = r.vfs("hgrc", "w", text=True) - fp.write("[paths]\n") - fp.write("default = %s\n" % default) - fp.close() + postshare(srcrepo, r, bookmarks=bookmarks) if update: r.ui.status(_("updating working directory\n")) @@ -257,8 +251,24 @@ continue _update(r, uprev) +def postshare(sourcerepo, destrepo, bookmarks=True): + """Called after a new shared repo is created. + + The new repo only has a requirements file and pointer to the source. + This function configures additional shared data. + + Extensions can wrap this function and write additional entries to + destrepo/.hg/shared to indicate additional pieces of data to be shared. + """ + default = sourcerepo.ui.config('paths', 'default') + if default: + fp = destrepo.vfs("hgrc", "w", text=True) + fp.write("[paths]\n") + fp.write("default = %s\n" % default) + fp.close() + if bookmarks: - fp = r.vfs('shared', 'w') + fp = destrepo.vfs('shared', 'w') fp.write('bookmarks\n') fp.close() @@ -546,13 +556,22 @@ "support clone by revision")) revs = [srcpeer.lookup(r) for r in rev] checkout = revs[0] - if destpeer.local(): + local = destpeer.local() + if local: if not stream: if pull: stream = False else: stream = None - destpeer.local().clone(srcpeer, heads=revs, stream=stream) + # internal config: ui.quietbookmarkmove + quiet = local.ui.backupconfig('ui', 'quietbookmarkmove') + try: + local.ui.setconfig( + 'ui', 'quietbookmarkmove', True, 'clone') + exchange.pull(local, srcpeer, revs, + streamclonerequested=stream) + finally: + local.ui.restoreconfig(quiet) elif srcrepo: exchange.push(srcrepo, destpeer, revs=revs, bookmarks=srcrepo._bookmarks.keys()) @@ -618,7 +637,9 @@ srcpeer.close() return srcpeer, destpeer -def _showstats(repo, stats): +def _showstats(repo, stats, quietempty=False): + if quietempty and not any(stats): + return repo.ui.status(_("%d files updated, %d files merged, " "%d files removed, %d files unresolved\n") % stats) @@ -628,13 +649,13 @@ When overwrite is set, changes are clobbered, merged else returns stats (see pydoc mercurial.merge.applyupdates)""" - return mergemod.update(repo, node, False, overwrite, None, + return mergemod.update(repo, node, False, overwrite, labels=['working copy', 'destination']) -def update(repo, node): +def update(repo, node, quietempty=False): """update the working directory to node, merging linear changes""" stats = updaterepo(repo, node, False) - _showstats(repo, stats) + _showstats(repo, stats, quietempty) if stats[3]: repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n")) return stats[3] > 0 @@ -642,18 +663,18 @@ # naming conflict in clone() _update = update -def clean(repo, node, show_stats=True): +def clean(repo, node, show_stats=True, quietempty=False): """forcibly switch the working directory to node, clobbering changes""" stats = updaterepo(repo, node, True) util.unlinkpath(repo.join('graftstate'), ignoremissing=True) if show_stats: - _showstats(repo, stats) + _showstats(repo, stats, quietempty) return stats[3] > 0 def merge(repo, node, force=None, remind=True): """Branch merge with node, resolving changes. Return true if any unresolved conflicts.""" - stats = mergemod.update(repo, node, True, force, False) + stats = mergemod.update(repo, node, True, force) _showstats(repo, stats) if stats[3]: repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
--- a/mercurial/hgweb/__init__.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/__init__.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,8 +6,22 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +from __future__ import absolute_import + import os -import hgweb_mod, hgwebdir_mod + +from ..i18n import _ + +from .. import ( + error, + util, +) + +from . import ( + hgweb_mod, + hgwebdir_mod, + server, +) def hgweb(config, name=None, baseui=None): '''create an hgweb wsgi object @@ -29,3 +43,83 @@ def hgwebdir(config, baseui=None): return hgwebdir_mod.hgwebdir(config, baseui=baseui) +class httpservice(object): + def __init__(self, ui, app, opts): + self.ui = ui + self.app = app + self.opts = opts + + def init(self): + util.setsignalhandler() + self.httpd = server.create_server(self.ui, self.app) + + if self.opts['port'] and not self.ui.verbose: + return + + if self.httpd.prefix: + prefix = self.httpd.prefix.strip('/') + '/' + else: + prefix = '' + + port = ':%d' % self.httpd.port + if port == ':80': + port = '' + + bindaddr = self.httpd.addr + if bindaddr == '0.0.0.0': + bindaddr = '*' + elif ':' in bindaddr: # IPv6 + bindaddr = '[%s]' % bindaddr + + fqaddr = self.httpd.fqaddr + if ':' in fqaddr: + fqaddr = '[%s]' % fqaddr + if self.opts['port']: + write = self.ui.status + else: + write = self.ui.write + write(_('listening at http://%s%s/%s (bound to %s:%d)\n') % + (fqaddr, port, prefix, bindaddr, self.httpd.port)) + self.ui.flush() # avoid buffering of status message + + def run(self): + self.httpd.serve_forever() + +def createservice(ui, repo, opts): + # this way we can check if something was given in the command-line + if opts.get('port'): + opts['port'] = util.getport(opts.get('port')) + + alluis = set([ui]) + if repo: + baseui = repo.baseui + alluis.update([repo.baseui, repo.ui]) + else: + baseui = ui + webconf = opts.get('web_conf') or opts.get('webdir_conf') + if webconf: + # load server settings (e.g. web.port) to "copied" ui, which allows + # hgwebdir to reload webconf cleanly + servui = ui.copy() + servui.readconfig(webconf, sections=['web']) + alluis.add(servui) + else: + servui = ui + + optlist = ("name templates style address port prefix ipv6" + " accesslog errorlog certificate encoding") + for o in optlist.split(): + val = opts.get(o, '') + if val in (None, ''): # should check against default options instead + continue + for u in alluis: + u.setconfig("web", o, val, 'serve') + + if webconf: + app = hgwebdir_mod.hgwebdir(webconf, baseui=baseui) + else: + if not repo: + raise error.RepoError(_("there is no Mercurial repository" + " here (.hg not found)")) + app = hgweb_mod.hgweb(repo, baseui=baseui) + return httpservice(servui, app, opts)
--- a/mercurial/hgweb/common.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/common.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,7 +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 errno, mimetypes, os +from __future__ import absolute_import + +import BaseHTTPServer +import errno +import mimetypes +import os HTTP_OK = 200 HTTP_NOT_MODIFIED = 304 @@ -102,8 +107,7 @@ raise AttributeError def _statusmessage(code): - from BaseHTTPServer import BaseHTTPRequestHandler - responses = BaseHTTPRequestHandler.responses + responses = BaseHTTPServer.BaseHTTPRequestHandler.responses return responses.get(code, ('Error', 'Unknown error'))[0] def statusmessage(code, message=None):
--- a/mercurial/hgweb/hgweb_mod.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/hgweb_mod.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,15 +6,41 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +from __future__ import absolute_import + import contextlib import os -from mercurial import ui, hg, hook, error, encoding, templater, util, repoview -from mercurial.templatefilters import websub -from common import ErrorResponse, permhooks, caching -from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST -from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR -from request import wsgirequest -import webcommands, protocol, webutil + +from .common import ( + ErrorResponse, + HTTP_BAD_REQUEST, + HTTP_NOT_FOUND, + HTTP_NOT_MODIFIED, + HTTP_OK, + HTTP_SERVER_ERROR, + caching, + permhooks, +) +from .request import wsgirequest + +from .. import ( + encoding, + error, + hg, + hook, + repoview, + templatefilters, + templater, + ui as uimod, + util, +) + +from . import ( + protocol, + webcommands, + webutil, + wsgicgi, +) perms = { 'changegroup': 'pull', @@ -158,7 +184,7 @@ or req.url.strip('/') or self.repo.root) def websubfilter(text): - return websub(text, self.websubtable) + return templatefilters.websub(text, self.websubtable) # create the templater @@ -195,7 +221,7 @@ if baseui: u = baseui.copy() else: - u = ui.ui() + u = uimod.ui() r = hg.repository(u, repo) else: # we trust caller to give us a private copy @@ -260,7 +286,6 @@ if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): raise RuntimeError("This function is only intended to be " "called while running as a CGI script.") - import mercurial.hgweb.wsgicgi as wsgicgi wsgicgi.launch(self) def __call__(self, env, respond): @@ -304,8 +329,8 @@ parts = parts[len(repo_parts):] query = '/'.join(parts) else: - query = req.env['QUERY_STRING'].split('&', 1)[0] - query = query.split(';', 1)[0] + query = req.env['QUERY_STRING'].partition('&')[0] + query = query.partition(';')[0] # process this if it's a protocol request # protocol bits don't need to create any URLs
--- a/mercurial/hgweb/hgwebdir_mod.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/hgwebdir_mod.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,15 +6,42 @@ # 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, re, time -from mercurial.i18n import _ -from mercurial import ui, hg, scmutil, util, templater -from mercurial import error, encoding -from common import ErrorResponse, get_mtime, staticfile, paritygen, ismember, \ - get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR -from hgweb_mod import hgweb, makebreadcrumb -from request import wsgirequest -import webutil +from __future__ import absolute_import + +import os +import re +import time + +from ..i18n import _ + +from .common import ( + ErrorResponse, + HTTP_NOT_FOUND, + HTTP_OK, + HTTP_SERVER_ERROR, + get_contact, + get_mtime, + ismember, + paritygen, + staticfile, +) +from .request import wsgirequest + +from .. import ( + encoding, + error, + hg, + scmutil, + templater, + ui as uimod, + util, +) + +from . import ( + hgweb_mod, + webutil, + wsgicgi, +) def cleannames(items): return [(util.pconvert(name).strip('/'), path) for name, path in items] @@ -108,7 +135,7 @@ if self.baseui: u = self.baseui.copy() else: - u = ui.ui() + u = uimod.ui() u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir') u.setconfig('ui', 'nontty', 'true', 'hgwebdir') # displaying bundling progress bar while serving feels wrong and may @@ -161,7 +188,6 @@ if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): raise RuntimeError("This function is only intended to be " "called while running as a CGI script.") - import mercurial.hgweb.wsgicgi as wsgicgi wsgicgi.launch(self) def __call__(self, env, respond): @@ -231,7 +257,7 @@ try: # ensure caller gets private copy of ui repo = hg.repository(self.ui.copy(), real) - return hgweb(repo).run_wsgi(req) + return hgweb_mod.hgweb(repo).run_wsgi(req) except IOError as inst: msg = inst.strerror raise ErrorResponse(HTTP_SERVER_ERROR, msg) @@ -426,7 +452,7 @@ self.updatereqenv(req.env) return tmpl("index", entries=entries, subdir=subdir, - pathdef=makebreadcrumb('/' + subdir, self.prefix), + pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix), sortcolumn=sortcolumn, descending=descending, **dict(sort))
--- a/mercurial/hgweb/protocol.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/protocol.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,9 +5,21 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import cgi, cStringIO, zlib, urllib -from mercurial import util, wireproto -from common import HTTP_OK +from __future__ import absolute_import + +import cStringIO +import cgi +import urllib +import zlib + +from .common import ( + HTTP_OK, +) + +from .. import ( + util, + wireproto, +) HGTYPE = 'application/mercurial-0.1' HGERRTYPE = 'application/hg-error'
--- a/mercurial/hgweb/request.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/request.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,9 +6,21 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import socket, cgi, errno -from mercurial import util -from common import ErrorResponse, statusmessage, HTTP_NOT_MODIFIED +from __future__ import absolute_import + +import cgi +import errno +import socket + +from .common import ( + ErrorResponse, + HTTP_NOT_MODIFIED, + statusmessage, +) + +from .. import ( + util, +) shortcuts = { 'cl': [('cmd', ['changelog']), ('rev', None)], @@ -80,7 +92,7 @@ if self._start_response is not None: self.headers.append(('Content-Type', type)) if filename: - filename = (filename.split('/')[-1] + filename = (filename.rpartition('/')[-1] .replace('\\', '\\\\').replace('"', '\\"')) self.headers.append(('Content-Disposition', 'inline; filename="%s"' % filename))
--- a/mercurial/hgweb/server.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/server.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,10 +6,27 @@ # 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, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback -from mercurial import util, error -from mercurial.hgweb import common -from mercurial.i18n import _ +from __future__ import absolute_import + +import BaseHTTPServer +import SocketServer +import errno +import os +import socket +import sys +import traceback +import urllib + +from ..i18n import _ + +from .. import ( + error, + util, +) + +from . import ( + common, +) def _splitURI(uri): """Return path and query that has been split from uri @@ -197,47 +214,6 @@ self.wfile.write('0\r\n\r\n') self.wfile.flush() -class _httprequesthandleropenssl(_httprequesthandler): - """HTTPS handler based on pyOpenSSL""" - - url_scheme = 'https' - - @staticmethod - def preparehttpserver(httpserver, ssl_cert): - try: - import OpenSSL - OpenSSL.SSL.Context - except ImportError: - raise error.Abort(_("SSL support is unavailable")) - ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) - ctx.use_privatekey_file(ssl_cert) - ctx.use_certificate_file(ssl_cert) - sock = socket.socket(httpserver.address_family, httpserver.socket_type) - httpserver.socket = OpenSSL.SSL.Connection(ctx, sock) - httpserver.server_bind() - httpserver.server_activate() - - def setup(self): - self.connection = self.request - self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) - self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) - - def do_write(self): - import OpenSSL - try: - _httprequesthandler.do_write(self) - except OpenSSL.SSL.SysCallError as inst: - if inst.args[0] != errno.EPIPE: - raise - - def handle_one_request(self): - import OpenSSL - try: - _httprequesthandler.handle_one_request(self) - except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError): - self.close_connection = True - pass - class _httprequesthandlerssl(_httprequesthandler): """HTTPS handler based on Python's ssl module""" @@ -260,8 +236,8 @@ self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) try: - from threading import activeCount - activeCount() # silence pyflakes + import threading + threading.activeCount() # silence pyflakes and bypass demandimport _mixin = SocketServer.ThreadingMixIn except ImportError: if util.safehasattr(os, "fork"): @@ -311,10 +287,7 @@ def create_server(ui, app): if ui.config('web', 'certificate'): - if sys.version_info >= (2, 6): - handler = _httprequesthandlerssl - else: - handler = _httprequesthandleropenssl + handler = _httprequesthandlerssl else: handler = _httprequesthandler
--- a/mercurial/hgweb/webcommands.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/webcommands.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,18 +5,43 @@ # 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, mimetypes, re, cgi, copy -import webutil -from mercurial import error, encoding, archival, templater, templatefilters -from mercurial.node import short, hex -from mercurial import util -from common import paritygen, staticfile, get_contact, ErrorResponse -from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND -from mercurial import graphmod, patch -from mercurial import scmutil -from mercurial.i18n import _ -from mercurial.error import ParseError, RepoLookupError, Abort -from mercurial import revset +from __future__ import absolute_import + +import cgi +import copy +import mimetypes +import os +import re + +from ..i18n import _ +from ..node import hex, short + +from .common import ( + ErrorResponse, + HTTP_FORBIDDEN, + HTTP_NOT_FOUND, + HTTP_OK, + get_contact, + paritygen, + staticfile, +) + +from .. import ( + archival, + encoding, + error, + graphmod, + patch, + revset, + scmutil, + templatefilters, + templater, + util, +) + +from . import ( + webutil, +) __all__ = [] commands = {} @@ -120,20 +145,10 @@ file=f, path=webutil.up(f), text=lines(), - rev=fctx.rev(), symrev=webutil.symrevorshortnode(req, fctx), - node=fctx.hex(), - author=fctx.user(), - date=fctx.date(), - desc=fctx.description(), - extra=fctx.extra(), - branch=webutil.nodebranchnodefault(fctx), - 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)) + permissions=fctx.manifest().flags(f), + **webutil.commonentry(web.repo, fctx)) @webcommand('file') def file(web, req, tmpl): @@ -225,7 +240,7 @@ revdef = 'reverse(%s)' % query try: tree = revset.parse(revdef) - except ParseError: + except error.ParseError: # can't parse to a revset tree return MODE_KEYWORD, query @@ -249,7 +264,8 @@ # RepoLookupError: no such revision, e.g. in 'revision:' # Abort: bookmark/tag not exists # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo - except (ParseError, RepoLookupError, Abort, LookupError): + except (error.ParseError, error.RepoLookupError, error.Abort, + LookupError): return MODE_KEYWORD, query def changelist(**map): @@ -263,20 +279,9 @@ yield tmpl('searchentry', parity=parity.next(), - author=ctx.user(), - parent=webutil.parents(ctx), - child=webutil.children(ctx), changelogtag=showtags, - desc=ctx.description(), - extra=ctx.extra(), - date=ctx.date(), files=files, - rev=ctx.rev(), - node=hex(n), - tags=webutil.nodetagsdict(web.repo, n), - bookmarks=webutil.nodebookmarksdict(web.repo, n), - inbranch=webutil.nodeinbranch(web.repo, ctx), - branches=webutil.nodebranchdict(web.repo, ctx)) + **webutil.commonentry(web.repo, ctx)) if count >= revcount: break @@ -546,20 +551,14 @@ "basename": d} return tmpl("manifest", - rev=ctx.rev(), symrev=symrev, - node=hex(node), path=abspath, up=webutil.up(abspath), upparity=parity.next(), fentries=filelist, dentries=dirlist, 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)) + **webutil.commonentry(web.repo, ctx)) @webcommand('tags') def tags(web, req, tmpl): @@ -693,22 +692,11 @@ revs = web.repo.changelog.revs(start, end - 1) for i in revs: ctx = web.repo[i] - n = ctx.node() - hn = hex(n) l.append(tmpl( - 'shortlogentry', + 'shortlogentry', parity=parity.next(), - author=ctx.user(), - desc=ctx.description(), - extra=ctx.extra(), - date=ctx.date(), - rev=i, - node=hn, - tags=webutil.nodetagsdict(web.repo, n), - bookmarks=webutil.nodebookmarksdict(web.repo, n), - inbranch=webutil.nodeinbranch(web.repo, ctx), - branches=webutil.nodebranchdict(web.repo, ctx))) + **webutil.commonentry(web.repo, ctx))) l.reverse() yield l @@ -753,12 +741,8 @@ raise if fctx is not None: - n = fctx.node() path = fctx.path() ctx = fctx.changectx() - else: - n = ctx.node() - # path already defined in except clause parity = paritygen(web.stripecount) style = web.config('web', 'style', 'paper') @@ -766,7 +750,7 @@ style = req.form['style'][0] diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style) - if fctx: + if fctx is not None: rename = webutil.renamelink(fctx) ctx = fctx else: @@ -774,20 +758,10 @@ ctx = ctx return tmpl("filediff", file=path, - node=hex(n), - rev=ctx.rev(), symrev=webutil.symrevorshortnode(req, ctx), - date=ctx.date(), - desc=ctx.description(), - extra=ctx.extra(), - author=ctx.user(), rename=rename, - 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=diffs, + **webutil.commonentry(web.repo, ctx)) diff = webcommand('diff')(filediff) @@ -812,7 +786,6 @@ if 'file' not in req.form: raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') path = webutil.cleanpath(web.repo, req.form['file'][0]) - rename = path in ctx and webutil.renamelink(ctx[path]) or [] parsecontext = lambda v: v == 'full' and -1 or int(v) if 'context' in req.form: @@ -828,6 +801,7 @@ return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))] return f.data().splitlines() + fctx = None parent = ctx.p1() leftrev = parent.rev() leftnode = parent.node() @@ -843,30 +817,26 @@ leftlines = filelines(pfctx) else: rightlines = () - fctx = ctx.parents()[0][path] - leftlines = filelines(fctx) + pfctx = ctx.parents()[0][path] + leftlines = filelines(pfctx) comparison = webutil.compare(tmpl, context, leftlines, rightlines) + if fctx is not None: + rename = webutil.renamelink(fctx) + ctx = fctx + else: + rename = [] + ctx = ctx return tmpl('filecomparison', file=path, - node=hex(ctx.node()), - rev=ctx.rev(), symrev=webutil.symrevorshortnode(req, ctx), - date=ctx.date(), - desc=ctx.description(), - extra=ctx.extra(), - author=ctx.user(), rename=rename, - 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, rightnode=hex(rightnode), - comparison=comparison) + comparison=comparison, + **webutil.commonentry(web.repo, ctx)) @webcommand('annotate') def annotate(web, req, tmpl): @@ -918,20 +888,10 @@ file=f, annotate=annotate, path=webutil.up(f), - rev=fctx.rev(), symrev=webutil.symrevorshortnode(req, fctx), - node=fctx.hex(), - author=fctx.user(), - date=fctx.date(), - desc=fctx.description(), - extra=fctx.extra(), rename=webutil.renamelink(fctx), - 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)) + permissions=fctx.manifest().flags(f), + **webutil.commonentry(web.repo, fctx)) @webcommand('filelog') def filelog(web, req, tmpl): @@ -993,23 +953,12 @@ for i in revs: iterfctx = fctx.filectx(i) - l.append({"parity": parity.next(), - "filerev": i, - "file": f, - "node": iterfctx.hex(), - "author": iterfctx.user(), - "date": iterfctx.date(), - "rename": webutil.renamelink(iterfctx), - "parent": webutil.parents(iterfctx), - "child": webutil.children(iterfctx), - "desc": iterfctx.description(), - "extra": iterfctx.extra(), - "tags": webutil.nodetagsdict(repo, iterfctx.node()), - "bookmarks": webutil.nodebookmarksdict( - repo, iterfctx.node()), - "branch": webutil.nodebranchnodefault(iterfctx), - "inbranch": webutil.nodeinbranch(repo, iterfctx), - "branches": webutil.nodebranchdict(repo, iterfctx)}) + l.append(dict( + parity=parity.next(), + filerev=i, + file=f, + rename=webutil.renamelink(iterfctx), + **webutil.commonentry(repo, iterfctx))) for e in reversed(l): yield e @@ -1018,11 +967,16 @@ revnav = webutil.filerevnav(web.repo, fctx.path()) nav = revnav.gen(end - 1, revcount, count) - return tmpl("filelog", file=f, node=fctx.hex(), nav=nav, + return tmpl("filelog", + file=f, + nav=nav, symrev=webutil.symrevorshortnode(req, fctx), entries=entries, latestentry=latestentry, - revcount=revcount, morevars=morevars, lessvars=lessvars) + revcount=revcount, + morevars=morevars, + lessvars=lessvars, + **webutil.commonentry(web.repo, fctx)) @webcommand('archive') def archive(web, req, tmpl): @@ -1248,7 +1202,7 @@ def _getdoc(e): doc = e[0].__doc__ if doc: - doc = _(doc).split('\n')[0] + doc = _(doc).partition('\n')[0] else: doc = _('(no help text available)') return doc @@ -1268,8 +1222,7 @@ The ``help`` template will be rendered when requesting help for a topic. ``helptopics`` will be rendered for the index of help topics. """ - from mercurial import commands # avoid cycle - from mercurial import help as helpmod # avoid cycle + from .. import commands, help as helpmod # avoid cycle topicname = req.form.get('node', [None])[0] if not topicname: @@ -1278,7 +1231,7 @@ yield {'topic': entries[0], 'summary': summary} early, other = [], [] - primary = lambda s: s.split('|')[0] + primary = lambda s: s.partition('|')[0] for c, e in commands.table.iteritems(): doc = _getdoc(e) if 'DEPRECATED' in doc or c.startswith('debug'): @@ -1303,10 +1256,35 @@ return tmpl('helptopics', topics=topics, earlycommands=earlycommands, othercommands=othercommands, title='Index') + # Render an index of sub-topics. + if topicname in helpmod.subtopics: + topics = [] + for entries, summary, _doc in helpmod.subtopics[topicname]: + topics.append({ + 'topic': '%s.%s' % (topicname, entries[0]), + 'basename': entries[0], + 'summary': summary, + }) + + return tmpl('helptopics', topics=topics, title=topicname, + subindex=True) + u = webutil.wsgiui() u.verbose = True + + # Render a page from a sub-topic. + if '.' in topicname: + # TODO implement support for rendering sections, like + # `hg help` works. + topic, subtopic = topicname.split('.', 1) + if topic not in helpmod.subtopics: + raise ErrorResponse(HTTP_NOT_FOUND) + else: + topic = topicname + subtopic = None + try: - doc = helpmod.help_(u, topicname) + doc = helpmod.help_(u, topic, subtopic=subtopic) except error.UnknownCommand: raise ErrorResponse(HTTP_NOT_FOUND) return tmpl('help', topic=topicname, doc=doc)
--- a/mercurial/hgweb/webutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/webutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,15 +6,32 @@ # 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, copy +from __future__ import absolute_import + +import copy +import difflib +import os import re -from mercurial import match, patch, error, ui, util, pathutil, context -from mercurial.i18n import _ -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 + +from ..i18n import _ +from ..node import hex, nullid, short + +from .common import ( + ErrorResponse, + HTTP_NOT_FOUND, + paritygen, +) + +from .. import ( + context, + error, + match, + patch, + pathutil, + templatefilters, + ui as uimod, + util, +) def up(p): if p[0] != "/": @@ -124,20 +141,28 @@ def hex(self, rev): return hex(self._changelog.node(self._revlog.linkrev(rev))) +class _siblings(object): + def __init__(self, siblings=[], hiderev=None): + self.siblings = [s for s in siblings if s.node() != nullid] + if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev: + self.siblings = [] -def _siblings(siblings=[], hiderev=None): - siblings = [s for s in siblings if s.node() != nullid] - if len(siblings) == 1 and siblings[0].rev() == hiderev: - return - for s in siblings: - d = {'node': s.hex(), 'rev': s.rev()} - d['user'] = s.user() - d['date'] = s.date() - d['description'] = s.description() - d['branch'] = s.branch() - if util.safehasattr(s, 'path'): - d['file'] = s.path() - yield d + def __iter__(self): + for s in self.siblings: + d = { + 'node': s.hex(), + 'rev': s.rev(), + 'user': s.user(), + 'date': s.date(), + 'description': s.description(), + 'branch': s.branch(), + } + if util.safehasattr(s, 'path'): + d['file'] = s.path() + yield d + + def __len__(self): + return len(self.siblings) def parents(ctx, hide=None): if isinstance(ctx, context.basefilectx): @@ -283,6 +308,25 @@ return fctx +def commonentry(repo, ctx): + node = ctx.node() + return { + 'rev': ctx.rev(), + 'node': hex(node), + 'author': ctx.user(), + 'desc': ctx.description(), + 'date': ctx.date(), + 'extra': ctx.extra(), + 'phase': ctx.phasestr(), + 'branch': nodebranchnodefault(ctx), + 'inbranch': nodeinbranch(repo, ctx), + 'branches': nodebranchdict(repo, ctx), + 'tags': nodetagsdict(repo, node), + 'bookmarks': nodebookmarksdict(repo, node), + 'parent': lambda **x: parents(ctx), + 'child': lambda **x: children(ctx), + } + def changelistentry(web, ctx, tmpl): '''Obtain a dictionary to be used for entries in a changelist. @@ -295,26 +339,18 @@ showtags = showtag(repo, tmpl, 'changelogtag', n) files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles) - return { - "author": ctx.user(), - "parent": parents(ctx, rev - 1), - "child": children(ctx, rev + 1), - "changelogtag": showtags, - "desc": ctx.description(), - "extra": ctx.extra(), - "date": ctx.date(), - "files": files, - "rev": rev, - "node": hex(n), - "tags": nodetagsdict(repo, n), - "bookmarks": nodebookmarksdict(repo, n), - "inbranch": nodeinbranch(repo, ctx), - "branches": nodebranchdict(repo, ctx) - } + entry = commonentry(repo, ctx) + entry.update( + parent=lambda **x: parents(ctx, rev - 1), + child=lambda **x: children(ctx, rev + 1), + changelogtag=showtags, + files=files, + ) + return entry def symrevorshortnode(req, ctx): if 'node' in req.form: - return revescape(req.form['node'][0]) + return templatefilters.revescape(req.form['node'][0]) else: return short(ctx.node()) @@ -351,29 +387,16 @@ return dict( diff=diff, - rev=ctx.rev(), - node=ctx.hex(), symrev=symrevorshortnode(req, ctx), - parent=tuple(parents(ctx)), - child=children(ctx), basenode=basectx.hex(), changesettag=showtags, changesetbookmark=showbookmarks, changesetbranch=showbranch, - author=ctx.user(), - desc=ctx.description(), - extra=ctx.extra(), - date=ctx.date(), - phase=ctx.phasestr(), files=files, diffsummary=lambda **x: diffsummary(diffstatsgen), diffstat=diffstats, archives=web.archivelist(ctx.hex()), - tags=nodetagsdict(web.repo, ctx.node()), - bookmarks=nodebookmarksdict(web.repo, ctx.node()), - branch=showbranch, - inbranch=nodeinbranch(web.repo, ctx), - branches=nodebranchdict(web.repo, ctx)) + **commonentry(web.repo, ctx)) def listfilediffs(tmpl, files, node, max): for f in files[:max]: @@ -537,7 +560,7 @@ yield {'name': key, 'value': str(value), 'separator': separator} separator = '&' -class wsgiui(ui.ui): +class wsgiui(uimod.ui): # default termwidth breaks under mod_wsgi def termwidth(self): return 80
--- a/mercurial/hgweb/wsgicgi.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/hgweb/wsgicgi.py Sun Jan 17 21:40:21 2016 -0600 @@ -8,9 +8,18 @@ # This was originally copied from the public domain code at # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side -import os, sys -from mercurial import util -from mercurial.hgweb import common +from __future__ import absolute_import + +import os +import sys + +from .. import ( + util, +) + +from . import ( + common, +) def launch(application): util.setbinary(sys.stdin)
--- a/mercurial/httpclient/__init__.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/httpclient/__init__.py Sun Jan 17 21:40:21 2016 -0600 @@ -36,6 +36,7 @@ * notices when the server responds early to a request * implements ssl inline instead of in a different class """ +from __future__ import absolute_import # Many functions in this file have too many arguments. # pylint: disable=R0913 @@ -48,8 +49,10 @@ import select import socket -import _readers -import socketutil +from . import ( + _readers, + socketutil, + ) logger = logging.getLogger(__name__) @@ -124,6 +127,12 @@ # pylint: disable=W0212 self._reader._close() + def getheader(self, header, default=None): + return self.headers.getheader(header, default=default) + + def getheaders(self): + return self.headers.items() + def readline(self): """Read a single line from the response body. @@ -279,6 +288,14 @@ # pylint: disable=W0212 self._load_response = self._reader._load +def _foldheaders(headers): + """Given some headers, rework them so we can safely overwrite values. + + >>> _foldheaders({'Accept-Encoding': 'wat'}) + {'accept-encoding': ('Accept-Encoding', 'wat')} + """ + return dict((k.lower(), (k, v)) for k, v in headers.iteritems()) + class HTTPConnection(object): """Connection to a single http server. @@ -292,7 +309,8 @@ def __init__(self, host, port=None, use_ssl=None, ssl_validator=None, timeout=TIMEOUT_DEFAULT, continue_timeout=TIMEOUT_ASSUME_CONTINUE, - proxy_hostport=None, ssl_wrap_socket=None, **ssl_opts): + proxy_hostport=None, proxy_headers=None, + ssl_wrap_socket=None, **ssl_opts): """Create a new HTTPConnection. Args: @@ -307,6 +325,13 @@ "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE. proxy_hostport: Optional. Tuple of (host, port) to use as an http proxy for the connection. Default is to not use a proxy. + proxy_headers: Optional dict of header keys and values to send to + a proxy when using CONNECT. For compatibility with + httplib, the Proxy-Authorization header may be + specified in headers for request(), which will clobber + any such header specified here if specified. Providing + this option and not proxy_hostport will raise an + ValueError. ssl_wrap_socket: Optional function to use for wrapping sockets. If unspecified, the one from the ssl module will be used if available, or something that's compatible with @@ -330,10 +355,7 @@ elif use_ssl is None: use_ssl = (port == 443) elif port is None: - if use_ssl: - port = 443 - else: - port = 80 + port = (use_ssl and 443 or 80) self.port = port if use_ssl and not socketutil.have_ssl: raise Exception('ssl requested but unavailable on this Python') @@ -346,13 +368,20 @@ self._current_response_taken = False if proxy_hostport is None: self._proxy_host = self._proxy_port = None + if proxy_headers: + raise ValueError( + 'proxy_headers may not be specified unless ' + 'proxy_hostport is also specified.') + else: + self._proxy_headers = {} else: self._proxy_host, self._proxy_port = proxy_hostport + self._proxy_headers = _foldheaders(proxy_headers or {}) self.timeout = timeout self.continue_timeout = continue_timeout - def _connect(self): + def _connect(self, proxy_headers): """Connect to the host and port specified in __init__.""" if self.sock: return @@ -362,10 +391,9 @@ sock = socketutil.create_connection((self._proxy_host, self._proxy_port)) if self.ssl: - # TODO proxy header support data = self._buildheaders('CONNECT', '%s:%d' % (self.host, self.port), - {}, HTTP_VER_1_0) + proxy_headers, HTTP_VER_1_0) sock.send(data) sock.setblocking(0) r = self.response_class(sock, self.timeout, 'CONNECT') @@ -468,10 +496,10 @@ return True return False - def _reconnect(self, where): + def _reconnect(self, where, pheaders): logger.info('reconnecting during %s', where) self.close() - self._connect() + self._connect(pheaders) def request(self, method, path, body=None, headers={}, expect_continue=False): @@ -492,11 +520,20 @@ logger.info('sending %s request for %s to %s on port %s', method, path, self.host, self.port) - hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems()) + hdrs = _foldheaders(headers) if hdrs.get('expect', ('', ''))[1].lower() == '100-continue': expect_continue = True elif expect_continue: hdrs['expect'] = ('Expect', '100-Continue') + # httplib compatibility: if the user specified a + # proxy-authorization header, that's actually intended for a + # proxy CONNECT action, not the real request, but only if + # we're going to use a proxy. + pheaders = dict(self._proxy_headers) + if self._proxy_host and self.ssl: + pa = hdrs.pop('proxy-authorization', None) + if pa is not None: + pheaders['proxy-authorization'] = pa chunked = False if body and HDR_CONTENT_LENGTH not in hdrs: @@ -513,7 +550,7 @@ # conditions where we'll want to retry, so make a note of the # state of self.sock fresh_socket = self.sock is None - self._connect() + self._connect(pheaders) outgoing_headers = self._buildheaders( method, path, hdrs, self.http_version) response = None @@ -588,7 +625,7 @@ logger.info( 'Connection appeared closed in read on first' ' request loop iteration, will retry.') - self._reconnect('read') + self._reconnect('read', pheaders) continue else: # We didn't just send the first data hunk, @@ -645,7 +682,7 @@ elif (e[0] not in (errno.ECONNRESET, errno.EPIPE) and not first): raise - self._reconnect('write') + self._reconnect('write', pheaders) amt = self.sock.send(out) logger.debug('sent %d', amt) first = False @@ -664,8 +701,8 @@ # data at all, and in all probability the socket was # closed before the server even saw our request. Try # the request again on a fresh socket. - logging.debug('response._select() failed during request().' - ' Assuming request needs to be retried.') + logger.debug('response._select() failed during request().' + ' Assuming request needs to be retried.') self.sock = None # Call this method explicitly to re-try the # request. We don't use self.request() because
--- a/mercurial/httpclient/_readers.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/httpclient/_readers.py Sun Jan 17 21:40:21 2016 -0600 @@ -31,6 +31,7 @@ This module is package-private. It is not expected that these will have any clients outside of httpplus. """ +from __future__ import absolute_import import httplib import logging @@ -98,11 +99,12 @@ return result def readto(self, delimstr, blocks = None): - """return available data chunks up to the first one in which delimstr - occurs. No data will be returned after delimstr -- the chunk in which - it occurs will be split and the remainder pushed back onto the available - data queue. If blocks is supplied chunks will be added to blocks, otherwise - a new list will be allocated. + """return available data chunks up to the first one in which + delimstr occurs. No data will be returned after delimstr -- + the chunk in which it occurs will be split and the remainder + pushed back onto the available data queue. If blocks is + supplied chunks will be added to blocks, otherwise a new list + will be allocated. """ if blocks is None: blocks = []
--- a/mercurial/httpclient/socketutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/httpclient/socketutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -32,6 +32,8 @@ socket.create_connection method, but fall back to the old methods if those are unavailable. """ +from __future__ import absolute_import + import logging import socket
--- a/mercurial/httpconnection.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/httpconnection.py Sun Jan 17 21:40:21 2016 -0600 @@ -7,16 +7,21 @@ # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + import logging +import os import socket import urllib import urllib2 -import os -from mercurial import httpclient -from mercurial import sslutil -from mercurial import util -from mercurial.i18n import _ +from .i18n import _ +from . import ( + httpclient, + sslutil, + util, +) # moved here from url.py to avoid a cycle class httpsendfile(object):
--- a/mercurial/httppeer.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/httppeer.py Sun Jan 17 21:40:21 2016 -0600 @@ -254,7 +254,7 @@ os.unlink(filename) def _callcompressable(self, cmd, **args): - stream = self._callstream(cmd, **args) + stream = self._callstream(cmd, **args) return util.chunkbuffer(zgenerator(stream)) def _abort(self, exception):
--- a/mercurial/keepalive.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/keepalive.py Sun Jan 17 21:40:21 2016 -0600 @@ -107,15 +107,17 @@ # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $ +from __future__ import absolute_import, print_function + import errno import httplib import socket +import sys import thread import urllib2 DEBUG = None -import sys if sys.version_info < (2, 4): HANDLE_ERRORS = 1 else: HANDLE_ERRORS = 0 @@ -537,13 +539,13 @@ # NOTE: we DO propagate the error, though, because we cannot simply # ignore the error... the caller will know if they can retry. if self.debuglevel > 0: - print "send:", repr(str) + print("send:", repr(str)) try: blocksize = 8192 read = getattr(str, 'read', None) if read is not None: if self.debuglevel > 0: - print "sending a read()able" + print("sending a read()able") data = read(blocksize) while data: self.sock.sendall(data) @@ -595,7 +597,7 @@ urllib2.install_opener(opener) pos = {0: 'off', 1: 'on'} for i in (0, 1): - print " fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i) + print(" fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i)) HANDLE_ERRORS = i try: fo = urllib2.urlopen(url) @@ -606,17 +608,18 @@ except AttributeError: status, reason = None, None except IOError as e: - print " EXCEPTION: %s" % e + print(" EXCEPTION: %s" % e) raise else: - print " status = %s, reason = %s" % (status, reason) + print(" status = %s, reason = %s" % (status, reason)) HANDLE_ERRORS = orig hosts = keepalive_handler.open_connections() - print "open connections:", hosts + print("open connections:", hosts) keepalive_handler.close_all() def continuity(url): - from util import md5 + from . import util + md5 = util.md5 format = '%25s: %s' # first fetch the file with the normal http handler @@ -626,7 +629,7 @@ foo = fo.read() fo.close() m = md5(foo) - print format % ('normal urllib', m.hexdigest()) + print(format % ('normal urllib', m.hexdigest())) # now install the keepalive handler and try again opener = urllib2.build_opener(HTTPHandler()) @@ -636,7 +639,7 @@ foo = fo.read() fo.close() m = md5(foo) - print format % ('keepalive read', m.hexdigest()) + print(format % ('keepalive read', m.hexdigest())) fo = urllib2.urlopen(url) foo = '' @@ -647,25 +650,25 @@ else: break fo.close() m = md5(foo) - print format % ('keepalive readline', m.hexdigest()) + print(format % ('keepalive readline', m.hexdigest())) def comp(N, url): - print ' making %i connections to:\n %s' % (N, url) + print(' making %i connections to:\n %s' % (N, url)) sys.stdout.write(' first using the normal urllib handlers') # first use normal opener opener = urllib2.build_opener() urllib2.install_opener(opener) t1 = fetch(N, url) - print ' TIME: %.3f s' % t1 + print(' TIME: %.3f s' % t1) sys.stdout.write(' now using the keepalive handler ') # now install the keepalive handler and try again opener = urllib2.build_opener(HTTPHandler()) urllib2.install_opener(opener) t2 = fetch(N, url) - print ' TIME: %.3f s' % t2 - print ' improvement factor: %.2f' % (t1 / t2) + print(' TIME: %.3f s' % t2) + print(' improvement factor: %.2f' % (t1 / t2)) def fetch(N, url, delay=0): import time @@ -684,7 +687,7 @@ for i in lens[1:]: j = j + 1 if not i == lens[0]: - print "WARNING: inconsistent length on read %i: %i" % (j, i) + print("WARNING: inconsistent length on read %i: %i" % (j, i)) return diff @@ -693,16 +696,16 @@ dbbackup = DEBUG class FakeLogger(object): def debug(self, msg, *args): - print msg % args + print(msg % args) info = warning = error = debug DEBUG = FakeLogger() - print " fetching the file to establish a connection" + print(" fetching the file to establish a connection") fo = urllib2.urlopen(url) data1 = fo.read() fo.close() i = 20 - print " waiting %i seconds for the server to close the connection" % i + print(" waiting %i seconds for the server to close the connection" % i) while i > 0: sys.stdout.write('\r %2i' % i) sys.stdout.flush() @@ -710,42 +713,41 @@ i -= 1 sys.stderr.write('\r') - print " fetching the file a second time" + print(" fetching the file a second time") fo = urllib2.urlopen(url) data2 = fo.read() fo.close() if data1 == data2: - print ' data are identical' + print(' data are identical') else: - print ' ERROR: DATA DIFFER' + print(' ERROR: DATA DIFFER') DEBUG = dbbackup def test(url, N=10): - print "checking error handler (do this on a non-200)" + print("checking error handler (do this on a non-200)") try: error_handler(url) except IOError: - print "exiting - exception will prevent further tests" + print("exiting - exception will prevent further tests") sys.exit() - print - print "performing continuity test (making sure stuff isn't corrupted)" + print('') + print("performing continuity test (making sure stuff isn't corrupted)") continuity(url) - print - print "performing speed comparison" + print('') + print("performing speed comparison") comp(N, url) - print - print "performing dropped-connection check" + print('') + print("performing dropped-connection check") test_timeout(url) if __name__ == '__main__': import time - import sys try: N = int(sys.argv[1]) url = sys.argv[2] except (IndexError, ValueError): - print "%s <integer> <url>" % sys.argv[0] + print("%s <integer> <url>" % sys.argv[0]) else: test(url, N)
--- a/mercurial/localrepo.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/localrepo.py Sun Jan 17 21:40:21 2016 -0600 @@ -4,21 +4,60 @@ # # 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, wdirrev, short -from i18n import _ + +from __future__ import absolute_import + +import errno +import inspect +import os +import random +import time import urllib -import peer, changegroup, subrepo, pushkey, obsolete, repoview -import changelog, dirstate, filelog, manifest, context, bookmarks, phases -import lock as lockmod -import transaction, store, encoding, exchange, bundle2 -import scmutil, util, extensions, hook, error, revset, cmdutil -import match as matchmod -import merge as mergemod -import tags as tagsmod -from lock import release -import weakref, errno, os, time, inspect, random -import branchmap, pathutil -import namespaces +import weakref + +from .i18n import _ +from .node import ( + hex, + nullid, + short, + wdirrev, +) +from . import ( + bookmarks, + branchmap, + bundle2, + changegroup, + changelog, + cmdutil, + context, + dirstate, + encoding, + error, + exchange, + extensions, + filelog, + hook, + lock as lockmod, + manifest, + match as matchmod, + merge as mergemod, + namespaces, + obsolete, + pathutil, + peer, + phases, + pushkey, + repoview, + revset, + scmutil, + store, + subrepo, + tags as tagsmod, + transaction, + util, +) + +release = lockmod.release propertycache = util.propertycache filecache = scmutil.filecache @@ -214,6 +253,8 @@ self.path = self.wvfs.join(".hg") self.origroot = path self.auditor = pathutil.pathauditor(self.root, self._checknested) + self.nofsauditor = pathutil.pathauditor(self.root, self._checknested, + realfs=False) self.vfs = scmutil.vfs(self.path) self.opener = self.vfs self.baseui = baseui @@ -258,8 +299,7 @@ '\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): + if scmutil.gdinitconfig(self.ui): self.requirements.add("generaldelta") if self.ui.configbool('experimental', 'treemanifest', False): self.requirements.add("treemanifest") @@ -359,6 +399,7 @@ aggressivemergedeltas = self.ui.configbool('format', 'aggressivemergedeltas', False) self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas + self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui) def _writerequirements(self): scmutil.writerequires(self.vfs, self.requirements) @@ -418,13 +459,13 @@ pass return proxycls(self, name) - @repofilecache('bookmarks') + @repofilecache('bookmarks', 'bookmarks.current') def _bookmarks(self): return bookmarks.bmstore(self) - @repofilecache('bookmarks.current') + @property def _activebookmark(self): - return bookmarks.readactive(self) + return self._bookmarks.active def bookmarkheads(self, bookmark): name = bookmark.split('@', 1)[0] @@ -517,15 +558,23 @@ return iter(self.changelog) def revs(self, expr, *args): - '''Return a list of revisions matching the given revset''' + '''Find revisions matching a revset. + + The revset is specified as a string ``expr`` that may contain + %-formatting to escape certain types. See ``revset.formatspec``. + + Return a revset.abstractsmartset, which is a list-like interface + that contains integer revisions. + ''' expr = revset.formatspec(expr, *args) m = revset.match(None, expr) return m(self) def set(self, expr, *args): - ''' - Yield a context for each matching revision, after doing arg - replacement via revset.formatspec + '''Find revisions matching a revset and emit changectx instances. + + This is a convenience wrapper around ``revs()`` that iterates the + result and is a generator of changectx instances. ''' for r in self.revs(expr, *args): yield self[r] @@ -751,6 +800,7 @@ return self._tagscache.nodetagscache.get(node, []) def nodebookmarks(self, node): + """return the list of bookmarks pointing to the specified node""" marks = [] for bookmark, n in self._bookmarks.iteritems(): if n == node: @@ -797,12 +847,13 @@ return repo[key].branch() def known(self, nodes): - nm = self.changelog.nodemap - pc = self._phasecache + cl = self.changelog + nm = cl.nodemap + filtered = cl.filteredrevs result = [] for n in nodes: r = nm.get(n) - resp = not (r is None or pc.phase(self, r) >= phases.secret) + resp = not (r is None or r in filtered) result.append(resp) return result @@ -840,13 +891,15 @@ f = f[1:] return filelog.filelog(self.svfs, f) + def parents(self, changeid=None): + '''get list of changectxs for parents of changeid''' + msg = 'repo.parents() is deprecated, use repo[%r].parents()' % changeid + self.ui.deprecwarn(msg, '3.7') + return self[changeid].parents() + def changectx(self, changeid): return self[changeid] - def parents(self, changeid=None): - '''get list of changectxs for parents of changeid''' - return self[changeid].parents() - def setparents(self, p1, p2=nullid): self.dirstate.beginparentchange() copies = self.dirstate.setparents(p1, p2) @@ -1032,9 +1085,15 @@ def txnclosehook(tr2): """To be run if transaction is successful, will schedule a hook run """ + # Don't reference tr2 in hook() so we don't hold a reference. + # This reduces memory consumption when there are multiple + # transactions per lock. This can likely go away if issue5045 + # fixes the function accumulation. + hookargs = tr2.hookargs + def hook(): reporef().hook('txnclose', throw=False, txnname=desc, - **tr2.hookargs) + **hookargs) reporef()._afterlock(hook) tr.addfinalize('txnclose-hook', txnclosehook) def txnaborthook(tr2): @@ -1073,8 +1132,7 @@ self.svfs.tryread("phaseroots")) def recover(self): - lock = self.lock() - try: + with self.lock(): if self.svfs.exists("journal"): self.ui.status(_("rolling back interrupted transaction\n")) vfsmap = {'': self.svfs, @@ -1086,8 +1144,6 @@ else: self.ui.warn(_("no interrupted transaction available\n")) return False - finally: - lock.release() def rollback(self, dryrun=False, force=False): wlock = lock = dsguard = None @@ -1161,15 +1217,14 @@ % self.dirstate.branch()) self.dirstate.invalidate() - parents = tuple([p.rev() for p in self.parents()]) + parents = tuple([p.rev() for p in self[None].parents()]) if len(parents) > 1: ui.status(_('working directory now based on ' 'revisions %d and %d\n') % parents) else: ui.status(_('working directory now based on ' 'revision %d\n') % parents) - ms = mergemod.mergestate(self) - ms.reset(self['.'].node()) + mergemod.mergestate.clean(self, self['.'].node()) # TODO: if we know which new heads may result from this rollback, pass # them to destroy(), which will prevent the branchhead cache from being @@ -1456,8 +1511,11 @@ match.explicitdir = vdirs.append match.bad = fail - wlock = self.wlock() + wlock = lock = tr = None try: + wlock = self.wlock() + lock = self.lock() # for recent changelog (see issue4368) + wctx = self[None] merge = len(wctx.parents()) > 1 @@ -1556,7 +1614,7 @@ if merge and cctx.deleted(): raise error.Abort(_("cannot commit merge with missing files")) - ms = mergemod.mergestate(self) + ms = mergemod.mergestate.read(self) if list(ms.unresolved()): raise error.Abort(_('unresolved merge conflicts ' @@ -1589,19 +1647,21 @@ try: self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2) + tr = self.transaction('commit') ret = self.commitctx(cctx, True) except: # re-raises if edited: self.ui.write( _('note: commit message saved in %s\n') % msgfn) raise - # update bookmarks, dirstate and mergestate bookmarks.update(self, [p1, p2], ret) cctx.markcommitted(ret) ms.reset() + tr.close() + finally: - wlock.release() + lockmod.release(tr, lock, wlock) def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2): # hack for command that use a temporary commit (eg: histedit) @@ -1838,22 +1898,6 @@ """ return util.hooks() - def clone(self, remote, heads=[], stream=None): - '''clone remote repository. - - keyword arguments: - heads: list of revs to clone (forces use of pull) - stream: use streaming clone if possible''' - # internal config: ui.quietbookmarkmove - quiet = self.ui.backupconfig('ui', 'quietbookmarkmove') - try: - self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone') - pullop = exchange.pull(self, remote, heads, - streamclonerequested=stream) - return pullop.cgresult - finally: - self.ui.restoreconfig(quiet) - def pushkey(self, namespace, key, old, new): try: tr = self.currenttransaction()
--- a/mercurial/lock.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/lock.py Sun Jan 17 21:40:21 2016 -0600 @@ -58,6 +58,12 @@ if self.acquirefn: self.acquirefn() + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self.release() + def __del__(self): if self.held: warnings.warn("use lock.release instead of del lock",
--- a/mercurial/lsprof.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/lsprof.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,12 @@ +from __future__ import absolute_import, print_function + +import _lsprof import sys -from _lsprof import Profiler, profiler_entry + +Profiler = _lsprof.Profiler + +# PyPy doesn't expose profiler_entry from the module. +profiler_entry = getattr(_lsprof, 'profiler_entry', None) __all__ = ['profile', 'Stats'] @@ -22,8 +29,13 @@ def sort(self, crit="inlinetime"): """XXX docstring""" - if crit not in profiler_entry.__dict__: + # profiler_entries isn't defined when running under PyPy. + if profiler_entry: + if crit not in profiler_entry.__dict__: + raise ValueError("Can't sort by %s" % crit) + elif self.data and not getattr(self.data[0], crit, None): raise ValueError("Can't sort by %s" % crit) + self.data.sort(key=lambda x: getattr(x, crit), reverse=True) for e in self.data: if e.calls: @@ -101,7 +113,7 @@ import os sys.argv = sys.argv[1:] if not sys.argv: - print >> sys.stderr, "usage: lsprof.py <script> <arguments...>" + print("usage: lsprof.py <script> <arguments...>", file=sys.stderr) sys.exit(2) sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0]))) stats = profile(execfile, sys.argv[0], globals(), locals())
--- a/mercurial/lsprofcalltree.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/lsprofcalltree.py Sun Jan 17 21:40:21 2016 -0600 @@ -10,6 +10,8 @@ of the GNU General Public License, incorporated herein by reference. """ +from __future__ import absolute_import, print_function + def label(code): if isinstance(code, str): return '~' + code # built-in functions ('~' sorts at the end) @@ -25,7 +27,7 @@ def output(self, out_file): self.out_file = out_file - print >> out_file, 'events: Ticks' + print('events: Ticks', file=out_file) self._print_summary() for entry in self.data: self._entry(entry) @@ -35,24 +37,23 @@ for entry in self.data: totaltime = int(entry.totaltime * 1000) max_cost = max(max_cost, totaltime) - print >> self.out_file, 'summary: %d' % (max_cost,) + print('summary: %d' % max_cost, file=self.out_file) def _entry(self, entry): out_file = self.out_file code = entry.code - #print >> out_file, 'ob=%s' % (code.co_filename,) if isinstance(code, str): - print >> out_file, 'fi=~' + print('fi=~', file=out_file) else: - print >> out_file, 'fi=%s' % (code.co_filename,) - print >> out_file, 'fn=%s' % (label(code),) + print('fi=%s' % code.co_filename, file=out_file) + print('fn=%s' % label(code), file=out_file) inlinetime = int(entry.inlinetime * 1000) if isinstance(code, str): - print >> out_file, '0 ', inlinetime + print('0 ', inlinetime, file=out_file) else: - print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime) + print('%d %d' % (code.co_firstlineno, inlinetime), file=out_file) # recursive calls are counted in entry.calls if entry.calls: @@ -67,20 +68,19 @@ for subentry in calls: self._subentry(lineno, subentry) - print >> out_file + print(file=out_file) def _subentry(self, lineno, subentry): out_file = self.out_file code = subentry.code - #print >> out_file, 'cob=%s' % (code.co_filename,) - print >> out_file, 'cfn=%s' % (label(code),) + print('cfn=%s' % label(code), file=out_file) if isinstance(code, str): - print >> out_file, 'cfi=~' - print >> out_file, 'calls=%d 0' % (subentry.callcount,) + print('cfi=~', file=out_file) + print('calls=%d 0' % subentry.callcount, file=out_file) else: - print >> out_file, 'cfi=%s' % (code.co_filename,) - print >> out_file, 'calls=%d %d' % ( - subentry.callcount, code.co_firstlineno) + print('cfi=%s' % code.co_filename, file=out_file) + print('calls=%d %d' % ( + subentry.callcount, code.co_firstlineno), file=out_file) totaltime = int(subentry.totaltime * 1000) - print >> out_file, '%d %d' % (lineno, totaltime) + print('%d %d' % (lineno, totaltime), file=out_file)
--- a/mercurial/mail.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/mail.py Sun Jan 17 21:40:21 2016 -0600 @@ -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 __future__ import absolute_import +from __future__ import absolute_import, print_function import email import os @@ -81,7 +81,7 @@ def _get_socket(self, host, port, timeout): if self.debuglevel > 0: - print >> sys.stderr, 'connect:', (host, port) + print('connect:', (host, port), file=sys.stderr) new_socket = socket.create_connection((host, port), timeout) new_socket = sslutil.wrapsocket(new_socket, self.keyfile, self.certfile,
--- a/mercurial/manifest.c Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/manifest.c Sun Jan 17 21:40:21 2016 -0600 @@ -242,7 +242,7 @@ hash = nodeof(l); consumed = pl + 41; flags = PyString_FromStringAndSize(l->start + consumed, - l->len - consumed - 1); + l->len - consumed - 1); if (!path || !hash || !flags) { goto done; } @@ -558,7 +558,7 @@ (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */ }; -/* sequence methods (important or __contains__ builds an iterator */ +/* sequence methods (important or __contains__ builds an iterator) */ static int lazymanifest_contains(lazymanifest *self, PyObject *key) { @@ -694,6 +694,9 @@ goto nomem; } copy = PyObject_New(lazymanifest, &lazymanifestType); + if (!copy) { + goto nomem; + } copy->dirty = true; copy->lines = malloc(self->maxlines * sizeof(line)); if (!copy->lines) { @@ -704,11 +707,13 @@ copy->pydata = self->pydata; Py_INCREF(self->pydata); for (i = 0; i < self->numlines; i++) { - PyObject *arg = PyString_FromString(self->lines[i].start); - PyObject *arglist = PyTuple_Pack(1, arg); - PyObject *result = PyObject_CallObject(matchfn, arglist); + PyObject *arglist = NULL, *result = NULL; + arglist = Py_BuildValue("(s)", self->lines[i].start); + if (!arglist) { + return NULL; + } + result = PyObject_CallObject(matchfn, arglist); Py_DECREF(arglist); - Py_DECREF(arg); /* if the callback raised an exception, just let it * through and give up */ if (!result) {
--- a/mercurial/manifest.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/manifest.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,11 +5,21 @@ # 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 mdiff, parsers, error, revlog, util -import array, struct +from __future__ import absolute_import + +import array +import heapq import os -import heapq +import struct + +from .i18n import _ +from . import ( + error, + mdiff, + parsers, + revlog, + util, +) propertycache = util.propertycache @@ -334,36 +344,44 @@ # zero copy representation of base as a buffer addbuf = util.buffer(base) - # start with a readonly loop that finds the offset of - # each line and creates the deltas - for f, todelete in changes: - # bs will either be the index of the item or the insert point - start, end = _msearch(addbuf, f, start) - if not todelete: - h, fl = self._lm[f] - l = "%s\0%s%s\n" % (f, revlog.hex(h), fl) - else: - if start == end: - # item we want to delete was not found, error out - raise AssertionError( - _("failed to remove %s from manifest") % f) - l = "" - if dstart is not None and dstart <= start and dend >= start: - if dend < end: + changes = list(changes) + if len(changes) < 1000: + # start with a readonly loop that finds the offset of + # each line and creates the deltas + for f, todelete in changes: + # bs will either be the index of the item or the insert point + start, end = _msearch(addbuf, f, start) + if not todelete: + h, fl = self._lm[f] + l = "%s\0%s%s\n" % (f, revlog.hex(h), fl) + else: + if start == end: + # item we want to delete was not found, error out + raise AssertionError( + _("failed to remove %s from manifest") % f) + l = "" + if dstart is not None and dstart <= start and dend >= start: + if dend < end: + dend = end + if l: + dline.append(l) + else: + if dstart is not None: + delta.append([dstart, dend, "".join(dline)]) + dstart = start dend = end - if l: - dline.append(l) - else: - if dstart is not None: - delta.append([dstart, dend, "".join(dline)]) - dstart = start - dend = end - dline = [l] + dline = [l] - if dstart is not None: - delta.append([dstart, dend, "".join(dline)]) - # apply the delta to the base, and get a delta for addrevision - deltatext, arraytext = _addlistdelta(base, delta) + if dstart is not None: + delta.append([dstart, dend, "".join(dline)]) + # apply the delta to the base, and get a delta for addrevision + deltatext, arraytext = _addlistdelta(base, delta) + else: + # For large changes, it's much cheaper to just build the text and + # diff it. + arraytext = array.array('c', self.text()) + deltatext = mdiff.textdiff(base, arraytext) + return arraytext, deltatext def _msearch(m, s, lo=0, hi=None): @@ -609,7 +627,7 @@ def setflag(self, f, flags): """Set the flags (symlink, executable) for path f.""" - assert 'd' not in flags + assert 't' not in flags self._load() dir, subpath = _splittopdir(f) if dir: @@ -732,9 +750,12 @@ def _matches(self, match): '''recursively generate a new manifest filtered by the match argument. ''' + + visit = match.visitdir(self._dir[:-1] or '.') + if visit == 'all': + return self.copy() ret = treemanifest(self._dir) - - if not match.visitdir(self._dir[:-1] or '.'): + if not visit: return ret self._load() @@ -807,7 +828,7 @@ def parse(self, text, readsubtree): for f, n, fl in _parse(text): - if fl == 'd': + if fl == 't': f = f + '/' self._dirs[f] = readsubtree(self._subpath(f), n) elif '/' in f: @@ -838,7 +859,7 @@ """ self._load() flags = self.flags - dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs] + dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs] files = [(f, self._files[f], flags(f)) for f in self._files] return _text(sorted(dirs + files), usemanifestv2) @@ -1024,3 +1045,8 @@ # Save nodeid so parent manifest can calculate its nodeid m.setnode(n) return n + + def clearcaches(self): + super(manifest, self).clearcaches() + self._mancache.clear() + self._dirlogcache = {'': self}
--- a/mercurial/match.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/match.py Sun Jan 17 21:40:21 2016 -0600 @@ -227,9 +227,15 @@ has potential matches in it or one of its subdirectories. This is based on the match's primary, included, and excluded patterns. + 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. + This function's behavior is undefined if it has returned False for one of the dir's parent directories. ''' + if self.prefix() and dir in self._fileroots: + return 'all' if dir in self._excluderoots: return False if (self._includeroots and @@ -626,7 +632,7 @@ _commentre = None -def readpatternfile(filepath, warn): +def readpatternfile(filepath, warn, sourceinfo=False): '''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. @@ -642,7 +648,11 @@ 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''' + pattern # pattern of the current default type + + if sourceinfo is set, returns a list of tuples: + (pattern, lineno, originalline). This is useful to debug ignore patterns. + ''' syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:', 'include': 'include', 'subinclude': 'subinclude'} @@ -650,13 +660,15 @@ patterns = [] fp = open(filepath) - for line in fp: + for lineno, line in enumerate(fp, start=1): if "#" in line: global _commentre if not _commentre: - _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*') + _commentre = util.re.compile(r'((?:^|[^\\])(?:\\\\)*)#.*') # remove comments prefixed by an even number of escapes - line = _commentre.sub(r'\1', line) + m = _commentre.search(line) + if m: + line = line[:m.end(1)] # fixup properly escaped comments that survived the above line = line.replace("\\#", "#") line = line.rstrip() @@ -683,6 +695,9 @@ linesyntax = rels line = line[len(s) + 1:] break - patterns.append(linesyntax + line) + if sourceinfo: + patterns.append((linesyntax + line, lineno, line)) + else: + patterns.append(linesyntax + line) fp.close() return patterns
--- a/mercurial/mdiff.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/mdiff.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,9 +5,20 @@ # 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 bdiff, mpatch, util, base85, error -import re, struct, zlib +from __future__ import absolute_import + +import re +import struct +import zlib + +from .i18n import _ +from . import ( + base85, + bdiff, + error, + mpatch, + util, +) def splitnewlines(text): '''like str.splitlines, but only split on newlines.''' @@ -365,7 +376,7 @@ return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)] def trivialdiffheader(length): - return struct.pack(">lll", 0, 0, length) + return struct.pack(">lll", 0, 0, length) if length else '' def replacediffheader(oldlen, newlen): return struct.pack(">lll", 0, oldlen, newlen)
--- a/mercurial/merge.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/merge.py Sun Jan 17 21:40:21 2016 -0600 @@ -16,6 +16,7 @@ from .node import ( bin, hex, + nullhex, nullid, nullrev, ) @@ -25,6 +26,7 @@ error, filemerge, obsolete, + scmutil, subrepo, util, worker, @@ -42,50 +44,69 @@ class mergestate(object): '''track 3-way merge state of individual files - it is stored on disk when needed. Two file are used, one with an old - format, one with a new format. Both contains similar data, but the new - format can store new kinds of field. - - Current new format is a list of arbitrary record of the form: - - [type][length][content] + The merge state is stored on disk when needed. Two files are used: one with + an old format (version 1), and one with a new format (version 2). Version 2 + stores a superset of the data in version 1, including new kinds of records + in the future. For more about the new format, see the documentation for + `_readrecordsv2`. - Type is a single character, length is a 4 bytes integer, content is an - arbitrary suites of bytes of length `length`. + Each record can contain arbitrary content, and has an associated type. This + `type` should be a letter. If `type` is uppercase, the record is mandatory: + versions of Mercurial that don't support it should abort. If `type` is + lowercase, the record can be safely ignored. - Type should be a letter. Capital letter are mandatory record, Mercurial - should abort if they are unknown. lower case record can be safely ignored. - - Currently known record: + Currently known records: L: the node of the "local" part of the merge (hexified version) O: the node of the "other" part of the merge (hexified version) F: a file to be merged entry + C: a change/delete or delete/change conflict D: a file that the external merge driver will merge internally (experimental) m: the external merge driver defined for this merge plus its run state (experimental) + X: unsupported mandatory record type (used in tests) + x: unsupported advisory record type (used in tests) Merge driver run states (experimental): u: driver-resolved files unmarked -- needs to be run next time we're about to resolve or commit m: driver-resolved files marked -- only needs to be run before commit s: success/skipped -- does not need to be run any more + ''' statepathv1 = 'merge/state' statepathv2 = 'merge/state2' + @staticmethod + def clean(repo, node=None, other=None): + """Initialize a brand new merge state, removing any existing state on + disk.""" + ms = mergestate(repo) + ms.reset(node, other) + return ms + + @staticmethod + def read(repo): + """Initialize the merge state, reading it from disk.""" + ms = mergestate(repo) + ms._read() + return ms + def __init__(self, repo): + """Initialize the merge state. + + Do not use this directly! Instead call read() or clean().""" self._repo = repo self._dirty = False - self._read() def reset(self, node=None, other=None): self._state = {} self._local = None self._other = None - if 'otherctx' in vars(self): - del self.otherctx + for var in ('localctx', 'otherctx'): + if var in vars(self): + delattr(self, var) if node: self._local = node self._other = other @@ -95,6 +116,7 @@ else: self._mdstate = 'u' shutil.rmtree(self._repo.join('merge'), True) + self._results = {} self._dirty = False def _read(self): @@ -106,10 +128,12 @@ self._state = {} self._local = None self._other = None - if 'otherctx' in vars(self): - del self.otherctx + for var in ('localctx', 'otherctx'): + if var in vars(self): + delattr(self, var) self._readmergedriver = None self._mdstate = 's' + unsupported = set() records = self._readrecords() for rtype, record in records: if rtype == 'L': @@ -125,14 +149,17 @@ self._readmergedriver = bits[0] self._mdstate = mdstate - elif rtype in 'FD': + elif rtype in 'FDC': bits = record.split('\0') self._state[bits[0]] = bits[1:] elif not rtype.islower(): - raise error.Abort(_('unsupported merge state record: %s') - % rtype) + unsupported.add(rtype) + self._results = {} self._dirty = False + if unsupported: + raise error.UnsupportedMergeRecords(unsupported) + def _readrecords(self): """Read merge state from disk and return a list of record (TYPE, data) @@ -206,8 +233,21 @@ def _readrecordsv2(self): """read on disk merge state for version 2 file - returns list of record [(TYPE, data), ...] - """ + This format is a list of arbitrary records of the form: + + [type][length][content] + + `type` is a single character, `length` is a 4 byte integer, and + `content` is an arbitrary byte sequence of length `length`. + + Mercurial versions prior to 3.7 have a bug where if there are + unsupported mandatory merge records, attempting to clear out the merge + state with hg update --clean or similar aborts. The 't' record type + works around that by writing out what those versions treat as an + advisory record, but later versions interpret as special: the first + character is the 'real' record type and everything onwards is the data. + + Returns list of records [(TYPE, data), ...].""" records = [] try: f = self._repo.vfs(self.statepathv2) @@ -221,6 +261,8 @@ off += 4 record = data[off:(off + length)] off += length + if rtype == 't': + rtype, record = record[0], record[1:] records.append((rtype, record)) f.close() except IOError as err: @@ -248,7 +290,15 @@ return configmergedriver @util.propertycache + def localctx(self): + if self._local is None: + raise RuntimeError("localctx accessed but self._local isn't set") + return self._repo[self._local] + + @util.propertycache def otherctx(self): + if self._other is None: + raise RuntimeError("localctx accessed but self._local isn't set") return self._repo[self._other] def active(self): @@ -266,20 +316,28 @@ def commit(self): """Write current state on disk (if necessary)""" if self._dirty: - records = [] - records.append(('L', hex(self._local))) - records.append(('O', hex(self._other))) - if self.mergedriver: - records.append(('m', '\0'.join([ - self.mergedriver, self._mdstate]))) - for d, v in self._state.iteritems(): - if v[0] == 'd': - records.append(('D', '\0'.join([d] + v))) - else: - records.append(('F', '\0'.join([d] + v))) + records = self._makerecords() self._writerecords(records) self._dirty = False + def _makerecords(self): + records = [] + records.append(('L', hex(self._local))) + records.append(('O', hex(self._other))) + if self.mergedriver: + records.append(('m', '\0'.join([ + self.mergedriver, self._mdstate]))) + for d, v in self._state.iteritems(): + if v[0] == 'd': + records.append(('D', '\0'.join([d] + v))) + # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by + # older versions of Mercurial + elif v[1] == nullhex or v[6] == nullhex: + records.append(('C', '\0'.join([d] + v))) + else: + records.append(('F', '\0'.join([d] + v))) + return records + def _writerecords(self, records): """Write current state on disk (both v1 and v2)""" self._writerecordsv1(records) @@ -298,10 +356,16 @@ f.close() def _writerecordsv2(self, records): - """Write current state on disk in a version 2 file""" + """Write current state on disk in a version 2 file + + See the docstring for _readrecordsv2 for why we use 't'.""" + # these are the records that all version 2 clients can read + whitelist = 'LOF' f = self._repo.vfs(self.statepathv2, 'w') for key, data in records: assert len(key) == 1 + if key not in whitelist: + key, data = 't', '%s%s' % (key, data) format = '>sI%is' % len(data) f.write(_pack(format, key, len(data), data)) f.close() @@ -315,8 +379,11 @@ note: also write the local version to the `.hg/merge` directory. """ - hash = util.sha1(fcl.path()).hexdigest() - self._repo.vfs.write('merge/' + hash, fcl.data()) + if fcl.isabsent(): + hash = nullhex + else: + hash = util.sha1(fcl.path()).hexdigest() + self._repo.vfs.write('merge/' + hash, fcl.data()) self._state[fd] = ['u', hash, fcl.path(), fca.path(), hex(fca.filenode()), fco.path(), hex(fco.filenode()), @@ -363,8 +430,9 @@ stateentry = self._state[dfile] state, hash, lfile, afile, anode, ofile, onode, flags = stateentry octx = self._repo[self._other] - fcd = wctx[dfile] - fco = octx[ofile] + fcd = self._filectxorabsent(hash, wctx, dfile) + fco = self._filectxorabsent(onode, octx, ofile) + # TODO: move this to filectxorabsent fca = self._repo.filectx(afile, fileid=anode) # "premerge" x flags flo = fco.flags() @@ -378,29 +446,127 @@ flags = flo if preresolve: # restore local - f = self._repo.vfs('merge/' + hash) - self._repo.wwrite(dfile, f.read(), flags) - f.close() - complete, r = filemerge.premerge(self._repo, self._local, lfile, - fcd, fco, fca, labels=labels) + if hash != nullhex: + f = self._repo.vfs('merge/' + hash) + self._repo.wwrite(dfile, f.read(), flags) + f.close() + else: + self._repo.wvfs.unlinkpath(dfile, ignoremissing=True) + complete, r, deleted = filemerge.premerge(self._repo, self._local, + lfile, fcd, fco, fca, + labels=labels) else: - complete, r = filemerge.filemerge(self._repo, self._local, lfile, - fcd, fco, fca, labels=labels) + complete, r, deleted = filemerge.filemerge(self._repo, self._local, + lfile, fcd, fco, fca, + labels=labels) if r is None: # no real conflict del self._state[dfile] self._dirty = True elif not r: self.mark(dfile, 'r') + + if complete: + action = None + if deleted: + if fcd.isabsent(): + # dc: local picked. Need to drop if present, which may + # happen on re-resolves. + action = 'f' + else: + # cd: remote picked (or otherwise deleted) + action = 'r' + else: + if fcd.isabsent(): # dc: remote picked + action = 'g' + elif fco.isabsent(): # cd: local picked + if dfile in self.localctx: + action = 'am' + else: + action = 'a' + # else: regular merges (no action necessary) + self._results[dfile] = r, action + return complete, r + def _filectxorabsent(self, hexnode, ctx, f): + if hexnode == nullhex: + return filemerge.absentfilectx(ctx, f) + else: + return ctx[f] + def preresolve(self, dfile, wctx, labels=None): + """run premerge process for dfile + + Returns whether the merge is complete, and the exit code.""" return self._resolve(True, dfile, wctx, labels=labels) def resolve(self, dfile, wctx, labels=None): - """rerun merge process for file path `dfile`""" + """run merge process (assuming premerge was run) for dfile + + Returns the exit code of the merge.""" return self._resolve(False, dfile, wctx, labels=labels)[1] + def counts(self): + """return counts for updated, merged and removed files in this + session""" + updated, merged, removed = 0, 0, 0 + for r, action in self._results.itervalues(): + if r is None: + updated += 1 + elif r == 0: + if action == 'r': + removed += 1 + else: + merged += 1 + return updated, merged, removed + + def unresolvedcount(self): + """get unresolved count for this merge (persistent)""" + return len([True for f, entry in self._state.iteritems() + if entry[0] == 'u']) + + def actions(self): + """return lists of actions to perform on the dirstate""" + actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []} + for f, (r, action) in self._results.iteritems(): + if action is not None: + actions[action].append((f, None, "merge result")) + return actions + + def recordactions(self): + """record remove/add/get actions in the dirstate""" + branchmerge = self._repo.dirstate.p2() != nullid + recordupdates(self._repo, self.actions(), branchmerge) + + def queueremove(self, f): + """queues a file to be removed from the dirstate + + Meant for use by custom merge drivers.""" + self._results[f] = 0, 'r' + + def queueadd(self, f): + """queues a file to be added to the dirstate + + Meant for use by custom merge drivers.""" + self._results[f] = 0, 'a' + + def queueget(self, f): + """queues a file to be marked modified in the dirstate + + Meant for use by custom merge drivers.""" + self._results[f] = 0, 'g' + +def _getcheckunknownconfig(repo, section, name): + config = repo.ui.config(section, name, default='abort') + valid = ['abort', 'ignore', 'warn'] + if config not in valid: + validstr = ', '.join(["'" + v + "'" for v in valid]) + raise error.ConfigError(_("%s.%s not valid " + "('%s' is none of %s)") + % (section, name, config, validstr)) + return config + def _checkunknownfile(repo, wctx, mctx, f, f2=None): if f2 is None: f2 = f @@ -415,25 +581,45 @@ files. For some actions, the result is to abort; for others, it is to choose a different action. """ - aborts = [] + conflicts = set() if not force: + abortconflicts = set() + warnconflicts = set() + def collectconflicts(conflicts, config): + if config == 'abort': + abortconflicts.update(conflicts) + elif config == 'warn': + warnconflicts.update(conflicts) + + unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown') + ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored') for f, (m, args, msg) in actions.iteritems(): if m in ('c', 'dc'): if _checkunknownfile(repo, wctx, mctx, f): - aborts.append(f) + conflicts.add(f) elif m == 'dg': if _checkunknownfile(repo, wctx, mctx, f, args[0]): - aborts.append(f) + conflicts.add(f) - for f in sorted(aborts): - repo.ui.warn(_("%s: untracked file differs\n") % f) - if aborts: - raise error.Abort(_("untracked files in working directory differ " - "from files in requested revision")) + ignoredconflicts = set([c for c in conflicts + if repo.dirstate._ignore(c)]) + unknownconflicts = conflicts - ignoredconflicts + collectconflicts(ignoredconflicts, ignoredconfig) + collectconflicts(unknownconflicts, unknownconfig) + for f in sorted(abortconflicts): + repo.ui.warn(_("%s: untracked file differs\n") % f) + if abortconflicts: + raise error.Abort(_("untracked files in working directory " + "differ from files in requested revision")) + + for f in sorted(warnconflicts): + repo.ui.warn(_("%s: replacing untracked file\n") % f) for f, (m, args, msg) in actions.iteritems(): + backup = f in conflicts if m == 'c': - actions[f] = ('g', args, msg) + flags, = args + actions[f] = ('g', (flags, backup), msg) elif m == 'cm': fl2, anc = args different = _checkunknownfile(repo, wctx, mctx, f) @@ -441,7 +627,7 @@ actions[f] = ('m', (f, f, None, False, anc), "remote differs from untracked local") else: - actions[f] = ('g', (fl2,), "remote created") + actions[f] = ('g', (fl2, backup), "remote created") def _forgetremoved(wctx, mctx, branchmerge): """ @@ -479,7 +665,7 @@ if actions: # k, dr, e and rd are no-op - for m in 'a', 'f', 'g', 'cd', 'dc': + for m in 'a', 'am', 'f', 'g', 'cd', 'dc': for f, args, msg in actions[m]: pmmf.add(f) for f, args, msg in actions['r']: @@ -528,15 +714,17 @@ This is currently not implemented -- it's an extension point.""" return True -def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, +def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher, acceptremote, followcopies): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update - partial = function to filter file lists + matcher = matcher to filter file lists acceptremote = accept the incoming changes without prompting """ + if matcher is not None and matcher.always(): + matcher = None copy, movewithdir, diverge, renamedelete = {}, {}, {}, {} @@ -550,7 +738,7 @@ repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" - % (bool(branchmerge), bool(force), bool(partial))) + % (bool(branchmerge), bool(force), bool(matcher))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() @@ -565,12 +753,13 @@ break # Compare manifests + if matcher is not None: + m1 = m1.matches(matcher) + m2 = m2.matches(matcher) diff = m1.diff(m2) actions = {} for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): - if partial and not partial(f): - continue if n1 and n2: # file exists on both local and remote side if f not in ma: fa = copy.get(f, None) @@ -590,11 +779,11 @@ if n1 == n2: # optimization: keep local content actions[f] = ('e', (fl2,), "update permissions") else: - actions[f] = ('g', (fl2,), "remote is newer") + actions[f] = ('g', (fl2, False), "remote is newer") elif nol and n2 == a: # remote only changed 'x' actions[f] = ('e', (fl2,), "update permissions") elif nol and n1 == a: # local only changed 'x' - actions[f] = ('g', (fl1,), "remote is newer") + actions[f] = ('g', (fl1, False), "remote is newer") else: # both changed something actions[f] = ('m', (f, f, f, False, pa.node()), "versions differ") @@ -618,7 +807,8 @@ if acceptremote: actions[f] = ('r', None, "remote delete") else: - actions[f] = ('cd', None, "prompt changed/deleted") + actions[f] = ('cd', (f, None, f, False, pa.node()), + "prompt changed/deleted") elif n1[20:] == 'a': # This extra 'a' is added by working copy manifest to mark # the file as locally added. We should forget it instead of @@ -668,7 +858,8 @@ if acceptremote: actions[f] = ('c', (fl2,), "remote recreating") else: - actions[f] = ('dc', (fl2,), "prompt deleted/changed") + actions[f] = ('dc', (None, f, f, False, pa.node()), + "prompt deleted/changed") return actions, diverge, renamedelete @@ -684,13 +875,12 @@ # remote did change but ended up with same content del actions[f] # don't get = keep local deleted -def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial, - acceptremote, followcopies): +def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, + acceptremote, followcopies, matcher=None): "Calculate the actions needed to merge mctx into wctx using ancestors" - if len(ancestors) == 1: # default actions, diverge, renamedelete = manifestmerge( - repo, wctx, mctx, ancestors[0], branchmerge, force, partial, + repo, wctx, mctx, ancestors[0], branchmerge, force, matcher, acceptremote, followcopies) _checkunknownfiles(repo, wctx, mctx, force, actions) @@ -705,7 +895,7 @@ for ancestor in ancestors: repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor) actions, diverge1, renamedelete1 = manifestmerge( - repo, wctx, mctx, ancestor, branchmerge, force, partial, + repo, wctx, mctx, ancestor, branchmerge, force, matcher, acceptremote, followcopies) _checkunknownfiles(repo, wctx, mctx, force, actions) @@ -811,12 +1001,27 @@ verbose = repo.ui.verbose fctx = mctx.filectx wwrite = repo.wwrite + ui = repo.ui i = 0 - for f, args, msg in actions: + for f, (flags, backup), msg in actions: repo.ui.debug(" %s: %s -> g\n" % (f, msg)) if verbose: repo.ui.note(_("getting %s\n") % f) - wwrite(f, fctx(f).data(), args[0]) + + if backup: + absf = repo.wjoin(f) + orig = scmutil.origpath(ui, repo, absf) + try: + # TODO Mercurial has always aborted if an untracked directory + # is replaced by a tracked file, or generally with + # file/directory merges. This needs to be sorted out. + if repo.wvfs.isfileorlink(f): + util.rename(absf, orig) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + wwrite(f, fctx(f).data(), flags) if i == 100: yield i, f i = 0 @@ -834,25 +1039,34 @@ describes how many files were affected by the update. """ - updated, merged, removed, unresolved = 0, 0, 0, 0 - ms = mergestate(repo) - ms.reset(wctx.p1().node(), mctx.node()) + updated, merged, removed = 0, 0, 0 + ms = mergestate.clean(repo, wctx.p1().node(), mctx.node()) moves = [] for m, l in actions.items(): l.sort() - # prescan for merges - for f, args, msg in actions['m']: + # 'cd' and 'dc' actions are treated like other merge conflicts + mergeactions = sorted(actions['cd']) + mergeactions.extend(sorted(actions['dc'])) + mergeactions.extend(actions['m']) + for f, args, msg in mergeactions: f1, f2, fa, move, anc = args if f == '.hgsubstate': # merged internally continue - repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f)) - fcl = wctx[f1] - fco = mctx[f2] + if f1 is None: + fcl = filemerge.absentfilectx(wctx, fa) + else: + repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f)) + fcl = wctx[f1] + if f2 is None: + fco = filemerge.absentfilectx(mctx, fa) + else: + fco = mctx[f2] actx = repo[anc] if fa in actx: fca = actx[fa] else: + # TODO: move to absentfilectx fca = repo.filectx(f1, fileid=nullrev) ms.add(fcl, fco, fca, f) if f1 != f and move: @@ -905,6 +1119,12 @@ z += 1 progress(_updating, z, item=f, total=numupdates, unit=_files) + # re-add/mark as modified (manifest only, just log it) + for f, args, msg in actions['am']: + repo.ui.debug(" %s: %s -> am\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + # keep (noop, just log it) for f, args, msg in actions['k']: repo.ui.debug(" %s: %s -> k\n" % (f, msg)) @@ -942,7 +1162,6 @@ util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags) updated += 1 - mergeactions = actions['m'] # the ordering is important here -- ms.mergedriver will raise if the merge # driver has changed, and we want to be able to bypass it when overwrite is # True @@ -965,7 +1184,7 @@ # premerge tocomplete = [] - for f, args, msg in actions['m']: + for f, args, msg in mergeactions: repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg)) z += 1 progress(_updating, z, item=f, total=numupdates, unit=_files) @@ -975,15 +1194,7 @@ continue audit(f) complete, r = ms.preresolve(f, wctx, labels=labels) - if complete: - if r is not None and r > 0: - unresolved += 1 - else: - if r is None: - updated += 1 - else: - merged += 1 - else: + if not complete: numupdates += 1 tocomplete.append((f, args, msg)) @@ -992,25 +1203,29 @@ repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg)) z += 1 progress(_updating, z, item=f, total=numupdates, unit=_files) - r = ms.resolve(f, wctx, labels=labels) - if r is not None and r > 0: - unresolved += 1 - else: - if r is None: - updated += 1 - else: - merged += 1 + ms.resolve(f, wctx, labels=labels) ms.commit() + unresolved = ms.unresolvedcount() + if usemergedriver and not unresolved and ms.mdstate() != 's': if not driverconclude(repo, ms, wctx, labels=labels): # XXX setting unresolved to at least 1 is a hack to make sure we # error out - return updated, merged, removed, max(unresolved, 1) + unresolved = max(unresolved, 1) ms.commit() + msupdated, msmerged, msremoved = ms.counts() + updated += msupdated + merged += msmerged + removed += msremoved + + extraactions = ms.actions() + for k, acts in extraactions.iteritems(): + actions[k].extend(acts) + progress(_updating, None, total=numupdates, unit=_files) return updated, merged, removed, unresolved @@ -1018,38 +1233,44 @@ def recordupdates(repo, actions, branchmerge): "record merge actions to the dirstate" # remove (must come first) - for f, args, msg in actions['r']: + for f, args, msg in actions.get('r', []): if branchmerge: repo.dirstate.remove(f) else: repo.dirstate.drop(f) # forget (must come first) - for f, args, msg in actions['f']: + for f, args, msg in actions.get('f', []): repo.dirstate.drop(f) # re-add - for f, args, msg in actions['a']: - if not branchmerge: + for f, args, msg in actions.get('a', []): + repo.dirstate.add(f) + + # re-add/mark as modified + for f, args, msg in actions.get('am', []): + if branchmerge: + repo.dirstate.normallookup(f) + else: repo.dirstate.add(f) # exec change - for f, args, msg in actions['e']: + for f, args, msg in actions.get('e', []): repo.dirstate.normallookup(f) # keep - for f, args, msg in actions['k']: + for f, args, msg in actions.get('k', []): pass # get - for f, args, msg in actions['g']: + for f, args, msg in actions.get('g', []): if branchmerge: repo.dirstate.otherparent(f) else: repo.dirstate.normal(f) # merge - for f, args, msg in actions['m']: + for f, args, msg in actions.get('m', []): f1, f2, fa, move, anc = args if branchmerge: # We've done a branch merge, mark this file as merged @@ -1074,7 +1295,7 @@ repo.dirstate.drop(f1) # directory rename, move local - for f, args, msg in actions['dm']: + for f, args, msg in actions.get('dm', []): f0, flag = args if branchmerge: repo.dirstate.add(f) @@ -1085,7 +1306,7 @@ repo.dirstate.drop(f0) # directory rename, get - for f, args, msg in actions['dg']: + for f, args, msg in actions.get('dg', []): f0, flag = args if branchmerge: repo.dirstate.add(f) @@ -1093,15 +1314,15 @@ else: repo.dirstate.normal(f) -def update(repo, node, branchmerge, force, partial, ancestor=None, - mergeancestor=False, labels=None): +def update(repo, node, branchmerge, force, ancestor=None, + mergeancestor=False, labels=None, matcher=None): """ Perform a merge between the working directory and the given node node = the node to update to, or None if unspecified branchmerge = whether to merge between branches force = whether to force branch merging or file overwriting - partial = a function to filter file lists (dirstate not updated) + matcher = a matcher to filter file lists (dirstate not updated) mergeancestor = whether it is merging with an ancestor. If true, we should accept the incoming changes for any prompts that occur. If false, merging with an ancestor (fast-forward) is only allowed @@ -1139,8 +1360,14 @@ """ onode = node - wlock = repo.wlock() - try: + # If we're doing a partial update, we need to skip updating + # the dirstate, so make a note of any partial-ness to the + # update here. + if matcher is None or matcher.always(): + partial = False + else: + partial = True + with repo.wlock(): wc = repo[None] pl = wc.parents() p1 = pl[0] @@ -1168,8 +1395,12 @@ fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) ### check phase - if not overwrite and len(pl) > 1: - raise error.Abort(_("outstanding uncommitted merge")) + if not overwrite: + if len(pl) > 1: + raise error.Abort(_("outstanding uncommitted merge")) + ms = mergestate.read(repo) + if list(ms.unresolved()): + raise error.Abort(_("outstanding merge conflicts")) if branchmerge: if pas == [p2]: raise error.Abort(_("merging with a working directory ancestor" @@ -1231,10 +1462,10 @@ ### calculate phase actionbyfile, diverge, renamedelete = calculateupdates( - repo, wc, p2, pas, branchmerge, force, partial, mergeancestor, - followcopies) + repo, wc, p2, pas, branchmerge, force, mergeancestor, + followcopies, matcher=matcher) # Convert to dictionary-of-lists format - actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split()) + actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split()) for f, (m, args, msg) in actionbyfile.iteritems(): if m not in actions: actions[m] = [] @@ -1248,35 +1479,32 @@ else: _checkcollision(repo, wc.manifest(), actions) - # Prompt and create actions. TODO: Move this towards resolve phase. + # Prompt and create actions. Most of this is in the resolve phase + # already, but we can't handle .hgsubstate in filemerge or + # subrepo.submerge yet so we have to keep prompting for it. for f, args, msg in sorted(actions['cd']): + if f != '.hgsubstate': + continue if repo.ui.promptchoice( _("local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?" "$$ &Changed $$ &Delete") % f, 0): actions['r'].append((f, None, "prompt delete")) + elif f in p1: + actions['am'].append((f, None, "prompt keep")) else: actions['a'].append((f, None, "prompt keep")) - del actions['cd'][:] for f, args, msg in sorted(actions['dc']): - flags, = args + if f != '.hgsubstate': + continue + f1, f2, fa, move, anc = args + flags = p2[f2].flags() if repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?" "$$ &Changed $$ &Deleted") % f, 0) == 0: - actions['g'].append((f, (flags,), "prompt recreating")) - del actions['dc'][:] - - ### apply phase - if not branchmerge: # just jump to the new rev - fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' - if not partial: - repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) - # note that we're in the middle of an update - repo.vfs.write('updatestate', p2.hex()) - - stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels) + actions['g'].append((f, (flags, False), "prompt recreating")) # divergent renames for f, fl in sorted(diverge.iteritems()): @@ -1292,6 +1520,16 @@ for nf in fl: repo.ui.warn(" %s\n" % nf) + ### apply phase + if not branchmerge: # just jump to the new rev + fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' + if not partial: + repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) + # note that we're in the middle of an update + repo.vfs.write('updatestate', p2.hex()) + + stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels) + if not partial: repo.dirstate.beginparentchange() repo.setparents(fp1, fp2) @@ -1302,25 +1540,24 @@ if not branchmerge: repo.dirstate.setbranch(p2.branch()) repo.dirstate.endparentchange() - finally: - wlock.release() if not partial: repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) return stats -def graft(repo, ctx, pctx, labels): +def graft(repo, ctx, pctx, labels, keepparent=False): """Do a graft-like merge. This is a merge where the merge ancestor is chosen such that one or more changesets are grafted onto the current changeset. In addition to the merge, this fixes up the dirstate to include only - a single parent and tries to duplicate any renames/copies - appropriately. + a single parent (if keepparent is False) and tries to duplicate any + renames/copies appropriately. ctx - changeset to rebase pctx - merge base, usually ctx.p1() labels - merge labels eg ['local', 'graft'] + keepparent - keep second parent if any """ # If we're grafting a descendant onto an ancestor, be sure to pass @@ -1331,12 +1568,17 @@ # which local deleted". mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node()) - stats = update(repo, ctx.node(), True, True, False, pctx.node(), + stats = update(repo, ctx.node(), True, True, pctx.node(), mergeancestor=mergeancestor, labels=labels) - # drop the second merge parent + pother = nullid + parents = ctx.parents() + if keepparent and len(parents) == 2 and pctx in parents: + parents.remove(pctx) + pother = parents[0].node() + repo.dirstate.beginparentchange() - repo.setparents(repo['.'].node(), nullid) + repo.setparents(repo['.'].node(), pother) repo.dirstate.write(repo.currenttransaction()) # fix up dirstate for copies and renames copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
--- a/mercurial/minirst.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/minirst.py Sun Jan 17 21:40:21 2016 -0600 @@ -368,7 +368,7 @@ return blocks def hgrole(blocks): - substs = [(':hg:`', '"hg '), ('`', '"')] + substs = [(':hg:`', "'hg "), ('`', "'")] for b in blocks: if b['type'] in ('paragraph', 'section'): # Turn :hg:`command` into "hg command". This also works @@ -661,12 +661,32 @@ sections = getsections(blocks) blocks = [] i = 0 + lastparents = [] + synthetic = [] + collapse = True while i < len(sections): name, nest, b = sections[i] del parents[nest:] - parents.append(name) + parents.append(i) if name == section: - b[0]['path'] = parents[3:] + if lastparents != parents: + llen = len(lastparents) + plen = len(parents) + if llen and llen != plen: + collapse = False + s = [] + for j in xrange(3, plen - 1): + parent = parents[j] + if (j >= llen or + lastparents[j] != parent): + s.append(len(blocks)) + sec = sections[parent][2] + blocks.append(sec[0]) + blocks.append(sec[-1]) + if s: + synthetic.append(s) + + lastparents = parents[:] blocks.extend(b) ## Also show all subnested sections @@ -674,18 +694,19 @@ i += 1 blocks.extend(sections[i][2]) i += 1 + if collapse: + synthetic.reverse() + for s in synthetic: + path = [blocks[i]['lines'][0] for i in s] + real = s[-1] + 2 + realline = blocks[real]['lines'] + realline[0] = ('"%s"' % + '.'.join(path + [realline[0]]).replace('"', '')) + del blocks[s[0]:real] if style == 'html': text = formathtml(blocks) else: - if len([b for b in blocks if b['type'] == 'definition']) > 1: - i = 0 - while i < len(blocks): - if blocks[i]['type'] == 'definition': - if 'path' in blocks[i]: - blocks[i]['lines'][0] = '"%s"' % '.'.join( - blocks[i]['path']) - i += 1 text = ''.join(formatblock(b, width) for b in blocks) if keep is None: return text
--- a/mercurial/node.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/node.py Sun Jan 17 21:40:21 2016 -0600 @@ -9,17 +9,18 @@ import binascii +# This ugly style has a noticeable effect in manifest parsing +hex = binascii.hexlify +bin = binascii.unhexlify + nullrev = -1 nullid = "\0" * 20 +nullhex = hex(nullid) # 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 - def short(node): return hex(node[:6])
--- a/mercurial/obsolete.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/obsolete.py Sun Jan 17 21:40:21 2016 -0600 @@ -67,10 +67,20 @@ comment associated with each format for details. """ -import errno, struct -import util, base85, node, parsers, error -import phases -from i18n import _ +from __future__ import absolute_import + +import errno +import struct + +from .i18n import _ +from . import ( + base85, + error, + node, + parsers, + phases, + util, +) _pack = struct.pack _unpack = struct.unpack @@ -1109,12 +1119,11 @@ def _computeobsoleteset(repo): """the set of obsolete revisions""" obs = set() - getrev = repo.changelog.nodemap.get - getphase = repo._phasecache.phase - for n in repo.obsstore.successors: - rev = getrev(n) - if rev is not None and getphase(repo, rev): - obs.add(rev) + getnode = repo.changelog.node + notpublic = repo.revs("not public()") + for r in notpublic: + if getnode(r) in repo.obsstore.successors: + obs.add(r) return obs @cachefor('unstable')
--- a/mercurial/osutil.c Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/osutil.c Sun Jan 17 21:40:21 2016 -0600 @@ -11,6 +11,7 @@ #include <Python.h> #include <fcntl.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <errno.h> @@ -19,6 +20,7 @@ #include <io.h> #else #include <dirent.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -456,7 +458,7 @@ requested_attr.bitmapcount = ATTR_BIT_MAP_COUNT; requested_attr.commonattr = (ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_MODTIME | ATTR_CMN_ACCESSMASK); - requested_attr.fileattr = ATTR_FILE_TOTALSIZE; + requested_attr.fileattr = ATTR_FILE_DATALENGTH; *fallback = false; @@ -613,9 +615,14 @@ int ret, kind; char *path; + /* With a large file count or on a slow filesystem, + don't block signals for long (issue4878). */ + if ((i % 1000) == 999 && PyErr_CheckSignals() == -1) + goto bail; + pypath = PySequence_GetItem(names, i); if (!pypath) - return NULL; + goto bail; path = PyString_AsString(pypath); if (path == NULL) { Py_DECREF(pypath); @@ -643,6 +650,69 @@ return NULL; } +/* + * recvfds() simply does not release GIL during blocking io operation because + * command server is known to be single-threaded. + */ + +static ssize_t recvfdstobuf(int sockfd, int **rfds, void *cbuf, size_t cbufsize) +{ + char dummy[1]; + struct iovec iov = {dummy, sizeof(dummy)}; + struct msghdr msgh = {0}; + struct cmsghdr *cmsg; + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = cbuf; + msgh.msg_controllen = (socklen_t)cbufsize; + if (recvmsg(sockfd, &msgh, 0) < 0) + return -1; + + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) + continue; + *rfds = (int *)CMSG_DATA(cmsg); + return (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + } + + *rfds = cbuf; + return 0; +} + +static PyObject *recvfds(PyObject *self, PyObject *args) +{ + int sockfd; + int *rfds = NULL; + ssize_t rfdscount, i; + char cbuf[256]; + PyObject *rfdslist = NULL; + + if (!PyArg_ParseTuple(args, "i", &sockfd)) + return NULL; + + rfdscount = recvfdstobuf(sockfd, &rfds, cbuf, sizeof(cbuf)); + if (rfdscount < 0) + return PyErr_SetFromErrno(PyExc_OSError); + + rfdslist = PyList_New(rfdscount); + if (!rfdslist) + goto bail; + for (i = 0; i < rfdscount; i++) { + PyObject *obj = PyInt_FromLong(rfds[i]); + if (!obj) + goto bail; + PyList_SET_ITEM(rfdslist, i, obj); + } + return rfdslist; + +bail: + Py_XDECREF(rfdslist); + return NULL; +} + #endif /* ndef _WIN32 */ static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs) @@ -811,6 +881,8 @@ {"statfiles", (PyCFunction)statfiles, METH_VARARGS | METH_KEYWORDS, "stat a series of files or symlinks\n" "Returns None for non-existent entries and entries of other types.\n"}, + {"recvfds", (PyCFunction)recvfds, METH_VARARGS, + "receive list of file descriptors via socket\n"}, #endif #ifdef __APPLE__ {
--- a/mercurial/parsers.c Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/parsers.c Sun Jan 17 21:40:21 2016 -0600 @@ -547,6 +547,44 @@ } /* + * Build a set of non-normal entries from the dirstate dmap +*/ +static PyObject *nonnormalentries(PyObject *self, PyObject *args) +{ + PyObject *dmap, *nonnset = NULL, *fname, *v; + Py_ssize_t pos; + + if (!PyArg_ParseTuple(args, "O!:nonnormalentries", + &PyDict_Type, &dmap)) + goto bail; + + nonnset = PySet_New(NULL); + if (nonnset == NULL) + goto bail; + + pos = 0; + while (PyDict_Next(dmap, &pos, &fname, &v)) { + dirstateTupleObject *t; + if (!dirstate_tuple_check(v)) { + PyErr_SetString(PyExc_TypeError, + "expected a dirstate tuple"); + goto bail; + } + t = (dirstateTupleObject *)v; + + if (t->state == 'n' && t->mtime != -1) + continue; + if (PySet_Add(nonnset, fname) == -1) + goto bail; + } + + return nonnset; +bail: + Py_XDECREF(nonnset); + return NULL; +} + +/* * Efficiently pack a dirstate object into its on-disk format. */ static PyObject *pack_dirstate(PyObject *self, PyObject *args) @@ -1286,19 +1324,21 @@ long phase; if (!PyArg_ParseTuple(args, "O", &roots)) - goto release_none; + goto done; if (roots == NULL || !PyList_Check(roots)) - goto release_none; + goto done; phases = calloc(len, 1); /* phase per rev: {0: public, 1: draft, 2: secret} */ - if (phases == NULL) - goto release_none; + if (phases == NULL) { + PyErr_NoMemory(); + goto done; + } /* 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; + goto done; PyList_SET_ITEM(phasessetlist, 0, Py_None); Py_INCREF(Py_None); @@ -1307,13 +1347,13 @@ phaseroots = PyList_GET_ITEM(roots, i); phaseset = PySet_New(NULL); if (phaseset == NULL) - goto release_phasesetlist; + goto release; PyList_SET_ITEM(phasessetlist, i+1, phaseset); if (!PyList_Check(phaseroots)) - goto release_phasesetlist; + goto release; minrevphase = add_roots_get_min(self, phaseroots, i+1, phases); if (minrevphase == -2) /* Error from add_roots_get_min */ - goto release_phasesetlist; + goto release; minrevallphases = MIN(minrevallphases, minrevphase); } /* Propagate the phase information from the roots to the revs */ @@ -1322,43 +1362,40 @@ for (i = minrevallphases; i < len; i++) { if (index_get_parents(self, i, parents, (int)len - 1) < 0) - goto release_phasesetlist; + goto release; 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_phasesetlist; + goto release; for (i = 0; i < len; i++) { + PyObject *phaseval; + 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); rev = PyInt_FromLong(i); + if (rev == NULL) + goto release; PySet_Add(phaseset, rev); Py_XDECREF(rev); } - PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phase)); + phaseval = PyInt_FromLong(phase); + if (phaseval == NULL) + goto release; + PyList_SET_ITEM(phaseslist, i, phaseval); } - 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: + ret = PyTuple_Pack(2, phaseslist, phasessetlist); + +release: Py_XDECREF(phaseslist); -release_phasesetlist: Py_XDECREF(phasessetlist); -release_phases: +done: free(phases); -release_none: return ret; } @@ -1404,8 +1441,10 @@ } nothead = calloc(len, 1); - if (nothead == NULL) + if (nothead == NULL) { + PyErr_NoMemory(); goto bail; + } for (i = 0; i < len; i++) { int isfiltered; @@ -1801,7 +1840,7 @@ if (node_check(val, &node, &nodelen) == -1) return NULL; rev = index_find_node(self, node, nodelen); - if (rev == -3) + if (rev == -3) return NULL; if (rev == -2) Py_RETURN_NONE; @@ -1995,19 +2034,19 @@ for (i = 0; i < 2; i++) { int p = parents[i]; - long nsp, sp; + long sp; int dp; if (p == -1) continue; dp = depth[p]; - nsp = sp = seen[p]; + sp = seen[p]; if (dp <= dv) { depth[p] = dv + 1; if (sp != sv) { interesting[sv] += 1; - nsp = seen[p] = sv; + seen[p] = sv; if (sp) { interesting[sp] -= 1; if (interesting[sp] == 0) @@ -2016,7 +2055,7 @@ } } else if (dv == dp - 1) { - nsp = sp | sv; + long nsp = sp | sv; if (nsp == sp) continue; seen[p] = nsp; @@ -2739,6 +2778,8 @@ static PyMethodDef methods[] = { {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"}, + {"nonnormalentries", nonnormalentries, METH_VARARGS, + "create a set containing non-normal entries of given dirstate\n"}, {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"}, {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"}, {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
--- a/mercurial/patch.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/patch.py Sun Jan 17 21:40:21 2016 -0600 @@ -6,14 +6,36 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +from __future__ import absolute_import + +import cStringIO import collections -import cStringIO, email, os, errno, re, posixpath, copy -import tempfile, zlib, shutil +import copy +import email +import errno +import os +import posixpath +import re +import shutil +import tempfile +import zlib -from i18n import _ -from node import hex, short -import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error -import pathutil +from .i18n import _ +from .node import ( + hex, + short, +) +from . import ( + base85, + copies, + diffhelpers, + encoding, + error, + mdiff, + pathutil, + scmutil, + util, +) gitre = re.compile('diff --git a/(.*) b/(.*)') tabsplitter = re.compile(r'(\t+|[^\t]+)') @@ -1106,8 +1128,8 @@ applied[newhunk.filename()].append(newhunk) else: fixoffset += chunk.removed - chunk.added - return sum([h for h in applied.itervalues() - if h[0].special() or len(h) > 1], []) + return (sum([h for h in applied.itervalues() + if h[0].special() or len(h) > 1], []), {}) class hunk(object): def __init__(self, desc, num, lr, context): self.number = num @@ -1446,7 +1468,7 @@ ''' - import crecord as crecordmod + from . import crecord as crecordmod newhunks = [] for c in hunks: if isinstance(c, crecordmod.uihunk): @@ -1491,7 +1513,6 @@ self.toline += len(self.before) + h.added self.before = [] self.hunk = [] - self.proc = '' self.context = context def addhunk(self, hunk): @@ -2078,8 +2099,7 @@ def changedfiles(ui, repo, patchpath, strip=1): backend = fsbackend(ui, repo.root) - fp = open(patchpath, 'rb') - try: + with open(patchpath, 'rb') as fp: changed = set() for state, values in iterhunks(fp): if state == 'file': @@ -2097,8 +2117,6 @@ elif state not in ('hunk', 'git'): raise error.Abort(_('unsupported parser state: %s') % state) return changed - finally: - fp.close() class GitDiffRequired(Exception): pass @@ -2146,7 +2164,7 @@ 'ignoreblanklines') if formatchanging: buildopts['text'] = opts and opts.get('text') - buildopts['nobinary'] = get('nobinary') + buildopts['nobinary'] = get('nobinary', forceplain=False) buildopts['noprefix'] = get('noprefix', forceplain=False) return mdiff.diffopts(**buildopts) @@ -2238,6 +2256,29 @@ if dst.startswith(relroot) and src.startswith(relroot))) + modifiedset = set(modified) + addedset = set(added) + removedset = set(removed) + for f in modified: + if f not in ctx1: + # Fix up added, since merged-in additions appear as + # modifications during merges + modifiedset.remove(f) + addedset.add(f) + for f in removed: + if f not in ctx1: + # Merged-in additions that are then removed are reported as removed. + # They are not in ctx1, so We don't want to show them in the diff. + removedset.remove(f) + modified = sorted(modifiedset) + added = sorted(addedset) + removed = sorted(removedset) + for dst, src in copy.items(): + if src not in ctx1: + # Files merged in during a merge and then copied/renamed are + # reported as copies. We want to show them in the diff as additions. + del copy[dst] + def difffn(opts, losedata): return trydiff(repo, revs, ctx1, ctx2, modified, added, removed, copy, getfilectx, opts, losedata, prefix, relroot) @@ -2309,7 +2350,7 @@ '''like diff(), but yields 2-tuples of (output, label) for ui.write()''' return difflabel(diff, *args, **kw) -def _filepairs(ctx1, modified, added, removed, copy, opts): +def _filepairs(modified, added, removed, copy, opts): '''generates tuples (f1, f2, copyop), where f1 is the name of the file before and f2 is the the name after. For added files, f1 will be None, and for removed files, f2 will be None. copyop may be set to None, 'copy' @@ -2319,11 +2360,6 @@ copyto = dict([(v, k) for k, v in copy.items()]) addedset, removedset = set(added), set(removed) - # Fix up added, since merged-in additions appear as - # modifications during merges - for f in modified: - if f not in ctx1: - addedset.add(f) for f in sorted(modified + added + removed): copyop = None @@ -2389,8 +2425,7 @@ raise AssertionError( "file %s doesn't start with relroot %s" % (f, relroot)) - for f1, f2, copyop in _filepairs( - ctx1, modified, added, removed, copy, opts): + for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts): content1 = None content2 = None flag1 = None
--- a/mercurial/pathencode.c Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pathencode.c Sun Jan 17 21:40:21 2016 -0600 @@ -517,8 +517,7 @@ newlen = _lowerencode(NULL, 0, path, len); ret = PyString_FromStringAndSize(NULL, newlen); if (ret) - newlen = _lowerencode(PyString_AS_STRING(ret), newlen, - path, len); + _lowerencode(PyString_AS_STRING(ret), newlen, path, len); return ret; }
--- a/mercurial/pathutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pathutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -23,15 +23,22 @@ - under top-level .hg - starts at the root of a windows drive - contains ".." + + More check are also done about the file system states: - traverses a symlink (e.g. a/symlink_here/b) - inside a nested repository (a callback can be used to approve some nested repositories, e.g., subrepositories) + + The file system checks are only done when 'realfs' is set to True (the + default). They should be disable then we are auditing path for operation on + stored history. ''' - def __init__(self, root, callback=None): + def __init__(self, root, callback=None, realfs=True): self.audited = set() self.auditeddir = set() self.root = root + self._realfs = realfs self.callback = callback if os.path.lexists(root) and not util.checkcase(root): self.normcase = util.normcase @@ -81,25 +88,8 @@ normprefix = os.sep.join(normparts) if normprefix in self.auditeddir: break - curpath = os.path.join(self.root, prefix) - try: - st = os.lstat(curpath) - 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): - raise - else: - if stat.S_ISLNK(st.st_mode): - raise error.Abort( - _('path %r traverses symbolic link %r') - % (path, prefix)) - elif (stat.S_ISDIR(st.st_mode) and - os.path.isdir(os.path.join(curpath, '.hg'))): - if not self.callback or not self.callback(curpath): - raise error.Abort(_("path '%s' is inside nested " - "repo %r") - % (path, prefix)) + if self._realfs: + self._checkfs(prefix, path) prefixes.append(normprefix) parts.pop() normparts.pop() @@ -109,6 +99,26 @@ # want to add "foo/bar/baz" before checking if there's a "foo/.hg" self.auditeddir.update(prefixes) + def _checkfs(self, prefix, path): + """raise exception if a file system backed check fails""" + curpath = os.path.join(self.root, prefix) + try: + st = os.lstat(curpath) + 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): + raise + else: + if stat.S_ISLNK(st.st_mode): + msg = _('path %r traverses symbolic link %r') % (path, prefix) + raise error.Abort(msg) + elif (stat.S_ISDIR(st.st_mode) and + os.path.isdir(os.path.join(curpath, '.hg'))): + if not self.callback or not self.callback(curpath): + msg = _("path '%s' is inside nested repo %r") + raise error.Abort(msg % (path, prefix)) + def check(self, path): try: self(path)
--- a/mercurial/phases.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/phases.py Sun Jan 17 21:40:21 2016 -0600 @@ -308,9 +308,19 @@ raise error.Abort(_('cannot change null revision phase')) currentroots = currentroots.copy() currentroots.update(newroots) - ctxs = repo.set('roots(%ln::)', currentroots) - currentroots.intersection_update(ctx.node() for ctx in ctxs) - self._updateroots(targetphase, currentroots, tr) + + # Only compute new roots for revs above the roots that are being + # retracted. + minnewroot = min(repo[n].rev() for n in newroots) + aboveroots = [n for n in currentroots + if repo[n].rev() >= minnewroot] + updatedroots = repo.set('roots(%ln::)', aboveroots) + + finalroots = set(n for n in currentroots if repo[n].rev() < + minnewroot) + finalroots.update(ctx.node() for ctx in updatedroots) + + self._updateroots(targetphase, finalroots, tr) repo.invalidatevolatilesets() def filterunknown(self, repo): @@ -394,26 +404,19 @@ def pushphase(repo, nhex, oldphasestr, newphasestr): """List phases root for serialization over pushkey""" repo = repo.unfiltered() - tr = None - lock = repo.lock() - try: + with repo.lock(): currentphase = repo[nhex].phase() newphase = abs(int(newphasestr)) # let's avoid negative index surprise oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise if currentphase == oldphase and newphase < oldphase: - tr = repo.transaction('pushkey-phase') - advanceboundary(repo, tr, newphase, [bin(nhex)]) - tr.close() + with repo.transaction('pushkey-phase') as tr: + advanceboundary(repo, tr, newphase, [bin(nhex)]) return 1 elif currentphase == newphase: # raced, but got correct result return 1 else: return 0 - finally: - if tr: - tr.release() - lock.release() def analyzeremotephases(repo, subset, roots): """Compute phases heads and root in a subset of node from root dict
--- a/mercurial/posix.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/posix.py Sun Jan 17 21:40:21 2016 -0600 @@ -15,7 +15,6 @@ import pwd import re import select -import socket import stat import sys import tempfile @@ -29,7 +28,16 @@ posixfile = open normpath = os.path.normpath samestat = os.path.samestat -oslink = os.link +try: + oslink = os.link +except AttributeError: + # Some platforms build Python without os.link on systems that are + # vaguely unix-like but don't have hardlink support. For those + # poor souls, just say we tried and that it failed so we fall back + # to copies. + def oslink(src, dst): + raise OSError(errno.EINVAL, + 'hardlinks not supported: %s to %s' % (src, dst)) unlink = os.unlink rename = os.rename removedirs = os.removedirs @@ -261,40 +269,17 @@ except UnicodeDecodeError: # OS X percent-encodes any bytes that aren't valid utf-8 s = '' - g = '' - l = 0 - for c in path: - o = ord(c) - if l and o < 128 or o >= 192: - # we want a continuation byte, but didn't get one - s += ''.join(["%%%02X" % ord(x) for x in g]) - g = '' - l = 0 - if l == 0 and o < 128: - # ascii - s += c - elif l == 0 and 194 <= o < 245: - # valid leading bytes - if o < 224: - l = 1 - elif o < 240: - l = 2 - else: - l = 3 - g = c - elif l > 0 and 128 <= o < 192: - # valid continuations - g += c - l -= 1 - if not l: - s += g - g = '' - else: - # invalid - s += "%%%02X" % o + pos = 0 + l = len(path) + while pos < l: + try: + c = encoding.getutf8char(path, pos) + pos += len(c) + except ValueError: + c = '%%%02X' % ord(path[pos]) + pos += 1 + s += c - # any remaining partial characters - s += ''.join(["%%%02X" % ord(x) for x in g]) u = s.decode('utf-8') # Decompose then lowercase (HFS+ technote specifies lower) @@ -569,46 +554,6 @@ def executablepath(): return None # available on Windows only -class unixdomainserver(socket.socket): - def __init__(self, join, subsystem): - '''Create a unix domain socket with the given prefix.''' - super(unixdomainserver, self).__init__(socket.AF_UNIX) - sockname = subsystem + '.sock' - self.realpath = self.path = join(sockname) - if os.path.islink(self.path): - if os.path.exists(self.path): - self.realpath = os.readlink(self.path) - else: - os.unlink(self.path) - try: - self.bind(self.realpath) - 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) - try: - self.bind(self.realpath) - os.symlink(self.realpath, self.path) - except (OSError, socket.error): - self.cleanup() - raise - else: - raise - self.listen(5) - - def cleanup(self): - def okayifmissing(f, path): - try: - f(path) - except OSError as err: - if err.errno != errno.ENOENT: - raise - - okayifmissing(os.unlink, self.path) - if self.realpath != self.path: - okayifmissing(os.unlink, self.realpath) - okayifmissing(os.rmdir, os.path.dirname(self.realpath)) - def statislink(st): '''check whether a stat result is a symlink''' return st and stat.S_ISLNK(st.st_mode)
--- a/mercurial/pure/base85.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pure/base85.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,6 +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. +from __future__ import absolute_import + import struct _b85chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
--- a/mercurial/pure/bdiff.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pure/bdiff.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,7 +5,11 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import struct, difflib, re +from __future__ import absolute_import + +import difflib +import re +import struct def splitnewlines(text): '''like str.splitlines, but only split on newlines.'''
--- a/mercurial/pure/diffhelpers.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pure/diffhelpers.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,6 +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. +from __future__ import absolute_import + def addlines(fp, hunk, lena, lenb, a, b): while True: todoa = lena - len(a)
--- a/mercurial/pure/mpatch.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pure/mpatch.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,11 +5,12 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +from __future__ import absolute_import + +import cStringIO import struct -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO + +StringIO = cStringIO.StringIO # This attempts to apply a series of patches in time proportional to # the total size of the patches, rather than patches * len(text). This
--- a/mercurial/pure/osutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pure/osutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,8 +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. +from __future__ import absolute_import + +import ctypes +import ctypes.util import os +import socket import stat as statmod +import sys def _mode_to_kind(mode): if statmod.S_ISREG(mode): @@ -57,8 +63,88 @@ if os.name != 'nt': posixfile = open + + _SCM_RIGHTS = 0x01 + _socklen_t = ctypes.c_uint + + if sys.platform == 'linux2': + # socket.h says "the type should be socklen_t but the definition of + # the kernel is incompatible with this." + _cmsg_len_t = ctypes.c_size_t + _msg_controllen_t = ctypes.c_size_t + _msg_iovlen_t = ctypes.c_size_t + else: + _cmsg_len_t = _socklen_t + _msg_controllen_t = _socklen_t + _msg_iovlen_t = ctypes.c_int + + class _iovec(ctypes.Structure): + _fields_ = [ + ('iov_base', ctypes.c_void_p), + ('iov_len', ctypes.c_size_t), + ] + + class _msghdr(ctypes.Structure): + _fields_ = [ + ('msg_name', ctypes.c_void_p), + ('msg_namelen', _socklen_t), + ('msg_iov', ctypes.POINTER(_iovec)), + ('msg_iovlen', _msg_iovlen_t), + ('msg_control', ctypes.c_void_p), + ('msg_controllen', _msg_controllen_t), + ('msg_flags', ctypes.c_int), + ] + + class _cmsghdr(ctypes.Structure): + _fields_ = [ + ('cmsg_len', _cmsg_len_t), + ('cmsg_level', ctypes.c_int), + ('cmsg_type', ctypes.c_int), + ('cmsg_data', ctypes.c_ubyte * 0), + ] + + _libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) + _recvmsg = _libc.recvmsg + _recvmsg.restype = getattr(ctypes, 'c_ssize_t', ctypes.c_long) + _recvmsg.argtypes = (ctypes.c_int, ctypes.POINTER(_msghdr), ctypes.c_int) + + def _CMSG_FIRSTHDR(msgh): + if msgh.msg_controllen < ctypes.sizeof(_cmsghdr): + return + cmsgptr = ctypes.cast(msgh.msg_control, ctypes.POINTER(_cmsghdr)) + return cmsgptr.contents + + # The pure version is less portable than the native version because the + # handling of socket ancillary data heavily depends on C preprocessor. + # Also, some length fields are wrongly typed in Linux kernel. + def recvfds(sockfd): + """receive list of file descriptors via socket""" + dummy = (ctypes.c_ubyte * 1)() + iov = _iovec(ctypes.cast(dummy, ctypes.c_void_p), ctypes.sizeof(dummy)) + cbuf = ctypes.create_string_buffer(256) + msgh = _msghdr(None, 0, + ctypes.pointer(iov), 1, + ctypes.cast(cbuf, ctypes.c_void_p), ctypes.sizeof(cbuf), + 0) + r = _recvmsg(sockfd, ctypes.byref(msgh), 0) + if r < 0: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + # assumes that the first cmsg has fds because it isn't easy to write + # portable CMSG_NXTHDR() with ctypes. + cmsg = _CMSG_FIRSTHDR(msgh) + if not cmsg: + return [] + if (cmsg.cmsg_level != socket.SOL_SOCKET or + cmsg.cmsg_type != _SCM_RIGHTS): + return [] + rfds = ctypes.cast(cmsg.cmsg_data, ctypes.POINTER(ctypes.c_int)) + rfdscount = ((cmsg.cmsg_len - _cmsghdr.cmsg_data.offset) / + ctypes.sizeof(ctypes.c_int)) + return [rfds[i] for i in xrange(rfdscount)] + else: - import ctypes, msvcrt + import msvcrt _kernel32 = ctypes.windll.kernel32 @@ -168,3 +254,9 @@ f = posixfile('foo.txt') f.name = 'bla' ''' return self._file.__setattr__(name, value) + + def __enter__(self): + return self._file.__enter__() + + def __exit__(self, exc_type, exc_value, exc_tb): + return self._file.__exit__(exc_type, exc_value, exc_tb)
--- a/mercurial/pure/parsers.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pure/parsers.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,8 +5,13 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -from mercurial.node import nullid -import struct, zlib, cStringIO +from __future__ import absolute_import + +import cStringIO +import struct +import zlib + +from .node import nullid _pack = struct.pack _unpack = struct.unpack
--- a/mercurial/pvec.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/pvec.py Sun Jan 17 21:40:21 2016 -0600 @@ -48,8 +48,13 @@ different branches ''' -import base85, util -from node import nullrev +from __future__ import absolute_import + +from .node import nullrev +from . import ( + base85, + util, +) _size = 448 # 70 chars b85-encoded _bytes = _size / 8
--- a/mercurial/py3kcompat.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/py3kcompat.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,9 +5,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 builtins +from __future__ import absolute_import -from numbers import Number +import builtins +import numbers + +Number = numbers.Number def bytesformatter(format, args): '''Custom implementation of a formatter for bytestrings.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/registrar.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,128 @@ +# registrar.py - utilities to register function for specific purpose +# +# Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +from . import ( + util, +) + +class funcregistrar(object): + """Base of decorator to register a fuction for specific purpose + + The least derived class can be defined by overriding 'table' and + 'formatdoc', for example:: + + symbols = {} + class keyword(funcregistrar): + table = symbols + formatdoc = ":%s: %s" + + @keyword('bar') + def barfunc(*args, **kwargs): + '''Explanation of bar keyword .... + ''' + pass + + In this case: + + - 'barfunc' is registered as 'bar' in 'symbols' + - online help uses ":bar: Explanation of bar keyword" + """ + + def __init__(self, decl): + """'decl' is a name or more descriptive string of a function + + Specification of 'decl' depends on registration purpose. + """ + self.decl = decl + + table = None + + def __call__(self, func): + """Execute actual registration for specified function + """ + name = self.getname() + + if func.__doc__ and not util.safehasattr(func, '_origdoc'): + doc = func.__doc__.strip() + func._origdoc = doc + if callable(self.formatdoc): + func.__doc__ = self.formatdoc(doc) + else: + # convenient shortcut for simple format + func.__doc__ = self.formatdoc % (self.decl, doc) + + self.table[name] = func + self.extraaction(name, func) + + return func + + def getname(self): + """Return the name of the registered function from self.decl + + Derived class should override this, if it allows more + descriptive 'decl' string than just a name. + """ + return self.decl + + def parsefuncdecl(self): + """Parse function declaration and return the name of function in it + """ + i = self.decl.find('(') + if i > 0: + return self.decl[:i] + else: + return self.decl + + def formatdoc(self, doc): + """Return formatted document of the registered function for help + + 'doc' is '__doc__.strip()' of the registered function. + + If this is overridden by non-callable object in derived class, + such value is treated as "format string" and used to format + document by 'self.formatdoc % (self.decl, doc)' for convenience. + """ + raise NotImplementedError() + + def extraaction(self, name, func): + """Execute exra action for registered function, if needed + """ + pass + +class delayregistrar(object): + """Decorator to delay actual registration until uisetup or so + + For example, the decorator class to delay registration by + 'keyword' funcregistrar can be defined as below:: + + class extkeyword(delayregistrar): + registrar = keyword + """ + def __init__(self): + self._list = [] + + registrar = None + + def __call__(self, *args, **kwargs): + """Return the decorator to delay actual registration until setup + """ + assert self.registrar is not None + def decorator(func): + # invocation of self.registrar() here can detect argument + # mismatching immediately + self._list.append((func, self.registrar(*args, **kwargs))) + return func + return decorator + + def setup(self): + """Execute actual registration + """ + while self._list: + func, decorator = self._list.pop(0) + decorator(func)
--- a/mercurial/repair.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/repair.py Sun Jan 17 21:40:21 2016 -0600 @@ -74,7 +74,8 @@ return s def strip(ui, repo, nodelist, backup=True, topic='backup'): - + # This function operates within a transaction of its own, but does + # not take any lock on the repo. # Simple way to maintain backwards compatibility for this # argument. if backup in ['none', 'strip']: @@ -159,26 +160,22 @@ msg = _('programming error: cannot strip from inside a transaction') raise error.Abort(msg, hint=_('contact your extension maintainer')) - tr = repo.transaction("strip") - offset = len(tr.entries) + try: + with repo.transaction("strip") as tr: + offset = len(tr.entries) - try: - tr.startgroup() - cl.strip(striprev, tr) - mfst.strip(striprev, tr) - for fn in files: - repo.file(fn).strip(striprev, tr) - tr.endgroup() + tr.startgroup() + cl.strip(striprev, tr) + mfst.strip(striprev, tr) + for fn in files: + repo.file(fn).strip(striprev, tr) + tr.endgroup() - try: for i in xrange(offset, len(tr.entries)): file, troffset, ignore = tr.entries[i] repo.svfs(file, 'a').truncate(troffset) if troffset == 0: repo.store.markremoved(file) - tr.close() - finally: - tr.release() if saveheads or savebases: ui.note(_("adding branch\n")) @@ -188,21 +185,29 @@ # silence internal shuffling chatter repo.ui.pushbuffer() if isinstance(gen, bundle2.unbundle20): - tr = repo.transaction('strip') - tr.hookargs = {'source': 'strip', - 'url': 'bundle:' + vfs.join(chgrpfile)} - try: + with repo.transaction('strip') as tr: + tr.hookargs = {'source': 'strip', + 'url': 'bundle:' + vfs.join(chgrpfile)} bundle2.applybundle(repo, gen, tr, source='strip', url='bundle:' + vfs.join(chgrpfile)) - tr.close() - finally: - tr.release() else: gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True) if not repo.ui.verbose: repo.ui.popbuffer() f.close() + for m in updatebm: + bm[m] = repo[newbmtarget].node() + lock = tr = None + try: + lock = repo.lock() + tr = repo.transaction('repair') + bm.recordchange(tr) + tr.close() + finally: + tr.release() + lock.release() + # remove undo files for undovfs, undofile in repo.undofiles(): try: @@ -212,9 +217,6 @@ ui.warn(_('error removing %s: %s\n') % (undovfs.join(undofile), str(e))) - for m in updatebm: - bm[m] = repo[newbmtarget].node() - bm.write() except: # re-raises if backupfile: ui.warn(_("strip failed, full bundle stored in '%s'\n") @@ -242,8 +244,7 @@ 'support fncache)\n')) return - lock = repo.lock() - try: + with repo.lock(): fnc = repo.store.fncache # Trigger load of fncache. if 'irrelevant' in fnc: @@ -287,16 +288,10 @@ fnc.entries = newentries fnc._dirty = True - tr = repo.transaction('fncache') - try: + with repo.transaction('fncache') as tr: fnc.write(tr) - tr.close() - finally: - tr.release() else: ui.write(_('fncache already up to date\n')) - finally: - lock.release() def stripbmrevset(repo, mark): """
--- a/mercurial/repoview.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/repoview.py Sun Jan 17 21:40:21 2016 -0600 @@ -300,22 +300,16 @@ # some cache may be implemented later unfi = self._unfilteredrepo unfichangelog = unfi.changelog + # bypass call to changelog.method + unfiindex = unfichangelog.index + unfilen = len(unfiindex) - 1 + unfinode = unfiindex[unfilen - 1][7] + revs = filterrevs(unfi, self.filtername) cl = self._clcache - newkey = (len(unfichangelog), unfichangelog.tip(), hash(revs), - unfichangelog._delayed) - if cl is not None: - # we need to check curkey too for some obscure reason. - # MQ test show a corruption of the underlying repo (in _clcache) - # without change in the cachekey. - oldfilter = cl.filteredrevs - try: - cl.filteredrevs = () # disable filtering for tip - curkey = (len(cl), cl.tip(), hash(oldfilter), cl._delayed) - finally: - cl.filteredrevs = oldfilter - if newkey != self._clcachekey or newkey != curkey: - cl = None + newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed) + if cl is not None and newkey != self._clcachekey: + cl = None # could have been made None by the previous if if cl is None: cl = copy.copy(unfichangelog)
--- a/mercurial/revlog.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/revlog.py Sun Jan 17 21:40:21 2016 -0600 @@ -11,13 +11,30 @@ and O(changes) merge between branches. """ -# import stuff from node for others to import from revlog +from __future__ import absolute_import + import collections +import errno import os -from node import bin, hex, nullid, nullrev -from i18n import _ -import ancestor, mdiff, parsers, error, util, templatefilters -import struct, zlib, errno +import struct +import zlib + +# import stuff from node for others to import from revlog +from .node import ( + bin, + hex, + nullid, + nullrev, +) +from .i18n import _ +from . import ( + ancestor, + error, + mdiff, + parsers, + templatefilters, + util, +) _pack = struct.pack _unpack = struct.unpack @@ -93,7 +110,7 @@ except zlib.error as e: raise RevlogError(_("revlog decompress error: %s") % str(e)) if t == 'u': - return bin[1:] + return util.buffer(bin, 1) raise RevlogError(_("unknown compression type %r") % t) # index v0: @@ -113,7 +130,7 @@ def parseindex(self, data, inline): s = self.size index = [] - nodemap = {nullid: nullrev} + nodemap = {nullid: nullrev} n = off = 0 l = len(data) while off + s <= l: @@ -206,14 +223,21 @@ self.indexfile = indexfile self.datafile = indexfile[:-2] + ".d" self.opener = opener + # 3-tuple of (node, rev, text) for a raw revision. self._cache = None + # 2-tuple of (rev, baserev) defining the base revision the delta chain + # begins at for a revision. self._basecache = None + # 2-tuple of (offset, data) of raw data from the revlog at an offset. self._chunkcache = (0, '') + # How much data to read and cache into the raw revlog data cache. self._chunkcachesize = 65536 self._maxchainlen = None self._aggressivemergedeltas = False self.index = [] + # Mapping of partial identifiers to full nodes. self._pcache = {} + # Mapping of revision integer to full node. self._nodecache = {nullid: nullrev} self._nodepos = None @@ -231,6 +255,7 @@ self._maxchainlen = opts['maxchainlen'] if 'aggressivemergedeltas' in opts: self._aggressivemergedeltas = opts['aggressivemergedeltas'] + self._lazydeltabase = bool(opts.get('lazydeltabase', False)) if self._chunkcachesize <= 0: raise RevlogError(_('revlog chunk cache size %r is not greater ' @@ -314,6 +339,11 @@ return False def clearcaches(self): + self._cache = None + self._basecache = None + self._chunkcache = (0, '') + self._pcache = {} + try: self._nodecache.clearcaches() except AttributeError: @@ -400,6 +430,41 @@ chaininfocache[rev] = r return r + def _deltachain(self, rev, stoprev=None): + """Obtain the delta chain for a revision. + + ``stoprev`` specifies a revision to stop at. If not specified, we + stop at the base of the chain. + + Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of + revs in ascending order and ``stopped`` is a bool indicating whether + ``stoprev`` was hit. + """ + chain = [] + + # Alias to prevent attribute lookup in tight loop. + index = self.index + generaldelta = self._generaldelta + + iterrev = rev + e = index[iterrev] + while iterrev != e[3] and iterrev != stoprev: + chain.append(iterrev) + if generaldelta: + iterrev = e[3] + else: + iterrev -= 1 + e = index[iterrev] + + if iterrev == stoprev: + stopped = True + else: + chain.append(iterrev) + stopped = False + + chain.reverse() + return chain, stopped + def flags(self, rev): return self.index[rev][0] & 0xFFFF def rawsize(self, rev): @@ -926,6 +991,10 @@ return hash(text, p1, p2) != node def _addchunk(self, offset, data): + """Add a segment to the revlog cache. + + Accepts an absolute offset and the data that is at that location. + """ o, d = self._chunkcache # try to add to existing cache if o + len(d) == offset and len(d) + len(data) < _chunksize: @@ -934,13 +1003,15 @@ self._chunkcache = offset, data def _loadchunk(self, offset, length, df=None): - """Load a chunk/segment from the revlog. + """Load a segment of raw data from the revlog. - Accepts absolute offset, length to read, and an optional existing + Accepts an absolute offset, length to read, and an optional existing file handle to read from. If an existing file handle is passed, it will be seeked and the original seek position will NOT be restored. + + Returns a str or buffer of raw byte data. """ if df is not None: closehandle = False @@ -968,6 +1039,16 @@ return d def _getchunk(self, offset, length, df=None): + """Obtain a segment of raw data from the revlog. + + Accepts an absolute offset, length of bytes to obtain, and an + optional file handle to the already-opened revlog. If the file + handle is used, it's original seek position will not be preserved. + + Requests for data may be returned from a cache. + + Returns a str or a buffer instance of raw byte data. + """ o, d = self._chunkcache l = len(d) @@ -982,21 +1063,54 @@ return self._loadchunk(offset, length, df=df) def _chunkraw(self, startrev, endrev, df=None): + """Obtain a segment of raw data corresponding to a range of revisions. + + Accepts the start and end revisions and an optional already-open + file handle to be used for reading. If the file handle is read, its + seek position will not be preserved. + + Requests for data may be satisfied by a cache. + + Returns a 2-tuple of (offset, data) for the requested range of + revisions. Offset is the integer offset from the beginning of the + revlog and data is a str or buffer of the raw byte data. + + Callers will need to call ``self.start(rev)`` and ``self.length(rev)`` + to determine where each revision's data begins and ends. + """ start = self.start(startrev) end = self.end(endrev) if self._inline: start += (startrev + 1) * self._io.size end += (endrev + 1) * self._io.size length = end - start - return self._getchunk(start, length, df=df) + + return start, self._getchunk(start, length, df=df) def _chunk(self, rev, df=None): - return decompress(self._chunkraw(rev, rev, df=df)) + """Obtain a single decompressed chunk for a revision. + + Accepts an integer revision and an optional already-open file handle + to be used for reading. If used, the seek position of the file will not + be preserved. + + Returns a str holding uncompressed data for the requested revision. + """ + return decompress(self._chunkraw(rev, rev, df=df)[1]) def _chunks(self, revs, df=None): - '''faster version of [self._chunk(rev) for rev in revs] + """Obtain decompressed chunks for the specified revisions. - Assumes that revs is in ascending order.''' + Accepts an iterable of numeric revisions that are assumed to be in + ascending order. Also accepts an optional already-open file handle + to be used for reading. If used, the seek position of the file will + not be preserved. + + This function is similar to calling ``self._chunk()`` multiple times, + but is faster. + + Returns a list with decompressed data for each requested revision. + """ if not revs: return [] start = self.start @@ -1008,15 +1122,8 @@ l = [] ladd = l.append - # preload the cache try: - while True: - # ensure that the cache doesn't change out from under us - _cache = self._chunkcache - self._chunkraw(revs[0], revs[-1], df=df) - if _cache == self._chunkcache: - break - offset, data = _cache + offset, data = self._chunkraw(revs[0], revs[-1], df=df) except OverflowError: # issue4215 - we can't cache a run of chunks greater than # 2G on Windows @@ -1032,6 +1139,7 @@ return l def _chunkclear(self): + """Clear the raw chunk cache.""" self._chunkcache = (0, '') def deltaparent(self, rev): @@ -1084,26 +1192,9 @@ raise RevlogError(_('incompatible revision flag %x') % (self.flags(rev) & ~REVIDX_KNOWN_FLAGS)) - # build delta chain - chain = [] - index = self.index # for performance - generaldelta = self._generaldelta - iterrev = rev - e = index[iterrev] - while iterrev != e[3] and iterrev != cachedrev: - chain.append(iterrev) - if generaldelta: - iterrev = e[3] - else: - iterrev -= 1 - e = index[iterrev] - - if iterrev == cachedrev: - # cache hit + chain, stopped = self._deltachain(rev, stoprev=cachedrev) + if stopped: text = self._cache[2] - else: - chain.append(iterrev) - chain.reverse() # drop cache to save memory self._cache = None @@ -1173,7 +1264,7 @@ df = self.opener(self.datafile, 'w') try: for r in self: - df.write(self._chunkraw(r, r)) + df.write(self._chunkraw(r, r)[1]) finally: df.close() @@ -1353,9 +1444,8 @@ curr = len(self) prev = curr - 1 base = chainbase = curr - chainlen = None offset = self.end(prev) - d = None + delta = None if self._basecache is None: self._basecache = (prev, self.chainbase(prev)) basecache = self._basecache @@ -1371,40 +1461,39 @@ # should we try to build a delta? if prev != nullrev: - if self._generaldelta: - if p2r != nullrev and self._aggressivemergedeltas: - d = builddelta(p1r) - d2 = builddelta(p2r) - p1good = self._isgooddelta(d, textlen) - p2good = self._isgooddelta(d2, textlen) - if p1good and p2good: - # If both are good deltas, choose the smallest - if d2[1] < d[1]: - d = d2 - elif p2good: - # If only p2 is good, use it - d = d2 - elif p1good: - pass - else: - # Neither is good, try against prev to hopefully save us - # a fulltext. - d = builddelta(prev) - else: + tested = set() + if cachedelta and self._generaldelta and self._lazydeltabase: + # Assume what we received from the server is a good choice + # build delta will reuse the cache + candidatedelta = builddelta(cachedelta[0]) + tested.add(cachedelta[0]) + if self._isgooddelta(candidatedelta, textlen): + delta = candidatedelta + if delta is None and self._generaldelta: + # exclude already lazy tested base if any + parents = [p for p in (p1r, p2r) + if p != nullrev and p not in tested] + if parents and not self._aggressivemergedeltas: # Pick whichever parent is closer to us (to minimize the - # chance of having to build a fulltext). Since - # nullrev == -1, any non-merge commit will always pick p1r. - drev = p2r if p2r > p1r else p1r - d = builddelta(drev) - # If the chosen delta will result in us making a full text, - # give it one last try against prev. - if drev != prev and not self._isgooddelta(d, textlen): - d = builddelta(prev) - else: - d = builddelta(prev) - dist, l, data, base, chainbase, chainlen, compresseddeltalen = d - - if not self._isgooddelta(d, textlen): + # chance of having to build a fulltext). + parents = [max(parents)] + tested.update(parents) + pdeltas = [] + for p in parents: + pd = builddelta(p) + if self._isgooddelta(pd, textlen): + pdeltas.append(pd) + if pdeltas: + delta = min(pdeltas, key=lambda x: x[1]) + if delta is None and prev not in tested: + # other approach failed try against prev to hopefully save us a + # fulltext. + candidatedelta = builddelta(prev) + if self._isgooddelta(candidatedelta, textlen): + delta = candidatedelta + if delta is not None: + dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta + else: text = buildtext() data = self.compress(text) l = len(data[1]) + len(data[0]) @@ -1503,6 +1592,7 @@ cs = chunkdata['cs'] deltabase = chunkdata['deltabase'] delta = chunkdata['delta'] + flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS content.append(node) @@ -1533,8 +1623,7 @@ raise error.CensoredBaseError(self.indexfile, self.node(baserev)) - flags = REVIDX_DEFAULT_FLAGS - if self._peek_iscensored(baserev, delta, flush): + if not flags and self._peek_iscensored(baserev, delta, flush): flags |= REVIDX_ISCENSORED # We assume consumers of addrevisioncb will want to retrieve
--- a/mercurial/revset.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/revset.py Sun Jan 17 21:40:21 2016 -0600 @@ -22,6 +22,7 @@ parser, pathutil, phases, + registrar, repoview, util, ) @@ -448,7 +449,8 @@ return subset - getset(repo, subset, x) def listset(repo, subset, a, b): - raise error.ParseError(_("can't use a list in this context")) + raise error.ParseError(_("can't use a list in this context"), + hint=_('see hg help "revsets.x or y"')) def keyvaluepair(repo, subset, k, v): raise error.ParseError(_("can't use a key-value pair in this context")) @@ -464,19 +466,87 @@ # functions +# symbols are callables like: +# fn(repo, subset, x) +# with: +# repo - current repository instance +# subset - of revisions to be examined +# x - argument in tree form +symbols = {} + +# symbols which can't be used for a DoS attack for any given input +# (e.g. those which accept regexes as plain strings shouldn't be included) +# functions that just return a lot of changesets (like all) don't count here +safesymbols = set() + +class predicate(registrar.funcregistrar): + """Decorator to register revset predicate + + Usage:: + + @predicate('mypredicate(arg1, arg2[, arg3])') + def mypredicatefunc(repo, subset, x): + '''Explanation of this revset predicate .... + ''' + pass + + The first string argument of the constructor is used also in + online help. + + Use 'extpredicate' instead of this to register revset predicate in + extensions. + """ + table = symbols + formatdoc = "``%s``\n %s" + getname = registrar.funcregistrar.parsefuncdecl + + def __init__(self, decl, safe=False): + """'safe' indicates whether a predicate is safe for DoS attack + """ + super(predicate, self).__init__(decl) + self.safe = safe + + def extraaction(self, name, func): + if self.safe: + safesymbols.add(name) + +class extpredicate(registrar.delayregistrar): + """Decorator to register revset predicate in extensions + + Usage:: + + revsetpredicate = revset.extpredicate() + + @revsetpredicate('mypredicate(arg1, arg2[, arg3])') + def mypredicatefunc(repo, subset, x): + '''Explanation of this revset predicate .... + ''' + pass + + def uisetup(ui): + revsetpredicate.setup() + + 'revsetpredicate' instance above can be used to decorate multiple + functions, and 'setup()' on it registers all such functions at + once. + """ + registrar = predicate + +@predicate('_destupdate') def _destupdate(repo, subset, x): # experimental revset for update destination args = getargsdict(x, 'limit', 'clean check') return subset & baseset([destutil.destupdate(repo, **args)[0]]) +@predicate('_destmerge') def _destmerge(repo, subset, x): # experimental revset for merge destination getargs(x, 0, 0, _("_mergedefaultdest takes no arguments")) return subset & baseset([destutil.destmerge(repo)]) +@predicate('adds(pattern)', safe=True) def adds(repo, subset, x): - """``adds(pattern)`` - Changesets that add a file matching pattern. + """Changesets that add a file matching pattern. The pattern without explicit kind like ``glob:`` is expected to be relative to the current directory and match against a file or a @@ -486,9 +556,9 @@ pat = getstring(x, _("adds requires a pattern")) return checkstatus(repo, subset, pat, 1) +@predicate('ancestor(*changeset)', safe=True) def ancestor(repo, subset, x): - """``ancestor(*changeset)`` - A greatest common ancestor of the changesets. + """A greatest common ancestor of the changesets. Accepts 0 or more changesets. Will return empty list when passed no args. @@ -518,12 +588,13 @@ s = _revancestors(repo, heads, followfirst) return subset & s +@predicate('ancestors(set)', safe=True) def ancestors(repo, subset, x): - """``ancestors(set)`` - Changesets that are ancestors of a changeset in set. + """Changesets that are ancestors of a changeset in set. """ return _ancestors(repo, subset, x) +@predicate('_firstancestors', safe=True) def _firstancestors(repo, subset, x): # ``_firstancestors(set)`` # Like ``ancestors(set)`` but follows only the first parents. @@ -546,18 +617,18 @@ ps.add(r) return subset & ps +@predicate('author(string)', safe=True) def author(repo, subset, x): - """``author(string)`` - Alias for ``user(string)``. + """Alias for ``user(string)``. """ # i18n: "author" is a keyword n = encoding.lower(getstring(x, _("author requires a string"))) kind, pattern, matcher = _substringmatcher(n) return subset.filter(lambda x: matcher(encoding.lower(repo[x].user()))) +@predicate('bisect(string)', safe=True) def bisect(repo, subset, x): - """``bisect(string)`` - Changesets marked in the specified bisect status: + """Changesets marked in the specified bisect status: - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip - ``goods``, ``bads`` : csets topologically good/bad @@ -574,12 +645,13 @@ # Backward-compatibility # - no help entry so that we do not advertise it any more +@predicate('bisected', safe=True) def bisected(repo, subset, x): return bisect(repo, subset, x) +@predicate('bookmark([name])', safe=True) def bookmark(repo, subset, x): - """``bookmark([name])`` - The named bookmark or all bookmarks. + """The named bookmark or all bookmarks. If `name` starts with `re:`, the remainder of the name is treated as a regular expression. To match a bookmark that actually starts with `re:`, @@ -615,8 +687,9 @@ bms -= set([node.nullrev]) return subset & bms +@predicate('branch(string or set)', safe=True) def branch(repo, subset, x): - """``branch(string or set)`` + """ All changesets belonging to the given branch or the branches of the given changesets. @@ -651,9 +724,9 @@ c = s.__contains__ return subset.filter(lambda r: c(r) or getbi(r)[0] in b) +@predicate('bumped()', safe=True) def bumped(repo, subset, x): - """``bumped()`` - Mutable changesets marked as successors of public changesets. + """Mutable changesets marked as successors of public changesets. Only non-public and non-obsolete changesets can be `bumped`. """ @@ -662,9 +735,9 @@ bumped = obsmod.getrevs(repo, 'bumped') return subset & bumped +@predicate('bundle()', safe=True) def bundle(repo, subset, x): - """``bundle()`` - Changesets in the bundle. + """Changesets in the bundle. Bundle must be specified by the -R option.""" @@ -722,25 +795,25 @@ # This does not break because of other fullreposet misbehavior. return baseset(cs) +@predicate('children(set)', safe=True) def children(repo, subset, x): - """``children(set)`` - Child changesets of changesets in set. + """Child changesets of changesets in set. """ s = getset(repo, fullreposet(repo), x) cs = _children(repo, subset, s) return subset & cs +@predicate('closed()', safe=True) def closed(repo, subset, x): - """``closed()`` - Changeset is closed. + """Changeset is closed. """ # i18n: "closed" is a keyword getargs(x, 0, 0, _("closed takes no arguments")) return subset.filter(lambda r: repo[r].closesbranch()) +@predicate('contains(pattern)') def contains(repo, subset, x): - """``contains(pattern)`` - The revision's manifest contains a file matching pattern (but might not + """The revision's manifest contains a file matching pattern (but might not modify it). See :hg:`help patterns` for information about file patterns. The pattern without explicit kind like ``glob:`` is expected to be @@ -765,9 +838,9 @@ return subset.filter(matches) +@predicate('converted([id])', safe=True) def converted(repo, subset, x): - """``converted([id])`` - Changesets converted from the given identifier in the old repository if + """Changesets converted from the given identifier in the old repository if present, or all converted changesets if no identifier is specified. """ @@ -787,18 +860,18 @@ return subset.filter(lambda r: _matchvalue(r)) +@predicate('date(interval)', safe=True) def date(repo, subset, x): - """``date(interval)`` - Changesets within the interval, see :hg:`help dates`. + """Changesets within the interval, see :hg:`help dates`. """ # i18n: "date" is a keyword ds = getstring(x, _("date requires a string")) dm = util.matchdate(ds) return subset.filter(lambda x: dm(repo[x].date()[0])) +@predicate('desc(string)', safe=True) def desc(repo, subset, x): - """``desc(string)`` - Search commit message for string. The match is case-insensitive. + """Search commit message for string. The match is case-insensitive. """ # i18n: "desc" is a keyword ds = encoding.lower(getstring(x, _("desc requires a string"))) @@ -828,20 +901,21 @@ result = subset & result return result +@predicate('descendants(set)', safe=True) def descendants(repo, subset, x): - """``descendants(set)`` - Changesets which are descendants of changesets in set. + """Changesets which are descendants of changesets in set. """ return _descendants(repo, subset, x) +@predicate('_firstdescendants', safe=True) def _firstdescendants(repo, subset, x): # ``_firstdescendants(set)`` # Like ``descendants(set)`` but follows only the first parents. return _descendants(repo, subset, x, followfirst=True) +@predicate('destination([set])', safe=True) def destination(repo, subset, x): - """``destination([set])`` - Changesets that were created by a graft, transplant or rebase operation, + """Changesets that were created by a graft, transplant or rebase operation, with the given revisions specified as the source. Omitting the optional set is the same as passing all(). """ @@ -883,8 +957,9 @@ return subset.filter(dests.__contains__) +@predicate('divergent()', safe=True) def divergent(repo, subset, x): - """``divergent()`` + """ Final successors of changesets with an alternative set of final successors. """ # i18n: "divergent" is a keyword @@ -892,18 +967,18 @@ divergent = obsmod.getrevs(repo, 'divergent') return subset & divergent +@predicate('extinct()', safe=True) def extinct(repo, subset, x): - """``extinct()`` - Obsolete changesets with obsolete descendants only. + """Obsolete changesets with obsolete descendants only. """ # i18n: "extinct" is a keyword getargs(x, 0, 0, _("extinct takes no arguments")) extincts = obsmod.getrevs(repo, 'extinct') return subset & extincts +@predicate('extra(label, [value])', safe=True) def extra(repo, subset, x): - """``extra(label, [value])`` - Changesets with the given label in the extra metadata, with the given + """Changesets with the given label in the extra metadata, with the given optional value. If `value` starts with `re:`, the remainder of the value is treated as @@ -931,9 +1006,9 @@ return subset.filter(lambda r: _matchvalue(r)) +@predicate('filelog(pattern)', safe=True) def filelog(repo, subset, x): - """``filelog(pattern)`` - Changesets connected to the specified filelog. + """Changesets connected to the specified filelog. For performance reasons, visits only revisions mentioned in the file-level filelog, rather than filtering through all changesets (much faster, but @@ -1046,9 +1121,9 @@ return subset & s +@predicate('first(set, [n])', safe=True) def first(repo, subset, x): - """``first(set, [n])`` - An alias for limit(). + """An alias for limit(). """ return limit(repo, subset, x) @@ -1072,31 +1147,33 @@ return subset & s +@predicate('follow([pattern])', safe=True) def follow(repo, subset, x): - """``follow([pattern])`` + """ An alias for ``::.`` (ancestors of the working directory's first parent). If pattern is specified, the histories of files matching given pattern is followed, including copies. """ return _follow(repo, subset, x, 'follow') +@predicate('_followfirst', safe=True) def _followfirst(repo, subset, x): # ``followfirst([pattern])`` # Like ``follow([pattern])`` but follows only the first parent of # every revisions or files revisions. return _follow(repo, subset, x, '_followfirst', followfirst=True) +@predicate('all()', safe=True) def getall(repo, subset, x): - """``all()`` - All changesets, the same as ``0:tip``. + """All changesets, the same as ``0:tip``. """ # i18n: "all" is a keyword getargs(x, 0, 0, _("all takes no arguments")) return subset & spanset(repo) # drop "null" if any +@predicate('grep(regex)') def grep(repo, subset, x): - """``grep(regex)`` - Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')`` + """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')`` to ensure special escape characters are handled correctly. Unlike ``keyword(string)``, the match is case-sensitive. """ @@ -1115,6 +1192,7 @@ return subset.filter(matches) +@predicate('_matchfiles', safe=True) def _matchfiles(repo, subset, x): # _matchfiles takes a revset list of prefixed arguments: # @@ -1164,17 +1242,25 @@ m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc, exclude=exc, ctx=repo[rev], default=default) + # This directly read the changelog data as creating changectx for all + # revisions is quite expensive. + getfiles = repo.changelog.readfiles + wdirrev = node.wdirrev def matches(x): - for f in repo[x].files(): + if x == wdirrev: + files = repo[x].files() + else: + files = getfiles(x) + for f in files: if m(f): return True return False return subset.filter(matches) +@predicate('file(pattern)', safe=True) def hasfile(repo, subset, x): - """``file(pattern)`` - Changesets affecting files matched by pattern. + """Changesets affecting files matched by pattern. For a faster but less accurate result, consider using ``filelog()`` instead. @@ -1185,9 +1271,9 @@ pat = getstring(x, _("file requires a pattern")) return _matchfiles(repo, subset, ('string', 'p:' + pat)) +@predicate('head()', safe=True) def head(repo, subset, x): - """``head()`` - Changeset is a named branch head. + """Changeset is a named branch head. """ # i18n: "head" is a keyword getargs(x, 0, 0, _("head takes no arguments")) @@ -1201,26 +1287,26 @@ # necessary to ensure we preserve the order in subset. return baseset(hs) & subset +@predicate('heads(set)', safe=True) def heads(repo, subset, x): - """``heads(set)`` - Members of set with no children in set. + """Members of set with no children in set. """ s = getset(repo, subset, x) ps = parents(repo, subset, x) return s - ps +@predicate('hidden()', safe=True) def hidden(repo, subset, x): - """``hidden()`` - Hidden changesets. + """Hidden changesets. """ # i18n: "hidden" is a keyword getargs(x, 0, 0, _("hidden takes no arguments")) hiddenrevs = repoview.filterrevs(repo, 'visible') return subset & hiddenrevs +@predicate('keyword(string)', safe=True) def keyword(repo, subset, x): - """``keyword(string)`` - Search commit message, user name, and names of changed files for + """Search commit message, user name, and names of changed files for string. The match is case-insensitive. """ # i18n: "keyword" is a keyword @@ -1233,9 +1319,9 @@ return subset.filter(matches) +@predicate('limit(set[, n[, offset]])', safe=True) def limit(repo, subset, x): - """``limit(set[, n[, offset]])`` - First n members of set, defaulting to 1, starting from offset. + """First n members of set, defaulting to 1, starting from offset. """ args = getargsdict(x, 'limit', 'set n offset') if 'set' not in args: @@ -1269,9 +1355,9 @@ result.append(y) return baseset(result) +@predicate('last(set, [n])', safe=True) def last(repo, subset, x): - """``last(set, [n])`` - Last n members of set, defaulting to 1. + """Last n members of set, defaulting to 1. """ # i18n: "last" is a keyword l = getargs(x, 1, 2, _("last requires one or two arguments")) @@ -1295,9 +1381,9 @@ result.append(y) return baseset(result) +@predicate('max(set)', safe=True) def maxrev(repo, subset, x): - """``max(set)`` - Changeset with highest revision number in set. + """Changeset with highest revision number in set. """ os = getset(repo, fullreposet(repo), x) try: @@ -1310,18 +1396,18 @@ pass return baseset() +@predicate('merge()', safe=True) def merge(repo, subset, x): - """``merge()`` - Changeset is a merge changeset. + """Changeset is a merge changeset. """ # i18n: "merge" is a keyword getargs(x, 0, 0, _("merge takes no arguments")) cl = repo.changelog return subset.filter(lambda r: cl.parentrevs(r)[1] != -1) +@predicate('branchpoint()', safe=True) def branchpoint(repo, subset, x): - """``branchpoint()`` - Changesets with more than one child. + """Changesets with more than one child. """ # i18n: "branchpoint" is a keyword getargs(x, 0, 0, _("branchpoint takes no arguments")) @@ -1338,9 +1424,9 @@ parentscount[p - baserev] += 1 return subset.filter(lambda r: parentscount[r - baserev] > 1) +@predicate('min(set)', safe=True) def minrev(repo, subset, x): - """``min(set)`` - Changeset with lowest revision number in set. + """Changeset with lowest revision number in set. """ os = getset(repo, fullreposet(repo), x) try: @@ -1353,9 +1439,9 @@ pass return baseset() +@predicate('modifies(pattern)', safe=True) def modifies(repo, subset, x): - """``modifies(pattern)`` - Changesets modifying files matched by pattern. + """Changesets modifying files matched by pattern. The pattern without explicit kind like ``glob:`` is expected to be relative to the current directory and match against a file or a @@ -1365,9 +1451,9 @@ pat = getstring(x, _("modifies requires a pattern")) return checkstatus(repo, subset, pat, 0) +@predicate('named(namespace)') def named(repo, subset, x): - """``named(namespace)`` - The changesets in a given namespace. + """The changesets in a given namespace. If `namespace` starts with `re:`, the remainder of the string is treated as a regular expression. To match a namespace that actually starts with `re:`, @@ -1403,9 +1489,9 @@ names -= set([node.nullrev]) return subset & names +@predicate('id(string)', safe=True) def node_(repo, subset, x): - """``id(string)`` - Revision non-ambiguously specified by the given hex string prefix. + """Revision non-ambiguously specified by the given hex string prefix. """ # i18n: "id" is a keyword l = getargs(x, 1, 1, _("id requires one argument")) @@ -1427,17 +1513,17 @@ result = baseset([rn]) return result & subset +@predicate('obsolete()', safe=True) def obsolete(repo, subset, x): - """``obsolete()`` - Mutable changeset with a newer version.""" + """Mutable changeset with a newer version.""" # i18n: "obsolete" is a keyword getargs(x, 0, 0, _("obsolete takes no arguments")) obsoletes = obsmod.getrevs(repo, 'obsolete') return subset & obsoletes +@predicate('only(set, [set])', safe=True) def only(repo, subset, x): - """``only(set, [set])`` - Changesets that are ancestors of the first set that are not ancestors + """Changesets that are ancestors of the first set that are not ancestors of any other head in the repo. If a second set is specified, the result is ancestors of the first set that are not ancestors of the second set (i.e. ::<set1> - ::<set2>). @@ -1461,8 +1547,9 @@ # some optimisations from the fact this is a baseset. return subset & results +@predicate('origin([set])', safe=True) def origin(repo, subset, x): - """``origin([set])`` + """ Changesets that were specified as a source for the grafts, transplants or rebases that created the given revisions. Omitting the optional set is the same as passing all(). If a changeset created by these operations is itself @@ -1492,9 +1579,9 @@ # some optimisations from the fact this is a baseset. return subset & o +@predicate('outgoing([path])', safe=True) def outgoing(repo, subset, x): - """``outgoing([path])`` - Changesets not found in the specified destination repository, or the + """Changesets not found in the specified destination repository, or the default push location. """ # Avoid cycles. @@ -1519,9 +1606,9 @@ o = set([cl.rev(r) for r in outgoing.missing]) return subset & o +@predicate('p1([set])', safe=True) def p1(repo, subset, x): - """``p1([set])`` - First parent of changesets in set, or the working directory. + """First parent of changesets in set, or the working directory. """ if x is None: p = repo[x].p1().rev() @@ -1538,9 +1625,9 @@ # some optimisations from the fact this is a baseset. return subset & ps +@predicate('p2([set])', safe=True) def p2(repo, subset, x): - """``p2([set])`` - Second parent of changesets in set, or the working directory. + """Second parent of changesets in set, or the working directory. """ if x is None: ps = repo[x].parents() @@ -1561,8 +1648,9 @@ # some optimisations from the fact this is a baseset. return subset & ps +@predicate('parents([set])', safe=True) def parents(repo, subset, x): - """``parents([set])`` + """ The set of all parents for all changesets in set, or the working directory. """ if x is None: @@ -1593,17 +1681,17 @@ condition = lambda r: phase(repo, r) == target return subset.filter(condition, cache=False) +@predicate('draft()', safe=True) def draft(repo, subset, x): - """``draft()`` - Changeset in draft phase.""" + """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) +@predicate('secret()', safe=True) def secret(repo, subset, x): - """``secret()`` - Changeset in secret phase.""" + """Changeset in secret phase.""" # i18n: "secret" is a keyword getargs(x, 0, 0, _("secret takes no arguments")) target = phases.secret @@ -1634,9 +1722,9 @@ ps.add(parents[1]) return subset & ps +@predicate('present(set)', safe=True) def present(repo, subset, x): - """``present(set)`` - An empty set, if any revision in set isn't found; otherwise, + """An empty set, if any revision in set isn't found; otherwise, all revisions in set. If any of specified revisions is not present in the local repository, @@ -1649,6 +1737,7 @@ return baseset() # for internal use +@predicate('_notpublic', safe=True) def _notpublic(repo, subset, x): getargs(x, 0, 0, "_notpublic takes no arguments") repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded @@ -1665,9 +1754,9 @@ condition = lambda r: phase(repo, r) != target return subset.filter(condition, cache=False) +@predicate('public()', safe=True) def public(repo, subset, x): - """``public()`` - Changeset in public phase.""" + """Changeset in public phase.""" # i18n: "public" is a keyword getargs(x, 0, 0, _("public takes no arguments")) phase = repo._phasecache.phase @@ -1675,16 +1764,16 @@ condition = lambda r: phase(repo, r) == target return subset.filter(condition, cache=False) +@predicate('remote([id [,path]])', safe=True) def remote(repo, subset, x): - """``remote([id [,path]])`` - Local revision that corresponds to the given identifier in a + """Local revision that corresponds to the given identifier in a remote repository, if present. Here, the '.' identifier is a synonym for the current local branch. """ from . import hg # avoid start-up nasties # i18n: "remote" is a keyword - l = getargs(x, 0, 2, _("remote takes one, two or no arguments")) + l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments")) q = '.' if len(l) > 0: @@ -1710,9 +1799,9 @@ return baseset([r]) return baseset() +@predicate('removes(pattern)', safe=True) def removes(repo, subset, x): - """``removes(pattern)`` - Changesets which remove files matching pattern. + """Changesets which remove files matching pattern. The pattern without explicit kind like ``glob:`` is expected to be relative to the current directory and match against a file or a @@ -1722,9 +1811,9 @@ pat = getstring(x, _("removes requires a pattern")) return checkstatus(repo, subset, pat, 2) +@predicate('rev(number)', safe=True) def rev(repo, subset, x): - """``rev(number)`` - Revision with the given numeric identifier. + """Revision with the given numeric identifier. """ # i18n: "rev" is a keyword l = getargs(x, 1, 1, _("rev requires one argument")) @@ -1738,9 +1827,9 @@ return baseset() return subset & baseset([l]) +@predicate('matching(revision [, field])', safe=True) def matching(repo, subset, x): - """``matching(revision [, field])`` - Changesets in which a given set of fields match the set of fields in the + """Changesets in which a given set of fields match the set of fields in the selected revision or set. To match more than one field pass the list of fields to match separated @@ -1850,17 +1939,17 @@ return subset.filter(matches) +@predicate('reverse(set)', safe=True) def reverse(repo, subset, x): - """``reverse(set)`` - Reverse order of set. + """Reverse order of set. """ l = getset(repo, subset, x) l.reverse() return l +@predicate('roots(set)', safe=True) def roots(repo, subset, x): - """``roots(set)`` - Changesets in set with no parent changeset in set. + """Changesets in set with no parent changeset in set. """ s = getset(repo, fullreposet(repo), x) parents = repo.changelog.parentrevs @@ -1871,9 +1960,9 @@ return True return subset & s.filter(filter) +@predicate('sort(set[, [-]key...])', safe=True) def sort(repo, subset, x): - """``sort(set[, [-]key...])`` - Sort set by keys. The default sort order is ascending, specify a key + """Sort set by keys. The default sort order is ascending, specify a key as ``-key`` to sort in descending order. The keys can be: @@ -1934,9 +2023,9 @@ l.sort() return baseset([e[-1] for e in l]) +@predicate('subrepo([pattern])') def subrepo(repo, subset, x): - """``subrepo([pattern])`` - Changesets that add, modify or remove the given subrepo. If no subrepo + """Changesets that add, modify or remove the given subrepo. If no subrepo pattern is named, any subrepo changes are returned. """ # i18n: "subrepo" is a keyword @@ -1983,9 +2072,9 @@ matcher = lambda s: pattern in s return kind, pattern, matcher +@predicate('tag([name])', safe=True) def tag(repo, subset, x): - """``tag([name])`` - The specified tag by name, or all tagged revisions if no name is given. + """The specified tag by name, or all tagged revisions if no name is given. If `name` starts with `re:`, the remainder of the name is treated as a regular expression. To match a tag that actually starts with `re:`, @@ -2012,12 +2101,13 @@ s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip']) return subset & s +@predicate('tagged', safe=True) def tagged(repo, subset, x): return tag(repo, subset, x) +@predicate('unstable()', safe=True) def unstable(repo, subset, x): - """``unstable()`` - Non-obsolete changesets with obsolete ancestors. + """Non-obsolete changesets with obsolete ancestors. """ # i18n: "unstable" is a keyword getargs(x, 0, 0, _("unstable takes no arguments")) @@ -2025,9 +2115,9 @@ return subset & unstables +@predicate('user(string)', safe=True) def user(repo, subset, x): - """``user(string)`` - User name contains string. The match is case-insensitive. + """User name contains string. The match is case-insensitive. If `string` starts with `re:`, the remainder of the string is treated as a regular expression. To match a user that actually contains `re:`, use @@ -2036,6 +2126,7 @@ return author(repo, subset, x) # experimental +@predicate('wdir', safe=True) def wdir(repo, subset, x): # i18n: "wdir" is a keyword getargs(x, 0, 0, _("wdir takes no arguments")) @@ -2044,6 +2135,7 @@ return baseset() # for internal use +@predicate('_list', safe=True) def _list(repo, subset, x): s = getstring(x, "internal error") if not s: @@ -2073,6 +2165,7 @@ return baseset(ls) # for internal use +@predicate('_intlist', safe=True) def _intlist(repo, subset, x): s = getstring(x, "internal error") if not s: @@ -2082,6 +2175,7 @@ return baseset([r for r in ls if r in s]) # for internal use +@predicate('_hexlist', safe=True) def _hexlist(repo, subset, x): s = getstring(x, "internal error") if not s: @@ -2091,157 +2185,6 @@ s = subset return baseset([r for r in ls if r in s]) -symbols = { - "_destupdate": _destupdate, - "_destmerge": _destmerge, - "adds": adds, - "all": getall, - "ancestor": ancestor, - "ancestors": ancestors, - "_firstancestors": _firstancestors, - "author": author, - "bisect": bisect, - "bisected": bisected, - "bookmark": bookmark, - "branch": branch, - "branchpoint": branchpoint, - "bumped": bumped, - "bundle": bundle, - "children": children, - "closed": closed, - "contains": contains, - "converted": converted, - "date": date, - "desc": desc, - "descendants": descendants, - "_firstdescendants": _firstdescendants, - "destination": destination, - "divergent": divergent, - "draft": draft, - "extinct": extinct, - "extra": extra, - "file": hasfile, - "filelog": filelog, - "first": first, - "follow": follow, - "_followfirst": _followfirst, - "grep": grep, - "head": head, - "heads": heads, - "hidden": hidden, - "id": node_, - "keyword": keyword, - "last": last, - "limit": limit, - "_matchfiles": _matchfiles, - "max": maxrev, - "merge": merge, - "min": minrev, - "modifies": modifies, - "named": named, - "obsolete": obsolete, - "only": only, - "origin": origin, - "outgoing": outgoing, - "p1": p1, - "p2": p2, - "parents": parents, - "present": present, - "public": public, - "_notpublic": _notpublic, - "remote": remote, - "removes": removes, - "rev": rev, - "reverse": reverse, - "roots": roots, - "sort": sort, - "secret": secret, - "subrepo": subrepo, - "matching": matching, - "tag": tag, - "tagged": tagged, - "user": user, - "unstable": unstable, - "wdir": wdir, - "_list": _list, - "_intlist": _intlist, - "_hexlist": _hexlist, -} - -# symbols which can't be used for a DoS attack for any given input -# (e.g. those which accept regexes as plain strings shouldn't be included) -# functions that just return a lot of changesets (like all) don't count here -safesymbols = set([ - "adds", - "all", - "ancestor", - "ancestors", - "_firstancestors", - "author", - "bisect", - "bisected", - "bookmark", - "branch", - "branchpoint", - "bumped", - "bundle", - "children", - "closed", - "converted", - "date", - "desc", - "descendants", - "_firstdescendants", - "destination", - "divergent", - "draft", - "extinct", - "extra", - "file", - "filelog", - "first", - "follow", - "_followfirst", - "head", - "heads", - "hidden", - "id", - "keyword", - "last", - "limit", - "_matchfiles", - "max", - "merge", - "min", - "modifies", - "obsolete", - "only", - "origin", - "outgoing", - "p1", - "p2", - "parents", - "present", - "public", - "_notpublic", - "remote", - "removes", - "rev", - "reverse", - "roots", - "sort", - "secret", - "matching", - "tag", - "tagged", - "user", - "unstable", - "wdir", - "_list", - "_intlist", - "_hexlist", -]) - methods = { "range": rangeset, "dagrange": dagrange, @@ -2347,7 +2290,7 @@ elif op == 'not': # Optimize not public() to _notpublic() because we have a fast version if x[1] == ('func', ('symbol', 'public'), None): - newsym = ('func', ('symbol', '_notpublic'), None) + newsym = ('func', ('symbol', '_notpublic'), None) o = optimize(newsym, not small) return o[0], o[1] else:
--- a/mercurial/scmposix.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/scmposix.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,11 @@ -import sys, os -import osutil +from __future__ import absolute_import + +import os +import sys + +from . import ( + osutil, +) def _rcfiles(path): rcs = [os.path.join(path, 'hgrc')]
--- a/mercurial/scmutil.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/scmutil.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,17 +5,37 @@ # 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 _ -from mercurial.node import wdirrev -import util, error, osutil, revset, similar, encoding, phases -import pathutil -import match as matchmod -import os, errno, re, glob, tempfile, shutil, stat +from __future__ import absolute_import + +import Queue +import contextlib +import errno +import glob +import os +import re +import shutil +import stat +import tempfile +import threading + +from .i18n import _ +from .node import wdirrev +from . import ( + encoding, + error, + match as matchmod, + osutil, + pathutil, + phases, + revset, + similar, + util, +) if os.name == 'nt': - import scmwindows as scmplatform + from . import scmwindows as scmplatform else: - import scmposix as scmplatform + from . import scmposix as scmplatform systemrcpath = scmplatform.systemrcpath userrcpath = scmplatform.userrcpath @@ -237,7 +257,7 @@ return [] def open(self, path, mode="r", text=False, atomictemp=False, - notindexed=False): + notindexed=False, backgroundclose=False): '''Open ``path`` file, which is relative to vfs root. Newly created directories are marked as "not to be indexed by @@ -245,42 +265,28 @@ for "write" mode access. ''' self.open = self.__call__ - return self.__call__(path, mode, text, atomictemp, notindexed) + return self.__call__(path, mode, text, atomictemp, notindexed, + backgroundclose=backgroundclose) def read(self, path): - fp = self(path, 'rb') - try: + with self(path, 'rb') as fp: return fp.read() - finally: - fp.close() def readlines(self, path, mode='rb'): - fp = self(path, mode=mode) - try: + with self(path, mode=mode) as fp: return fp.readlines() - finally: - fp.close() def write(self, path, data): - fp = self(path, 'wb') - try: + with self(path, 'wb') as fp: return fp.write(data) - finally: - fp.close() def writelines(self, path, data, mode='wb', notindexed=False): - fp = self(path, mode=mode, notindexed=notindexed) - try: + with self(path, mode=mode, notindexed=notindexed) as fp: return fp.writelines(data) - finally: - fp.close() def append(self, path, data): - fp = self(path, 'ab') - try: + with self(path, 'ab') as fp: return fp.write(data) - finally: - fp.close() def basename(self, path): """return base element of a path (as os.path.basename would do) @@ -434,6 +440,27 @@ for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror): yield (dirpath[prefixlen:], dirs, files) + @contextlib.contextmanager + def backgroundclosing(self, ui, expectedcount=-1): + """Allow files to be closed asynchronously. + + When this context manager is active, ``backgroundclose`` can be passed + to ``__call__``/``open`` to result in the file possibly being closed + asynchronously, on a background thread. + """ + # This is an arbitrary restriction and could be changed if we ever + # have a use case. + vfs = getattr(self, 'vfs', self) + if getattr(vfs, '_backgroundfilecloser', None): + raise error.Abort('can only have 1 active background file closer') + + with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc: + try: + vfs._backgroundfilecloser = bfc + yield bfc + finally: + vfs._backgroundfilecloser = None + class vfs(abstractvfs): '''Operate files relative to a base directory @@ -446,22 +473,22 @@ if realpath: base = os.path.realpath(base) self.base = base - self._setmustaudit(audit) + self.mustaudit = audit self.createmode = None self._trustnlink = None - def _getmustaudit(self): + @property + def mustaudit(self): return self._audit - def _setmustaudit(self, onoff): + @mustaudit.setter + def mustaudit(self, onoff): self._audit = onoff if onoff: self.audit = pathutil.pathauditor(self.base) else: self.audit = util.always - mustaudit = property(_getmustaudit, _setmustaudit) - @util.propertycache def _cansymlink(self): return util.checklink(self.base) @@ -476,12 +503,25 @@ os.chmod(name, self.createmode & 0o666) def __call__(self, path, mode="r", text=False, atomictemp=False, - notindexed=False): + notindexed=False, backgroundclose=False): '''Open ``path`` file, which is relative to vfs root. Newly created directories are marked as "not to be indexed by the content indexing service", if ``notindexed`` is specified for "write" mode access. + + If ``backgroundclose`` is passed, the file may be closed asynchronously. + It can only be used if the ``self.backgroundclosing()`` context manager + is active. This should only be specified if the following criteria hold: + + 1. There is a potential for writing thousands of files. Unless you + are writing thousands of files, the performance benefits of + asynchronously closing files is not realized. + 2. Files are opened exactly once for the ``backgroundclosing`` + active duration and are therefore free of race conditions between + closing a file on a background thread and reopening it. (If the + file were opened multiple times, there could be unflushed data + because the original file handle hasn't been flushed/closed yet.) ''' if self._audit: r = util.checkosfilename(path) @@ -509,11 +549,10 @@ else: # nlinks() may behave differently for files on Windows # shares if the file is open. - fd = util.posixfile(f) - nlink = util.nlinks(f) - if nlink < 1: - nlink = 2 # force mktempcopy (issue1922) - fd.close() + with util.posixfile(f): + nlink = util.nlinks(f) + if nlink < 1: + nlink = 2 # force mktempcopy (issue1922) except (OSError, IOError) as e: if e.errno != errno.ENOENT: raise @@ -527,6 +566,14 @@ fp = util.posixfile(f, mode) if nlink == 0: self._fixfilemode(f) + + if backgroundclose: + if not self._backgroundfilecloser: + raise error.Abort('backgroundclose can only be used when a ' + 'backgroundclosing context manager is active') + + fp = delayclosedfile(fp, self._backgroundfilecloser) + return fp def symlink(self, src, dst): @@ -560,14 +607,14 @@ def __init__(self, vfs): self.vfs = vfs - def _getmustaudit(self): + @property + def mustaudit(self): return self.vfs.mustaudit - def _setmustaudit(self, onoff): + @mustaudit.setter + def mustaudit(self, onoff): self.vfs.mustaudit = onoff - mustaudit = property(_getmustaudit, _setmustaudit) - class filtervfs(abstractvfs, auditvfs): '''Wrapper vfs for filtering filenames with a function.''' @@ -821,6 +868,26 @@ '''Return a matcher that will efficiently match exactly these files.''' return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn) +def origpath(ui, repo, filepath): + '''customize where .orig files are created + + Fetch user defined path from config file: [ui] origbackuppath = <path> + Fall back to default (filepath) if not specified + ''' + origbackuppath = ui.config('ui', 'origbackuppath', None) + if origbackuppath is None: + return filepath + ".orig" + + filepathfromroot = os.path.relpath(filepath, start=repo.root) + fullorigpath = repo.wjoin(origbackuppath, filepathfromroot) + + origbackupdir = repo.vfs.dirname(fullorigpath) + if not repo.vfs.exists(origbackupdir): + ui.note(_('creating directory: %s\n') % origbackupdir) + util.makedirs(origbackupdir) + + return fullorigpath + ".orig" + def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None): if opts is None: opts = {} @@ -962,14 +1029,11 @@ '''Marks the files in unknown as added, the files in deleted as removed, and the files in renames as copied.''' wctx = repo[None] - wlock = repo.wlock() - try: + with repo.wlock(): wctx.forget(deleted) wctx.add(unknown) for new, old in renames.iteritems(): wctx.copy(old, new) - finally: - wlock.release() def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): """Update the dirstate to reflect the intent of copying src to dst. For @@ -1010,10 +1074,9 @@ return requirements def writerequires(opener, requirements): - reqfile = opener("requires", "w") - for r in sorted(requirements): - reqfile.write("%s\n" % r) - reqfile.close() + with opener('requires', 'w') as fp: + for r in sorted(requirements): + fp.write("%s\n" % r) class filecachesubentry(object): def __init__(self, path, stat): @@ -1181,3 +1244,136 @@ subprocess.""" return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args, **kwargs) + +def gdinitconfig(ui): + """helper function to know if a repo should be created as general delta + """ + # experimental config: format.generaldelta + return (ui.configbool('format', 'generaldelta', False) + or ui.configbool('format', 'usegeneraldelta', True)) + +def gddeltaconfig(ui): + """helper function to know if incoming delta should be optimised + """ + # experimental config: format.generaldelta + return ui.configbool('format', 'generaldelta', False) + +class delayclosedfile(object): + """Proxy for a file object whose close is delayed. + + Do not instantiate outside of the vfs layer. + """ + + def __init__(self, fh, closer): + object.__setattr__(self, '_origfh', fh) + object.__setattr__(self, '_closer', closer) + + def __getattr__(self, attr): + return getattr(self._origfh, attr) + + def __setattr__(self, attr, value): + return setattr(self._origfh, attr, value) + + def __delattr__(self, attr): + return delattr(self._origfh, attr) + + def __enter__(self): + return self._origfh.__enter__() + + def __exit__(self, exc_type, exc_value, exc_tb): + self._closer.close(self._origfh) + + def close(self): + self._closer.close(self._origfh) + +class backgroundfilecloser(object): + """Coordinates background closing of file handles on multiple threads.""" + def __init__(self, ui, expectedcount=-1): + self._running = False + self._entered = False + self._threads = [] + self._threadexception = None + + # Only Windows/NTFS has slow file closing. So only enable by default + # on that platform. But allow to be enabled elsewhere for testing. + defaultenabled = os.name == 'nt' + enabled = ui.configbool('worker', 'backgroundclose', defaultenabled) + + if not enabled: + return + + # There is overhead to starting and stopping the background threads. + # Don't do background processing unless the file count is large enough + # to justify it. + minfilecount = ui.configint('worker', 'backgroundcloseminfilecount', + 2048) + # FUTURE dynamically start background threads after minfilecount closes. + # (We don't currently have any callers that don't know their file count) + if expectedcount > 0 and expectedcount < minfilecount: + return + + # Windows defaults to a limit of 512 open files. A buffer of 128 + # should give us enough headway. + maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384) + threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4) + + ui.debug('starting %d threads for background file closing\n' % + threadcount) + + self._queue = Queue.Queue(maxsize=maxqueue) + self._running = True + + for i in range(threadcount): + t = threading.Thread(target=self._worker, name='backgroundcloser') + self._threads.append(t) + t.start() + + def __enter__(self): + self._entered = True + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self._running = False + + # Wait for threads to finish closing so open files don't linger for + # longer than lifetime of context manager. + for t in self._threads: + t.join() + + def _worker(self): + """Main routine for worker thread.""" + while True: + try: + fh = self._queue.get(block=True, timeout=0.100) + # Need to catch or the thread will terminate and + # we could orphan file descriptors. + try: + fh.close() + except Exception as e: + # Stash so can re-raise from main thread later. + self._threadexception = e + except Queue.Empty: + if not self._running: + break + + def close(self, fh): + """Schedule a file for closing.""" + if not self._entered: + raise error.Abort('can only call close() when context manager ' + 'active') + + # If a background thread encountered an exception, raise now so we fail + # fast. Otherwise we may potentially go on for minutes until the error + # is acted on. + if self._threadexception: + e = self._threadexception + self._threadexception = None + raise e + + # If we're not actively running, close synchronously. + if not self._running: + fh.close() + return + + self._queue.put(fh, block=True, timeout=None) +
--- a/mercurial/scmwindows.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/scmwindows.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,12 @@ +from __future__ import absolute_import + +import _winreg import os -import osutil -import util -import _winreg + +from . import ( + osutil, + util, +) def systemrcpath(): '''return default os-specific hgrc search path'''
--- a/mercurial/similar.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/similar.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,10 +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. -from i18n import _ -import util -import mdiff -import bdiff +from __future__ import absolute_import + +from .i18n import _ +from . import ( + bdiff, + mdiff, + util, +) def _findexactmatches(repo, added, removed): '''find renamed files that have no changes
--- a/mercurial/sshpeer.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/sshpeer.py Sun Jan 17 21:40:21 2016 -0600 @@ -22,6 +22,11 @@ def release(self): self.repo.unlock() self.repo = None + def __enter__(self): + return self + def __exit__(self, exc_type, exc_val, exc_tb): + if self.repo: + self.release() def __del__(self): if self.repo: self.release()
--- a/mercurial/statichttprepo.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/statichttprepo.py Sun Jan 17 21:40:21 2016 -0600 @@ -35,6 +35,13 @@ self.pos = 0 self.opener = opener self.name = url + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + def seek(self, pos): self.pos = pos def read(self, bytes=None):
--- a/mercurial/store.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/store.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,9 +5,19 @@ # 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 scmutil, util, parsers, error -import os, stat, errno +from __future__ import absolute_import + +import errno +import os +import stat + +from .i18n import _ +from . import ( + error, + parsers, + scmutil, + util, +) _sha = util.sha1
--- a/mercurial/streamclone.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/streamclone.py Sun Jan 17 21:40:21 2016 -0600 @@ -137,8 +137,7 @@ raise error.ResponseError( _('unexpected response from remote server:'), l) - lock = repo.lock() - try: + with repo.lock(): consumev1(repo, fp, filecount, bytecount) # new requirements = old non-format requirements + @@ -153,8 +152,6 @@ branchmap.replacecache(repo, rbranchmap) repo.invalidate() - finally: - lock.release() def allowservergeneration(ui): """Whether streaming clones are allowed from the server.""" @@ -186,15 +183,12 @@ entries = [] total_bytes = 0 # Get consistent snapshot of repo, lock during scan. - lock = repo.lock() - try: + with repo.lock(): 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)) @@ -212,12 +206,7 @@ # 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 + yield svfs.read(name) else: for chunk in util.filechunkiter(svfs(name), limit=size): yield chunk @@ -301,38 +290,35 @@ Like "streamout," the status line added by the wire protocol is not handled by this function. """ - lock = repo.lock() - try: + with repo.lock(): repo.ui.status(_('%d files to transfer, %s of data\n') % (filecount, util.bytecount(bytecount))) handled_bytes = 0 repo.ui.progress(_('clone'), 0, total=bytecount) start = time.time() - tr = repo.transaction(_('clone')) - try: - for i in xrange(filecount): - # 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=bytecount) - ofp.write(chunk) - ofp.close() - tr.close() - finally: - tr.release() + with repo.transaction('clone'): + with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount): + for i in xrange(filecount): + # 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 + path = store.decodedir(name) + with repo.svfs(path, 'w', backgroundclose=True) as ofp: + for chunk in util.filechunkiter(fp, limit=size): + handled_bytes += len(chunk) + repo.ui.progress(_('clone'), handled_bytes, + total=bytecount) + ofp.write(chunk) # Writing straight to files circumvented the inmemory caches repo.invalidate() @@ -344,19 +330,8 @@ repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') % (util.bytecount(bytecount), elapsed, util.bytecount(bytecount / elapsed))) - finally: - lock.release() -def applybundlev1(repo, fp): - """Apply the content from a stream clone bundle version 1. - - We assume the 4 byte header has been read and validated and the file handle - is at the 2 byte compression identifier. - """ - if len(repo): - raise error.Abort(_('cannot apply stream clone bundle on non-empty ' - 'repo')) - +def readbundle1header(fp): compression = fp.read(2) if compression != 'UN': raise error.Abort(_('only uncompressed stream clone bundles are ' @@ -371,6 +346,20 @@ 'requirements not properly encoded')) requirements = set(requires.rstrip('\0').split(',')) + + return filecount, bytecount, requirements + +def applybundlev1(repo, fp): + """Apply the content from a stream clone bundle version 1. + + We assume the 4 byte header has been read and validated and the file handle + is at the 2 byte compression identifier. + """ + if len(repo): + raise error.Abort(_('cannot apply stream clone bundle on non-empty ' + 'repo')) + + filecount, bytecount, requirements = readbundle1header(fp) missingreqs = requirements - repo.supportedformats if missingreqs: raise error.Abort(_('unable to apply stream clone: '
--- a/mercurial/subrepo.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/subrepo.py Sun Jan 17 21:40:21 2016 -0600 @@ -631,11 +631,8 @@ self._initrepo(r, state[0], create) def storeclean(self, path): - lock = self._repo.lock() - try: + with self._repo.lock(): return self._storeclean(path) - finally: - lock.release() def _storeclean(self, path): clean = True @@ -679,13 +676,10 @@ store may be "clean" versus a given remote repo, but not versus another ''' cachefile = _getstorehashcachename(remotepath) - lock = self._repo.lock() - try: + with self._repo.lock(): storehash = list(self._calcstorehash(remotepath)) vfs = self._cachestorehashvfs vfs.writelines(cachefile, storehash, mode='w', notindexed=True) - finally: - lock.release() def _getctx(self): '''fetch the context for this subrepo revision, possibly a workingctx @@ -1910,7 +1904,7 @@ status = self.status(None) names = status.modified for name in names: - bakname = "%s.orig" % name + bakname = scmutil.origpath(self.ui, self._subparent, name) self.ui.note(_('saving current version of %s as %s\n') % (name, bakname)) self.wvfs.rename(name, bakname)
--- a/mercurial/templatefilters.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templatefilters.py Sun Jan 17 21:40:21 2016 -0600 @@ -223,7 +223,7 @@ raise TypeError('cannot encode type %s' % obj.__class__.__name__) def _uescape(c): - if ord(c) < 0x80: + if 0x20 <= ord(c) < 0x80: return c else: return '\\u%04x' % ord(c)
--- a/mercurial/templatekw.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templatekw.py Sun Jan 17 21:40:21 2016 -0600 @@ -7,7 +7,7 @@ from __future__ import absolute_import -from .node import hex +from .node import hex, nullid from . import ( error, hbisect, @@ -34,7 +34,7 @@ self.joinfmt = lambda x: x.values()[0] def __iter__(self): return self.gen - def __call__(self): + def itermaps(self): makemap = self._makemap for x in self.values: yield makemap(x) @@ -340,6 +340,21 @@ """ return showlist('file', args['ctx'].files(), **args) +def showgraphnode(repo, ctx, **args): + """:graphnode: String. The character representing the changeset node in + an ASCII revision graph""" + wpnodes = repo.dirstate.parents() + if wpnodes[1] == nullid: + wpnodes = wpnodes[:1] + if ctx.node() in wpnodes: + return '@' + elif ctx.obsolete(): + return 'x' + elif ctx.closesbranch(): + return '_' + else: + return 'o' + def showlatesttag(**args): """:latesttag: List of strings. The global tags on the most recent globally tagged ancestor of this changeset. @@ -398,6 +413,27 @@ args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)}) return templ('manifest', **args) +def shownames(namespace, **args): + """helper method to generate a template keyword for a namespace""" + ctx = args['ctx'] + repo = ctx.repo() + ns = repo.names[namespace] + names = ns.names(repo, ctx.node()) + return showlist(ns.templatename, names, plural=namespace, **args) + +def shownamespaces(**args): + """:namespaces: Dict of lists. Names attached to this changeset per + namespace.""" + ctx = args['ctx'] + repo = ctx.repo() + namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()), + **args)) + for k, ns in repo.names.iteritems()) + f = _showlist('namespace', list(namespaces), **args) + return _hybrid(f, namespaces, + lambda k: {'namespace': k, 'names': namespaces[k]}, + lambda x: x['namespace']) + def shownode(repo, ctx, templ, **args): """:node: String. The changeset identification hash, as a 40 hexadecimal digit string. @@ -474,14 +510,6 @@ subrepos.append(sub) # removed in ctx return showlist('subrepo', sorted(subrepos), **args) -def shownames(namespace, **args): - """helper method to generate a template keyword for a namespace""" - ctx = args['ctx'] - repo = ctx.repo() - ns = repo.names[namespace] - names = ns.names(repo, ctx.node()) - return showlist(ns.templatename, names, plural=namespace, **args) - # don't remove "showtags" definition, even though namespaces will put # a helper function for "tags" keyword into "keywords" map automatically, # because online help text is built without namespaces initialization @@ -518,9 +546,11 @@ 'file_dels': showfiledels, 'file_mods': showfilemods, 'files': showfiles, + 'graphnode': showgraphnode, 'latesttag': showlatesttag, 'latesttagdistance': showlatesttagdistance, 'manifest': showmanifest, + 'namespaces': shownamespaces, 'node': shownode, 'p1rev': showp1rev, 'p1node': showp1node,
--- a/mercurial/templater.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templater.py Sun Jan 17 21:40:21 2016 -0600 @@ -281,8 +281,8 @@ def runmap(context, mapping, data): func, data, ctmpl = data d = func(context, mapping, data) - if callable(d): - d = d() + if util.safehasattr(d, 'itermaps'): + d = d.itermaps() lm = mapping.copy() @@ -336,7 +336,7 @@ specifying files to include or exclude.""" if len(args) > 2: # i18n: "diff" is a keyword - raise error.ParseError(_("diff expects one, two or no arguments")) + raise error.ParseError(_("diff expects zero, one, or two arguments")) def getpatterns(i): if i < len(args): @@ -432,7 +432,7 @@ raise error.ParseError(_("get() expects a dict as first argument")) key = args[1][0](context, mapping, args[1][1]) - yield dictarg.get(key) + return dictarg.get(key) def if_(context, mapping, args): """:if(expr, then[, else]): Conditionally execute based on the result of @@ -483,9 +483,9 @@ raise error.ParseError(_("join expects one or two arguments")) joinset = args[0][0](context, mapping, args[0][1]) - if callable(joinset): + if util.safehasattr(joinset, 'itermaps'): jf = joinset.joinfmt - joinset = [jf(x) for x in joinset()] + joinset = [jf(x) for x in joinset.itermaps()] joiner = " " if len(args) > 1: @@ -832,7 +832,7 @@ paths = templatepaths() if not paths: return _('no templates found, try `hg debuginstall` for more info') - dirlist = os.listdir(paths[0]) + dirlist = os.listdir(paths[0]) stylelist = [] for file in dirlist: split = file.split(".")
--- a/mercurial/templates/gitweb/bookmarks.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/gitweb/bookmarks.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,9 +1,9 @@ {header} <title>{repo|escape}: Bookmarks</title> <link rel="alternate" type="application/atom+xml" - href="{url|urlescape}atom-bookmarks" title="Atom feed for {repo|escape}"/> + href="{url|urlescape}atom-bookmarks" title="Atom feed for {repo|escape}: bookmarks"/> <link rel="alternate" type="application/rss+xml" - href="{url|urlescape}rss-bookmarks" title="RSS feed for {repo|escape}"/> + href="{url|urlescape}rss-bookmarks" title="RSS feed for {repo|escape}: bookmarks"/> </head> <body>
--- a/mercurial/templates/gitweb/branches.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/gitweb/branches.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,9 +1,9 @@ {header} <title>{repo|escape}: Branches</title> <link rel="alternate" type="application/atom+xml" - href="{url|urlescape}atom-branches" title="Atom feed for {repo|escape}"/> + href="{url|urlescape}atom-branches" title="Atom feed for {repo|escape}: branches"/> <link rel="alternate" type="application/rss+xml" - href="{url|urlescape}rss-branches" title="RSS feed for {repo|escape}"/> + href="{url|urlescape}rss-branches" title="RSS feed for {repo|escape}: branches"/> </head> <body>
--- a/mercurial/templates/gitweb/help.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/gitweb/help.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,9 +1,9 @@ {header} <title>Help: {topic}</title> <link rel="alternate" type="application/atom+xml" - href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}"/> + href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/> <link rel="alternate" type="application/rss+xml" - href="{url|urlescape}rss-tags" title="RSS feed for {repo|escape}"/> + href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/> </head> <body>
--- a/mercurial/templates/gitweb/helptopics.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/gitweb/helptopics.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,9 +1,9 @@ {header} <title>Help: {title}</title> <link rel="alternate" type="application/atom+xml" - href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}"/> + href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/> <link rel="alternate" type="application/rss+xml" - href="{url|urlescape}rss-tags" title="RSS feed for {repo|escape}"/> + href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/> </head> <body> @@ -21,7 +21,10 @@ <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> | <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> | <a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> | -help +{if(subindex, + '<a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>', + 'help' +)} <br/> </div> @@ -30,11 +33,16 @@ <tr><td colspan="2"><h2><a name="main" href="#topics">Topics</a></h2></td></tr> {topics % helpentry} +{if(earlycommands, ' <tr><td colspan="2"><h2><a name="main" href="#main">Main Commands</a></h2></td></tr> {earlycommands % helpentry} +')} +{if(othercommands, ' <tr><td colspan="2"><h2><a name="other" href="#other">Other Commands</a></h2></td></tr> {othercommands % helpentry} +')} + </table> {footer}
--- a/mercurial/templates/gitweb/map Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/gitweb/map Sun Jan 17 21:40:21 2016 -0600 @@ -14,7 +14,7 @@ helpentry = ' <tr><td> <a href="{url|urlescape}help/{topic|escape}{sessionvars%urlparameter}"> - {topic|escape} + {if(basename, '{basename|escape}', '{topic|escape}')} </a> </td><td> {summary|escape}
--- a/mercurial/templates/gitweb/tags.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/gitweb/tags.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,9 +1,9 @@ {header} <title>{repo|escape}: Tags</title> <link rel="alternate" type="application/atom+xml" - href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}"/> + href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}: tags"/> <link rel="alternate" type="application/rss+xml" - href="{url|urlescape}rss-tags" title="RSS feed for {repo|escape}"/> + href="{url|urlescape}rss-tags" title="RSS feed for {repo|escape}: tags"/> </head> <body>
--- a/mercurial/templates/monoblue/bookmarks.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/monoblue/bookmarks.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,7 @@ {header} <title>{repo|escape}: Bookmarks</title> - <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/> - <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/> + <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-bookmarks" title="Atom feed for {repo|escape}: bookmarks"/> + <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-bookmarks" title="RSS feed for {repo|escape}: bookmarks"/> </head> <body>
--- a/mercurial/templates/monoblue/branches.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/monoblue/branches.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,7 @@ {header} <title>{repo|escape}: Branches</title> - <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-branches" title="Atom feed for {repo|escape}"/> - <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-branches" title="RSS feed for {repo|escape}"/> + <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-branches" title="Atom feed for {repo|escape}: branches"/> + <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-branches" title="RSS feed for {repo|escape}: branches"/> </head> <body>
--- a/mercurial/templates/monoblue/helptopics.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/monoblue/helptopics.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -26,7 +26,10 @@ <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li> <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li> <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li> - <li class="current">help</li> + {if(subindex, + '<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>', + '<li class="current">help</li>' + )} </ul> </div> @@ -35,11 +38,15 @@ <tr><td colspan="2"><h2><a name="main" href="#topics">Topics</a></h2></td></tr> {topics % helpentry} + {if(earlycommands, ' <tr><td colspan="2"><h2><a name="main" href="#main">Main Commands</a></h2></td></tr> {earlycommands % helpentry} + ')} + {if(othercommands, ' <tr><td colspan="2"><h2><a name="other" href="#other">Other Commands</a></h2></td></tr> {othercommands % helpentry} + ')} </table> {footer}
--- a/mercurial/templates/monoblue/map Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/monoblue/map Sun Jan 17 21:40:21 2016 -0600 @@ -14,7 +14,7 @@ helpentry = ' <tr><td> <a href="{url|urlescape}help/{topic|escape}{sessionvars%urlparameter}"> - {topic|escape} + {if(basename, '{basename|escape}', '{topic|escape}')} </a> </td><td> {summary|escape} @@ -23,7 +23,7 @@ naventry = '<a href="{url|urlescape}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> ' navshortentry = '<a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> ' navgraphentry = '<a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">{label|escape}</a> ' -filenaventry = '<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a>' +filenaventry = '<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> ' filedifflink = '<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a> ' filenodelink = ' <tr class="parity{parity}">
--- a/mercurial/templates/monoblue/tags.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/monoblue/tags.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,7 @@ {header} <title>{repo|escape}: Tags</title> - <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/> - <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/> + <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}: tags"/> + <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-tags" title="RSS feed for {repo|escape}: tags"/> </head> <body>
--- a/mercurial/templates/paper/filelog.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/paper/filelog.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -36,7 +36,7 @@ <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li> </ul> <div class="atom-logo"> -<a href="{url|urlescape}atom-log/{node|short}/{file|urlescape}" title="subscribe to atom feed"> +<a href="{url|urlescape}atom-log/tip/{file|urlescape}" title="subscribe to atom feed"> <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" /> </a> </div> @@ -44,7 +44,10 @@ <div class="main"> <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2> -<h3>log {file|escape}</h3> +<h3> + log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a> + {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag} +</h3> <form class="search" action="{url|urlescape}log"> {sessionvars%hiddenformentry}
--- a/mercurial/templates/paper/helptopics.tmpl Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/paper/helptopics.tmpl Sun Jan 17 21:40:21 2016 -0600 @@ -17,7 +17,10 @@ <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li> </ul> <ul> -<li class="active">help</li> +{if(subindex, + '<li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>', + '<li class="active">help</li>' +)} </ul> </div> @@ -32,11 +35,16 @@ <tr><td colspan="2"><h2><a name="main" href="#topics">Topics</a></h2></td></tr> {topics % helpentry} +{if(earlycommands, ' <tr><td colspan="2"><h2><a name="main" href="#main">Main Commands</a></h2></td></tr> {earlycommands % helpentry} +')} +{if(othercommands, ' <tr><td colspan="2"><h2><a name="other" href="#other">Other Commands</a></h2></td></tr> {othercommands % helpentry} +')} + </table> </div> </div>
--- a/mercurial/templates/paper/map Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/paper/map Sun Jan 17 21:40:21 2016 -0600 @@ -15,7 +15,7 @@ helpentry = ' <tr><td> <a href="{url|urlescape}help/{topic|escape}{sessionvars%urlparameter}"> - {topic|escape} + {if(basename, '{basename|escape}', '{topic|escape}')} </a> </td><td> {summary|escape}
--- a/mercurial/templates/static/mercurial.js Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/templates/static/mercurial.js Sun Jan 17 21:40:21 2016 -0600 @@ -50,18 +50,6 @@ this.cell_height = this.box_size; } - function colorPart(num) { - num *= 255 - num = num < 0 ? 0 : num; - num = num > 255 ? 255 : num; - var digits = Math.round(num).toString(16); - if (num < 16) { - return '0' + digits; - } else { - return digits; - } - } - this.setColor = function(color, bg, fg) { // Set the colour.
--- a/mercurial/transaction.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/transaction.py Sun Jan 17 21:40:21 2016 -0600 @@ -333,6 +333,14 @@ if self.count > 0 and self.usages == 0: self._abort() + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + self.close() + self.release() + def running(self): return self.count > 0 @@ -431,22 +439,21 @@ self.opener.unlink(self._backupjournal) if self.opener.isfile(self.journal): self.opener.unlink(self.journal) - if True: - for l, _f, b, c in self._backupentries: - if l not in self._vfsmap and c: - self.report("couldn't remove %s: unknown cache location" - "%s\n" % (b, l)) - continue - vfs = self._vfsmap[l] - if b and vfs.exists(b): - try: - vfs.unlink(b) - except (IOError, OSError, error.Abort) as inst: - if not c: - raise - # Abort may be raise by read only opener - self.report("couldn't remove %s: %s\n" - % (vfs.join(b), inst)) + for l, _f, b, c in self._backupentries: + if l not in self._vfsmap and c: + self.report("couldn't remove %s: unknown cache location" + "%s\n" % (b, l)) + continue + vfs = self._vfsmap[l] + if b and vfs.exists(b): + try: + vfs.unlink(b) + except (IOError, OSError, error.Abort) as inst: + if not c: + raise + # Abort may be raise by read only opener + self.report("couldn't remove %s: %s\n" + % (vfs.join(b), inst)) self._backupentries = [] self.journal = None
--- a/mercurial/ui.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/ui.py Sun Jan 17 21:40:21 2016 -0600 @@ -95,9 +95,12 @@ def __init__(self, src=None): # _buffers: used for temporary capture of output self._buffers = [] - # _bufferstates: - # should the temporary capture include stderr and subprocess output + # 3-tuple describing how each buffer in the stack behaves. + # Values are (capture stderr, capture subprocesses, apply labels). self._bufferstates = [] + # When a buffer is active, defines whether we are expanding labels. + # This exists to prevent an extra list lookup. + self._bufferapplylabels = None self.quiet = self.verbose = self.debugflag = self.tracebackflag = False self._reportuntrusted = True self._ocfg = config.config() # overlay @@ -278,6 +281,39 @@ "%s.%s = %s\n" % (section, n, uvalue)) return value + def configsuboptions(self, section, name, default=None, untrusted=False): + """Get a config option and all sub-options. + + Some config options have sub-options that are declared with the + format "key:opt = value". This method is used to return the main + option and all its declared sub-options. + + Returns a 2-tuple of ``(option, sub-options)``, where `sub-options`` + is a dict of defined sub-options where keys and values are strings. + """ + data = self._data(untrusted) + main = data.get(section, name, default) + if self.debugflag and not untrusted and self._reportuntrusted: + uvalue = self._ucfg.get(section, name) + if uvalue is not None and uvalue != main: + self.debug('ignoring untrusted configuration option ' + '%s.%s = %s\n' % (section, name, uvalue)) + + sub = {} + prefix = '%s:' % name + for k, v in data.items(section): + if k.startswith(prefix): + sub[k[len(prefix):]] = v + + if self.debugflag and not untrusted and self._reportuntrusted: + for k, v in sub.items(): + uvalue = self._ucfg.get(section, '%s:%s' % (name, k)) + if uvalue is not None and uvalue != v: + self.debug('ignoring untrusted configuration option ' + '%s:%s.%s = %s\n' % (section, name, k, uvalue)) + + return main, sub + def configpath(self, section, name, default=None, untrusted=False): 'get a path config item, expanded relative to repo root or config file' v = self.config(section, name, default, untrusted) @@ -471,12 +507,21 @@ result = default or [] return result + def hasconfig(self, section, name, untrusted=False): + return self._data(untrusted).hasitem(section, name) + def has_section(self, section, untrusted=False): '''tell whether section exists in config.''' return section in self._data(untrusted) - def configitems(self, section, untrusted=False): + def configitems(self, section, untrusted=False, ignoresub=False): items = self._data(untrusted).items(section) + if ignoresub: + newitems = {} + for k, v in items: + if ':' not in k: + newitems[k] = v + items = newitems.items() if self.debugflag and not untrusted and self._reportuntrusted: for k, v in self._ucfg.items(section): if self._tcfg.get(section, k) != v: @@ -573,18 +618,13 @@ def paths(self): return paths(self) - def pushbuffer(self, error=False, subproc=False): + def pushbuffer(self, error=False, subproc=False, labeled=False): """install a buffer to capture standard output of the ui object If error is True, the error output will be captured too. If subproc is True, output from subprocesses (typically hooks) will be - captured too.""" - self._buffers.append([]) - self._bufferstates.append((error, subproc)) - - def popbuffer(self, labeled=False): - '''pop the last buffer and return the buffered output + captured too. If labeled is True, any labels associated with buffered output will be handled. By default, this has no effect @@ -592,8 +632,19 @@ handle this argument and returned styled output. If output is being buffered so it can be captured and parsed or processed, labeled should not be set to True. - ''' + """ + self._buffers.append([]) + self._bufferstates.append((error, subproc, labeled)) + self._bufferapplylabels = labeled + + def popbuffer(self): + '''pop the last buffer and return the buffered output''' self._bufferstates.pop() + if self._bufferstates: + self._bufferapplylabels = self._bufferstates[-1][2] + else: + self._bufferapplylabels = None + return "".join(self._buffers.pop()) def write(self, *args, **opts): @@ -613,12 +664,12 @@ "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]) + self._buffers[-1].extend(a for a in args) else: + self._progclear() for a in args: - self.fout.write(str(a)) + self.fout.write(a) def write_err(self, *args, **opts): self._progclear() @@ -628,7 +679,7 @@ if not getattr(self.fout, 'closed', False): self.fout.flush() for a in args: - self.ferr.write(str(a)) + self.ferr.write(a) # stderr may be buffered under win32 when redirected to files, # including stdout. if not getattr(self.ferr, 'closed', False): @@ -757,7 +808,7 @@ self.write(r, "\n") return r except EOFError: - raise error.Abort(_('response expected')) + raise error.ResponseExpected() @staticmethod def extractchoices(prompt): @@ -817,7 +868,7 @@ else: return getpass.getpass('') except EOFError: - raise error.Abort(_('response expected')) + raise error.ResponseExpected() def status(self, *msg, **opts): '''write status message to output (if ui.quiet is False) @@ -851,10 +902,12 @@ self.write(*msg, **opts) def edit(self, text, user, extra=None, editform=None, pending=None): - if extra is None: - extra = {} - (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt", - text=True) + extra_defaults = { 'prefix': 'editor' } + if extra is not None: + extra_defaults.update(extra) + extra = extra_defaults + (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-', + suffix=".txt", text=True) try: f = os.fdopen(fd, "w") f.write(text) @@ -1008,15 +1061,31 @@ ''' return msg - def develwarn(self, msg): - """issue a developer warning message""" + def develwarn(self, msg, stacklevel=1): + """issue a developer warning message + + Use 'stacklevel' to report the offender some layers further up in the + stack. + """ msg = 'devel-warn: ' + msg + stacklevel += 1 # get in develwarn if self.tracebackflag: - util.debugstacktrace(msg, 2, self.ferr, self.fout) + util.debugstacktrace(msg, stacklevel, self.ferr, self.fout) else: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) - self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4])) + self.write_err('%s at: %s:%s (%s)\n' + % ((msg,) + calframe[stacklevel][1:4])) + + def deprecwarn(self, msg, version): + """issue a deprecation warning + + - msg: message explaining what is deprecated and how to upgrade, + - version: last version where the API will be supported, + """ + msg += ("\n(compatibility will be dropped after Mercurial-%s," + " update your code.)") % version + self.develwarn(msg, stacklevel=2) class paths(dict): """Represents a collection of paths and their configs. @@ -1027,28 +1096,15 @@ def __init__(self, ui): dict.__init__(self) - for name, loc in ui.configitems('paths'): + for name, loc in ui.configitems('paths', ignoresub=True): # No location is the same as not existing. if not loc: continue - - # TODO ignore default-push once all consumers stop referencing it - # since it is handled specifically below. - - self[name] = path(name, rawloc=loc) - - # Handle default-push, which is a one-off that defines the push URL for - # the "default" path. - defaultpush = ui.config('paths', 'default-push') - if defaultpush: - # "default-push" can be defined without "default" entry. This is a - # bit weird, but is allowed for backwards compatibility. - if 'default' not in self: - self['default'] = path('default', rawloc=defaultpush) - self['default']._pushloc = defaultpush + loc, sub = ui.configsuboptions('paths', name) + self[name] = path(ui, name, rawloc=loc, suboptions=sub) def getpath(self, name, default=None): - """Return a ``path`` from a string, falling back to a default. + """Return a ``path`` from a string, falling back to default. ``name`` can be a named path or locations. Locations are filesystem paths or URIs. @@ -1058,13 +1114,16 @@ """ # Only fall back to default if no path was requested. if name is None: - if default: + if not default: + default = () + elif not isinstance(default, (tuple, list)): + default = (default,) + for k in default: try: - return self[default] + return self[k] except KeyError: - return None - else: - return None + continue + return None # Most likely empty string. # This may need to raise in the future. @@ -1076,19 +1135,57 @@ except KeyError: # Try to resolve as a local path or URI. try: - return path(None, rawloc=name) + # We don't pass sub-options in, so no need to pass ui instance. + return path(None, None, rawloc=name) except ValueError: raise error.RepoError(_('repository %s does not exist') % name) - assert False +_pathsuboptions = {} + +def pathsuboption(option, attr): + """Decorator used to declare a path sub-option. + + Arguments are the sub-option name and the attribute it should set on + ``path`` instances. + + The decorated function will receive as arguments a ``ui`` instance, + ``path`` instance, and the string value of this option from the config. + The function should return the value that will be set on the ``path`` + instance. + + This decorator can be used to perform additional verification of + sub-options and to change the type of sub-options. + """ + def register(func): + _pathsuboptions[option] = (attr, func) + return func + return register + +@pathsuboption('pushurl', 'pushloc') +def pushurlpathoption(ui, path, value): + u = util.url(value) + # Actually require a URL. + if not u.scheme: + ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name) + return None + + # Don't support the #foo syntax in the push URL to declare branch to + # push. + if u.fragment: + ui.warn(_('("#fragment" in paths.%s:pushurl not supported; ' + 'ignoring)\n') % path.name) + u.fragment = None + + return str(u) class path(object): """Represents an individual path and its configuration.""" - def __init__(self, name, rawloc=None, pushloc=None): + def __init__(self, ui, name, rawloc=None, suboptions=None): """Construct a path from its config options. + ``ui`` is the ``ui`` instance the path is coming from. ``name`` is the symbolic name of the path. ``rawloc`` is the raw location, as defined in the config. ``pushloc`` is the raw locations pushes should be made to. @@ -1113,7 +1210,6 @@ self.name = name self.rawloc = rawloc self.loc = str(u) - self._pushloc = pushloc # When given a raw location but not a symbolic name, validate the # location is valid. @@ -1121,6 +1217,19 @@ raise ValueError('location is not a URL or path to a local ' 'repo: %s' % rawloc) + suboptions = suboptions or {} + + # Now process the sub-options. If a sub-option is registered, its + # attribute will always be present. The value will be None if there + # was no valid sub-option. + for suboption, (attr, func) in _pathsuboptions.iteritems(): + if suboption not in suboptions: + setattr(self, attr, None) + continue + + value = func(ui, self, suboptions[suboption]) + setattr(self, attr, value) + def _isvalidlocalpath(self, path): """Returns True if the given path is a potentially valid repository. This is its own function so that extensions can change the definition of @@ -1129,8 +1238,17 @@ return os.path.isdir(os.path.join(path, '.hg')) @property - def pushloc(self): - return self._pushloc or self.loc + def suboptions(self): + """Return sub-options and their values for this path. + + This is intended to be used for presentation purposes. + """ + d = {} + for subopt, (attr, _func) in _pathsuboptions.iteritems(): + value = getattr(self, attr) + if value is not None: + d[subopt] = value + return d # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created
--- a/mercurial/unionrepo.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/unionrepo.py Sun Jan 17 21:40:21 2016 -0600 @@ -51,6 +51,7 @@ rev = self.revlog2.index[rev2] # rev numbers - in revlog2, very different from self.rev _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev + flags = _start & 0xFFFF if linkmapper is None: # link is to same revlog assert linkrev == rev2 # we never link back @@ -69,7 +70,7 @@ p1node = self.revlog2.node(p1rev) p2node = self.revlog2.node(p2rev) - e = (None, None, None, base, + e = (flags, None, None, base, link, self.rev(p1node), self.rev(p2node), node) self.index.insert(-1, e) self.nodemap[node] = n
--- a/mercurial/util.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/util.py Sun Jan 17 21:40:21 2016 -0600 @@ -13,22 +13,47 @@ hide platform-specific details from the core. """ -import i18n -_ = i18n._ -import error, osutil, encoding, parsers -import errno, shutil, sys, tempfile, traceback +from __future__ import absolute_import + +import bz2 +import calendar +import collections +import datetime +import errno +import gc +import hashlib +import imp +import os import re as remod -import os, time, datetime, calendar, textwrap, signal, collections -import stat -import imp, socket, urllib -import gc -import bz2 +import shutil +import signal +import socket +import subprocess +import sys +import tempfile +import textwrap +import time +import traceback +import urllib import zlib +from . import ( + encoding, + error, + i18n, + osutil, + parsers, +) + if os.name == 'nt': - import windows as platform + from . import windows as platform else: - import posix as platform + from . import posix as platform + +md5 = hashlib.md5 +sha1 = hashlib.sha1 +sha512 = hashlib.sha512 +_ = i18n._ cachestat = platform.cachestat checkexec = platform.checkexec @@ -88,58 +113,21 @@ _notset = object() +# disable Python's problematic floating point timestamps (issue4836) +# (Python hypocritically says you shouldn't change this behavior in +# libraries, and sure enough Mercurial is not a library.) +os.stat_float_times(False) + def safehasattr(thing, attr): return getattr(thing, attr, _notset) is not _notset -def sha1(s=''): - ''' - Low-overhead wrapper around Python's SHA support - - >>> f = _fastsha1 - >>> a = sha1() - >>> a = f() - >>> a.hexdigest() - 'da39a3ee5e6b4b0d3255bfef95601890afd80709' - ''' - - return _fastsha1(s) - -def _fastsha1(s=''): - # This function will import sha1 from hashlib or sha (whichever is - # available) and overwrite itself with it on the first call. - # Subsequent calls will go directly to the imported function. - if sys.version_info >= (2, 5): - from hashlib import sha1 as _sha1 - else: - from sha import sha as _sha1 - global _fastsha1, sha1 - _fastsha1 = sha1 = _sha1 - return _sha1(s) - -def md5(s=''): - try: - from hashlib import md5 as _md5 - except ImportError: - from md5 import md5 as _md5 - global md5 - md5 = _md5 - return _md5(s) - DIGESTS = { 'md5': md5, 'sha1': sha1, + 'sha512': sha512, } # List of digest types from strongest to weakest -DIGESTS_BY_STRENGTH = ['sha1', 'md5'] - -try: - import hashlib - DIGESTS.update({ - 'sha512': hashlib.sha512, - }) - DIGESTS_BY_STRENGTH.insert(0, 'sha512') -except ImportError: - pass +DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5'] for k in DIGESTS_BY_STRENGTH: assert k in DIGESTS @@ -233,7 +221,6 @@ def buffer(sliceable, offset=0): return memoryview(sliceable)[offset:] -import subprocess closefds = os.name == 'posix' _chunksize = 4096 @@ -359,11 +346,64 @@ def version(): """Return version information if available.""" try: - import __version__ + from . import __version__ return __version__.version except ImportError: return 'unknown' +def versiontuple(v=None, n=4): + """Parses a Mercurial version string into an N-tuple. + + The version string to be parsed is specified with the ``v`` argument. + If it isn't defined, the current Mercurial version string will be parsed. + + ``n`` can be 2, 3, or 4. Here is how some version strings map to + returned values: + + >>> v = '3.6.1+190-df9b73d2d444' + >>> versiontuple(v, 2) + (3, 6) + >>> versiontuple(v, 3) + (3, 6, 1) + >>> versiontuple(v, 4) + (3, 6, 1, '190-df9b73d2d444') + + >>> versiontuple('3.6.1+190-df9b73d2d444+20151118') + (3, 6, 1, '190-df9b73d2d444+20151118') + + >>> v = '3.6' + >>> versiontuple(v, 2) + (3, 6) + >>> versiontuple(v, 3) + (3, 6, None) + >>> versiontuple(v, 4) + (3, 6, None, None) + """ + if not v: + v = version() + parts = v.split('+', 1) + if len(parts) == 1: + vparts, extra = parts[0], None + else: + vparts, extra = parts + + vints = [] + for i in vparts.split('.'): + try: + vints.append(int(i)) + except ValueError: + break + # (3, 6) -> (3, 6, None) + while len(vints) < 3: + vints.append(None) + + if n == 2: + return (vints[0], vints[1]) + if n == 3: + return (vints[0], vints[1], vints[2]) + if n == 4: + return (vints[0], vints[1], vints[2], extra) + # used by parsedate defaultdateformats = ( '%Y-%m-%d %H:%M:%S', @@ -470,34 +510,183 @@ self._list.insert(index, key) dict.__setitem__(self, key, val) +class _lrucachenode(object): + """A node in a doubly linked list. + + Holds a reference to nodes on either side as well as a key-value + pair for the dictionary entry. + """ + __slots__ = ('next', 'prev', 'key', 'value') + + def __init__(self): + self.next = None + self.prev = None + + self.key = _notset + self.value = None + + def markempty(self): + """Mark the node as emptied.""" + self.key = _notset + class lrucachedict(object): - '''cache most recent gets from or sets to this dictionary''' - def __init__(self, maxsize): + """Dict that caches most recent accesses and sets. + + The dict consists of an actual backing dict - indexed by original + key - and a doubly linked circular list defining the order of entries in + the cache. + + The head node is the newest entry in the cache. If the cache is full, + we recycle head.prev and make it the new head. Cache accesses result in + the node being moved to before the existing head and being marked as the + new head node. + """ + def __init__(self, max): self._cache = {} - self._maxsize = maxsize - self._order = collections.deque() + + self._head = head = _lrucachenode() + head.prev = head + head.next = head + self._size = 1 + self._capacity = max + + def __len__(self): + return len(self._cache) + + def __contains__(self, k): + return k in self._cache - def __getitem__(self, key): - value = self._cache[key] - self._order.remove(key) - self._order.append(key) - return value + def __iter__(self): + # We don't have to iterate in cache order, but why not. + n = self._head + for i in range(len(self._cache)): + yield n.key + n = n.next + + def __getitem__(self, k): + node = self._cache[k] + self._movetohead(node) + return node.value + + def __setitem__(self, k, v): + node = self._cache.get(k) + # Replace existing value and mark as newest. + if node is not None: + node.value = v + self._movetohead(node) + return + + if self._size < self._capacity: + node = self._addcapacity() + else: + # Grab the last/oldest item. + node = self._head.prev - def __setitem__(self, key, value): - if key not in self._cache: - if len(self._cache) >= self._maxsize: - del self._cache[self._order.popleft()] - else: - self._order.remove(key) - self._cache[key] = value - self._order.append(key) + # At capacity. Kill the old entry. + if node.key is not _notset: + del self._cache[node.key] + + node.key = k + node.value = v + self._cache[k] = node + # And mark it as newest entry. No need to adjust order since it + # is already self._head.prev. + self._head = node - def __contains__(self, key): - return key in self._cache + def __delitem__(self, k): + node = self._cache.pop(k) + node.markempty() + + # Temporarily mark as newest item before re-adjusting head to make + # this node the oldest item. + self._movetohead(node) + self._head = node.next + + # Additional dict methods. + + def get(self, k, default=None): + try: + return self._cache[k] + except KeyError: + return default def clear(self): + n = self._head + while n.key is not _notset: + n.markempty() + n = n.next + self._cache.clear() - self._order = collections.deque() + + def copy(self): + result = lrucachedict(self._capacity) + n = self._head.prev + # Iterate in oldest-to-newest order, so the copy has the right ordering + for i in range(len(self._cache)): + result[n.key] = n.value + n = n.prev + return result + + def _movetohead(self, node): + """Mark a node as the newest, making it the new head. + + When a node is accessed, it becomes the freshest entry in the LRU + list, which is denoted by self._head. + + Visually, let's make ``N`` the new head node (* denotes head): + + previous/oldest <-> head <-> next/next newest + + ----<->--- A* ---<->----- + | | + E <-> D <-> N <-> C <-> B + + To: + + ----<->--- N* ---<->----- + | | + E <-> D <-> C <-> B <-> A + + This requires the following moves: + + C.next = D (node.prev.next = node.next) + D.prev = C (node.next.prev = node.prev) + E.next = N (head.prev.next = node) + N.prev = E (node.prev = head.prev) + N.next = A (node.next = head) + A.prev = N (head.prev = node) + """ + head = self._head + # C.next = D + node.prev.next = node.next + # D.prev = C + node.next.prev = node.prev + # N.prev = E + node.prev = head.prev + # N.next = A + # It is tempting to do just "head" here, however if node is + # adjacent to head, this will do bad things. + node.next = head.prev.next + # E.next = N + node.next.prev = node + # A.prev = N + node.prev.next = node + + self._head = node + + def _addcapacity(self): + """Add a node to the circular linked list. + + The new node is inserted before the head node. + """ + head = self._head + node = _lrucachenode() + head.prev.next = node + node.prev = head.prev + node.next = head + head.prev = node + self._size += 1 + return node def lrucachefunc(func): '''cache most recent results of function calls''' @@ -567,10 +756,7 @@ if code: raise Abort(_("command '%s' failed: %s") % (cmd, explainexit(code))) - fp = open(outname, 'rb') - r = fp.read() - fp.close() - return r + return readfile(outname) finally: try: if inname: @@ -697,7 +883,7 @@ imp.is_frozen("__main__")) # tools/freeze # the location of data files matching the source code -if mainfrozen(): +if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app': # executable version (py2exe) doesn't support __file__ datapath = os.path.dirname(sys.executable) else: @@ -718,7 +904,11 @@ if hg: _sethgexecutable(hg) elif mainfrozen(): - _sethgexecutable(sys.executable) + if getattr(sys, 'frozen', None) == 'macosx_app': + # Env variable set by py2app + _sethgexecutable(os.environ['EXECUTABLEPATH']) + else: + _sethgexecutable(sys.executable) elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg': _sethgexecutable(mainmod.__file__) else: @@ -960,20 +1150,6 @@ except AttributeError: return os.stat(fp.name) -def statmtimesec(st): - """Get mtime as integer of seconds - - 'int(st.st_mtime)' cannot be used because st.st_mtime is computed as - 'sec + 1e-9 * nsec' and double-precision floating-point type is too narrow - to represent nanoseconds. If 'nsec' is close to 1 sec, 'int(st.st_mtime)' - can be 'sec + 1'. (issue4836) - """ - try: - return st[stat.ST_MTIME] - except (TypeError, IndexError): - # osutil.stat doesn't allow index access and its st_mtime is int - return st.st_mtime - # File system features def checkcase(path): @@ -1269,25 +1445,16 @@ os.chmod(name, mode) def readfile(path): - fp = open(path, 'rb') - try: + with open(path, 'rb') as fp: return fp.read() - finally: - fp.close() def writefile(path, text): - fp = open(path, 'wb') - try: + with open(path, 'wb') as fp: fp.write(text) - finally: - fp.close() def appendfile(path, text): - fp = open(path, 'ab') - try: + with open(path, 'ab') as fp: fp.write(text) - finally: - fp.close() class chunkbuffer(object): """Allow arbitrary sized chunks of data to be efficiently read from an @@ -1416,9 +1583,10 @@ if "%1" in format or "%2" in format or "%z" in format: sign = (tz > 0) and "-" or "+" minutes = abs(tz) // 60 + q, r = divmod(minutes, 60) format = format.replace("%z", "%1%2") - format = format.replace("%1", "%c%02d" % (sign, minutes // 60)) - format = format.replace("%2", "%02d" % (minutes % 60)) + format = format.replace("%1", "%c%02d" % (sign, q)) + format = format.replace("%2", "%02d" % r) try: t = time.gmtime(float(t) - tz) except ValueError: @@ -1864,7 +2032,11 @@ get either the python call or current executable. """ if mainfrozen(): - return [sys.executable] + if getattr(sys, 'frozen', None) == 'macosx_app': + # Env variable set by py2app + return [os.environ['EXECUTABLEPATH']] + else: + return [sys.executable] return gethgcmd() def rundetached(args, condfn): @@ -2288,9 +2460,9 @@ u.user = u.passwd = None return str(u) -def isatty(fd): +def isatty(fp): try: - return fd.isatty() + return fp.isatty() except AttributeError: return False @@ -2465,6 +2637,66 @@ return chunkbuffer(generator(fh)) return func +class ctxmanager(object): + '''A context manager for use in 'with' blocks to allow multiple + contexts to be entered at once. This is both safer and more + flexible than contextlib.nested. + + Once Mercurial supports Python 2.7+, this will become mostly + unnecessary. + ''' + + def __init__(self, *args): + '''Accepts a list of no-argument functions that return context + managers. These will be invoked at __call__ time.''' + self._pending = args + self._atexit = [] + + def __enter__(self): + return self + + def enter(self): + '''Create and enter context managers in the order in which they were + passed to the constructor.''' + values = [] + for func in self._pending: + obj = func() + values.append(obj.__enter__()) + self._atexit.append(obj.__exit__) + del self._pending + return values + + def atexit(self, func, *args, **kwargs): + '''Add a function to call when this context manager exits. The + ordering of multiple atexit calls is unspecified, save that + they will happen before any __exit__ functions.''' + def wrapper(exc_type, exc_val, exc_tb): + func(*args, **kwargs) + self._atexit.append(wrapper) + return func + + def __exit__(self, exc_type, exc_val, exc_tb): + '''Context managers are exited in the reverse order from which + they were created.''' + received = exc_type is not None + suppressed = False + pending = None + self._atexit.reverse() + for exitfunc in self._atexit: + try: + if exitfunc(exc_type, exc_val, exc_tb): + suppressed = True + exc_type = None + exc_val = None + exc_tb = None + except BaseException: + pending = sys.exc_info() + exc_type, exc_val, exc_tb = pending = sys.exc_info() + del self._atexit + if pending: + raise exc_val + return received and suppressed + def _bz2(): d = bz2.BZ2Decompressor() # Bzip2 stream start with BZ, but we stripped it.
--- a/mercurial/verify.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/verify.py Sun Jan 17 21:40:21 2016 -0600 @@ -22,11 +22,8 @@ ) def verify(repo): - lock = repo.lock() - try: - return _verify(repo) - finally: - lock.release() + with repo.lock(): + return verifier(repo).verify() def _normpath(f): # under hg < 2.4, convert didn't sanitize paths properly, so a @@ -35,304 +32,353 @@ f = f.replace('//', '/') return f -def _verify(repo): - repo = repo.unfiltered() - mflinkrevs = {} - filelinkrevs = {} - filenodes = {} - revisions = 0 - badrevs = set() - errors = [0] - warnings = [0] - ui = repo.ui - cl = repo.changelog - mf = repo.manifest - lrugetctx = util.lrucachefunc(repo.changectx) +def _validpath(repo, path): + """Returns False if a path should NOT be treated as part of a repo. + + For all in-core cases, this returns True, as we have no way for a + path to be mentioned in the history but not actually be + relevant. For narrow clones, this is important because many + filelogs will be missing, and changelog entries may mention + modified files that are outside the narrow scope. + """ + return True - if not repo.url().startswith('file:'): - raise error.Abort(_("cannot verify bundle or remote repos")) +class verifier(object): + def __init__(self, repo): + self.repo = repo.unfiltered() + self.ui = repo.ui + self.badrevs = set() + self.errors = 0 + self.warnings = 0 + self.havecl = len(repo.changelog) > 0 + self.havemf = len(repo.manifest) > 0 + self.revlogv1 = repo.changelog.version != revlog.REVLOGV0 + self.lrugetctx = util.lrucachefunc(repo.changectx) + self.refersmf = False + self.fncachewarned = False - def err(linkrev, msg, filename=None): + def warn(self, msg): + self.ui.warn(msg + "\n") + self.warnings += 1 + + def err(self, linkrev, msg, filename=None): if linkrev is not None: - badrevs.add(linkrev) + self.badrevs.add(linkrev) else: linkrev = '?' msg = "%s: %s" % (linkrev, msg) if filename: msg = "%s@%s" % (filename, msg) - ui.warn(" " + msg + "\n") - errors[0] += 1 + self.ui.warn(" " + msg + "\n") + self.errors += 1 - def exc(linkrev, msg, inst, filename=None): - if isinstance(inst, KeyboardInterrupt): - ui.warn(_("interrupted")) - raise + def exc(self, linkrev, msg, inst, filename=None): if not str(inst): inst = repr(inst) - err(linkrev, "%s: %s" % (msg, inst), filename) + self.err(linkrev, "%s: %s" % (msg, inst), filename) - def warn(msg): - ui.warn(msg + "\n") - warnings[0] += 1 - - def checklog(obj, name, linkrev): - if not len(obj) and (havecl or havemf): - err(linkrev, _("empty or missing %s") % name) + def checklog(self, obj, name, linkrev): + if not len(obj) and (self.havecl or self.havemf): + self.err(linkrev, _("empty or missing %s") % name) return d = obj.checksize() if d[0]: - err(None, _("data length off by %d bytes") % d[0], name) + self.err(None, _("data length off by %d bytes") % d[0], name) if d[1]: - err(None, _("index contains %d extra bytes") % d[1], name) + self.err(None, _("index contains %d extra bytes") % d[1], name) if obj.version != revlog.REVLOGV0: - if not revlogv1: - warn(_("warning: `%s' uses revlog format 1") % name) - elif revlogv1: - warn(_("warning: `%s' uses revlog format 0") % name) + if not self.revlogv1: + self.warn(_("warning: `%s' uses revlog format 1") % name) + elif self.revlogv1: + self.warn(_("warning: `%s' uses revlog format 0") % name) - def checkentry(obj, i, node, seen, linkrevs, f): + def checkentry(self, obj, i, node, seen, linkrevs, f): lr = obj.linkrev(obj.rev(node)) - if lr < 0 or (havecl and lr not in linkrevs): - if lr < 0 or lr >= len(cl): + if lr < 0 or (self.havecl and lr not in linkrevs): + if lr < 0 or lr >= len(self.repo.changelog): msg = _("rev %d points to nonexistent changeset %d") else: msg = _("rev %d points to unexpected changeset %d") - err(None, msg % (i, lr), f) + self.err(None, msg % (i, lr), f) if linkrevs: if f and len(linkrevs) > 1: try: # attempt to filter down to real linkrevs linkrevs = [l for l in linkrevs - if lrugetctx(l)[f].filenode() == node] + if self.lrugetctx(l)[f].filenode() == node] except Exception: pass - warn(_(" (expected %s)") % " ".join(map(str, linkrevs))) + self.warn(_(" (expected %s)") % " ".join(map(str, linkrevs))) lr = None # can't be trusted try: p1, p2 = obj.parents(node) if p1 not in seen and p1 != nullid: - err(lr, _("unknown parent 1 %s of %s") % + self.err(lr, _("unknown parent 1 %s of %s") % (short(p1), short(node)), f) if p2 not in seen and p2 != nullid: - err(lr, _("unknown parent 2 %s of %s") % + self.err(lr, _("unknown parent 2 %s of %s") % (short(p2), short(node)), f) except Exception as inst: - exc(lr, _("checking parents of %s") % short(node), inst, f) + self.exc(lr, _("checking parents of %s") % short(node), inst, f) if node in seen: - err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f) + self.err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f) seen[node] = i return lr - if os.path.exists(repo.sjoin("journal")): - ui.warn(_("abandoned transaction found - run hg recover\n")) + def verify(self): + repo = self.repo + + ui = repo.ui - revlogv1 = cl.version != revlog.REVLOGV0 - if ui.verbose or not revlogv1: - ui.status(_("repository uses revlog format %d\n") % - (revlogv1 and 1 or 0)) + if not repo.url().startswith('file:'): + raise error.Abort(_("cannot verify bundle or remote repos")) + + if os.path.exists(repo.sjoin("journal")): + ui.warn(_("abandoned transaction found - run hg recover\n")) - havecl = len(cl) > 0 - havemf = len(mf) > 0 + if ui.verbose or not self.revlogv1: + ui.status(_("repository uses revlog format %d\n") % + (self.revlogv1 and 1 or 0)) + + mflinkrevs, filelinkrevs = self._verifychangelog() - ui.status(_("checking changesets\n")) - refersmf = False - seen = {} - checklog(cl, "changelog", 0) - total = len(repo) - for i in repo: - ui.progress(_('checking'), i, total=total, unit=_('changesets')) - n = cl.node(i) - checkentry(cl, i, n, seen, [i], "changelog") + filenodes = self._verifymanifest(mflinkrevs) + + self._crosscheckfiles(mflinkrevs, filelinkrevs, filenodes) + + totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs) - try: - changes = cl.read(n) - if changes[0] != nullid: - mflinkrevs.setdefault(changes[0], []).append(i) - refersmf = True - for f in changes[3]: - filelinkrevs.setdefault(_normpath(f), []).append(i) - except Exception as inst: - refersmf = True - exc(i, _("unpacking changeset %s") % short(n), inst) - ui.progress(_('checking'), None) + ui.status(_("%d files, %d changesets, %d total revisions\n") % + (totalfiles, len(repo.changelog), filerevisions)) + if self.warnings: + ui.warn(_("%d warnings encountered!\n") % self.warnings) + if self.fncachewarned: + ui.warn(_('hint: run "hg debugrebuildfncache" to recover from ' + 'corrupt fncache\n')) + if self.errors: + ui.warn(_("%d integrity errors encountered!\n") % self.errors) + if self.badrevs: + ui.warn(_("(first damaged changeset appears to be %d)\n") + % min(self.badrevs)) + return 1 - ui.status(_("checking manifests\n")) - seen = {} - if refersmf: - # Do not check manifest if there are only changelog entries with - # null manifests. - checklog(mf, "manifest", 0) - total = len(mf) - for i in mf: - ui.progress(_('checking'), i, total=total, unit=_('manifests')) - n = mf.node(i) - lr = checkentry(mf, i, n, seen, mflinkrevs.get(n, []), "manifest") - if n in mflinkrevs: - del mflinkrevs[n] - else: - err(lr, _("%s not in changesets") % short(n), "manifest") + def _verifychangelog(self): + ui = self.ui + repo = self.repo + cl = repo.changelog + + ui.status(_("checking changesets\n")) + mflinkrevs = {} + filelinkrevs = {} + seen = {} + self.checklog(cl, "changelog", 0) + total = len(repo) + for i in repo: + ui.progress(_('checking'), i, total=total, unit=_('changesets')) + n = cl.node(i) + self.checkentry(cl, i, n, seen, [i], "changelog") - try: - for f, fn in mf.readdelta(n).iteritems(): - if not f: - 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 as inst: - exc(lr, _("reading manifest delta %s") % short(n), inst) - ui.progress(_('checking'), None) - - ui.status(_("crosschecking files in changesets and manifests\n")) + try: + changes = cl.read(n) + if changes[0] != nullid: + mflinkrevs.setdefault(changes[0], []).append(i) + self.refersmf = True + for f in changes[3]: + if _validpath(repo, f): + filelinkrevs.setdefault(_normpath(f), []).append(i) + except Exception as inst: + self.refersmf = True + self.exc(i, _("unpacking changeset %s") % short(n), inst) + ui.progress(_('checking'), None) + return mflinkrevs, filelinkrevs - total = len(mflinkrevs) + len(filelinkrevs) + len(filenodes) - count = 0 - if havemf: - for c, m in sorted([(c, m) for m in mflinkrevs - for c in mflinkrevs[m]]): - count += 1 - if m == nullid: - continue - ui.progress(_('crosschecking'), count, total=total) - err(c, _("changeset refers to unknown manifest %s") % short(m)) - mflinkrevs = None # del is bad here due to scope issues + def _verifymanifest(self, mflinkrevs): + repo = self.repo + ui = self.ui + mf = self.repo.manifest - for f in sorted(filelinkrevs): - count += 1 - ui.progress(_('crosschecking'), count, total=total) - if f not in filenodes: - lr = filelinkrevs[f][0] - err(lr, _("in changeset but not in manifest"), f) + ui.status(_("checking manifests\n")) + filenodes = {} + seen = {} + if self.refersmf: + # Do not check manifest if there are only changelog entries with + # null manifests. + self.checklog(mf, "manifest", 0) + total = len(mf) + for i in mf: + ui.progress(_('checking'), i, total=total, unit=_('manifests')) + n = mf.node(i) + lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), + "manifest") + if n in mflinkrevs: + del mflinkrevs[n] + else: + self.err(lr, _("%s not in changesets") % short(n), "manifest") - if havecl: - for f in sorted(filenodes): - count += 1 - ui.progress(_('crosschecking'), count, total=total) - if f not in filelinkrevs: - try: - fl = repo.file(f) - lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]]) - except Exception: - lr = None - err(lr, _("in manifest but not in changeset"), f) + try: + for f, fn in mf.readdelta(n).iteritems(): + if not f: + self.err(lr, _("file without name in manifest")) + elif f != "/dev/null": # ignore this in very old repos + if _validpath(repo, f): + filenodes.setdefault( + _normpath(f), {}).setdefault(fn, lr) + except Exception as inst: + self.exc(lr, _("reading manifest delta %s") % short(n), inst) + ui.progress(_('checking'), None) + + return filenodes - ui.progress(_('crosschecking'), None) - - ui.status(_("checking files\n")) + def _crosscheckfiles(self, mflinkrevs, filelinkrevs, filenodes): + repo = self.repo + ui = self.ui + ui.status(_("crosschecking files in changesets and manifests\n")) - storefiles = set() - for f, f2, size in repo.store.datafiles(): - if not f: - err(None, _("cannot decode filename '%s'") % f2) - elif size > 0 or not revlogv1: - storefiles.add(_normpath(f)) + total = len(mflinkrevs) + len(filelinkrevs) + len(filenodes) + count = 0 + if self.havemf: + for c, m in sorted([(c, m) for m in mflinkrevs + for c in mflinkrevs[m]]): + count += 1 + if m == nullid: + continue + ui.progress(_('crosschecking'), count, total=total) + self.err(c, _("changeset refers to unknown manifest %s") % + short(m)) + mflinkrevs = None # del is bad here due to scope issues - fncachewarned = False - files = sorted(set(filenodes) | set(filelinkrevs)) - total = len(files) - for i, f in enumerate(files): - ui.progress(_('checking'), i, item=f, total=total) - try: - linkrevs = filelinkrevs[f] - except KeyError: - # in manifest but not in changelog - linkrevs = [] + for f in sorted(filelinkrevs): + count += 1 + ui.progress(_('crosschecking'), count, total=total) + if f not in filenodes: + lr = filelinkrevs[f][0] + self.err(lr, _("in changeset but not in manifest"), f) - if linkrevs: - lr = linkrevs[0] - else: - lr = None + if self.havecl: + for f in sorted(filenodes): + count += 1 + ui.progress(_('crosschecking'), count, total=total) + if f not in filelinkrevs: + try: + fl = repo.file(f) + lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]]) + except Exception: + lr = None + self.err(lr, _("in manifest but not in changeset"), f) - try: - fl = repo.file(f) - except error.RevlogError as e: - err(lr, _("broken revlog! (%s)") % e, f) - continue + ui.progress(_('crosschecking'), None) - for ff in fl.files(): - try: - storefiles.remove(ff) - except KeyError: - warn(_(" warning: revlog '%s' not in fncache!") % ff) - fncachewarned = True + def _verifyfiles(self, filenodes, filelinkrevs): + repo = self.repo + ui = self.ui + lrugetctx = self.lrugetctx + revlogv1 = self.revlogv1 + havemf = self.havemf + ui.status(_("checking files\n")) + + storefiles = set() + for f, f2, size in repo.store.datafiles(): + if not f: + self.err(None, _("cannot decode filename '%s'") % f2) + elif size > 0 or not revlogv1: + storefiles.add(_normpath(f)) - checklog(fl, f, lr) - seen = {} - rp = None - for i in fl: - revisions += 1 - n = fl.node(i) - lr = checkentry(fl, i, n, seen, linkrevs, f) - if f in filenodes: - if havemf and n not in filenodes[f]: - err(lr, _("%s not in manifests") % (short(n)), f) - else: - del filenodes[f][n] + files = sorted(set(filenodes) | set(filelinkrevs)) + total = len(files) + revisions = 0 + for i, f in enumerate(files): + ui.progress(_('checking'), i, item=f, total=total) + try: + linkrevs = filelinkrevs[f] + except KeyError: + # in manifest but not in changelog + linkrevs = [] - # verify contents + if linkrevs: + lr = linkrevs[0] + else: + lr = None + try: - l = len(fl.read(n)) - rp = fl.renamed(n) - if l != fl.size(i): - if len(fl.revision(n)) != fl.size(i): - 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 as inst: - exc(lr, _("unpacking %s") % short(n), inst, f) + fl = repo.file(f) + except error.RevlogError as e: + self.err(lr, _("broken revlog! (%s)") % e, f) + continue + + for ff in fl.files(): + try: + storefiles.remove(ff) + except KeyError: + self.warn(_(" warning: revlog '%s' not in fncache!") % ff) + self.fncachewarned = True + + self.checklog(fl, f, lr) + seen = {} + rp = None + for i in fl: + revisions += 1 + n = fl.node(i) + lr = self.checkentry(fl, i, n, seen, linkrevs, f) + if f in filenodes: + if havemf and n not in filenodes[f]: + self.err(lr, _("%s not in manifests") % (short(n)), f) + else: + del filenodes[f][n] - # check renames - try: - if rp: - if lr is not None and ui.verbose: - ctx = lrugetctx(lr) - found = False - for pctx in ctx.parents(): - if rp[0] in pctx: - found = True - break - if not found: - warn(_("warning: copy source of '%s' not" - " in parents of %s") % (f, ctx)) - fl2 = repo.file(rp[0]) - if not len(fl2): - err(lr, _("empty or missing copy source revlog %s:%s") - % (rp[0], short(rp[1])), f) - elif rp[1] == nullid: - ui.note(_("warning: %s@%s: copy source" - " revision is nullid %s:%s\n") - % (f, lr, rp[0], short(rp[1]))) - else: - fl2.rev(rp[1]) - except Exception as inst: - exc(lr, _("checking rename of %s") % short(n), inst, f) + # verify contents + try: + l = len(fl.read(n)) + rp = fl.renamed(n) + if l != fl.size(i): + if len(fl.revision(n)) != fl.size(i): + self.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": + self.err(lr, _("censored file data"), f) + except Exception as inst: + self.exc(lr, _("unpacking %s") % short(n), inst, f) - # cross-check - if f in filenodes: - fns = [(lr, n) for n, lr in filenodes[f].iteritems()] - for lr, node in sorted(fns): - err(lr, _("%s in manifests not found") % short(node), f) - ui.progress(_('checking'), None) - - for f in storefiles: - warn(_("warning: orphan revlog '%s'") % f) + # check renames + try: + if rp: + if lr is not None and ui.verbose: + ctx = lrugetctx(lr) + found = False + for pctx in ctx.parents(): + if rp[0] in pctx: + found = True + break + if not found: + self.warn(_("warning: copy source of '%s' not" + " in parents of %s") % (f, ctx)) + fl2 = repo.file(rp[0]) + if not len(fl2): + self.err(lr, _("empty or missing copy source " + "revlog %s:%s") % (rp[0], short(rp[1])), f) + elif rp[1] == nullid: + ui.note(_("warning: %s@%s: copy source" + " revision is nullid %s:%s\n") + % (f, lr, rp[0], short(rp[1]))) + else: + fl2.rev(rp[1]) + except Exception as inst: + self.exc(lr, _("checking rename of %s") % short(n), inst, f) - ui.status(_("%d files, %d changesets, %d total revisions\n") % - (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: - ui.warn(_("(first damaged changeset appears to be %d)\n") - % min(badrevs)) - return 1 + # cross-check + if f in filenodes: + fns = [(lr, n) for n, lr in filenodes[f].iteritems()] + for lr, node in sorted(fns): + self.err(lr, _("%s in manifests not found") % short(node), + f) + ui.progress(_('checking'), None) + + for f in storefiles: + self.warn(_("warning: orphan revlog '%s'") % f) + + return len(files), revisions
--- a/mercurial/windows.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/windows.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,11 +5,23 @@ # 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 osutil, encoding -import errno, msvcrt, os, re, stat, sys, _winreg +from __future__ import absolute_import -import win32 +import _winreg +import errno +import msvcrt +import os +import re +import stat +import sys + +from .i18n import _ +from . import ( + encoding, + osutil, + win32, +) + executablepath = win32.executablepath getuser = win32.getuser hidewindow = win32.hidewindow
--- a/mercurial/wireproto.py Wed Jan 06 11:01:55 2016 -0800 +++ b/mercurial/wireproto.py Sun Jan 17 21:40:21 2016 -0600 @@ -30,6 +30,10 @@ util, ) +bundle2required = _( + 'incompatible Mercurial client; bundle2 required\n' + '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n') + class abstractserverproto(object): """abstract class that summarizes the protocol API @@ -166,7 +170,13 @@ # client side class wirepeer(peer.peerrepository): + """Client-side interface for communicating with a peer repository. + Methods commonly call wire protocol commands of the same name. + + See also httppeer.py and sshpeer.py for protocol-specific + implementations of this interface. + """ def batch(self): if self.capable('batch'): return remotebatch(self) @@ -481,6 +491,35 @@ % (cmd, ",".join(others))) return opts +def bundle1allowed(repo, action): + """Whether a bundle1 operation is allowed from the server. + + Priority is: + + 1. server.bundle1gd.<action> (if generaldelta active) + 2. server.bundle1.<action> + 3. server.bundle1gd (if generaldelta active) + 4. server.bundle1 + """ + ui = repo.ui + gd = 'generaldelta' in repo.requirements + + if gd: + v = ui.configbool('server', 'bundle1gd.%s' % action, None) + if v is not None: + return v + + v = ui.configbool('server', 'bundle1.%s' % action, None) + if v is not None: + return v + + if gd: + v = ui.configbool('server', 'bundle1gd', None) + if v is not None: + return v + + return ui.configbool('server', 'bundle1', True) + # list of commands commands = {} @@ -585,7 +624,7 @@ caps.append('stream') # otherwise, add 'streamreqs' detailing our local revlog format else: - caps.append('streamreqs=%s' % ','.join(requiredformats)) + caps.append('streamreqs=%s' % ','.join(sorted(requiredformats))) if repo.ui.configbool('experimental', 'bundle2-advertise', True): capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo)) caps.append('bundle2=' + urllib.quote(capsblob)) @@ -646,6 +685,11 @@ elif keytype != 'plain': raise KeyError('unknown getbundle option type %s' % keytype) + + if not bundle1allowed(repo, 'pull'): + if not exchange.bundle2requested(opts.get('bundlecaps')): + return ooberror(bundle2required) + cg = exchange.getbundle(repo, 'serve', **opts) return streamres(proto.groupchunks(cg)) @@ -757,6 +801,10 @@ proto.getfile(fp) fp.seek(0) gen = exchange.readbundle(repo.ui, fp, None) + if (isinstance(gen, changegroupmod.cg1unpacker) + and not bundle1allowed(repo, 'push')): + return ooberror(bundle2required) + r = exchange.unbundle(repo, gen, their_heads, 'serve', proto._client()) if util.safehasattr(r, 'addpart'):
--- a/setup.py Wed Jan 06 11:01:55 2016 -0800 +++ b/setup.py Sun Jan 17 21:40:21 2016 -0600 @@ -9,22 +9,14 @@ raise SystemExit("Mercurial requires Python 2.6 or later.") if sys.version_info[0] >= 3: - def b(s): - '''A helper function to emulate 2.6+ bytes literals using string - literals.''' - return s.encode('latin1') printf = eval('print') libdir_escape = 'unicode_escape' else: libdir_escape = 'string_escape' - def b(s): - '''A helper function to emulate 2.6+ bytes literals using string - literals.''' - return s def printf(*args, **kwargs): f = kwargs.get('file', sys.stdout) end = kwargs.get('end', '\n') - f.write(b(' ').join(args) + end) + f.write(b' '.join(args) + end) # Solaris Python packaging brain damage try: @@ -79,11 +71,16 @@ from distutils.command.build import build from distutils.command.build_ext import build_ext from distutils.command.build_py import build_py +from distutils.command.build_scripts import build_scripts from distutils.command.install_lib import install_lib from distutils.command.install_scripts import install_scripts from distutils.spawn import spawn, find_executable from distutils import file_util -from distutils.errors import CCompilerError, DistutilsExecError +from distutils.errors import ( + CCompilerError, + DistutilsError, + DistutilsExecError, +) from distutils.sysconfig import get_python_inc, get_config_var from distutils.version import StrictVersion @@ -102,6 +99,7 @@ scripts = ['hg'] if os.name == 'nt': + # We remove hg.bat if we are able to build hg.exe. scripts.append('contrib/win32/hg.bat') # simplified version of distutils.ccompiler.CCompiler.has_function @@ -166,22 +164,20 @@ # fine, we don't want to load it anyway. Python may warn about # a missing __init__.py in mercurial/locale, we also ignore that. err = [e for e in err.splitlines() - if not e.startswith(b('not trusting file')) \ - and not e.startswith(b('warning: Not importing')) \ - and not e.startswith(b('obsolete feature not enabled'))] + if not e.startswith(b'not trusting file') \ + and not e.startswith(b'warning: Not importing') \ + and not e.startswith(b'obsolete feature not enabled')] if err: printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr) - printf(b('\n').join([b(' ') + e for e in err]), file=sys.stderr) + printf(b'\n'.join([b' ' + e for e in err]), file=sys.stderr) return '' return out version = '' -# Execute hg out of this directory with a custom environment which -# includes the pure Python modules in mercurial/pure. We also take -# care to not use any hgrc files and do no localization. -pypath = ['mercurial', os.path.join('mercurial', 'pure')] -env = {'PYTHONPATH': os.pathsep.join(pypath), +# Execute hg out of this directory with a custom environment which takes care +# to not use any hgrc files and do no localization. +env = {'HGMODULEPOLICY': 'py', 'HGRCPATH': '', 'LANGUAGE': 'C'} if 'LD_LIBRARY_PATH' in os.environ: @@ -214,7 +210,7 @@ kw = dict([[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]) if 'tag' in kw: - version = kw['tag'] + version = kw['tag'] elif 'latesttag' in kw: if 'changessincelatesttag' in kw: version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw @@ -306,6 +302,31 @@ log.warn("Failed to build optional extension '%s' (skipping)", ext.name) +class hgbuildscripts(build_scripts): + def run(self): + if os.name != 'nt': + return build_scripts.run(self) + + exebuilt = False + try: + self.run_command('build_hgexe') + exebuilt = True + except (DistutilsError, CCompilerError): + log.warn('failed to build optional hg.exe') + + if exebuilt: + # Copying hg.exe to the scripts build directory ensures it is + # installed by the install_scripts command. + hgexecommand = self.get_finalized_command('build_hgexe') + dest = os.path.join(self.build_dir, 'hg.exe') + self.mkpath(self.build_dir) + self.copy_file(hgexecommand.hgexepath, dest) + + # Remove hg.bat because it is redundant with hg.exe. + self.scripts.remove('contrib/win32/hg.bat') + + return build_scripts.run(self) + class hgbuildpy(build_py): if convert2to3: fixer_names = sorted(set(getfixers("lib2to3.fixes") + @@ -315,11 +336,6 @@ build_py.finalize_options(self) if self.distribution.pure: - if self.py_modules is None: - self.py_modules = [] - for ext in self.distribution.ext_modules: - if ext.name.startswith("mercurial."): - self.py_modules.append("mercurial.pure.%s" % ext.name[10:]) self.distribution.ext_modules = [] else: h = os.path.join(get_python_inc(), 'Python.h') @@ -327,14 +343,21 @@ raise SystemExit('Python headers are required to build ' 'Mercurial but weren\'t found in %s' % h) - def find_modules(self): - modules = build_py.find_modules(self) - for module in modules: - if module[0] == "mercurial.pure": - if module[1] != "__init__": - yield ("mercurial", module[1], module[2]) + def copy_file(self, *args, **kwargs): + dst, copied = build_py.copy_file(self, *args, **kwargs) + + if copied and dst.endswith('__init__.py'): + if self.distribution.pure: + modulepolicy = 'py' else: - yield module + modulepolicy = 'c' + content = open(dst, 'rb').read() + content = content.replace(b'@MODULELOADPOLICY@', + modulepolicy.encode(libdir_escape)) + with open(dst, 'wb') as fh: + fh.write(content) + + return dst, copied class buildhgextindex(Command): description = 'generate prebuilt index of hgext (for frozen package)' @@ -389,6 +412,11 @@ libraries=[], output_dir=self.build_temp) + @property + def hgexepath(self): + dir = os.path.dirname(self.get_ext_fullpath('dummy')) + return os.path.join(self.build_temp, dir, 'hg.exe') + class hginstalllib(install_lib): ''' This is a specialization of install_lib that replaces the copy_file used @@ -443,6 +471,25 @@ def run(self): install_scripts.run(self) + # It only makes sense to replace @LIBDIR@ with the install path if + # the install path is known. For wheels, the logic below calculates + # the libdir to be "../..". This is because the internal layout of a + # wheel archive looks like: + # + # mercurial-3.6.1.data/scripts/hg + # mercurial/__init__.py + # + # When installing wheels, the subdirectories of the "<pkg>.data" + # directory are translated to system local paths and files therein + # are copied in place. The mercurial/* files are installed into the + # site-packages directory. However, the site-packages directory + # isn't known until wheel install time. This means we have no clue + # at wheel generation time what the installed site-packages directory + # will be. And, wheels don't appear to provide the ability to register + # custom code to run during wheel installation. This all means that + # we can't reliably set the libdir in wheels: the default behavior + # of looking in sys.path must do. + if (os.path.splitdrive(self.install_dir)[0] != os.path.splitdrive(self.install_lib)[0]): # can't make relative paths from one drive to another, so use an @@ -453,7 +500,7 @@ rest = self.install_dir[len(common):] uplevel = len([n for n in os.path.split(rest) if n]) - libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):] + libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):] for outfile in self.outfiles: fp = open(outfile, 'rb') @@ -461,10 +508,18 @@ fp.close() # skip binary files - if b('\0') in data: + if b'\0' in data: continue - data = data.replace(b('@LIBDIR@'), libdir.encode(libdir_escape)) + # During local installs, the shebang will be rewritten to the final + # install path. During wheel packaging, the shebang has a special + # value. + if data.startswith(b'#!python'): + log.info('not rewriting @LIBDIR@ in %s because install path ' + 'not known' % outfile) + continue + + data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape)) fp = open(outfile, 'wb') fp.write(data) fp.close() @@ -473,6 +528,7 @@ 'build_mo': hgbuildmo, 'build_ext': hgbuildext, 'build_py': hgbuildpy, + 'build_scripts': hgbuildscripts, 'build_hgextindex': buildhgextindex, 'install_lib': hginstalllib, 'install_scripts': hginstallscripts, @@ -480,11 +536,10 @@ } packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient', + 'mercurial.pure', 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf', 'hgext.largefiles'] -pymodules = [] - common_depends = ['mercurial/util.h'] osutil_ldflags = [] @@ -536,6 +591,7 @@ packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo', 'help/*.txt', + 'help/internals/*.txt', 'default.d/*.rc', 'dummycert.pem']} @@ -636,7 +692,6 @@ ], scripts=scripts, packages=packages, - py_modules=pymodules, ext_modules=extmodules, data_files=datafiles, package_data=packagedata,
--- a/tests/autodiff.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/autodiff.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,13 @@ # Extension dedicated to test patch.diff() upgrade modes -# -# -from mercurial import cmdutil, scmutil, patch, error + +from __future__ import absolute_import + +from mercurial import ( + cmdutil, + error, + patch, + scmutil, +) cmdtable = {} command = cmdutil.command(cmdtable)
--- a/tests/dumbhttp.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/dumbhttp.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,13 +1,22 @@ #!/usr/bin/env python +from __future__ import absolute_import + """ Small and dumb HTTP server for use in tests. """ -from optparse import OptionParser -import BaseHTTPServer, SimpleHTTPServer, signal, sys +import optparse +import BaseHTTPServer +import signal +import SimpleHTTPServer +import sys -from mercurial import cmdutil +from mercurial import ( + cmdutil, +) + +OptionParser = optparse.OptionParser class simplehttpservice(object): def __init__(self, host, port):
--- a/tests/f Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/f Sun Jan 17 21:40:21 2016 -0600 @@ -44,7 +44,7 @@ if opts.type: facts.append('file') if opts.hexdump or opts.dump or opts.md5: - content = file(f).read() + content = file(f, 'rb').read() elif islink: if opts.type: facts.append('link')
--- a/tests/fakedirstatewritetime.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/fakedirstatewritetime.py Sun Jan 17 21:40:21 2016 -0600 @@ -5,7 +5,15 @@ # - 'workingctx._checklookup()' (= 'repo.status()') # - 'committablectx.markcommitted()' -from mercurial import context, dirstate, extensions, parsers, util +from __future__ import absolute_import + +from mercurial import ( + context, + dirstate, + extensions, + parsers, + util, +) def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): # execute what original parsers.pack_dirstate should do actually
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/fakemergerecord.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,25 @@ +# Extension to write out fake unsupported records into the merge state +# +# + +from __future__ import absolute_import + +from mercurial import ( + cmdutil, + merge, +) + +cmdtable = {} +command = cmdutil.command(cmdtable) + +@command('fakemergerecord', + [('X', 'mandatory', None, 'add a fake mandatory record'), + ('x', 'advisory', None, 'add a fake advisory record')], '') +def fakemergerecord(ui, repo, *pats, **opts): + ms = merge.mergestate.read(repo) + records = ms._makerecords() + if opts.get('mandatory'): + records.append(('X', 'mandatory record')) + if opts.get('advisory'): + records.append(('x', 'advisory record')) + ms._writerecords(records)
--- a/tests/fakepatchtime.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/fakepatchtime.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,7 +1,13 @@ # extension to emulate invoking 'patch.internalpatch()' at the time # specified by '[fakepatchtime] fakenow' -from mercurial import extensions, patch as patchmod, util +from __future__ import absolute_import + +from mercurial import ( + extensions, + patch as patchmod, + util, +) def internalpatch(orig, ui, repo, patchobj, strip, prefix='', files=None,
--- a/tests/filterpyflakes.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/filterpyflakes.py Sun Jan 17 21:40:21 2016 -0600 @@ -2,7 +2,10 @@ # Filter output by pyflakes to control which warnings we check -import sys, re +from __future__ import absolute_import + +import re +import sys def makekey(typeandline): """
--- a/tests/generate-working-copy-states.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/generate-working-copy-states.py Sun Jan 17 21:40:21 2016 -0600 @@ -29,8 +29,10 @@ # $ hg forget *_*_*-untracked # $ rm *_*_missing-* +from __future__ import absolute_import + +import os import sys -import os # Generates pairs of (filename, contents), where 'contents' is a list # describing the file's content at each revision (or in the working copy).
--- a/tests/get-with-headers.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/get-with-headers.py Sun Jan 17 21:40:21 2016 -0600 @@ -3,18 +3,15 @@ """This does HTTP GET requests given a host:port and path and returns a subset of the headers plus the body of the result.""" -import httplib, sys +from __future__ import absolute_import + +import httplib +import json +import os +import sys try: - import json -except ImportError: - try: - import simplejson as json - except ImportError: - json = None - -try: - import msvcrt, os + import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) except ImportError: @@ -58,11 +55,6 @@ # Pretty print JSON. This also has the beneficial side-effect # of verifying emitted JSON is well-formed. if formatjson: - if not json: - print 'no json module not available' - print 'did you forget a #require json?' - sys.exit(1) - # json.dumps() will print trailing newlines. Eliminate them # to make tests easier to write. data = json.loads(data)
--- a/tests/heredoctest.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/heredoctest.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import sys globalvars = {}
--- a/tests/hghave.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/hghave.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import errno import os import re @@ -68,7 +70,7 @@ sys.exit(1) def matchoutput(cmd, regexp, ignorestatus=False): - """Return True if cmd executes successfully and its output + """Return the match object if cmd executes successfully and its output is matched by the supplied regular expression. """ r = re.compile(regexp) @@ -458,8 +460,20 @@ @check("pure", "running with pure Python code") def has_pure(): - return os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure" + return any([ + os.environ.get("HGMODULEPOLICY") == "py", + os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure", + ]) @check("slow", "allow slow tests") def has_slow(): return os.environ.get('HGTEST_SLOW') == 'slow' + +@check("hypothesis", "is Hypothesis installed") +def has_hypothesis(): + try: + import hypothesis + hypothesis.given + return True + except ImportError: + return False
--- a/tests/hgweberror.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/hgweberror.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,6 +1,10 @@ # A dummy extension that installs an hgweb command that throws an Exception. -from mercurial.hgweb import webcommands +from __future__ import absolute_import + +from mercurial.hgweb import ( + webcommands, +) def raiseerror(web, req, tmpl): '''Dummy web command that raises an uncaught Exception.'''
--- a/tests/histedit-helpers.sh Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/histedit-helpers.sh Sun Jan 17 21:40:21 2016 -0600 @@ -2,5 +2,6 @@ grep -v 'saving bundle' | grep -v 'saved backup' | \ grep -v added | grep -v adding | \ grep -v "unable to find 'e' for patching" | \ - grep -v "e: No such file or directory" + grep -v "e: No such file or directory" | \ + cat }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/hypothesishelpers.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,62 @@ +# Helper module to use the Hypothesis tool in tests +# +# Copyright 2015 David R. MacIver +# +# For details see http://hypothesis.readthedocs.org + +import os +import sys +import traceback + +from hypothesis.settings import set_hypothesis_home_dir +import hypothesis.strategies as st +from hypothesis import given, Settings + +# hypothesis store data regarding generate example and code +set_hypothesis_home_dir(os.path.join( + os.getenv('TESTTMP'), ".hypothesis" +)) + +def check(*args, **kwargs): + """decorator to make a function a hypothesis test + + Decorated function are run immediately (to be used doctest style)""" + def accept(f): + # Workaround for https://github.com/DRMacIver/hypothesis/issues/206 + # Fixed in version 1.13 (released 2015 october 29th) + f.__module__ = '__anon__' + try: + given(*args, settings=Settings(max_examples=2000), **kwargs)(f)() + except Exception: + traceback.print_exc(file=sys.stdout) + sys.exit(1) + return accept + + +def roundtrips(data, decode, encode): + """helper to tests function that must do proper encode/decode roundtripping + """ + @given(data) + def testroundtrips(value): + encoded = encode(value) + decoded = decode(encoded) + if decoded != value: + raise ValueError( + "Round trip failed: %s(%r) -> %s(%r) -> %r" % ( + encode.__name__, value, decode.__name__, encoded, + decoded + )) + try: + testroundtrips() + except Exception: + # heredoc swallow traceback, we work around it + traceback.print_exc(file=sys.stdout) + raise + print("Round trip OK") + + +# strategy for generating bytestring that might be an issue for Mercurial +bytestrings = ( + st.builds(lambda s, e: s.encode(e), st.text(), st.sampled_from([ + 'utf-8', 'utf-16', + ]))) | st.binary()
--- a/tests/run-tests.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/run-tests.py Sun Jan 17 21:40:21 2016 -0600 @@ -154,6 +154,7 @@ defaults = { 'jobs': ('HGTEST_JOBS', 1), 'timeout': ('HGTEST_TIMEOUT', 180), + 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500), 'port': ('HGTEST_PORT', 20059), 'shell': ('HGTEST_SHELL', 'sh'), } @@ -236,6 +237,9 @@ parser.add_option("-t", "--timeout", type="int", help="kill errant tests after TIMEOUT seconds" " (default: $%s or %d)" % defaults['timeout']) + parser.add_option("--slowtimeout", type="int", + help="kill errant slow tests after SLOWTIMEOUT seconds" + " (default: $%s or %d)" % defaults['slowtimeout']) parser.add_option("--time", action="store_true", help="time how long each test takes") parser.add_option("--json", action="store_true", @@ -263,6 +267,8 @@ help='run statprof on run-tests') parser.add_option('--allow-slow-tests', action='store_true', help='allow extremely slow tests') + parser.add_option('--showchannels', action='store_true', + help='show scheduling channels') for option, (envvar, default) in defaults.items(): defaults[option] = type(default)(os.environ.get(envvar, default)) @@ -327,7 +333,11 @@ if options.timeout != defaults['timeout']: sys.stderr.write( 'warning: --timeout option ignored with --debug\n') + if options.slowtimeout != defaults['slowtimeout']: + sys.stderr.write( + 'warning: --slowtimeout option ignored with --debug\n') options.timeout = 0 + options.slowtimeout = 0 if options.py3k_warnings: if PYTHON3: parser.error( @@ -339,6 +349,9 @@ else: options.whitelisted = {} + if options.showchannels: + options.nodiff = True + return (options, args) def rename(src, dst): @@ -430,7 +443,8 @@ debug=False, timeout=defaults['timeout'], startport=defaults['port'], extraconfigopts=None, - py3kwarnings=False, shell=None): + py3kwarnings=False, shell=None, + slowtimeout=defaults['slowtimeout']): """Create a test from parameters. path is the full path to the file defining the test. @@ -444,7 +458,9 @@ output. timeout controls the maximum run time of the test. It is ignored when - debug is True. + debug is True. See slowtimeout for tests with #require slow. + + slowtimeout overrides timeout if the test has #require slow. startport controls the starting port number to use for this test. Each test will reserve 3 port numbers for execution. It is the caller's @@ -469,6 +485,7 @@ self._keeptmpdir = keeptmpdir self._debug = debug self._timeout = timeout + self._slowtimeout = slowtimeout self._startport = startport self._extraconfigopts = extraconfigopts or [] self._py3kwarnings = py3kwarnings @@ -568,6 +585,8 @@ result.testsRun -= 1 except WarnTest as e: result.addWarn(self, str(e)) + except ReportedTest as e: + pass except self.failureException as e: # This differs from unittest in that we don't capture # the stack trace. This is for historical reasons and @@ -922,7 +941,12 @@ print(stdout) sys.exit(1) - return ret == 0 + if ret != 0: + return False, stdout + + if 'slow' in reqs: + self._timeout = self._slowtimeout + return True, None def _parsetest(self, lines): # We generate a shell script which outputs unique markers to line @@ -967,8 +991,9 @@ lsplit = l.split() if len(lsplit) < 2 or lsplit[0] != b'#require': after.setdefault(pos, []).append(' !!! invalid #require\n') - if not self._hghave(lsplit[1:]): - script = [b"exit 80\n"] + haveresult, message = self._hghave(lsplit[1:]) + if not haveresult: + script = [b'echo "%s"\nexit 80\n' % message] break after.setdefault(pos, []).append(l) elif l.startswith(b'#if'): @@ -977,7 +1002,7 @@ 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:]) + skipping = not self._hghave(lsplit[1:])[0] after.setdefault(pos, []).append(l) elif l.startswith(b'#else'): if skipping is None: @@ -1220,6 +1245,9 @@ class WarnTest(Exception): """Raised to indicate that a test warned.""" +class ReportedTest(Exception): + """Raised to indicate that a test already reported.""" + class TestResult(unittest._TextTestResult): """Holds results when executing via unittest.""" # Don't worry too much about accessing the non-public _TextTestResult. @@ -1244,7 +1272,7 @@ self.warned = [] self.times = [] - self._firststarttime = None + self._firststarttime = None # Data stored for the benefit of generating xunit reports. self.successes = [] self.faildata = {} @@ -1256,10 +1284,13 @@ self.stop() else: with iolock: - if not self._options.nodiff: - self.stream.write('\nERROR: %s output changed\n' % test) + if reason == "timed out": + self.stream.write('t') + else: + if not self._options.nodiff: + self.stream.write('\nERROR: %s output changed\n' % test) + self.stream.write('!') - self.stream.write('!') self.stream.flush() def addSuccess(self, test): @@ -1335,6 +1366,7 @@ self.addFailure( test, 'server failed to start (HGPORT=%s)' % test._startport) + raise ReportedTest('server failed to start') else: self.stream.write('\n') for line in lines: @@ -1398,7 +1430,7 @@ def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None, retest=False, keywords=None, loop=False, runs_per_test=1, - loadtest=None, + loadtest=None, showchannels=False, *args, **kwargs): """Create a new instance that can run tests with a configuration. @@ -1410,6 +1442,8 @@ executes on its own thread. Tests actually spawn new processes, so state mutation should not be an issue. + If there is only one job, it will use the main thread. + whitelist and blacklist denote tests that have been whitelisted and blacklisted, respectively. These arguments don't belong in TestSuite. Instead, whitelist and blacklist should be handled by the thing that @@ -1435,6 +1469,7 @@ self._loop = loop self._runs_per_test = runs_per_test self._loadtest = loadtest + self._showchannels = showchannels def run(self, result): # We have a number of filters that need to be applied. We do this @@ -1481,7 +1516,14 @@ done = queue.Queue() running = 0 + channels = [""] * self._jobs + def job(test, result): + for n, v in enumerate(channels): + if not v: + channel = n + break + channels[channel] = "=" + test.name[5:].split(".")[0] try: test(result) done.put(None) @@ -1490,9 +1532,33 @@ except: # re-raises done.put(('!', test, 'run-test raised an error, see traceback')) raise + channels[channel] = '' + + def stat(): + count = 0 + while channels: + d = '\n%03s ' % count + for n, v in enumerate(channels): + if v: + d += v[0] + channels[n] = v[1:] or '.' + else: + d += ' ' + d += ' ' + with iolock: + sys.stdout.write(d + ' ') + sys.stdout.flush() + for x in xrange(10): + if channels: + time.sleep(.1) + count += 1 stoppedearly = False + if self._showchannels: + statthread = threading.Thread(target=stat, name="stat") + statthread.start() + try: while tests or running: if not done.empty() or running == self._jobs or not tests: @@ -1513,9 +1579,12 @@ self._loadtest(test.name, num_tests[0])) else: tests.append(test) - t = threading.Thread(target=job, name=test.name, - args=(test, result)) - t.start() + if self._jobs == 1: + job(test, result) + else: + t = threading.Thread(target=job, name=test.name, + args=(test, result)) + t.start() running += 1 # If we stop early we still need to wait on started tests to @@ -1533,8 +1602,53 @@ for test in runtests: test.abort() + channels = [] + return result +# Save the most recent 5 wall-clock runtimes of each test to a +# human-readable text file named .testtimes. Tests are sorted +# alphabetically, while times for each test are listed from oldest to +# newest. + +def loadtimes(testdir): + times = [] + try: + with open(os.path.join(testdir, '.testtimes-')) as fp: + for line in fp: + ts = line.split() + times.append((ts[0], [float(t) for t in ts[1:]])) + except IOError as err: + if err.errno != errno.ENOENT: + raise + return times + +def savetimes(testdir, result): + saved = dict(loadtimes(testdir)) + maxruns = 5 + skipped = set([str(t[0]) for t in result.skipped]) + for tdata in result.times: + test, real = tdata[0], tdata[3] + if test not in skipped: + ts = saved.setdefault(test, []) + ts.append(real) + ts[:] = ts[-maxruns:] + + fd, tmpname = tempfile.mkstemp(prefix='.testtimes', + dir=testdir, text=True) + with os.fdopen(fd, 'w') as fp: + for name, ts in sorted(saved.iteritems()): + fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts]))) + timepath = os.path.join(testdir, '.testtimes') + try: + os.unlink(timepath) + except OSError: + pass + try: + os.rename(tmpname, timepath) + except OSError: + pass + class TextTestRunner(unittest.TextTestRunner): """Custom unittest test runner that uses appropriate settings.""" @@ -1568,8 +1682,7 @@ self.stream.writeln('Errored %s: %s' % (test.name, msg)) if self._runner.options.xunit: - xuf = open(self._runner.options.xunit, 'wb') - try: + with open(self._runner.options.xunit, 'wb') as xuf: timesd = dict((t[0], t[3]) for t in result.times) doc = minidom.Document() s = doc.createElement('testsuite') @@ -1596,15 +1709,12 @@ t.appendChild(cd) s.appendChild(t) xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) - finally: - xuf.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: + with open(jsonpath, 'w') as fp: timesd = {} for tdata in result.times: test = tdata[0] @@ -1622,15 +1732,16 @@ '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])} + 'end': ('%0.3f' % timesd[tc.name][4]), + 'diff': result.faildata.get(tc.name, ''), + } outcome[tc.name] = tres jsonout = json.dumps(outcome, sort_keys=True, indent=4) fp.writelines(("testreport =", jsonout)) - finally: - fp.close() self._runner._checkhglib('Tested') + savetimes(self._runner._testdir, result) self.stream.writeln( '# Ran %d tests, %d skipped, %d warned, %d failed.' % (result.testsRun, @@ -1724,21 +1835,37 @@ else: # keywords for slow tests slow = {b'svn': 10, - b'gendoc': 10, - b'check-code-hg': 100, + b'cvs': 10, + b'hghave': 10, + b'largefiles-update': 10, + b'run-tests': 10, + b'corruption': 10, + b'race': 10, + b'i18n': 10, + b'check': 100, + b'gendoc': 100, + b'contrib-perf': 200, } + perf = {} def sortkey(f): # run largest tests first, as they tend to take the longest try: - val = -os.stat(f).st_size - except OSError as e: - if e.errno != errno.ENOENT: - raise - return -1e9 # file does not exist, tell early - for kw, mul in slow.items(): - if kw in f: - val *= mul - return val + return perf[f] + except KeyError: + try: + val = -os.stat(f).st_size + except OSError as e: + if e.errno != errno.ENOENT: + raise + perf[f] = -1e9 # file does not exist, tell early + return -1e9 + for kw, mul in slow.items(): + if kw in f: + val *= mul + if f.endswith('.py'): + val /= 10.0 + perf[f] = val / 1000.0 + return perf[f] tests.sort(key=sortkey) self._testdir = osenvironb[b'TESTDIR'] = getattr( @@ -1914,6 +2041,7 @@ keywords=kws, loop=self.options.loop, runs_per_test=self.options.runs_per_test, + showchannels=self.options.showchannels, tests=tests, loadtest=self._gettest) verbosity = 1 if self.options.verbose: @@ -1940,11 +2068,11 @@ 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 + port = self.options.port + self._portoffset for idx in xrange(portneeded): if not checkportisavailable(port + idx): allfree = False
--- a/tests/svn/svndump-encoding.sh Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/svn/svndump-encoding.sh Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,5 @@ +#!/bin/sh # -*- coding: utf-8 -*- -#!/bin/sh # # Use this script to generate encoding.svndump #
--- a/tests/test-alias.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-alias.t Sun Jan 17 21:40:21 2016 -0600 @@ -173,6 +173,16 @@ $ hg commit -Amfoo adding foo +infer repository + + $ cd .. + +#if no-outer-repo + $ hg shortlog alias/foo + 0 e63c23eaa88a | 1970-01-01 00:00 +0000 +#endif + + $ cd alias with opts @@ -360,11 +370,11 @@ sub $ hg --cwd .. subalias > /dev/null hg: unknown command 'subalias' - (did you mean one of idalias?) + (did you mean idalias?) [255] $ hg -R .. subalias > /dev/null hg: unknown command 'subalias' - (did you mean one of idalias?) + (did you mean idalias?) [255] @@ -372,7 +382,7 @@ $ hg mainalias > /dev/null hg: unknown command 'mainalias' - (did you mean one of idalias?) + (did you mean idalias?) [255] $ hg -R .. mainalias main
--- a/tests/test-ancestor.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-ancestor.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,6 +1,21 @@ -from mercurial import ancestor, commands, hg, ui, util +from __future__ import absolute_import + +import binascii +import getopt +import math +import os +import random +import sys +import time + from mercurial.node import nullrev -import binascii, getopt, math, os, random, sys, time +from mercurial import ( + ancestor, + commands, + hg, + ui, + util, +) def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7): '''nodes: total number of nodes in the graph
--- a/tests/test-audit-path.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-audit-path.t Sun Jan 17 21:40:21 2016 -0600 @@ -27,6 +27,45 @@ abort: path 'b/b' traverses symbolic link 'b' (glob) [255] + $ hg commit -m 'add symlink b' + + +Test symlink traversing when accessing history: +----------------------------------------------- + +(build a changeset where the path exists as a directory) + + $ hg up 0 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkdir b + $ echo c > b/a + $ hg add b/a + $ hg ci -m 'add directory b' + created new head + +Test that hg cat does not do anything wrong the working copy has 'b' as directory + + $ hg cat b/a + c + $ hg cat -r "desc(directory)" b/a + c + $ hg cat -r "desc(symlink)" b/a + b/a: no such file in rev bc151a1f53bd + [1] + +Test that hg cat does not do anything wrong the working copy has 'b' as a symlink (issue4749) + + $ hg up 'desc(symlink)' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg cat b/a + b/a: no such file in rev bc151a1f53bd + [1] + $ hg cat -r "desc(directory)" b/a + c + $ hg cat -r "desc(symlink)" b/a + b/a: no such file in rev bc151a1f53bd + [1] + #endif
--- a/tests/test-backout.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-backout.t Sun Jan 17 21:40:21 2016 -0600 @@ -61,7 +61,7 @@ $ echo grapes >> a $ hg commit -d '2 0' -m grapes - $ hg backout --commit -d '4 0' 1 --tool=:fail + $ hg backout -d '4 0' 1 --tool=:fail 0 files updated, 0 files merged, 1 files removed, 0 files unresolved changeset 3:1c2161e97c0a backs out changeset 1:22cb4f70d813 $ hg summary @@ -75,7 +75,7 @@ $ echo ypples > a $ hg commit -d '5 0' -m ypples - $ hg backout --commit -d '6 0' 2 --tool=:fail + $ hg backout -d '6 0' 2 --tool=:fail 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges [1] @@ -371,7 +371,7 @@ phases: 3 draft without --merge - $ hg backout -d '3 0' 1 --tool=true + $ hg backout --no-commit -d '3 0' 1 --tool=true 1 files updated, 0 files merged, 0 files removed, 0 files unresolved changeset 22bca4c721e5 backed out, don't forget to commit. $ hg locate b @@ -511,7 +511,7 @@ adding file2 without --merge - $ hg backout -r 1 --tool=true + $ hg backout --no-commit -r 1 --tool=true 0 files updated, 0 files merged, 1 files removed, 0 files unresolved changeset bf1602f437f3 backed out, don't forget to commit. $ hg branch @@ -686,7 +686,7 @@ * version 2 records local: b71750c4b0fdf719734971e3ef90dbeab5919a2d other: a30dd8addae3ce71b8667868478542bc417439e6 - file: foo (state "u", hash 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33) + file: foo (record type "F", state "u", hash 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33) local path: foo (flags "") ancestor path: foo (node f89532f44c247a0e993d63e3a734dd781ab04708) other path: foo (node f50039b486d6fa1a90ae51778388cad161f425ee) @@ -694,7 +694,7 @@ $ hg debugmergestate * version 1 records local: b71750c4b0fdf719734971e3ef90dbeab5919a2d - file: foo (state "u", hash 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33) + file: foo (record type "F", state "u", hash 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33) local path: foo (flags "") ancestor path: foo (node f89532f44c247a0e993d63e3a734dd781ab04708) other path: foo (node not stored in v1 format) @@ -709,11 +709,12 @@ update: (current) phases: 3 draft $ hg resolve --all --debug - picked tool ':merge' for foo (binary False symlink False) + picked tool ':merge' for foo (binary False symlink False changedelete False) merging foo my foo@b71750c4b0fd+ other foo@a30dd8addae3 ancestor foo@913609522437 premerge successful (no more unresolved files) + continue: hg commit $ hg status M foo ? foo.orig
--- a/tests/test-bad-extension.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bad-extension.t Sun Jan 17 21:40:21 2016 -0600 @@ -32,9 +32,9 @@ *** 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 + could not import hgext.badext2 (No module named *badext2): trying badext2 (glob) Traceback (most recent call last): - ImportError: No module named badext2 + ImportError: No module named *badext2 (glob) *** failed to import extension badext2: No module named badext2 Traceback (most recent call last): ImportError: No module named badext2
--- a/tests/test-bundle-r.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bundle-r.t Sun Jan 17 21:40:21 2016 -0600 @@ -161,6 +161,9 @@ $ hg -R test bundle --base 2 -r tip test-bundle-branch1.hg test-3 abort: --base is incompatible with specifying a destination [255] + $ hg -R test bundle -a -r tip test-bundle-branch1.hg test-3 + abort: --all is incompatible with specifying a destination + [255] $ hg -R test bundle -r tip test-bundle-branch1.hg abort: repository default-push not found! [255] @@ -171,6 +174,9 @@ 4 changesets found $ hg -R test bundle --base 2 test-bundle-all.hg 6 changesets found + $ hg -R test bundle --base 2 --all test-bundle-all-2.hg + ignoring --base because --all was specified + 9 changesets found $ hg -R test bundle --base 3 -r tip test-bundle-should-fail.hg 1 changesets found
--- a/tests/test-bundle-type.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bundle-type.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,9 @@ + + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + bundle w/o type option $ hg init t1 @@ -37,26 +43,36 @@ > f -q -B6 -D ../b$t; echo > cd ../t$t > hg debugbundle ../b$t + > hg debugbundle --spec ../b$t > echo > cd .. > done % test bundle type None searching for changes 1 changesets found - HG10UN - c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + HG20\x00\x00 (esc) + Stream params: {} + changegroup -- "{'version': '02'}" + c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + none-v2 % test bundle type bzip2 searching for changes 1 changesets found - HG10BZ - c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + HG20\x00\x00 (esc) + Stream params: {'Compression': 'BZ'} + changegroup -- "{'version': '02'}" + c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + bzip2-v2 % test bundle type gzip searching for changes 1 changesets found - HG10GZ - c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + HG20\x00\x00 (esc) + Stream params: {'Compression': 'GZ'} + changegroup -- "{'version': '02'}" + c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + gzip-v2 % test bundle type none-v2 searching for changes @@ -65,6 +81,7 @@ Stream params: {} changegroup -- "{'version': '02'}" c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + none-v2 % test bundle type v2 searching for changes @@ -73,18 +90,21 @@ Stream params: {'Compression': 'BZ'} changegroup -- "{'version': '02'}" c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + bzip2-v2 % test bundle type v1 searching for changes 1 changesets found HG10BZ c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + bzip2-v1 % test bundle type gzip-v1 searching for changes 1 changesets found HG10GZ c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf + gzip-v1 test garbage file
--- a/tests/test-bundle.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bundle.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,9 @@ + + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + Setting up test $ hg init test @@ -224,7 +230,7 @@ adding manifests adding file changes added 9 changesets with 7 changes to 4 files (+1 heads) - changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=bundle:../full.hg (glob) + changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=bundle:../full.hg (glob) (run 'hg heads' to see heads, 'hg merge' to merge) Rollback empty @@ -247,7 +253,7 @@ adding manifests adding file changes added 9 changesets with 7 changes to 4 files (+1 heads) - changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=bundle:empty+full.hg (glob) + changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=bundle:empty+full.hg (glob) (run 'hg heads' to see heads, 'hg merge' to merge) Cannot produce streaming clone bundles with "hg bundle" @@ -260,15 +266,18 @@ packed1 is produced properly $ hg -R test debugcreatestreamclonebundle packed.hg - writing 2608 bytes for 6 files - bundle requirements: revlogv1 + writing 2663 bytes for 6 files + bundle requirements: generaldelta, revlogv1 $ f -B 64 --size --sha1 --hexdump packed.hg - packed.hg: size=2758, sha1=864c1c7b490bac9f2950ef5a660668378ac0524e + packed.hg: size=2826, sha1=e139f97692a142b19cdcff64a69697d5307ce6d4 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 06 00 00 |HGS1UN..........| - 0010: 00 00 00 00 0a 30 00 09 72 65 76 6c 6f 67 76 31 |.....0..revlogv1| - 0020: 00 64 61 74 61 2f 61 64 69 66 66 65 72 65 6e 74 |.data/adifferent| - 0030: 66 69 6c 65 2e 69 00 31 33 39 0a 00 01 00 01 00 |file.i.139......| + 0010: 00 00 00 00 0a 67 00 16 67 65 6e 65 72 61 6c 64 |.....g..generald| + 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 00 64 61 |elta,revlogv1.da| + 0030: 74 61 2f 61 64 69 66 66 65 72 65 6e 74 66 69 6c |ta/adifferentfil| + + $ hg debugbundle --spec packed.hg + none-packed1;requirements%3Dgeneraldelta%2Crevlogv1 generaldelta requirement is listed in stream clone bundles @@ -288,6 +297,9 @@ 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 00 64 61 |elta,revlogv1.da| 0030: 74 61 2f 66 6f 6f 2e 69 00 36 34 0a 00 03 00 01 |ta/foo.i.64.....| + $ hg debugbundle --spec packedgd.hg + none-packed1;requirements%3Dgeneraldelta%2Crevlogv1 + Unpacking packed1 bundles with "hg unbundle" isn't allowed $ hg init packed @@ -299,8 +311,8 @@ packed1 can be consumed from debug command $ hg -R packed debugapplystreamclonebundle packed.hg - 6 files to transfer, 2.55 KB of data - transferred 2.55 KB in *.* seconds (*) (glob) + 6 files to transfer, 2.60 KB of data + transferred 2.60 KB in *.* seconds (* */sec) (glob) Does not work on non-empty repo @@ -695,6 +707,8 @@ list of changesets: 1a38c1b849e8b70c756d2d80b0b9a3ac0b7ea11a 057f4db07f61970e1c11e83be79e9d08adc4dc31 + bundle2-output-bundle: "HG20", (1 params) 1 parts total + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundling: 1/2 changesets (50.00%) bundling: 2/2 changesets (100.00%) bundling: 1/2 manifests (50.00%)
--- a/tests/test-bundle2-exchange.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bundle2-exchange.t Sun Jan 17 21:40:21 2016 -0600 @@ -51,7 +51,7 @@ added 8 changesets with 7 changes to 7 files (+3 heads) pre-close-tip:02de42196ebe draft postclose-tip:02de42196ebe draft - txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:* HG_TXNNAME=unbundle (glob) + txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:* HG_TXNNAME=unbundle (glob) bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob) (run 'hg heads' to see heads, 'hg merge' to merge) @@ -85,7 +85,7 @@ 1 new obsolescence markers pre-close-tip:9520eea781bc draft postclose-tip:9520eea781bc draft - txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) + txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob) updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -113,7 +113,7 @@ 1 new obsolescence markers pre-close-tip:24b6387c8c8c draft postclose-tip:24b6387c8c8c draft - txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) + txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg -R other log -G @@ -257,7 +257,7 @@ remote: lock: free remote: wlock: free remote: postclose-tip:eea13746799a public book_eea1 - remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob) + remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_NODE_LAST=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob) updating bookmark book_eea1 pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de @@ -290,7 +290,7 @@ updating bookmark book_02de pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de - txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) + txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) ssh://user@dummy/main HG_URL=ssh://user@dummy/main (run 'hg heads' to see heads, 'hg merge' to merge) $ hg -R other debugobsolete @@ -315,7 +315,7 @@ updating bookmark book_42cc pre-close-tip:42ccdea3bb16 draft book_42cc postclose-tip:42ccdea3bb16 draft book_42cc - txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) + txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob) http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/ (run 'hg heads .' to see heads, 'hg merge' to merge) $ cat main-error.log @@ -341,7 +341,7 @@ remote: lock: free remote: wlock: free remote: postclose-tip:5fddd98957c8 draft book_5fdd - remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:ssh:127.0.0.1 (glob) + remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_NODE_LAST=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:ssh:127.0.0.1 (glob) updating bookmark book_5fdd pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de @@ -395,7 +395,7 @@ remote: lock: free remote: wlock: free remote: postclose-tip:32af7686d403 public book_32af - remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=32af7686d403cf45b5d95f2d70cebea587ac806a HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:http:127.0.0.1: (glob) + remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=32af7686d403cf45b5d95f2d70cebea587ac806a HG_NODE_LAST=32af7686d403cf45b5d95f2d70cebea587ac806a HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:http:127.0.0.1: (glob) updating bookmark book_32af pre-close-tip:02de42196ebe draft book_02de postclose-tip:02de42196ebe draft book_02de @@ -951,3 +951,167 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files + + $ cd .. + +Servers can disable bundle1 for clone/pull operations + + $ killdaemons.py + $ hg init bundle2onlyserver + $ cd bundle2onlyserver + $ cat > .hg/hgrc << EOF + > [server] + > bundle1.pull = false + > EOF + + $ touch foo + $ hg -q commit -A -m initial + + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + + $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT/ not-bundle2 + requesting all changes + abort: remote error: + incompatible Mercurial client; bundle2 required + (see https://www.mercurial-scm.org/wiki/IncompatibleClient) + [255] + $ killdaemons.py + $ cd .. + +bundle1 can still pull non-generaldelta repos when generaldelta bundle1 disabled + + $ hg --config format.usegeneraldelta=false init notgdserver + $ cd notgdserver + $ cat > .hg/hgrc << EOF + > [server] + > bundle1gd.pull = false + > EOF + + $ touch foo + $ hg -q commit -A -m initial + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + + $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT/ not-bundle2-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 + + $ killdaemons.py + $ cd ../bundle2onlyserver + +bundle1 pull can be disabled for generaldelta repos only + + $ cat > .hg/hgrc << EOF + > [server] + > bundle1gd.pull = false + > EOF + + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT/ not-bundle2 + requesting all changes + abort: remote error: + incompatible Mercurial client; bundle2 required + (see https://www.mercurial-scm.org/wiki/IncompatibleClient) + [255] + + $ killdaemons.py + +Verify the global server.bundle1 option works + + $ cat > .hg/hgrc << EOF + > [server] + > bundle1 = false + > EOF + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT not-bundle2 + requesting all changes + abort: remote error: + incompatible Mercurial client; bundle2 required + (see https://www.mercurial-scm.org/wiki/IncompatibleClient) + [255] + $ killdaemons.py + + $ cat > .hg/hgrc << EOF + > [server] + > bundle1gd = false + > EOF + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + + $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT/ not-bundle2 + requesting all changes + abort: remote error: + incompatible Mercurial client; bundle2 required + (see https://www.mercurial-scm.org/wiki/IncompatibleClient) + [255] + + $ killdaemons.py + + $ cd ../notgdserver + $ cat > .hg/hgrc << EOF + > [server] + > bundle1gd = false + > EOF + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + + $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT/ not-bundle2-2 + 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 + + $ killdaemons.py + $ cd ../bundle2onlyserver + +Verify bundle1 pushes can be disabled + + $ cat > .hg/hgrc << EOF + > [server] + > bundle1.push = false + > [web] + > allow_push = * + > push_ssl = false + > EOF + + $ hg serve -p $HGPORT -d --pid-file=hg.pid -E error.log + $ cat hg.pid >> $DAEMON_PIDS + $ cd .. + + $ hg clone http://localhost:$HGPORT bundle2-only + 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 bundle2-only + $ echo commit > foo + $ hg commit -m commit + $ hg --config experimental.bundle2-exp=false push + pushing to http://localhost:$HGPORT/ + searching for changes + abort: remote error: + incompatible Mercurial client; bundle2 required + (see https://www.mercurial-scm.org/wiki/IncompatibleClient) + [255] + + $ hg push + 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
--- a/tests/test-bundle2-format.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bundle2-format.t Sun Jan 17 21:40:21 2016 -0600 @@ -13,7 +13,7 @@ > code. We still need to be able to test it while it grow up. > """ > - > import sys, os + > import sys, os, gc > from mercurial import cmdutil > from mercurial import util > from mercurial import bundle2 @@ -158,6 +158,7 @@ > # too zealous. It's important for this test that the break > # occur while we're in the middle of a part. > break + > gc.collect() > ui.write('fake timeout complete.\n') > return > try:
--- a/tests/test-bundle2-multiple-changegroups.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-bundle2-multiple-changegroups.t Sun Jan 17 21:40:21 2016 -0600 @@ -82,16 +82,16 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files - pretxnchangegroup hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + pretxnchangegroup hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_NODE_LAST=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) remote: changegroup2 adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files - pretxnchangegroup hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) - changegroup hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + pretxnchangegroup hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + changegroup hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_NODE_LAST=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) - changegroup hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + changegroup hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) pullop.cgresult is 1 (run 'hg update' to get a working copy) @@ -152,17 +152,17 @@ adding manifests adding file changes added 2 changesets with 2 changes to 2 files (+1 heads) - pretxnchangegroup hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + pretxnchangegroup hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_NODE_LAST=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) remote: changegroup2 adding changesets adding manifests adding file changes added 3 changesets with 3 changes to 3 files (+1 heads) - pretxnchangegroup hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) - changegroup hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + pretxnchangegroup hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_NODE_LAST=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + changegroup hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_NODE_LAST=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) - changegroup hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + changegroup hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_NODE_LAST=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=1d14c3ce6ac0582d2809220d33e8cd7a696e0156 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) @@ -225,16 +225,16 @@ adding manifests adding file changes added 1 changesets with 0 changes to 0 files (-1 heads) - pretxnchangegroup hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + pretxnchangegroup hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_NODE_LAST=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) remote: changegroup2 adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files - pretxnchangegroup hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) - changegroup hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + pretxnchangegroup hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + changegroup hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_NODE_LAST=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) - changegroup hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) + changegroup hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) incoming hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/repo (glob) pullop.cgresult is -2 (run 'hg update' to get a working copy)
--- a/tests/test-check-code-hg.t Wed Jan 06 11:01:55 2016 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -#require test-repo - - $ check_code="$TESTDIR"/../contrib/check-code.py - $ cd "$TESTDIR"/.. - -New errors are not allowed. Warnings are strongly discouraged. -(The writing "no-che?k-code" is for not skipping this file when checking.) - - $ hg locate | sed 's-\\-/-g' | - > xargs "$check_code" --warnings --per-file=0 || false - Skipping hgext/zeroconf/Zeroconf.py it has no-che?k-code (glob) - Skipping i18n/polib.py it has no-che?k-code (glob) - Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob) - Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob) - Skipping mercurial/httpclient/socketutil.py it has no-che?k-code (glob)
--- a/tests/test-check-code.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-check-code.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,234 +1,15 @@ - $ cat > correct.py <<EOF - > def toto(arg1, arg2): - > del arg2 - > return (5 + 6, 9) - > EOF - $ cat > wrong.py <<EOF - > def toto( arg1, arg2): - > del(arg2) - > return ( 5+6, 9) - > EOF - $ cat > quote.py <<EOF - > # let's use quote in comments - > (''' ( 4x5 ) - > but """\\''' and finally''', - > """let's fool checkpatch""", '1+2', - > '"""', 42+1, """and - > ( 4-1 ) """, "( 1+1 )\" and ") - > a, '\\\\\\\\', "\\\\\\" x-2", "c-1" - > EOF - $ cat > classstyle.py <<EOF - > class newstyle_class(object): - > pass - > - > class oldstyle_class: - > pass - > - > class empty(): - > pass - > - > no_class = 1: - > pass - > EOF +#require test-repo + $ check_code="$TESTDIR"/../contrib/check-code.py - $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py - ./wrong.py:1: - > def toto( arg1, arg2): - gratuitous whitespace in () or [] - ./wrong.py:2: - > del(arg2) - Python keyword is not a function - ./wrong.py:3: - > return ( 5+6, 9) - gratuitous whitespace in () or [] - missing whitespace in expression - ./quote.py:5: - > '"""', 42+1, """and - missing whitespace in expression - ./classstyle.py:4: - > class oldstyle_class: - old-style class, use class foo(object) - ./classstyle.py:7: - > class empty(): - class foo() creates old style object, use class foo(object) - [1] - $ cat > python3-compat.py << EOF - > foo <> bar - > reduce(lambda a, b: a + b, [1, 2, 3, 4]) - > dict(key=value) - > EOF - $ "$check_code" python3-compat.py - python3-compat.py:1: - > foo <> bar - <> operator is not available in Python 3+, use != - python3-compat.py:2: - > reduce(lambda a, b: a + b, [1, 2, 3, 4]) - reduce is not available in Python 3+ - python3-compat.py:3: - > dict(key=value) - dict() is different in Py2 and 3 and is slower than {} - [1] + $ cd "$TESTDIR"/.. - $ cat > is-op.py <<EOF - > # is-operator comparing number or string literal - > x = None - > y = x is 'foo' - > y = x is "foo" - > y = x is 5346 - > y = x is -6 - > y = x is not 'foo' - > y = x is not "foo" - > y = x is not 5346 - > y = x is not -6 - > EOF - - $ "$check_code" ./is-op.py - ./is-op.py:3: - > y = x is 'foo' - object comparison with literal - ./is-op.py:4: - > y = x is "foo" - object comparison with literal - ./is-op.py:5: - > y = x is 5346 - object comparison with literal - ./is-op.py:6: - > y = x is -6 - object comparison with literal - ./is-op.py:7: - > y = x is not 'foo' - object comparison with literal - ./is-op.py:8: - > y = x is not "foo" - object comparison with literal - ./is-op.py:9: - > y = x is not 5346 - object comparison with literal - ./is-op.py:10: - > y = x is not -6 - object comparison with literal - [1] +New errors are not allowed. Warnings are strongly discouraged. +(The writing "no-che?k-code" is for not skipping this file when checking.) - $ cat > for-nolineno.py <<EOF - > except: - > EOF - $ "$check_code" for-nolineno.py --nolineno - for-nolineno.py:0: - > except: - naked except clause - [1] - - $ cat > warning.t <<EOF - > $ function warnonly { - > > } - > $ diff -N aaa - > $ function onwarn {} - > EOF - $ "$check_code" warning.t - $ "$check_code" --warn warning.t - warning.t:1: - > $ function warnonly { - warning: don't use 'function', use old style - warning.t:3: - > $ diff -N aaa - warning: don't use 'diff -N' - warning.t:4: - > $ function onwarn {} - warning: don't use 'function', use old style - [1] - $ cat > raise-format.py <<EOF - > raise SomeException, message - > # this next line is okay - > raise SomeException(arg1, arg2) - > EOF - $ "$check_code" not-existing.py raise-format.py - Skipping*not-existing.py* (glob) - raise-format.py:1: - > raise SomeException, message - don't use old-style two-argument raise, use Exception(message) - [1] - - $ cat > rst.py <<EOF - > """problematic rst text - > - > .. note:: - > wrong - > """ - > - > ''' - > - > .. note:: - > - > valid - > - > new text - > - > .. note:: - > - > also valid - > ''' - > - > """mixed - > - > .. note:: - > - > good - > - > .. note:: - > plus bad - > """ - > EOF - $ $check_code -w rst.py - rst.py:3: - > .. note:: - warning: add two newlines after '.. note::' - rst.py:26: - > .. note:: - warning: add two newlines after '.. note::' - [1] - - $ cat > ./map-inside-gettext.py <<EOF - > print _("map inside gettext %s" % v) - > - > print _("concatenating " " by " " space %s" % v) - > print _("concatenating " + " by " + " '+' %s" % v) - > - > print _("mapping operation in different line %s" - > % v) - > - > print _( - > "leading spaces inside of '(' %s" % v) - > EOF - $ "$check_code" ./map-inside-gettext.py - ./map-inside-gettext.py:1: - > print _("map inside gettext %s" % v) - don't use % inside _() - ./map-inside-gettext.py:3: - > print _("concatenating " " by " " space %s" % v) - don't use % inside _() - ./map-inside-gettext.py:4: - > print _("concatenating " + " by " + " '+' %s" % v) - don't use % inside _() - ./map-inside-gettext.py:6: - > print _("mapping operation in different line %s" - don't use % inside _() - ./map-inside-gettext.py:9: - > print _( - don't use % inside _() - [1] - -web templates - - $ mkdir -p mercurial/templates - $ cat > mercurial/templates/example.tmpl <<EOF - > {desc} - > {desc|escape} - > {desc|firstline} - > {desc|websub} - > EOF - - $ "$check_code" --warnings mercurial/templates/example.tmpl - mercurial/templates/example.tmpl:2: - > {desc|escape} - warning: follow desc keyword with either firstline or websub - [1] + $ hg locate | sed 's-\\-/-g' | + > xargs "$check_code" --warnings --per-file=0 || false + Skipping hgext/zeroconf/Zeroconf.py it has no-che?k-code (glob) + Skipping i18n/polib.py it has no-che?k-code (glob) + Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob) + Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob) + Skipping mercurial/httpclient/socketutil.py it has no-che?k-code (glob)
--- a/tests/test-check-commit-hg.t Wed Jan 06 11:01:55 2016 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -#require test-repo - -Enable obsolescence to avoid the warning issue when obsmarker are found - - $ cat >> $HGRCPATH << EOF - > [experimental] - > evolution=createmarkers - > EOF - -Go back in the hg repo - - $ cd $TESTDIR/.. - - $ for node in `hg log --rev 'draft() and ::.' --template '{node|short}\n'`; do - > hg export $node | contrib/check-commit > ${TESTTMP}/check-commit.out - > if [ $? -ne 0 ]; then - > echo "Revision $node does not comply to rules" - > echo '------------------------------------------------------' - > cat ${TESTTMP}/check-commit.out - > echo - > fi - > done - -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-commit.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,24 @@ +#require test-repo + +Enable obsolescence to avoid the warning issue when obsmarker are found + + $ cat >> $HGRCPATH << EOF + > [experimental] + > evolution=createmarkers + > EOF + +Go back in the hg repo + + $ cd $TESTDIR/.. + + $ for node in `hg log --rev 'not public() and ::.' --template '{node|short}\n'`; do + > hg export $node | contrib/check-commit > ${TESTTMP}/check-commit.out + > if [ $? -ne 0 ]; then + > echo "Revision $node does not comply to rules" + > echo '------------------------------------------------------' + > cat ${TESTTMP}/check-commit.out + > echo + > fi + > done + +
--- a/tests/test-check-config-hg.t Wed Jan 06 11:01:55 2016 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#require test-repo - - $ cd "$TESTDIR"/.. - -New errors are not allowed. Warnings are strongly discouraged. - - $ hg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' | - > 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-config.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,8 @@ +#require test-repo + + $ cd "$TESTDIR"/.. + +New errors are not allowed. Warnings are strongly discouraged. + + $ hg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' | + > xargs python contrib/check-config.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-execute.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,23 @@ +#require test-repo execbit + + $ cd "`dirname "$TESTDIR"`" + +look for python scripts without the execute bit + + $ hg files 'set:**.py and not exec() and grep(r"^#!.*?python")' + [1] + +look for python scripts with execute bit but not shebang + + $ hg files 'set:**.py and exec() and not grep(r"^#!.*?python")' + [1] + +look for shell scripts with execute bit but not shebang + + $ hg files 'set:**.sh and exec() and not grep(r"^#!.*(ba)?sh")' + [1] + +look for non scripts with no shebang + + $ hg files 'set:exec() and not **.sh and not **.py and not grep(r"^#!")' + [1]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-py3-compat.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,181 @@ +#require test-repo + + $ cd "$TESTDIR"/.. + + $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py + contrib/casesmash.py not using absolute_import + contrib/check-code.py not using absolute_import + contrib/check-code.py requires print_function + contrib/check-config.py not using absolute_import + contrib/check-config.py requires print_function + contrib/debugcmdserver.py not using absolute_import + contrib/debugcmdserver.py requires print_function + contrib/debugshell.py not using absolute_import + contrib/fixpax.py not using absolute_import + contrib/fixpax.py requires print_function + contrib/hgclient.py not using absolute_import + contrib/hgclient.py requires print_function + contrib/hgfixes/fix_bytes.py not using absolute_import + contrib/hgfixes/fix_bytesmod.py not using absolute_import + contrib/hgfixes/fix_leftover_imports.py not using absolute_import + contrib/import-checker.py not using absolute_import + contrib/import-checker.py requires print_function + contrib/memory.py not using absolute_import + contrib/perf.py not using absolute_import + contrib/python-hook-examples.py not using absolute_import + contrib/revsetbenchmarks.py not using absolute_import + contrib/revsetbenchmarks.py requires print_function + contrib/showstack.py not using absolute_import + contrib/synthrepo.py not using absolute_import + contrib/win32/hgwebdir_wsgi.py not using absolute_import + doc/check-seclevel.py not using absolute_import + doc/gendoc.py not using absolute_import + doc/hgmanpage.py not using absolute_import + hgext/__init__.py not using absolute_import + hgext/acl.py not using absolute_import + hgext/blackbox.py not using absolute_import + hgext/bugzilla.py not using absolute_import + hgext/censor.py not using absolute_import + hgext/children.py not using absolute_import + hgext/churn.py not using absolute_import + hgext/clonebundles.py not using absolute_import + hgext/color.py not using absolute_import + hgext/convert/__init__.py not using absolute_import + hgext/convert/bzr.py not using absolute_import + hgext/convert/common.py not using absolute_import + hgext/convert/convcmd.py not using absolute_import + hgext/convert/cvs.py not using absolute_import + hgext/convert/cvsps.py not using absolute_import + hgext/convert/darcs.py not using absolute_import + hgext/convert/filemap.py not using absolute_import + hgext/convert/git.py not using absolute_import + hgext/convert/gnuarch.py not using absolute_import + hgext/convert/hg.py not using absolute_import + hgext/convert/monotone.py not using absolute_import + hgext/convert/p4.py not using absolute_import + hgext/convert/subversion.py not using absolute_import + hgext/convert/transport.py not using absolute_import + hgext/eol.py not using absolute_import + hgext/extdiff.py not using absolute_import + hgext/factotum.py not using absolute_import + hgext/fetch.py not using absolute_import + hgext/gpg.py not using absolute_import + hgext/graphlog.py not using absolute_import + hgext/hgcia.py not using absolute_import + hgext/hgk.py not using absolute_import + hgext/highlight/__init__.py not using absolute_import + hgext/highlight/highlight.py not using absolute_import + hgext/histedit.py not using absolute_import + hgext/keyword.py not using absolute_import + hgext/largefiles/__init__.py not using absolute_import + hgext/largefiles/basestore.py not using absolute_import + hgext/largefiles/lfcommands.py not using absolute_import + hgext/largefiles/lfutil.py not using absolute_import + hgext/largefiles/localstore.py not using absolute_import + hgext/largefiles/overrides.py not using absolute_import + hgext/largefiles/proto.py not using absolute_import + hgext/largefiles/remotestore.py not using absolute_import + hgext/largefiles/reposetup.py not using absolute_import + hgext/largefiles/uisetup.py not using absolute_import + hgext/largefiles/wirestore.py not using absolute_import + hgext/mq.py not using absolute_import + hgext/notify.py not using absolute_import + hgext/pager.py not using absolute_import + hgext/patchbomb.py not using absolute_import + hgext/purge.py not using absolute_import + hgext/rebase.py not using absolute_import + hgext/record.py not using absolute_import + hgext/relink.py not using absolute_import + hgext/schemes.py not using absolute_import + hgext/share.py not using absolute_import + hgext/shelve.py not using absolute_import + hgext/strip.py not using absolute_import + hgext/transplant.py not using absolute_import + hgext/win32mbcs.py not using absolute_import + hgext/win32text.py not using absolute_import + hgext/zeroconf/Zeroconf.py not using absolute_import + hgext/zeroconf/Zeroconf.py requires print_function + hgext/zeroconf/__init__.py not using absolute_import + i18n/check-translation.py not using absolute_import + i18n/polib.py not using absolute_import + mercurial/cmdutil.py not using absolute_import + mercurial/commands.py not using absolute_import + setup.py not using absolute_import + tests/filterpyflakes.py requires print_function + tests/generate-working-copy-states.py requires print_function + tests/get-with-headers.py requires print_function + tests/heredoctest.py requires print_function + tests/hypothesishelpers.py not using absolute_import + tests/hypothesishelpers.py requires print_function + tests/killdaemons.py not using absolute_import + tests/md5sum.py not using absolute_import + tests/mockblackbox.py not using absolute_import + tests/printenv.py not using absolute_import + tests/readlink.py not using absolute_import + tests/readlink.py requires print_function + tests/revlog-formatv0.py not using absolute_import + tests/run-tests.py not using absolute_import + tests/seq.py not using absolute_import + tests/seq.py requires print_function + tests/silenttestrunner.py not using absolute_import + tests/silenttestrunner.py requires print_function + tests/sitecustomize.py not using absolute_import + tests/svn-safe-append.py not using absolute_import + tests/svnxml.py not using absolute_import + tests/test-ancestor.py requires print_function + tests/test-atomictempfile.py not using absolute_import + tests/test-batching.py not using absolute_import + tests/test-batching.py requires print_function + tests/test-bdiff.py not using absolute_import + tests/test-bdiff.py requires print_function + tests/test-context.py not using absolute_import + tests/test-context.py requires print_function + tests/test-demandimport.py not using absolute_import + tests/test-demandimport.py requires print_function + tests/test-dispatch.py not using absolute_import + tests/test-dispatch.py requires print_function + tests/test-doctest.py not using absolute_import + tests/test-duplicateoptions.py not using absolute_import + tests/test-duplicateoptions.py requires print_function + tests/test-filecache.py not using absolute_import + tests/test-filecache.py requires print_function + tests/test-filelog.py not using absolute_import + tests/test-filelog.py requires print_function + tests/test-hg-parseurl.py not using absolute_import + tests/test-hg-parseurl.py requires print_function + tests/test-hgweb-auth.py not using absolute_import + tests/test-hgweb-auth.py requires print_function + tests/test-hgwebdir-paths.py not using absolute_import + tests/test-hybridencode.py not using absolute_import + tests/test-hybridencode.py requires print_function + tests/test-lrucachedict.py not using absolute_import + tests/test-lrucachedict.py requires print_function + tests/test-manifest.py not using absolute_import + tests/test-minirst.py not using absolute_import + tests/test-minirst.py requires print_function + tests/test-parseindex2.py not using absolute_import + tests/test-parseindex2.py requires print_function + tests/test-pathencode.py not using absolute_import + tests/test-pathencode.py requires print_function + tests/test-propertycache.py not using absolute_import + tests/test-propertycache.py requires print_function + tests/test-revlog-ancestry.py not using absolute_import + tests/test-revlog-ancestry.py requires print_function + tests/test-run-tests.py not using absolute_import + tests/test-simplemerge.py not using absolute_import + tests/test-status-inprocess.py not using absolute_import + tests/test-status-inprocess.py requires print_function + tests/test-symlink-os-yes-fs-no.py not using absolute_import + tests/test-trusted.py not using absolute_import + tests/test-trusted.py requires print_function + tests/test-ui-color.py not using absolute_import + tests/test-ui-color.py requires print_function + tests/test-ui-config.py not using absolute_import + tests/test-ui-config.py requires print_function + tests/test-ui-verbosity.py not using absolute_import + tests/test-ui-verbosity.py requires print_function + tests/test-url.py not using absolute_import + tests/test-url.py requires print_function + tests/test-walkrepo.py requires print_function + tests/test-wireproto.py requires print_function + tests/tinyproxy.py requires print_function
--- a/tests/test-check-pyflakes.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-check-pyflakes.t Sun Jan 17 21:40:21 2016 -0600 @@ -7,6 +7,6 @@ $ hg locate 'set:**.py or grep("^!#.*python")' 2>/dev/null \ > | xargs pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py" - tests/filterpyflakes.py:58: undefined name 'undefinedname' + tests/filterpyflakes.py:61: undefined name 'undefinedname'
--- a/tests/test-clone-r.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-clone-r.t Sun Jan 17 21:40:21 2016 -0600 @@ -63,7 +63,7 @@ 2 96 48 ..... 2 626a32663c2f 8b89697eba2c 000000000000 (re) 3 144 48 ..... 3 f54c32f13478 626a32663c2f 000000000000 (re) 4 192 .. ..... 6 de68e904d169 626a32663c2f 000000000000 (re) - 5 2.. 68 ..... 7 09bb521d218d de68e904d169 000000000000 (re) + 5 2.. .. ..... 7 09bb521d218d de68e904d169 000000000000 (re) 6 3.. 54 ..... 8 1fde233dfb0f f54c32f13478 000000000000 (re) $ hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-uncompressed.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,48 @@ +#require serve + + $ hg init server + $ cd server + $ touch foo + $ hg -q commit -A -m initial + >>> for i in range(1024): + ... with open(str(i), 'wb') as fh: + ... fh.write(str(i)) + $ hg -q commit -A -m 'add a lot of files' + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ cd .. + +Basic clone + + $ hg clone --uncompressed -U http://localhost:$HGPORT clone1 + streaming all changes + 1027 files to transfer, 96.3 KB of data + transferred 96.3 KB in * seconds (*/sec) (glob) + searching for changes + no changes found + +Clone with background file closing enabled + + $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --uncompressed -U http://localhost:$HGPORT clone-background | grep -v adding + using http://localhost:$HGPORT/ + sending capabilities command + sending branchmap command + streaming all changes + sending stream_out command + 1027 files to transfer, 96.3 KB of data + starting 4 threads for background file closing + transferred 96.3 KB in * seconds (*/sec) (glob) + 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-bundle: 1 parts total + checking for updated bookmarks + preparing listkeys for "phases" + sending listkeys command + received listkey for "phases": 58 bytes
--- a/tests/test-clonebundles.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-clonebundles.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,9 @@ Set up a server + $ cat >> $HGRCPATH << EOF + > [format] + > usegeneraldelta=yes + > EOF $ hg init server $ cd server $ cat >> .hg/hgrc << EOF @@ -16,27 +20,6 @@ $ cat hg.pid >> $DAEMON_PIDS $ cd .. -Feature disabled by default -(client should not request manifest) - - $ hg clone -U http://localhost:$HGPORT feature-disabled - requesting all changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 2 files - - $ cat server/access.log - * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) - * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) - * - - [*] "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=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phase%2Cbookmarks (glob) - * - - [*] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases (glob) - - $ cat >> $HGRCPATH << EOF - > [experimental] - > clonebundles = true - > EOF - Missing manifest should not result in server lookup $ hg --verbose clone -U http://localhost:$HGPORT no-manifest @@ -46,7 +29,7 @@ adding file changes added 2 changesets with 2 changes to 2 files - $ tail -4 server/access.log + $ cat server/access.log * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob) * - - [*] "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=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phase%2Cbookmarks (glob) @@ -64,25 +47,14 @@ adding file changes added 2 changesets with 2 changes to 2 files -Server advertises presence of feature to client requesting full clone - - $ hg --config experimental.clonebundles=false clone -U http://localhost:$HGPORT advertise-on-clone - requesting all changes - remote: this server supports the experimental "clone bundles" feature that should enable faster and more reliable cloning - remote: help test it by setting the "experimental.clonebundles" config flag to "true" - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 2 files - Manifest file with invalid URL aborts $ echo 'http://does.not.exist/bundle.hg' > server/.hg/clonebundles.manifest $ hg clone http://localhost:$HGPORT 404-url applying clone bundle from http://does.not.exist/bundle.hg - error fetching bundle: * not known (glob) + error fetching bundle: (.* not known|getaddrinfo failed) (re) abort: error applying bundle - (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false") + (if this error persists, consider contacting the server operator or disable clone bundles via "--config ui.clonebundles=false") [255] Server is not running aborts @@ -90,9 +62,9 @@ $ echo "http://localhost:$HGPORT1/bundle.hg" > server/.hg/clonebundles.manifest $ hg clone http://localhost:$HGPORT server-not-runner applying clone bundle from http://localhost:$HGPORT1/bundle.hg - error fetching bundle: Connection refused + error fetching bundle: * refused* (glob) abort: error applying bundle - (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false") + (if this error persists, consider contacting the server operator or disable clone bundles via "--config ui.clonebundles=false") [255] Server returns 404 @@ -103,7 +75,7 @@ applying clone bundle from http://localhost:$HGPORT1/bundle.hg HTTP error fetching bundle: HTTP Error 404: File not found abort: error applying bundle - (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false") + (if this error persists, consider contacting the server operator or disable clone bundles via "--config ui.clonebundles=false") [255] We can override failure to fall back to regular clone @@ -127,7 +99,7 @@ changes. If this output changes, we could break old clients. $ f --size --hexdump partial.hg - partial.hg: size=208 + partial.hg: size=207 0000: 48 47 31 30 47 5a 78 9c 63 60 60 98 17 ac 12 93 |HG10GZx.c``.....| 0010: f0 ac a9 23 45 70 cb bf 0d 5f 59 4e 4a 7f 79 21 |...#Ep..._YNJ.y!| 0020: 9b cc 40 24 20 a0 d7 ce 2c d1 38 25 cd 24 25 d5 |..@$ ...,.8%.$%.| @@ -139,8 +111,8 @@ 0080: 41 d6 24 59 18 a4 a4 9a a6 18 1a 5b 98 9b 5a 98 |A.$Y.......[..Z.| 0090: 9a 18 26 9b a6 19 98 1a 99 99 26 a6 18 9a 98 24 |..&.......&....$| 00a0: 26 59 a6 25 5a 98 a5 18 a6 24 71 41 35 b1 43 dc |&Y.%Z....$qA5.C.| - 00b0: 96 b0 83 f7 e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a |.....E..V....R..| - 00c0: 78 ed fc d5 76 f1 36 95 dc 05 07 00 ad 39 5e d3 |x...v.6......9^.| + 00b0: 16 b2 83 f7 e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a |.....E..V....R..| + 00c0: 78 ed fc d5 76 f1 36 35 dc 05 00 36 ed 5e c7 |x...v.65...6.^.| $ echo "http://localhost:$HGPORT1/partial.hg" > server/.hg/clonebundles.manifest $ hg clone -U http://localhost:$HGPORT partial-bundle @@ -185,11 +157,11 @@ by old clients. $ f --size --hexdump full.hg - full.hg: size=408 + full.hg: size=406 0000: 48 47 32 30 00 00 00 0e 43 6f 6d 70 72 65 73 73 |HG20....Compress| 0010: 69 6f 6e 3d 47 5a 78 9c 63 60 60 90 e5 76 f6 70 |ion=GZx.c``..v.p| 0020: f4 73 77 75 0f f2 0f 0d 60 00 02 46 06 76 a6 b2 |.swu....`..F.v..| - 0030: d4 a2 e2 cc fc 3c 03 23 06 06 e6 7d 40 b1 4d c1 |.....<.#...}@.M.| + 0030: d4 a2 e2 cc fc 3c 03 23 06 06 e6 65 40 b1 4d c1 |.....<.#...e@.M.| 0040: 2a 31 09 cf 9a 3a 52 04 b7 fc db f0 95 e5 a4 f4 |*1...:R.........| 0050: 97 17 b2 c9 0c 14 00 02 e6 d9 99 25 1a a7 a4 99 |...........%....| 0060: a4 a4 1a 5b 58 a4 19 27 9b a4 59 a4 1a 59 a4 99 |...[X..'..Y..Y..| @@ -208,10 +180,10 @@ 0130: 34 31 c5 d0 c4 24 31 c9 32 2d d1 c2 2c c5 30 25 |41...$1.2-..,.0%| 0140: 09 e4 ee 85 8f 85 ff 88 ab 89 36 c7 2a c4 47 34 |..........6.*.G4| 0150: fe f8 ec 7b 73 37 3f c3 24 62 1d 8d 4d 1d 9e 40 |...{s7?.$b..M..@| - 0160: 06 3b 10 14 36 a4 38 10 04 d8 21 01 5a b2 83 f7 |.;..6.8...!.Z...| + 0160: 06 3b 10 14 36 a4 38 10 04 d8 21 01 9a b1 83 f7 |.;..6.8...!.....| 0170: e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a 78 ed fc d5 |.E..V....R..x...| - 0180: 76 f1 36 25 81 49 c0 ad 30 c0 0e 49 8f 54 b7 9e |v.6%.I..0..I.T..| - 0190: d4 1c 09 00 bb 8d f0 bd |........| + 0180: 76 f1 36 25 81 89 c7 ad ec 90 34 48 75 2b 89 49 |v.6%......4Hu+.I| + 0190: bf 00 d6 97 f0 8d |......| $ echo "http://localhost:$HGPORT1/full.hg" > server/.hg/clonebundles.manifest $ hg clone -U http://localhost:$HGPORT full-bundle @@ -308,7 +280,7 @@ $ hg -R server debugcreatestreamclonebundle packed.hg writing 613 bytes for 4 files - bundle requirements: revlogv1 + bundle requirements: generaldelta, revlogv1 No bundle spec should work @@ -384,7 +356,7 @@ Preferring an undefined attribute will take first entry - $ hg --config experimental.clonebundleprefers=foo=bar clone -U http://localhost:$HGPORT prefer-foo + $ hg --config ui.clonebundleprefers=foo=bar clone -U http://localhost:$HGPORT prefer-foo applying clone bundle from http://localhost:$HGPORT1/gz-a.hg adding changesets adding manifests @@ -396,7 +368,7 @@ Preferring bz2 type will download first entry of that type - $ hg --config experimental.clonebundleprefers=COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-bz + $ hg --config ui.clonebundleprefers=COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-bz applying clone bundle from http://localhost:$HGPORT1/bz2-a.hg adding changesets adding manifests @@ -408,7 +380,7 @@ Preferring multiple values of an option works - $ hg --config experimental.clonebundleprefers=COMPRESSION=unknown,COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-multiple-bz + $ hg --config ui.clonebundleprefers=COMPRESSION=unknown,COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-multiple-bz applying clone bundle from http://localhost:$HGPORT1/bz2-a.hg adding changesets adding manifests @@ -420,7 +392,7 @@ Sorting multiple values should get us back to original first entry - $ hg --config experimental.clonebundleprefers=BUNDLESPEC=unknown,BUNDLESPEC=gzip-v2,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-multiple-gz + $ hg --config ui.clonebundleprefers=BUNDLESPEC=unknown,BUNDLESPEC=gzip-v2,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-multiple-gz applying clone bundle from http://localhost:$HGPORT1/gz-a.hg adding changesets adding manifests @@ -432,7 +404,7 @@ Preferring multiple attributes has correct order - $ hg --config experimental.clonebundleprefers=extra=b,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-separate-attributes + $ hg --config ui.clonebundleprefers=extra=b,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-separate-attributes applying clone bundle from http://localhost:$HGPORT1/bz2-b.hg adding changesets adding manifests @@ -451,7 +423,7 @@ > http://localhost:$HGPORT1/bz2-b.hg BUNDLESPEC=bzip2-v2 extra=b > EOF - $ hg --config experimental.clonebundleprefers=extra=b clone -U http://localhost:$HGPORT prefer-partially-defined-attribute + $ hg --config ui.clonebundleprefers=extra=b clone -U http://localhost:$HGPORT prefer-partially-defined-attribute applying clone bundle from http://localhost:$HGPORT1/gz-b.hg adding changesets adding manifests
--- a/tests/test-command-template.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-command-template.t Sun Jan 17 21:40:21 2016 -0600 @@ -3304,6 +3304,28 @@ 1 f 0 f +Test namespaces dict + + $ hg log -T '{rev}{namespaces % " {namespace}={join(names, ",")}"}\n' + 2 bookmarks=bar,foo tags=tip branches=text.{rev} + 1 bookmarks=baz tags= branches=text.{rev} + 0 bookmarks= tags= branches=default + $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}' + bookmarks: bar foo + tags: tip + branches: text.{rev} + $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}' + bookmarks: + bar + foo + tags: + tip + branches: + text.{rev} + $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}' + bar + foo + Test stringify on sub expressions $ cd ..
--- a/tests/test-commit-amend.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-commit-amend.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,8 @@ + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + $ hg init Setup: @@ -115,15 +120,15 @@ stripping amended changeset 74609c7f506e 1 changesets found uncompressed size of bundle content: - 250 (changelog) - 143 (manifests) - 109 a + 270 (changelog) + 163 (manifests) + 129 a saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend-backup.hg (glob) 1 changesets found uncompressed size of bundle content: - 246 (changelog) - 143 (manifests) - 109 a + 266 (changelog) + 163 (manifests) + 129 a adding branch adding changesets adding manifests @@ -259,15 +264,15 @@ stripping amended changeset 5f357c7560ab 1 changesets found uncompressed size of bundle content: - 238 (changelog) - 143 (manifests) - 111 a + 258 (changelog) + 163 (manifests) + 131 a saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend-backup.hg (glob) 1 changesets found uncompressed size of bundle content: - 246 (changelog) - 143 (manifests) - 111 a + 266 (changelog) + 163 (manifests) + 131 a adding branch adding changesets adding manifests @@ -302,15 +307,15 @@ stripping amended changeset 7ab3bf440b54 2 changesets found uncompressed size of bundle content: - 450 (changelog) - 282 (manifests) - 209 a + 490 (changelog) + 322 (manifests) + 249 a saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend-backup.hg (glob) 1 changesets found uncompressed size of bundle content: - 246 (changelog) - 143 (manifests) - 113 a + 266 (changelog) + 163 (manifests) + 133 a adding branch adding changesets adding manifests @@ -805,9 +810,11 @@ $ hg up -q default $ echo aa >> aa $ hg ci -m aa - $ hg merge -q bar + $ hg merge -q bar --config ui.interactive=True << EOF + > c + > EOF local changed aa which remote deleted - use (c)hanged version or (d)elete? c + use (c)hanged version, (d)elete, or leave (u)nresolved? c $ hg ci -m 'merge bar (with conflicts)' $ hg log --config diff.git=1 -pr . changeset: 33:97a298b0c59f @@ -1138,14 +1145,14 @@ R olddirname/commonfile.py R olddirname/newfile.py $ hg debugindex newdirname/newfile.py - rev offset length base linkrev nodeid p1 p2 - 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000 + rev offset length delta linkrev nodeid p1 p2 + 0 0 88 -1 3 34a4d536c0c0 000000000000 000000000000 $ echo a >> newdirname/commonfile.py $ hg ci --amend -m bug $ hg debugrename newdirname/newfile.py newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob) $ hg debugindex newdirname/newfile.py - rev offset length base linkrev nodeid p1 p2 - 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000 + rev offset length delta linkrev nodeid p1 p2 + 0 0 88 -1 3 34a4d536c0c0 000000000000 000000000000
--- a/tests/test-commit-interactive-curses.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-commit-interactive-curses.t Sun Jan 17 21:40:21 2016 -0600 @@ -41,6 +41,10 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: a +Check that commit -i works with no changes + $ hg commit -i + no changes to record + Committing only one file $ echo "a" >> a @@ -71,6 +75,7 @@ - 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 +- toggle on and off the amend mode (to check that it toggles off) - edit the hunk and quit the editor immediately with non-zero status - commit @@ -88,6 +93,8 @@ > KEY_DOWN > KEY_DOWN > TOGGLE + > a + > a > e > X > EOF @@ -166,3 +173,23 @@ $ hg st ? testModeCommands +Amend option works + $ echo "hello world" > x + $ hg diff -c . + diff -r a6735021574d -r 2b0e9be4d336 x + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/x Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +hello + $ cat <<EOF >testModeCommands + > a + > X + > EOF + $ hg commit -i -m "newly added file" -d "0 0" + saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-28bbe4e2-amend-backup.hg (glob) + $ hg diff -c . + diff -r a6735021574d -r c1d239d165ae x + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/x Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +hello world
--- a/tests/test-commit-interactive.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-commit-interactive.t Sun Jan 17 21:40:21 2016 -0600 @@ -153,7 +153,7 @@ Add binary file - $ hg bundle --base -2 tip.bundle + $ hg bundle --type v1 --base -2 tip.bundle 1 changesets found $ hg add tip.bundle $ hg commit -i -d '4 0' -m binary<<EOF @@ -178,7 +178,7 @@ Change binary file - $ hg bundle --base -2 tip.bundle + $ hg bundle --base -2 --type v1 tip.bundle 1 changesets found $ hg commit -i -d '5 0' -m binary-change<<EOF > y @@ -202,7 +202,7 @@ Rename and change binary file $ hg mv tip.bundle top.bundle - $ hg bundle --base -2 top.bundle + $ hg bundle --base -2 --type v1 top.bundle 1 changesets found $ hg commit -i -d '6 0' -m binary-change-rename<<EOF > y @@ -318,7 +318,7 @@ 1 hunks, 1 lines changed examine changes to 'plain'? [Ynesfdaq?] y - @@ -9,3 +9,4 @@ + @@ -9,3 +9,4 @@ 8 9 10 11 @@ -327,6 +327,124 @@ record this change to 'plain'? [Ynesfdaq?] y +Record showfunc should preserve function across sections + + $ cat > f1.py <<EOF + > def annotate(ui, repo, *pats, **opts): + > """show changeset information by line for each file + > + > List changes in files, showing the revision id responsible for + > each line. + > + > This command is useful for discovering when a change was made and + > by whom. + > + > If you include -f/-u/-d, the revision number is suppressed unless + > you also include -the revision number is suppressed unless + > you also include -n. + > + > Without the -a/--text option, annotate will avoid processing files + > it detects as binary. With -a, annotate will annotate the file + > anyway, although the results will probably be neither useful + > nor desirable. + > + > Returns 0 on success. + > """ + > return 0 + > def archive(ui, repo, dest, **opts): + > '''create an unversioned archive of a repository revision + > + > By default, the revision used is the parent of the working + > directory; use -r/--rev to specify a different revision. + > + > The archive type is automatically detected based on file + > extension (to override, use -t/--type). + > + > .. container:: verbose + > + > Valid types are: + > EOF + $ hg add f1.py + $ hg commit -m funcs + $ cat > f1.py <<EOF + > def annotate(ui, repo, *pats, **opts): + > """show changeset information by line for each file + > + > List changes in files, showing the revision id responsible for + > each line + > + > This command is useful for discovering when a change was made and + > by whom. + > + > Without the -a/--text option, annotate will avoid processing files + > it detects as binary. With -a, annotate will annotate the file + > anyway, although the results will probably be neither useful + > nor desirable. + > + > Returns 0 on success. + > """ + > return 0 + > def archive(ui, repo, dest, **opts): + > '''create an unversioned archive of a repository revision + > + > By default, the revision used is the parent of the working + > directory; use -r/--rev to specify a different revision. + > + > The archive type is automatically detected based on file + > extension (or override using -t/--type). + > + > .. container:: verbose + > + > Valid types are: + > EOF + $ hg commit -i -m interactive <<EOF + > y + > y + > y + > y + > EOF + diff --git a/f1.py b/f1.py + 3 hunks, 6 lines changed + examine changes to 'f1.py'? [Ynesfdaq?] y + + @@ -2,8 +2,8 @@ def annotate(ui, repo, *pats, **opts): + """show changeset information by line for each file + + List changes in files, showing the revision id responsible for + - each line. + + each line + + This command is useful for discovering when a change was made and + by whom. + + record change 1/3 to 'f1.py'? [Ynesfdaq?] y + + @@ -6,11 +6,7 @@ def annotate(ui, repo, *pats, **opts): + + This command is useful for discovering when a change was made and + by whom. + + - If you include -f/-u/-d, the revision number is suppressed unless + - you also include -the revision number is suppressed unless + - you also include -n. + - + Without the -a/--text option, annotate will avoid processing files + it detects as binary. With -a, annotate will annotate the file + anyway, although the results will probably be neither useful + record change 2/3 to 'f1.py'? [Ynesfdaq?] y + + @@ -26,7 +22,7 @@ def archive(ui, repo, dest, **opts): + directory; use -r/--rev to specify a different revision. + + The archive type is automatically detected based on file + - extension (to override, use -t/--type). + + extension (or override using -t/--type). + + .. container:: verbose + + record change 3/3 to 'f1.py'? [Ynesfdaq?] y + + Modify end of plain file, add EOL $ echo >> plain @@ -342,7 +460,7 @@ 1 hunks, 1 lines changed examine changes to 'plain'? [Ynesfdaq?] y - @@ -9,4 +9,4 @@ + @@ -9,4 +9,4 @@ 8 9 10 11 @@ -387,7 +505,7 @@ 4 record change 1/3 to 'plain'? [Ynesfdaq?] y - @@ -8,5 +8,3 @@ + @@ -8,5 +8,3 @@ 7 8 9 10 @@ -406,13 +524,13 @@ $ hg tip -p - changeset: 11:21df83db12b8 + changeset: 13:f941910cff62 tag: tip user: test date: Thu Jan 01 00:00:10 1970 +0000 summary: begin-and-end - diff -r ddb8b281c3ff -r 21df83db12b8 plain + diff -r 33abe24d946c -r f941910cff62 plain --- a/plain Thu Jan 01 00:00:10 1970 +0000 +++ b/plain Thu Jan 01 00:00:10 1970 +0000 @@ -1,4 +1,4 @@ @@ -427,7 +545,7 @@ 10 -11 -7264f99c5f5ff3261504828afa4fb4d406c3af54 - diff -r ddb8b281c3ff -r 21df83db12b8 plain2 + diff -r 33abe24d946c -r f941910cff62 plain2 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000 @@ -1,1 +1,2 @@ @@ -478,13 +596,13 @@ $ hg tip -p - changeset: 12:99337501826f + changeset: 14:4915f538659b tag: tip user: test date: Thu Jan 01 00:00:11 1970 +0000 summary: end-only - diff -r 21df83db12b8 -r 99337501826f plain + diff -r f941910cff62 -r 4915f538659b plain --- a/plain Thu Jan 01 00:00:10 1970 +0000 +++ b/plain Thu Jan 01 00:00:11 1970 +0000 @@ -7,4 +7,4 @@ @@ -516,13 +634,13 @@ $ hg tip -p - changeset: 13:bbd45465d540 + changeset: 15:1b1f93d4b94b tag: tip user: test date: Thu Jan 01 00:00:12 1970 +0000 summary: begin-only - diff -r 99337501826f -r bbd45465d540 plain + diff -r 4915f538659b -r 1b1f93d4b94b plain --- a/plain Thu Jan 01 00:00:11 1970 +0000 +++ b/plain Thu Jan 01 00:00:12 1970 +0000 @@ -1,6 +1,3 @@ @@ -624,13 +742,13 @@ $ hg tip -p - changeset: 15:f34a7937ec33 + changeset: 17:41cf3f5c55ae tag: tip user: test date: Thu Jan 01 00:00:14 1970 +0000 summary: middle-only - diff -r 82c065d0b850 -r f34a7937ec33 plain + diff -r a69d252246e1 -r 41cf3f5c55ae plain --- a/plain Thu Jan 01 00:00:13 1970 +0000 +++ b/plain Thu Jan 01 00:00:14 1970 +0000 @@ -1,5 +1,10 @@ @@ -656,7 +774,7 @@ 1 hunks, 2 lines changed examine changes to 'plain'? [Ynesfdaq?] y - @@ -9,3 +9,5 @@ + @@ -9,3 +9,5 @@ 6 7 8 9 @@ -666,13 +784,13 @@ $ hg tip -p - changeset: 16:f9900b71a04c + changeset: 18:58a72f46bc24 tag: tip user: test date: Thu Jan 01 00:00:15 1970 +0000 summary: end-only - diff -r f34a7937ec33 -r f9900b71a04c plain + diff -r 41cf3f5c55ae -r 58a72f46bc24 plain --- a/plain Thu Jan 01 00:00:14 1970 +0000 +++ b/plain Thu Jan 01 00:00:15 1970 +0000 @@ -9,3 +9,5 @@ @@ -705,13 +823,13 @@ $ hg tip -p - changeset: 18:61be427a9deb + changeset: 20:e0f6b99f6c49 tag: tip user: test date: Thu Jan 01 00:00:16 1970 +0000 summary: subdir-change - diff -r a7ffae4d61cb -r 61be427a9deb subdir/a + diff -r abd26b51de37 -r e0f6b99f6c49 subdir/a --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000 @@ -1,1 +1,2 @@ @@ -813,13 +931,13 @@ $ hg tip -p - changeset: 20:b3df3dda369a + changeset: 22:6afbbefacf35 tag: tip user: test date: Thu Jan 01 00:00:18 1970 +0000 summary: x - diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2 + diff -r b73c401c693c -r 6afbbefacf35 subdir/f2 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000 @@ -1,1 +1,2 @@ @@ -838,13 +956,13 @@ $ hg tip -p - changeset: 21:38ec577f126b + changeset: 23:715028a33949 tag: tip user: test date: Thu Jan 01 00:00:19 1970 +0000 summary: y - diff -r b3df3dda369a -r 38ec577f126b subdir/f1 + diff -r 6afbbefacf35 -r 715028a33949 subdir/f1 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000 @@ -1,1 +1,2 @@ @@ -877,7 +995,7 @@ $ hg tip --config diff.git=True -p - changeset: 22:3261adceb075 + changeset: 24:db967c1e5884 tag: tip user: test date: Thu Jan 01 00:00:20 1970 +0000 @@ -915,7 +1033,7 @@ $ hg tip --config diff.git=True -p - changeset: 23:b429867550db + changeset: 25:88903aef81c3 tag: tip user: test date: Thu Jan 01 00:00:21 1970 +0000 @@ -946,7 +1064,7 @@ 1 hunks, 1 lines changed examine changes to 'subdir/f1'? [Ynesfdaq?] y - @@ -2,3 +2,4 @@ + @@ -2,3 +2,4 @@ a a a b @@ -955,7 +1073,7 @@ $ hg tip --config diff.git=True -p - changeset: 24:0b082130c20a + changeset: 26:7af84b6cf560 tag: tip user: test date: Thu Jan 01 00:00:22 1970 +0000 @@ -998,7 +1116,7 @@ $ hg tip --config diff.git=True -p - changeset: 22:0d463bd428f5 + changeset: 24:c26cfe2c4eb0 tag: tip user: test date: Thu Jan 01 00:00:20 1970 +0000 @@ -1034,7 +1152,7 @@ $ hg tip --config diff.git=True -p - changeset: 23:0eab41a3e524 + changeset: 25:a48d2d60adde tag: tip user: test date: Thu Jan 01 00:00:21 1970 +0000 @@ -1063,7 +1181,7 @@ 1 hunks, 1 lines changed examine changes to 'subdir/f1'? [Ynesfdaq?] y - @@ -2,3 +2,4 @@ + @@ -2,3 +2,4 @@ a a a b @@ -1072,7 +1190,7 @@ $ hg tip --config diff.git=True -p - changeset: 24:f4f718f27b7c + changeset: 26:5cc89ae210fa tag: tip user: test date: Thu Jan 01 00:00:22 1970 +0000 @@ -1096,7 +1214,7 @@ Abort early when a merge is in progress $ hg up 4 - 1 files updated, 0 files merged, 6 files removed, 0 files unresolved + 1 files updated, 0 files merged, 7 files removed, 0 files unresolved $ touch iwillmergethat $ hg add iwillmergethat @@ -1108,7 +1226,7 @@ $ hg ci -m'new head' $ hg up default - 6 files updated, 0 files merged, 2 files removed, 0 files unresolved + 7 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg merge thatbranch 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -1379,7 +1497,7 @@ 1 hunks, 1 lines changed examine changes to 'subdir/f1'? [Ynesfdaq?] y - @@ -3,3 +3,4 @@ + @@ -3,3 +3,4 @@ a a b c @@ -1390,7 +1508,7 @@ $ hg status -A subdir/f1 C subdir/f1 $ hg tip -p - changeset: 28:* (glob) + changeset: 30:* (glob) tag: tip user: test date: Thu Jan 01 00:00:24 1970 +0000 @@ -1418,7 +1536,7 @@ 1 hunks, 1 lines changed examine changes to 'subdir/f1'? [Ynesfdaq?] y - @@ -4,3 +4,4 @@ + @@ -4,3 +4,4 @@ a b c d @@ -1449,7 +1567,7 @@ 1 hunks, 1 lines changed examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y - @@ -11,3 +11,4 @@ + @@ -11,3 +11,4 @@ 8 9 10 11 @@ -1460,7 +1578,7 @@ $ hg status -A plain3 C plain3 $ hg tip - changeset: 30:* (glob) + changeset: 32:* (glob) tag: tip user: test date: Thu Jan 01 00:00:23 1970 +0000 @@ -1525,7 +1643,7 @@ The #if execbit block above changes the hashes here on some systems $ hg tip -p - changeset: 32:* (glob) + changeset: 34:* (glob) tag: tip user: test date: Thu Jan 01 00:00:23 1970 +0000
--- a/tests/test-completion.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-completion.t Sun Jan 17 21:40:21 2016 -0600 @@ -80,6 +80,7 @@ debugdag debugdata debugdate + debugdeltachain debugdirstate debugdiscovery debugextensions @@ -223,7 +224,7 @@ update: clean, check, date, rev, tool addremove: similarity, subrepos, include, exclude, dry-run archive: no-decode, prefix, rev, type, subrepos, include, exclude - backout: merge, commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user + backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user bisect: reset, good, bad, skip, extend, command, noupdate bookmarks: force, rev, delete, rename, inactive, template branch: force, clean @@ -235,7 +236,7 @@ debugancestor: debugapplystreamclonebundle: debugbuilddag: mergeable-file, overwritten-file, new-file - debugbundle: all + debugbundle: all, spec debugcheckstate: debugcommands: debugcomplete: options @@ -243,6 +244,7 @@ debugdag: tags, branches, dots, spaces debugdata: changelog, manifest, dir debugdate: extended + debugdeltachain: changelog, manifest, dir, template debugdirstate: nodates, datesort debugdiscovery: old, nonheads, ssh, remotecmd, insecure debugextensions: template @@ -251,7 +253,7 @@ debuggetbundle: head, common, type debugignore: debugindex: changelog, manifest, dir, format - debugindexdot: + debugindexdot: changelog, manifest, dir debuginstall: debugknown: debuglabelcomplete: @@ -276,7 +278,7 @@ graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude heads: rev, topo, active, closed, style, template - help: extension, command, keyword + help: extension, command, keyword, system identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos @@ -284,7 +286,7 @@ manifest: rev, all, template outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos parents: rev, style, template - paths: + paths: template phase: public, draft, secret, force, rev recover: rename: after, force, include, exclude, dry-run
--- a/tests/test-context.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-context.py Sun Jan 17 21:40:21 2016 -0600 @@ -16,7 +16,11 @@ repo[None].add(['foo']) repo.commit(text='commit1', date="0 0") -print "workingfilectx.date =", repo[None]['foo'].date() +if os.name == 'nt': + d = repo[None]['foo'].date() + print "workingfilectx.date = (%d, %d)" % (d[0], d[1]) +else: + print "workingfilectx.date =", repo[None]['foo'].date() # test memctx with non-ASCII commit message
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-contrib-check-code.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,234 @@ + $ cat > correct.py <<EOF + > def toto(arg1, arg2): + > del arg2 + > return (5 + 6, 9) + > EOF + $ cat > wrong.py <<EOF + > def toto( arg1, arg2): + > del(arg2) + > return ( 5+6, 9) + > EOF + $ cat > quote.py <<EOF + > # let's use quote in comments + > (''' ( 4x5 ) + > but """\\''' and finally''', + > """let's fool checkpatch""", '1+2', + > '"""', 42+1, """and + > ( 4-1 ) """, "( 1+1 )\" and ") + > a, '\\\\\\\\', "\\\\\\" x-2", "c-1" + > EOF + $ cat > classstyle.py <<EOF + > class newstyle_class(object): + > pass + > + > class oldstyle_class: + > pass + > + > class empty(): + > pass + > + > no_class = 1: + > pass + > EOF + $ check_code="$TESTDIR"/../contrib/check-code.py + $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py + ./wrong.py:1: + > def toto( arg1, arg2): + gratuitous whitespace in () or [] + ./wrong.py:2: + > del(arg2) + Python keyword is not a function + ./wrong.py:3: + > return ( 5+6, 9) + gratuitous whitespace in () or [] + missing whitespace in expression + ./quote.py:5: + > '"""', 42+1, """and + missing whitespace in expression + ./classstyle.py:4: + > class oldstyle_class: + old-style class, use class foo(object) + ./classstyle.py:7: + > class empty(): + class foo() creates old style object, use class foo(object) + [1] + $ cat > python3-compat.py << EOF + > foo <> bar + > reduce(lambda a, b: a + b, [1, 2, 3, 4]) + > dict(key=value) + > EOF + $ "$check_code" python3-compat.py + python3-compat.py:1: + > foo <> bar + <> operator is not available in Python 3+, use != + python3-compat.py:2: + > reduce(lambda a, b: a + b, [1, 2, 3, 4]) + reduce is not available in Python 3+ + python3-compat.py:3: + > dict(key=value) + dict() is different in Py2 and 3 and is slower than {} + [1] + + $ cat > is-op.py <<EOF + > # is-operator comparing number or string literal + > x = None + > y = x is 'foo' + > y = x is "foo" + > y = x is 5346 + > y = x is -6 + > y = x is not 'foo' + > y = x is not "foo" + > y = x is not 5346 + > y = x is not -6 + > EOF + + $ "$check_code" ./is-op.py + ./is-op.py:3: + > y = x is 'foo' + object comparison with literal + ./is-op.py:4: + > y = x is "foo" + object comparison with literal + ./is-op.py:5: + > y = x is 5346 + object comparison with literal + ./is-op.py:6: + > y = x is -6 + object comparison with literal + ./is-op.py:7: + > y = x is not 'foo' + object comparison with literal + ./is-op.py:8: + > y = x is not "foo" + object comparison with literal + ./is-op.py:9: + > y = x is not 5346 + object comparison with literal + ./is-op.py:10: + > y = x is not -6 + object comparison with literal + [1] + + $ cat > for-nolineno.py <<EOF + > except: + > EOF + $ "$check_code" for-nolineno.py --nolineno + for-nolineno.py:0: + > except: + naked except clause + [1] + + $ cat > warning.t <<EOF + > $ function warnonly { + > > } + > $ diff -N aaa + > $ function onwarn {} + > EOF + $ "$check_code" warning.t + $ "$check_code" --warn warning.t + warning.t:1: + > $ function warnonly { + warning: don't use 'function', use old style + warning.t:3: + > $ diff -N aaa + warning: don't use 'diff -N' + warning.t:4: + > $ function onwarn {} + warning: don't use 'function', use old style + [1] + $ cat > raise-format.py <<EOF + > raise SomeException, message + > # this next line is okay + > raise SomeException(arg1, arg2) + > EOF + $ "$check_code" not-existing.py raise-format.py + Skipping*not-existing.py* (glob) + raise-format.py:1: + > raise SomeException, message + don't use old-style two-argument raise, use Exception(message) + [1] + + $ cat > rst.py <<EOF + > """problematic rst text + > + > .. note:: + > wrong + > """ + > + > ''' + > + > .. note:: + > + > valid + > + > new text + > + > .. note:: + > + > also valid + > ''' + > + > """mixed + > + > .. note:: + > + > good + > + > .. note:: + > plus bad + > """ + > EOF + $ $check_code -w rst.py + rst.py:3: + > .. note:: + warning: add two newlines after '.. note::' + rst.py:26: + > .. note:: + warning: add two newlines after '.. note::' + [1] + + $ cat > ./map-inside-gettext.py <<EOF + > print _("map inside gettext %s" % v) + > + > print _("concatenating " " by " " space %s" % v) + > print _("concatenating " + " by " + " '+' %s" % v) + > + > print _("mapping operation in different line %s" + > % v) + > + > print _( + > "leading spaces inside of '(' %s" % v) + > EOF + $ "$check_code" ./map-inside-gettext.py + ./map-inside-gettext.py:1: + > print _("map inside gettext %s" % v) + don't use % inside _() + ./map-inside-gettext.py:3: + > print _("concatenating " " by " " space %s" % v) + don't use % inside _() + ./map-inside-gettext.py:4: + > print _("concatenating " + " by " + " '+' %s" % v) + don't use % inside _() + ./map-inside-gettext.py:6: + > print _("mapping operation in different line %s" + don't use % inside _() + ./map-inside-gettext.py:9: + > print _( + don't use % inside _() + [1] + +web templates + + $ mkdir -p mercurial/templates + $ cat > mercurial/templates/example.tmpl <<EOF + > {desc} + > {desc|escape} + > {desc|firstline} + > {desc|websub} + > EOF + + $ "$check_code" --warnings mercurial/templates/example.tmpl + mercurial/templates/example.tmpl:2: + > {desc|escape} + warning: follow desc keyword with either firstline or websub + [1]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-contrib-check-commit.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,111 @@ +Test the 'check-commit' script +============================== + +A fine patch: + + $ cat > patch-with-long-header.diff << EOF + > # HG changeset patch + > # User timeless <timeless@mozdev.org> + > # Date 1448911706 0 + > # Mon Nov 30 19:28:26 2015 +0000 + > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88 + > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58 + > # ClownJoke This is a veryly long header that should not be warned about because its not the description + > bundle2: use Oxford comma (issue123) (BC) + > + > diff --git a/hgext/transplant.py b/hgext/transplant.py + > --- a/hgext/transplant.py + > +++ b/hgext/transplant.py + > @@ -599,7 +599,7 @@ + > return + > if not (opts.get('source') or revs or + > opts.get('merge') or opts.get('branch')): + > - raise error.Abort(_('no source URL, branch revision or revision ' + > + raise error.Abort(_('no source URL, branch revision, or revision ' + > 'list provided')) + > if opts.get('all'): + > + > + def blahblah(x): + > + pass + > EOF + $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit + +A patch with lots of errors: + + $ cat > patch-with-long-header.diff << EOF + > # HG changeset patch + > # User timeless + > # Date 1448911706 0 + > # Mon Nov 30 19:28:26 2015 +0000 + > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88 + > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58 + > # ClownJoke This is a veryly long header that should not be warned about because its not the description + > transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244) + > + > diff --git a/hgext/transplant.py b/hgext/transplant.py + > --- a/hgext/transplant.py + > +++ b/hgext/transplant.py + > @@ -599,7 +599,7 @@ + > return + > if not (opts.get('source') or revs or + > opts.get('merge') or opts.get('branch')): + > - raise error.Abort(_('no source URL, branch revision or revision ' + > + raise error.Abort(_('no source URL, branch revision, or revision ' + > 'list provided')) + > if opts.get('all'): + > EOF + $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit + 1: username is not an email address + # User timeless + 7: summary keyword should be most user-relevant one-word command or topic + transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244) + 7: (BC) needs to be uppercase + transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244) + 7: use (issueDDDD) instead of bug + transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244) + 7: no space allowed between issue and number + transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244) + 7: summary line too long (limit is 78) + transplant/foo: this summary is way too long use Oxford comma (bc) (bug123) (issue 244) + [1] + +A patch with other errors: + + $ cat > patch-with-long-header.diff << EOF + > # HG changeset patch + > # User timeless + > # Date 1448911706 0 + > # Mon Nov 30 19:28:26 2015 +0000 + > # Node ID c41cb6d2b7dbd62b1033727f8606b8c09fc4aa88 + > # Parent 42aa0e570eaa364a622bc4443b0bcb79b1100a58 + > # ClownJoke This is a veryly long header that should not be warned about because its not the description + > This has no topic and ends with a period. + > + > diff --git a/hgext/transplant.py b/hgext/transplant.py + > --- a/hgext/transplant.py + > +++ b/hgext/transplant.py + > @@ -599,7 +599,7 @@ + > if opts.get('all'): + > + > + + > + def blah_blah(x): + > + pass + > + + > + > EOF + $ cat patch-with-long-header.diff | $TESTDIR/../contrib/check-commit + 1: username is not an email address + # User timeless + 7: don't capitalize summary lines + This has no topic and ends with a period. + 7: summary line doesn't start with 'topic: ' + This has no topic and ends with a period. + 7: don't add trailing period on summary line + This has no topic and ends with a period. + 15: adds double empty line + + + 16: adds a function with foo_bar naming + + def blah_blah(x): + 19: adds double empty line + + + [1]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-contrib-perf.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,149 @@ +#require test-repo + +Set vars: + + $ CONTRIBDIR="$TESTDIR/../contrib" + +Prepare repo: + + $ hg init + + $ echo this is file a > a + $ hg add a + $ hg commit -m first + + $ echo adding to file a >> a + $ hg commit -m second + + $ echo adding more to file a >> a + $ hg commit -m third + + $ hg up -r 0 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo merge-this >> a + $ hg commit -m merge-able + created new head + + $ hg up -r 2 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +perfstatus + + $ cat >> $HGRCPATH << EOF + > [extensions] + > perfstatusext=$CONTRIBDIR/perf.py + > [perf] + > presleep=0 + > stub=on + > parentscount=1 + > EOF + $ hg help perfstatusext + perfstatusext extension - helper extension to measure performance + + list of commands: + + perfaddremove + (no help text available) + perfancestors + (no help text available) + perfancestorset + (no help text available) + perfannotate (no help text available) + perfbranchmap + benchmark the update of a branchmap + perfcca (no help text available) + perfchangeset + (no help text available) + perfctxfiles (no help text available) + perfdiffwd Profile diff of working directory changes + perfdirfoldmap + (no help text available) + perfdirs (no help text available) + perfdirstate (no help text available) + perfdirstatedirs + (no help text available) + perfdirstatefoldmap + (no help text available) + perfdirstatewrite + (no help text available) + perffncacheencode + (no help text available) + perffncacheload + (no help text available) + perffncachewrite + (no help text available) + perfheads (no help text available) + perfindex (no help text available) + perfloadmarkers + benchmark the time to parse the on-disk markers for a repo + perflog (no help text available) + perflookup (no help text available) + perflrucachedict + (no help text available) + perfmanifest (no help text available) + perfmergecalculate + (no help text available) + perfmoonwalk benchmark walking the changelog backwards + perfnodelookup + (no help text available) + perfparents (no help text available) + perfpathcopies + (no help text available) + perfrawfiles (no help text available) + perfrevlog Benchmark reading a series of revisions from a revlog. + perfrevlogrevision + Benchmark obtaining a revlog revision. + perfrevrange (no help text available) + perfrevset benchmark the execution time of a revset + perfstartup (no help text available) + perfstatus (no help text available) + perftags (no help text available) + perftemplating + (no help text available) + perfvolatilesets + benchmark the computation of various volatile set + perfwalk (no help text available) + + (use "hg help -v perfstatusext" to show built-in aliases and global options) + $ hg perfaddremove + $ hg perfancestors + $ hg perfancestorset 2 + $ hg perfannotate a + $ hg perfbranchmap + $ hg perfcca + $ hg perfchangeset 2 + $ hg perfctxfiles 2 + $ hg perfdiffwd + $ hg perfdirfoldmap + $ hg perfdirs + $ hg perfdirstate + $ hg perfdirstatedirs + $ hg perfdirstatefoldmap + $ hg perfdirstatewrite + $ hg perffncacheencode + $ hg perffncacheload + $ hg perffncachewrite + $ hg perfheads + $ hg perfindex + $ hg perfloadmarkers + $ hg perflog + $ hg perflookup 2 + $ hg perflrucache + $ hg perfmanifest 2 + $ hg perfmergecalculate -r 3 + $ hg perfmoonwalk + $ hg perfnodelookup 2 + $ hg perfpathcopies 1 2 + $ hg perfrawfiles 2 + $ hg perfrevlog .hg/store/data/a.i + $ hg perfrevlogrevision -m 0 + $ hg perfrevrange + $ hg perfrevset 'all()' + $ hg perfstartup + $ hg perfstatus + $ hg perftags + $ hg perftemplating + $ hg perfvolatilesets + $ hg perfwalk + $ hg perfparents +
--- a/tests/test-convert-filemap.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-convert-filemap.t Sun Jan 17 21:40:21 2016 -0600 @@ -740,4 +740,48 @@ - converted/a - toberemoved + $ cd .. +Test case where cleanp2 contains a file that doesn't exist in p2 - for +example because filemap changed. + + $ hg init cleanp2 + $ cd cleanp2 + $ touch f f1 f2 && hg ci -Aqm '0' + $ echo f1 > f1 && echo >> f && hg ci -m '1' + $ hg up -qr0 && echo f2 > f2 && echo >> f && hg ci -qm '2' + $ echo "include f" > filemap + $ hg convert --filemap filemap . + assuming destination .-hg + initializing destination .-hg repository + scanning source... + sorting... + converting... + 2 0 + 1 1 + 0 2 + $ hg merge && hg ci -qm '3' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ echo "include ." > filemap + $ hg convert --filemap filemap . + assuming destination .-hg + scanning source... + sorting... + converting... + 0 3 + $ hg -R .-hg log -G -T '{shortest(node)} {desc}\n{files % "- {file}\n"}\n' + o e9ed 3 + |\ + | o 33a0 2 + | | - f + | | + o | f73e 1 + |/ - f + | + o d681 0 + - f + + $ hg -R .-hg mani -r tip + f + $ cd ..
--- a/tests/test-convert-git.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-convert-git.t Sun Jan 17 21:40:21 2016 -0600 @@ -54,7 +54,7 @@ $ hg convert --config extensions.progress= --config progress.assume-tty=1 \ > --config progress.delay=0 --config progress.changedelay=0 \ > --config progress.refresh=0 --config progress.width=60 \ - > --datesort git-repo + > --config progress.format='topic, bar, number' --datesort git-repo \r (no-eol) (esc) scanning [======> ] 1/6\r (no-eol) (esc) scanning [=============> ] 2/6\r (no-eol) (esc) @@ -173,7 +173,8 @@ $ hg convert --datesort git-repo2 fullrepo \ > --config extensions.progress= --config progress.assume-tty=1 \ > --config progress.delay=0 --config progress.changedelay=0 \ - > --config progress.refresh=0 --config progress.width=60 + > --config progress.refresh=0 --config progress.width=60 \ + > --config progress.format='topic, bar, number' \r (no-eol) (esc) scanning [===> ] 1/9\r (no-eol) (esc) scanning [========> ] 2/9\r (no-eol) (esc) @@ -533,8 +534,7 @@ $ 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) + $ hg convert git-repo6 hg-repo6 --traceback 2>&1 | grep -v "fatal: Path '.gitmodules' does not exist" initializing destination hg-repo6 repository scanning source... sorting... @@ -689,10 +689,7 @@ $ cd git-repo7 $ echo a >> a - $ git commit -am "move master forward" - [master 0c81947] move master forward - Author: nottest <test@example.org> - 1 file changed, 1 insertion(+) + $ git commit -q -am "move master forward" $ cd .. $ rm -rf hg-repo7 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
--- a/tests/test-convert.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-convert.t Sun Jan 17 21:40:21 2016 -0600 @@ -57,7 +57,7 @@ <source ID> <destination ID> If the file doesn't exist, it's automatically created. It's updated on - each commit copied, so "hg convert" can be interrupted and can be run + each commit copied, so 'hg convert' can be interrupted and can be run repeatedly to copy new commits. The authormap is a simple text file that maps each source commit author to
--- a/tests/test-copy-move-merge.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-copy-move-merge.t Sun Jan 17 21:40:21 2016 -0600 @@ -35,12 +35,12 @@ preserving a for resolve of c removing a b: remote moved from a -> m (premerge) - picked tool ':merge' for b (binary False symlink False) + picked tool ':merge' for b (binary False symlink False changedelete False) merging a and b to b my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc premerge successful c: remote moved from a -> m (premerge) - picked tool ':merge' for c (binary False symlink False) + picked tool ':merge' for c (binary False symlink False changedelete False) merging a and c to c my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc premerge successful @@ -80,10 +80,12 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob) $ hg up -qC 2 - $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True + $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True --config ui.interactive=True << EOF + > c + > EOF rebasing 2:add3f11052fa "other" (tip) remote changed a which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c $ cat b 1
--- a/tests/test-copy.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-copy.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,9 @@ +# enable bundle2 in advance + + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF $ mkdir part1 $ cd part1 @@ -87,7 +93,7 @@ copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 $ md5sum.py .hg/store/data/b.i - 4999f120a3b88713bbefddd195cf5133 .hg/store/data/b.i + 44913824c8f5890ae218f9829535922e .hg/store/data/b.i $ hg cat b > bsum $ md5sum.py bsum 60b725f10c9c85c70d97880dfe8191b3 bsum
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ctxmanager.py Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,79 @@ +from __future__ import absolute_import + +import silenttestrunner +import unittest + +from mercurial.util import ctxmanager + +class contextmanager(object): + def __init__(self, name, trace): + self.name = name + self.entered = False + self.exited = False + self.trace = trace + + def __enter__(self): + self.entered = True + self.trace(('enter', self.name)) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.exited = exc_type, exc_val, exc_tb + self.trace(('exit', self.name)) + + def __repr__(self): + return '<ctx %r>' % self.name + +class ctxerror(Exception): + pass + +class raise_on_enter(contextmanager): + def __enter__(self): + self.trace(('raise', self.name)) + raise ctxerror(self.name) + +class raise_on_exit(contextmanager): + def __exit__(self, exc_type, exc_val, exc_tb): + self.trace(('raise', self.name)) + raise ctxerror(self.name) + +def ctxmgr(name, trace): + return lambda: contextmanager(name, trace) + +class test_ctxmanager(unittest.TestCase): + def test_basics(self): + trace = [] + addtrace = trace.append + with ctxmanager(ctxmgr('a', addtrace), ctxmgr('b', addtrace)) as c: + a, b = c.enter() + c.atexit(addtrace, ('atexit', 'x')) + c.atexit(addtrace, ('atexit', 'y')) + self.assertEqual(trace, [('enter', 'a'), ('enter', 'b'), + ('atexit', 'y'), ('atexit', 'x'), + ('exit', 'b'), ('exit', 'a')]) + + def test_raise_on_enter(self): + trace = [] + addtrace = trace.append + def go(): + with ctxmanager(ctxmgr('a', addtrace), + lambda: raise_on_enter('b', addtrace)) as c: + c.enter() + addtrace('unreachable') + self.assertRaises(ctxerror, go) + self.assertEqual(trace, [('enter', 'a'), ('raise', 'b'), ('exit', 'a')]) + + def test_raise_on_exit(self): + trace = [] + addtrace = trace.append + def go(): + with ctxmanager(ctxmgr('a', addtrace), + lambda: raise_on_exit('b', addtrace)) as c: + c.enter() + addtrace('running') + self.assertRaises(ctxerror, go) + self.assertEqual(trace, [('enter', 'a'), ('enter', 'b'), 'running', + ('raise', 'b'), ('exit', 'a')]) + +if __name__ == '__main__': + silenttestrunner.main(__name__)
--- a/tests/test-debugbundle.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-debugbundle.t Sun Jan 17 21:40:21 2016 -0600 @@ -6,13 +6,13 @@ $ touch a ; hg add a ; hg ci -ma $ touch b ; hg add b ; hg ci -mb $ touch c ; hg add c ; hg ci -mc - $ hg bundle --base 0 --rev tip bundle.hg -v + $ hg bundle --base 0 --rev tip bundle.hg -v --type v1 2 changesets found uncompressed size of bundle content: 332 (changelog) 282 (manifests) - 105 b - 105 c + 93 b + 93 c Terse output: @@ -34,9 +34,9 @@ ae25a31b30b3490a981e7b96a3238cc69583fda1 686dbf0aeca417636fa26a9121c681eabbb15a20 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 686dbf0aeca417636fa26a9121c681eabbb15a20 55 b - b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 0000000000000000000000000000000000000000 12 + b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 0000000000000000000000000000000000000000 0 c - b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0000000000000000000000000000000000000000 12 + b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0000000000000000000000000000000000000000 0 $ cd ..
--- a/tests/test-debugcommands.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-debugcommands.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,8 @@ + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + $ hg init debugrevlog $ cd debugrevlog $ echo a > a @@ -5,7 +10,7 @@ adding a $ hg debugrevlog -m format : 1 - flags : inline + flags : inline, generaldelta revisions : 1 merges : 0 ( 0.00%) @@ -27,18 +32,44 @@ Test debugindex, with and without the --debug flag $ hg debugindex a - rev offset length .... linkrev nodeid p1 p2 (re) + rev offset length ..... linkrev nodeid p1 p2 (re) 0 0 3 .... 0 b789fdd96dc2 000000000000 000000000000 (re) $ hg --debug debugindex a - rev offset length .... linkrev nodeid p1 p2 (re) + rev offset length ..... linkrev nodeid p1 p2 (re) 0 0 3 .... 0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 (re) $ hg debugindex -f 1 a - rev flag offset length size .... link p1 p2 nodeid (re) + rev flag offset length size ..... link p1 p2 nodeid (re) 0 0000 0 3 2 .... 0 -1 -1 b789fdd96dc2 (re) $ hg --debug debugindex -f 1 a - rev flag offset length size .... link p1 p2 nodeid (re) + rev flag offset length size ..... link p1 p2 nodeid (re) 0 0000 0 3 2 .... 0 -1 -1 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 (re) +debugdelta chain basic output + + $ hg debugdeltachain -m + rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio + 0 1 1 -1 base 44 43 44 1.02326 44 0 0.00000 + + $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen}\n' + 0 1 1 + + $ hg debugdeltachain -m -Tjson + [ + { + "chainid": 1, + "chainlen": 1, + "chainratio": 1.02325581395, + "chainsize": 44, + "compsize": 44, + "deltatype": "base", + "extradist": 0, + "extraratio": 0.0, + "lindist": 44, + "prevrev": -1, + "rev": 0, + "uncompsize": 43 + } + ] Test max chain len $ cat >> $HGRCPATH << EOF
--- a/tests/test-default-push.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-default-push.t Sun Jan 17 21:40:21 2016 -0600 @@ -77,3 +77,32 @@ $ hg --cwd b push doesnotexist abort: repository doesnotexist does not exist! [255] + +:pushurl is used when defined + + $ hg -q clone a pushurlsource + $ hg -q clone a pushurldest + $ cd pushurlsource + +Windows needs a leading slash to make a URL that passes all of the checks + $ WD=`pwd` +#if windows + $ WD="/$WD" +#endif + $ cat > .hg/hgrc << EOF + > [paths] + > default = https://example.com/not/relevant + > default:pushurl = file://$WD/../pushurldest + > EOF + + $ touch pushurl + $ hg -q commit -A -m 'add pushurl' + $ hg push + pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + + $ cd ..
--- a/tests/test-demandimport.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-demandimport.py Sun Jan 17 21:40:21 2016 -0600 @@ -34,6 +34,11 @@ print "util =", f(util) print "util.system =", f(util.system) +from mercurial import hgweb +print "hgweb =", f(hgweb) +print "hgweb_mod =", f(hgweb.hgweb_mod) +print "hgweb =", f(hgweb) + import re as fred print "fred =", f(fred)
--- a/tests/test-demandimport.py.out Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-demandimport.py.out Sun Jan 17 21:40:21 2016 -0600 @@ -5,6 +5,9 @@ util.system = <function system at 0x?> util = <module 'mercurial.util' from '?'> util.system = <function system at 0x?> +hgweb = <unloaded module 'hgweb'> +hgweb_mod = <unloaded module 'hgweb_mod'> +hgweb = <module 'mercurial.hgweb' from '?'> fred = <unloaded module 're'> re = <unloaded module 'sys'> fred = <unloaded module 're'>
--- a/tests/test-devel-warnings.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-devel-warnings.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,6 +1,6 @@ $ cat << EOF > buggylocking.py - > """A small extension that acquire locks in the wrong order + > """A small extension that tests our developer warnings > """ > > from mercurial import cmdutil, repair, revset @@ -47,6 +47,12 @@ > repair.strip(repo.ui, repo, [repo['.'].node()]) > finally: > lo.release() + > @command('oldanddeprecated', [], '') + > def oldanddeprecated(ui, repo): + > """test deprecation warning API""" + > def foobar(ui): + > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337') + > foobar(ui) > > def oldstylerevset(repo, subset, x): > return list(subset) @@ -114,5 +120,22 @@ $ 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 + $ hg oldanddeprecated + devel-warn: foorbar is deprecated, go shopping + (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:53 (oldanddeprecated) + $ hg oldanddeprecated --traceback + devel-warn: foorbar is deprecated, go shopping + (compatibility will be dropped after Mercurial-42.1337, update your code.) at: + */hg:* in <module> (glob) + */mercurial/dispatch.py:* in run (glob) + */mercurial/dispatch.py:* in dispatch (glob) + */mercurial/dispatch.py:* in _runcatch (glob) + */mercurial/dispatch.py:* in _dispatch (glob) + */mercurial/dispatch.py:* in runcommand (glob) + */mercurial/dispatch.py:* in _runcommand (glob) + */mercurial/dispatch.py:* in checkargs (glob) + */mercurial/dispatch.py:* in <lambda> (glob) + */mercurial/util.py:* in check (glob) + $TESTTMP/buggylocking.py:* in oldanddeprecated (glob) $ cd ..
--- a/tests/test-diff-binary-file.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-diff-binary-file.t Sun Jan 17 21:40:21 2016 -0600 @@ -44,6 +44,26 @@ diff --git a/binfile.bin b/binfile.bin Binary file binfile.bin has changed + $ HGPLAIN=1 hg diff --config diff.nobinary=True --git -r 0 -r 1 + diff --git a/binfile.bin b/binfile.bin + index 37ba3d1c6f17137d9c5f5776fa040caf5fe73ff9..58dc31a9e2f40f74ff3b45903f7d620b8e5b7356 + GIT binary patch + literal 594 + zc$@)J0<HatP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00009a7bBm000XU + z000XU0RWnu7ytkO2XskIMF-Uh9TW;VpMjwv0005-Nkl<ZD9@FWPs=e;7{<>W$NUkd + zX$nnYLt$-$V!?uy+1V%`z&Eh=ah|duER<4|QWhju3gb^nF*8iYobxWG-qqXl=2~5M + z*IoDB)sG^CfNuoBmqLTVU^<;@nwHP!1wrWd`{(mHo6VNXWtyh{alzqmsH*yYzpvLT + zLdY<T=ks|woh-`&01!ej#(xbV1f|pI*=%;d-%F*E*X#ZH`4I%6SS+$EJDE&ct=8po + ziN#{?_j|kD%Cd|oiqds`xm@;oJ-^?NG3Gdqrs?5u*zI;{nogxsx~^|Fn^Y?Gdc6<; + zfMJ+iF1J`LMx&A2?dEwNW8ClebzPTbIh{@$hS6*`kH@1d%Lo7fA#}N1)oN7`gm$~V + z+wDx#)OFqMcE{s!JN0-xhG8ItAjVkJwEcb`3WWlJfU2r?;Pd%dmR+q@mSri5q9_W- + zaR2~ECX?B2w+zELozC0s*6Z~|QG^f{3I#<`?)Q7U-JZ|q5W;9Q8i_=pBuSzunx=U; + z9C)5jBoYw9^?EHyQl(M}1OlQcCX>lXB*ODN003Z&P17_@)3Pi=i0wb04<W?v-u}7K + zXmmQA+wDgE!qR9o8jr`%=ab_&uh(l?R=r;Tjiqon91I2-hIu?57~@*4h7h9uORK#= + gQItJW-{SoTm)8|5##k|m00000NkvXXu0mjf3JwksH2?qr + + + $ hg diff --git -r 2 -r 3 diff --git a/binfile.bin b/nonbinfile copy from binfile.bin
--- a/tests/test-diff-color.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-diff-color.t Sun Jan 17 21:40:21 2016 -0600 @@ -91,7 +91,7 @@ 1 hunks, 1 lines changed \x1b[0;33mexamine changes to 'a'? [Ynesfdaq?]\x1b[0m y (esc) - \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc) + \x1b[0;35m@@ -2,7 +2,7 @@ c\x1b[0m (esc) c a a @@ -121,7 +121,7 @@ 1 hunks, 1 lines changed \x1b[0;33mexamine changes to 'a'? [Ynesfdaq?]\x1b[0m y (esc) - \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc) + \x1b[0;35m@@ -2,7 +2,7 @@ c\x1b[0m (esc) c a a
--- a/tests/test-diffdir.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-diffdir.t Sun Jan 17 21:40:21 2016 -0600 @@ -38,3 +38,40 @@ $ hg diff -r tip -r "" hg: parse error: empty query [255] + +Remove a file that was added via merge. Since the file is not in parent 1, +it should not be in the diff. + + $ hg ci -m 'a=foo' a + $ hg co -Cq null + $ echo 123 > b + $ hg add b + $ hg ci -m "b" + created new head + $ hg merge 1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg rm -f a + $ hg diff --nodates + +Rename a file that was added via merge. Since the rename source is not in +parent 1, the diff should be relative to /dev/null + + $ hg co -Cq 2 + $ hg merge 1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg mv a a2 + $ hg diff --nodates + diff -r cf44b38435e5 a2 + --- /dev/null + +++ b/a2 + @@ -0,0 +1,1 @@ + +foo + $ hg diff --nodates --git + diff --git a/a2 b/a2 + new file mode 100644 + --- /dev/null + +++ b/a2 + @@ -0,0 +1,1 @@ + +foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-dirstate-nonnormalset.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,22 @@ + $ cat >> $HGRCPATH << EOF + > [ui] + > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n" + > [extensions] + > dirstateparanoidcheck = $TESTDIR/../contrib/dirstatenonnormalcheck.py + > [experimental] + > nonnormalparanoidcheck = True + > [devel] + > all-warnings=True + > EOF + $ mkcommit() { + > echo "$1" > "$1" + > hg add "$1" + > hg ci -m "add $1" + > } + + $ hg init testrepo + $ cd testrepo + $ mkcommit a + $ mkcommit b + $ mkcommit c + $ hg status
--- a/tests/test-doctest.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-doctest.py Sun Jan 17 21:40:21 2016 -0600 @@ -11,6 +11,7 @@ mod = getattr(mod, testtarget) doctest.testmod(mod, optionflags=optionflags) +testmod('mercurial.changegroup') testmod('mercurial.changelog') testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE) testmod('mercurial.dispatch')
--- a/tests/test-double-merge.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-double-merge.t Sun Jan 17 21:40:21 2016 -0600 @@ -38,12 +38,12 @@ preserving foo for resolve of bar preserving foo for resolve of foo bar: remote copied from foo -> m (premerge) - picked tool ':merge' for bar (binary False symlink False) + picked tool ':merge' for bar (binary False symlink False changedelete False) merging foo and bar to bar my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc premerge successful foo: versions differ -> m (premerge) - picked tool ':merge' for foo (binary False symlink False) + picked tool ':merge' for foo (binary False symlink False changedelete False) merging foo my foo@6a0df1dad128+ other foo@484bf6903104 ancestor foo@e6dc8efe11cc premerge successful
--- a/tests/test-encoding.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-encoding.t Sun Jan 17 21:40:21 2016 -0600 @@ -272,3 +272,14 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ cd .. + +Test roundtrip encoding/decoding of utf8b for generated data + +#if hypothesis + + >>> from hypothesishelpers import * + >>> from mercurial import encoding + >>> roundtrips(st.binary(), encoding.fromutf8b, encoding.toutf8b) + Round trip OK + +#endif
--- a/tests/test-eol-hook.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-eol-hook.t Sun Jan 17 21:40:21 2016 -0600 @@ -206,13 +206,13 @@ adding file changes added 3 changesets with 3 changes to 2 files (+1 heads) error: pretxnchangegroup hook failed: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings d.txt in a7040e68714f should not have CRLF line endings - b.txt in fbcf9b1025f5 should not have CRLF line endings transaction abort! rollback completed abort: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings d.txt in a7040e68714f should not have CRLF line endings - b.txt in fbcf9b1025f5 should not have CRLF line endings [255] $ cd ..
--- a/tests/test-export.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-export.t Sun Jan 17 21:40:21 2016 -0600 @@ -137,6 +137,25 @@ foo-9 +foo-10 +No filename should be printed if stdout is specified explicitly: + + $ hg export -v 1 -o - + exporting patch: + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID d1c9656e973cfb5aebd5499bbd2cb350e3b12266 + # Parent 871558de6af2e8c244222f8eea69b782c94ce3df + foo-1 + + diff -r 871558de6af2 -r d1c9656e973c foo + --- a/foo Thu Jan 01 00:00:00 1970 +0000 + +++ b/foo Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,2 @@ + foo-0 + +foo-1 + Checking if only alphanumeric characters are used in the file name (%m option): $ echo "line" >> foo
--- a/tests/test-extdiff.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-extdiff.t Sun Jan 17 21:40:21 2016 -0600 @@ -126,6 +126,25 @@ diff-like tools yield a non-zero exit code #endif +issue3153: ensure using extdiff with removed subrepos doesn't crash: + + $ hg init suba + $ cd suba + $ echo suba > suba + $ hg add + adding suba + $ hg ci -m "adding suba file" + $ cd .. + $ echo suba=suba > .hgsub + $ hg add + adding .hgsub + $ hg ci -Sm "adding subrepo" + $ echo > .hgsub + $ hg ci -m "removing subrepo" + $ hg falabala -r 4 -r 5 -S + diffing a.398e36faf9c6 a.5ab95fb166c4 + [1] + issue4463: usage of command line configuration without additional quoting $ cat <<EOF >> $HGRCPATH
--- a/tests/test-extension.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-extension.t Sun Jan 17 21:40:21 2016 -0600 @@ -206,7 +206,7 @@ > from extroot.bar import s > buf.append('from extroot.bar import s: %s' % s) > EOF - $ hg --config extensions.extroot=$TESTTMP/extroot root + $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root) (extroot) from extroot.bar import *: this is extroot.bar (extroot) import extroot.sub1.baz: this is extroot.sub1.baz (extroot) import extroot: this is extroot.__init__ @@ -367,6 +367,15 @@ $ echo 'debugextension = !' >> $HGRCPATH +Asking for help about a deprecated extension should do something useful: + + $ hg help glog + 'glog' is provided by the following extension: + + graphlog command to view revision graphs from a shell (DEPRECATED) + + (use "hg help extensions" for information on enabling extensions) + Extension module help vs command help: $ echo 'extdiff =' >> $HGRCPATH @@ -422,7 +431,7 @@ to directories containing snapshots of files to compare. The extdiff extension also allows you to configure new diff commands, so you - do not need to type "hg extdiff -p kdiff3" always. + do not need to type 'hg extdiff -p kdiff3' always. [extdiff] # add new command that runs GNU diff(1) in 'context diff' mode @@ -460,7 +469,7 @@ [diff-tools] kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child - You can use -I/-X and list of file or directory names like normal "hg diff" + You can use -I/-X and list of file or directory names like normal 'hg diff' command. The extdiff extension makes snapshots of only needed files, so running the external diff program will actually be pretty fast (at least faster than having to compare the entire tree). @@ -1009,6 +1018,50 @@ throw 1.twentythree +Refuse to load extensions with minimum version requirements + + $ cat > minversion1.py << EOF + > from mercurial import util + > util.version = lambda: '3.5.2' + > minimumhgversion = '3.6' + > EOF + $ hg --config extensions.minversion=minversion1.py version + (third party extension minversion requires version 3.6 or newer of Mercurial; disabling) + Mercurial Distributed SCM (version 3.5.2) + (see https://mercurial-scm.org for more information) + + Copyright (C) 2005-* Matt Mackall and others (glob) + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + $ cat > minversion2.py << EOF + > from mercurial import util + > util.version = lambda: '3.6' + > minimumhgversion = '3.7' + > EOF + $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third' + (third party extension minversion requires version 3.7 or newer of Mercurial; disabling) + +Can load version that is only off by point release + + $ cat > minversion2.py << EOF + > from mercurial import util + > util.version = lambda: '3.6.1' + > minimumhgversion = '3.6' + > EOF + $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third' + [1] + +Can load minimum version identical to current + + $ cat > minversion3.py << EOF + > from mercurial import util + > util.version = lambda: '3.5' + > minimumhgversion = '3.5' + > EOF + $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third' + [1] + Restore HGRCPATH $ HGRCPATH=$ORGHGRCPATH
--- a/tests/test-fileset-generated.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-fileset-generated.t Sun Jan 17 21:40:21 2016 -0600 @@ -47,6 +47,13 @@ ! missing_content2_missing-tracked ! missing_missing_missing-tracked + $ hg st -A 'set:missing()' + ! content1_content1_missing-tracked + ! content1_content2_missing-tracked + ! content1_missing_missing-tracked + ! missing_content2_missing-tracked + ! missing_missing_missing-tracked + $ hg st -A 'set:unknown()' ? content1_missing_content1-untracked ? content1_missing_content3-untracked
--- a/tests/test-fileset.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-fileset.t Sun Jan 17 21:40:21 2016 -0600 @@ -73,6 +73,8 @@ a2 $ fileset 'deleted()' a1 + $ fileset 'missing()' + a1 $ fileset 'unknown()' c3 $ fileset 'ignored()' @@ -133,6 +135,10 @@ $ fileset 'size("bar")' hg: parse error: couldn't parse size: bar [255] + $ fileset '(1k, 2k)' + hg: parse error: can't use a list in this context + (see hg help "filesets.x or y") + [255] $ fileset 'size(1k)' 1k $ fileset '(1k or 2k) and size("< 2k")' @@ -163,7 +169,7 @@ $ hg merge merging b2 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark') - 6 files updated, 0 files merged, 1 files removed, 1 files unresolved + * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob) use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon [1] $ fileset 'resolved()' @@ -295,16 +301,68 @@ >>> open('mac', 'wb').write("mac\r") $ hg add dos mixed mac +(remove a1, to examine safety of 'eol' on removed files) + $ rm a1 + $ fileset 'eol(dos)' dos mixed $ fileset 'eol(unix)' + mixed .hgsub .hgsubstate - a1 b1 b2 c1 - mixed $ fileset 'eol(mac)' mac + +Test safety of 'encoding' on removed files + +#if symlink + $ fileset 'encoding("ascii")' + dos + mac + mixed + .hgsub + .hgsubstate + 1k + 2k + b1 + b2 + b2link + bin + c1 +#else + $ fileset 'encoding("ascii")' + dos + mac + mixed + .hgsub + .hgsubstate + 1k + 2k + b1 + b2 + bin + c1 +#endif + +Test detection of unintentional 'matchctx.existing()' invocation + + $ cat > $TESTTMP/existingcaller.py <<EOF + > from mercurial import fileset + > + > @fileset.predicate('existingcaller()', callexisting=False) + > def existingcaller(mctx, x): + > # this 'mctx.existing()' invocation is unintentional + > return [f for f in mctx.existing()] + > EOF + + $ cat >> .hg/hgrc <<EOF + > [extensions] + > existingcaller = $TESTTMP/existingcaller.py + > EOF + + $ fileset 'existingcaller()' 2>&1 | tail -1 + AssertionError: unexpected existing() invocation
--- a/tests/test-fncache.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-fncache.t Sun Jan 17 21:40:21 2016 -0600 @@ -96,6 +96,7 @@ .hg/phaseroots .hg/requires .hg/undo + .hg/undo.backup.dirstate .hg/undo.backupfiles .hg/undo.bookmarks .hg/undo.branch @@ -132,6 +133,7 @@ .hg/store/undo .hg/store/undo.backupfiles .hg/store/undo.phaseroots + .hg/undo.backup.dirstate .hg/undo.bookmarks .hg/undo.branch .hg/undo.desc @@ -203,18 +205,36 @@ $ cat > exceptionext.py <<EOF > import os > from mercurial import commands, error - > from mercurial.extensions import wrapfunction + > from mercurial.extensions import wrapcommand, wrapfunction > > def lockexception(orig, vfs, lockname, wait, releasefn, *args, **kwargs): > def releasewrap(): + > l.held = False # ensure __del__ is a noop > raise error.Abort("forced lock failure") - > return orig(vfs, lockname, wait, releasewrap, *args, **kwargs) + > l = orig(vfs, lockname, wait, releasewrap, *args, **kwargs) + > return l > > def reposetup(ui, repo): > wrapfunction(repo, '_lock', lockexception) > > cmdtable = {} > + > # wrap "commit" command to prevent wlock from being '__del__()'-ed + > # at the end of dispatching (for intentional "forced lcok failure") + > def commitwrap(orig, ui, repo, *pats, **opts): + > repo = repo.unfiltered() # to use replaced repo._lock certainly + > wlock = repo.wlock() + > try: + > return orig(ui, repo, *pats, **opts) + > finally: + > # multiple 'relase()' is needed for complete releasing wlock, + > # because "forced" abort at last releasing store lock + > # prevents wlock from being released at same 'lockmod.release()' + > for i in range(wlock.held): + > wlock.release() + > + > def extsetup(ui): + > wrapcommand(commands.table, "commit", commitwrap) > EOF $ extpath=`pwd`/exceptionext.py $ hg init fncachetxn
--- a/tests/test-gendoc.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-gendoc.t Sun Jan 17 21:40:21 2016 -0600 @@ -17,9 +17,10 @@ > cmp -s gendoc-C.txt gendoc-$LOCALE.txt && echo '** NOTHING TRANSLATED **' > fi > + > echo "checking for parse errors" + > python "$TESTDIR/../doc/docchecker" gendoc-$LOCALE.txt > # We call runrst without adding "--halt warning" to make it report > # all errors instead of stopping on the first one. - > echo "checking for parse errors" > python "$TESTDIR/../doc/runrst" html gendoc-$LOCALE.txt /dev/null > done @@ -31,6 +32,8 @@ % extracting documentation from de checking for parse errors + Die Dateien werden dem Projektarchiv beim n\xc3\xa4chsten \xc3\x9cbernehmen (commit) hinzugef\xc3\xbcgt. Um dies vorher r\xc3\xbcckg\xc3\xa4ngig zu machen, siehe:hg:`forget`. (esc) + warning: please have a space before :hg: % extracting documentation from el checking for parse errors
--- a/tests/test-generaldelta.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-generaldelta.t Sun Jan 17 21:40:21 2016 -0600 @@ -3,10 +3,11 @@ implementation of parentdelta: third manifest revision would be fully inserted due to big distance from its paren revision (zero). - $ hg init repo + $ hg init repo --config format.generaldelta=no --config format.usegeneraldelta=no $ cd repo $ echo foo > foo $ echo bar > bar + $ echo baz > baz $ hg commit -q -Am boo $ hg clone --pull . ../gdrepo -q --config format.generaldelta=yes $ for r in 1 2 3; do @@ -62,7 +63,7 @@ o 0 3903 a $ cd .. - $ hg init client + $ hg init client --config format.generaldelta=false --config format.usegeneraldelta=false $ cd client $ hg pull -q ../server -r 4 $ hg debugindex x @@ -71,10 +72,55 @@ $ cd .. +Test "usegeneraldelta" config +(repo are general delta, but incoming bundle are not re-deltified) + +delta coming from the server base delta server are not recompressed. +(also include the aggressive version for comparison) + + $ hg clone repo --pull --config format.usegeneraldelta=1 usegd + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 6 changes to 3 files (+2 heads) + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg clone repo --pull --config format.generaldelta=1 full + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 6 changes to 3 files (+2 heads) + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R repo debugindex -m + rev offset length base linkrev nodeid p1 p2 + 0 0 104 0 0 cef96823c800 000000000000 000000000000 + 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000 + 2 161 57 0 2 134fdc6fd680 cef96823c800 000000000000 + 3 218 104 3 3 723508934dad cef96823c800 000000000000 + $ hg -R usegd debugindex -m + rev offset length delta linkrev nodeid p1 p2 + 0 0 104 -1 0 cef96823c800 000000000000 000000000000 + 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000 + 2 161 57 1 2 134fdc6fd680 cef96823c800 000000000000 + 3 218 57 0 3 723508934dad cef96823c800 000000000000 + $ hg -R full debugindex -m + rev offset length delta linkrev nodeid p1 p2 + 0 0 104 -1 0 cef96823c800 000000000000 000000000000 + 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000 + 2 161 57 0 2 134fdc6fd680 cef96823c800 000000000000 + 3 218 57 0 3 723508934dad cef96823c800 000000000000 + Test format.aggressivemergedeltas $ hg init --config format.generaldelta=1 aggressive $ cd aggressive + $ cat << EOF >> .hg/hgrc + > [format] + > generaldelta = 1 + > EOF $ touch a b c d e $ hg commit -Aqm side1 $ hg up -q null @@ -87,8 +133,8 @@ $ hg debugindex -m rev offset length delta linkrev nodeid p1 p2 0 0 59 -1 0 8dde941edb6e 000000000000 000000000000 - 1 59 59 -1 1 315c023f341d 000000000000 000000000000 - 2 118 65 1 2 2ab389a983eb 315c023f341d 8dde941edb6e + 1 59 61 0 1 315c023f341d 000000000000 000000000000 + 2 120 65 1 2 2ab389a983eb 315c023f341d 8dde941edb6e $ hg strip -q -r . --config extensions.strip= @@ -99,8 +145,8 @@ $ hg debugindex -m rev offset length delta linkrev nodeid p1 p2 0 0 59 -1 0 8dde941edb6e 000000000000 000000000000 - 1 59 59 -1 1 315c023f341d 000000000000 000000000000 - 2 118 62 0 2 2ab389a983eb 315c023f341d 8dde941edb6e + 1 59 61 0 1 315c023f341d 000000000000 000000000000 + 2 120 62 0 2 2ab389a983eb 315c023f341d 8dde941edb6e Test that strip bundle use bundle2 $ hg --config extensions.strip= strip .
--- a/tests/test-globalopts.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-globalopts.t Sun Jan 17 21:40:21 2016 -0600 @@ -349,6 +349,7 @@ glossary Glossary hgignore Syntax for Mercurial Ignore Files hgweb Configuring hgweb + internals Technical implementation topics merge-tools Merge Tools multirevs Specifying Multiple Revisions patterns File Name Patterns @@ -431,6 +432,7 @@ glossary Glossary hgignore Syntax for Mercurial Ignore Files hgweb Configuring hgweb + internals Technical implementation topics merge-tools Merge Tools multirevs Specifying Multiple Revisions patterns File Name Patterns
--- a/tests/test-glog.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-glog.t Sun Jan 17 21:40:21 2016 -0600 @@ -1429,7 +1429,8 @@ > | sed 's/.*nodetag/nodetag/' > log.nodes > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \ > | sed 's/.*nodetag/nodetag/' > glog.nodes - > diff -u log.nodes glog.nodes | grep '^[-+@ ]' || : + > (cmp log.nodes glog.nodes || diff -u log.nodes glog.nodes) \ + > | grep '^[-+@ ]' || : > } glog always reorders nodes which explains the difference with log @@ -2400,4 +2401,25 @@ @ 3:5918b8d165d1 | +node template with changeset_printer: + + $ hg log -Gqr 5:7 --config ui.graphnodetemplate='{rev}' + 7 7:02dbb8e276b8 + | + 6 6:fc281d8ff18d + |\ + 5 | 5:99b31f1c2782 + | | + +node template with changeset_templater (shared cache variable): + + $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \ + > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}' + o 7 foo-bar+1 + | + # 6 foo-bar+0 + |\ + o | 5 null+5 + | | + $ cd ..
--- a/tests/test-graft.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-graft.t Sun Jan 17 21:40:21 2016 -0600 @@ -62,6 +62,7 @@ [255] $ hg graft -r 1 2 + warning: inconsistent use of --rev might give unexpected revision ordering! skipping ancestor revision 2:5c095ad7e90f skipping ancestor revision 1:5d205f8b35b6 [255] @@ -138,7 +139,7 @@ grafting 4:9c233e8e184d "4" grafting 3:4c60f11aa304 "3" - $ HGEDITOR=cat hg graft 1 5 4 3 'merge()' 2 --debug + $ HGEDITOR=cat hg graft 1 5 'merge()' 2 --debug skipping ungraftable merge revision 6 scanning for duplicate grafts skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7) @@ -154,7 +155,7 @@ ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6 preserving b for resolve of b b: local copied/moved from a -> m (premerge) - picked tool ':merge' for b (binary False symlink False) + picked tool ':merge' for b (binary False symlink False changedelete False) merging b and a to b my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622 premerge successful @@ -174,6 +175,8 @@ e committing manifest committing changelog + $ HGEDITOR=cat hg graft 4 3 --log --debug + scanning for duplicate grafts grafting 4:9c233e8e184d "4" searching for copies back to rev 1 resolving manifests @@ -184,17 +187,22 @@ getting d b: remote unchanged -> k e: versions differ -> m (premerge) - picked tool ':merge' for e (binary False symlink False) + picked tool ':merge' for e (binary False symlink False changedelete False) merging e my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622 e: versions differ -> m (merge) - picked tool ':merge' for e (binary False symlink False) + picked tool ':merge' for e (binary False symlink False changedelete False) my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622 warning: conflicts while merging e! (edit, then use 'hg resolve --mark') abort: unresolved conflicts, can't continue - (use hg resolve and hg graft --continue) + (use hg resolve and hg graft --continue --log) [255] +Summary should mention graft: + + $ hg summary |grep graft + commit: 2 modified, 2 unknown, 1 unresolved (graft in progress) + Commit while interrupted should fail: $ hg ci -m 'commit interrupted graft' @@ -239,6 +247,7 @@ $ echo b > e $ hg resolve -m e (no more unresolved files) + continue: hg graft --continue Continue with a revision should fail: @@ -433,6 +442,7 @@ $ echo b > a $ hg resolve -m a (no more unresolved files) + continue: hg graft --continue $ hg graft -c grafting 1:5d205f8b35b6 "1" $ hg export tip --git @@ -462,6 +472,7 @@ $ hg resolve --all merging a and b to b (no more unresolved files) + continue: hg graft --continue $ hg graft -c grafting 2:5c095ad7e90f "2" $ hg export tip --git @@ -752,6 +763,7 @@ $ echo abc > a $ hg resolve -m a (no more unresolved files) + continue: hg graft --continue $ hg graft -c grafting 28:50a516bb8b57 "28" $ cat a
--- a/tests/test-hardlinks.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hardlinks.t Sun Jan 17 21:40:21 2016 -0600 @@ -229,6 +229,7 @@ 2 r4/.hg/store/undo.backup.phaseroots 2 r4/.hg/store/undo.backupfiles 2 r4/.hg/store/undo.phaseroots + 2 r4/.hg/undo.backup.dirstate 2 r4/.hg/undo.bookmarks 2 r4/.hg/undo.branch 2 r4/.hg/undo.desc @@ -264,6 +265,7 @@ 2 r4/.hg/store/undo.backup.phaseroots 2 r4/.hg/store/undo.backupfiles 2 r4/.hg/store/undo.phaseroots + 2 r4/.hg/undo.backup.dirstate 2 r4/.hg/undo.bookmarks 2 r4/.hg/undo.branch 2 r4/.hg/undo.desc
--- a/tests/test-help.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-help.t Sun Jan 17 21:40:21 2016 -0600 @@ -111,6 +111,7 @@ glossary Glossary hgignore Syntax for Mercurial Ignore Files hgweb Configuring hgweb + internals Technical implementation topics merge-tools Merge Tools multirevs Specifying Multiple Revisions patterns File Name Patterns @@ -187,6 +188,7 @@ glossary Glossary hgignore Syntax for Mercurial Ignore Files hgweb Configuring hgweb + internals Technical implementation topics merge-tools Merge Tools multirevs Specifying Multiple Revisions patterns File Name Patterns @@ -219,7 +221,7 @@ [extensions] myfeature = ~/.hgext/myfeature.py - See "hg help config" for more information on configuration files. + See 'hg help config' for more information on configuration files. Extensions are not loaded by default for a variety of reasons: they can increase startup overhead; they may be meant for advanced usage only; they @@ -250,7 +252,6 @@ censor erase file content at a given revision churn command to display statistics about repository history clonebundles advertise pre-generated bundles to seed clones - (experimental) color colorize output from some commands convert import revisions from foreign VCS repositories into Mercurial @@ -343,9 +344,10 @@ Schedule files to be version controlled and added to the repository. The files will be added to the repository at the next commit. To undo an - add before that, see "hg forget". + add before that, see 'hg forget'. - If no names are given, add all files to the repository. + If no names are given, add all files to the repository (except files + matching ".hgignore"). Returns 0 if all files are successfully added. @@ -368,21 +370,35 @@ Schedule files to be version controlled and added to the repository. The files will be added to the repository at the next commit. To undo an - add before that, see "hg forget". + add before that, see 'hg forget'. - If no names are given, add all files to the repository. + If no names are given, add all files to the repository (except files + matching ".hgignore"). - An example showing how new (unknown) files are added automatically by "hg - add": + Examples: + + - New (unknown) files are added automatically by 'hg add': - $ ls - foo.c - $ hg status - ? foo.c - $ hg add - adding foo.c - $ hg status - A foo.c + $ ls + foo.c + $ hg status + ? foo.c + $ hg add + adding foo.c + $ hg status + A foo.c + + - Specific files to be added can be specified: + + $ ls + bar.c foo.c + $ hg status + ? bar.c + ? foo.c + $ hg add bar.c + $ hg status + A bar.c + ? foo.c Returns 0 if all files are successfully added. @@ -481,14 +497,14 @@ Differences between files are shown using the unified diff format. Note: - diff may generate unexpected results for merges, as it will default to - comparing against the working directory's first parent changeset if no - revisions are specified. + 'hg diff' may generate unexpected results for merges, as it will + default to comparing against the working directory's first parent + changeset if no revisions are specified. When two revision arguments are given, then changes are shown between those revisions. If only one revision is specified then that revision is compared to the working directory, and, when no revisions are specified, - the working directory files are compared to its parent. + the working directory files are compared to its first parent. Alternatively you can specify -c/--change with a revision to see the changes in that changeset relative to its first parent. @@ -498,7 +514,7 @@ with undesirable results. Use the -g/--git option to generate diffs in the git extended diff format. - For more information, read "hg help diffs". + For more information, read 'hg help diffs'. Returns 0 on success. @@ -541,10 +557,10 @@ explicitly requested with -u/--unknown or -i/--ignored. Note: - status may appear to disagree with diff if permissions have changed or - a merge has occurred. The standard diff format does not report - permission changes and diff only reports changes relative to one merge - parent. + 'hg status' may appear to disagree with diff if permissions have + changed or a merge has occurred. The standard diff format does not + report permission changes and diff only reports changes relative to one + merge parent. If one revision is given, it is used as the base revision. If two revisions are given, the differences between them are shown. The --change @@ -628,12 +644,12 @@ $ hg .log hg: unknown command '.log' - (did you mean one of log?) + (did you mean log?) [255] $ hg log. hg: unknown command 'log.' - (did you mean one of log?) + (did you mean log?) [255] $ hg pu.lh hg: unknown command 'pu.lh' @@ -760,6 +776,7 @@ glossary Glossary hgignore Syntax for Mercurial Ignore Files hgweb Configuring hgweb + internals Technical implementation topics merge-tools Merge Tools multirevs Specifying Multiple Revisions patterns File Name Patterns @@ -799,6 +816,8 @@ description debugdata dump the contents of a data file revision debugdate parse and display a date + debugdeltachain + dump information about delta chains in a revlog debugdirstate show the contents of the current dirstate debugdiscovery @@ -809,7 +828,8 @@ debugfsinfo show information detected about current filesystem debuggetbundle retrieves a bundle from a repo - debugignore display the combined ignore pattern + debugignore display the combined ignore pattern and information about + ignored files debugindex dump the contents of an index file debugindexdot dump an index DAG as a graphviz dot file @@ -847,6 +867,175 @@ (use "hg help -v debug" to show built-in aliases and global options) +internals topic renders index of available sub-topics + + $ hg help internals + Technical implementation topics + """"""""""""""""""""""""""""""" + + bundles container for exchange of repository data + changegroups representation of revlog data + revlogs revision storage mechanism + +sub-topics can be accessed + + $ hg help internals.changegroups + Changegroups + ============ + + Changegroups are representations of repository revlog data, specifically + the changelog, manifest, and filelogs. + + There are 3 versions of changegroups: "1", "2", and "3". From a high- + level, versions "1" and "2" are almost exactly the same, with the only + difference being a header on entries in the changeset segment. Version "3" + adds support for exchanging treemanifests and includes revlog flags in the + delta header. + + Changegroups consists of 3 logical segments: + + +---------------------------------+ + | | | | + | changeset | manifest | filelogs | + | | | | + +---------------------------------+ + + The principle building block of each segment is a *chunk*. A *chunk* is a + framed piece of data: + + +---------------------------------------+ + | | | + | length | data | + | (32 bits) | <length> bytes | + | | | + +---------------------------------------+ + + Each chunk starts with a 32-bit big-endian signed integer indicating the + length of the raw data that follows. + + There is a special case chunk that has 0 length ("0x00000000"). We call + this an *empty chunk*. + + Delta Groups + ------------ + + A *delta group* expresses the content of a revlog as a series of deltas, + or patches against previous revisions. + + Delta groups consist of 0 or more *chunks* followed by the *empty chunk* + to signal the end of the delta group: + + +------------------------------------------------------------------------+ + | | | | | | + | chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 | + | (32 bits) | (various) | (32 bits) | (various) | (32 bits) | + | | | | | | + +------------------------------------------------------------+-----------+ + + Each *chunk*'s data consists of the following: + + +-----------------------------------------+ + | | | | + | delta header | mdiff header | delta | + | (various) | (12 bytes) | (various) | + | | | | + +-----------------------------------------+ + + The *length* field is the byte length of the remaining 3 logical pieces of + data. The *delta* is a diff from an existing entry in the changelog. + + The *delta header* is different between versions "1", "2", and "3" of the + changegroup format. + + Version 1: + + +------------------------------------------------------+ + | | | | | + | node | p1 node | p2 node | link node | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | + | | | | | + +------------------------------------------------------+ + + Version 2: + + +------------------------------------------------------------------+ + | | | | | | + | node | p1 node | p2 node | base node | link node | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | + | | | | | | + +------------------------------------------------------------------+ + + Version 3: + + +------------------------------------------------------------------------------+ + | | | | | | | + | node | p1 node | p2 node | base node | link node | flags | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) | + | | | | | | | + +------------------------------------------------------------------------------+ + + The *mdiff header* consists of 3 32-bit big-endian signed integers + describing offsets at which to apply the following delta content: + + +-------------------------------------+ + | | | | + | offset | old length | new length | + | (32 bits) | (32 bits) | (32 bits) | + | | | | + +-------------------------------------+ + + In version 1, the delta is always applied against the previous node from + the changegroup or the first parent if this is the first entry in the + changegroup. + + In version 2, the delta base node is encoded in the entry in the + changegroup. This allows the delta to be expressed against any parent, + which can result in smaller deltas and more efficient encoding of data. + + Changeset Segment + ----------------- + + The *changeset segment* consists of a single *delta group* holding + changelog data. It is followed by an *empty chunk* to denote the boundary + to the *manifests segment*. + + Manifest Segment + ---------------- + + The *manifest segment* consists of a single *delta group* holding manifest + data. It is followed by an *empty chunk* to denote the boundary to the + *filelogs segment*. + + Filelogs Segment + ---------------- + + The *filelogs* segment consists of multiple sub-segments, each + corresponding to an individual file whose data is being described: + + +--------------------------------------+ + | | | | | + | filelog0 | filelog1 | filelog2 | ... | + | | | | | + +--------------------------------------+ + + In version "3" of the changegroup format, filelogs may include directory + logs when treemanifests are in use. directory logs are identified by + having a trailing '/' on their filename (see below). + + The final filelog sub-segment is followed by an *empty chunk* to denote + the end of the segment and the overall changegroup. + + Each filelog sub-segment consists of the following: + + +------------------------------------------+ + | | | | + | filename size | filename | delta group | + | (32 bits) | (various) | (various) | + | | | | + +------------------------------------------+ + + That is, a *chunk* consisting of the filename (not terminated or padded) + followed by N chunks constituting the *delta group* for this file. Test list of commands with command with no help text @@ -974,6 +1163,31 @@ $ hg help config.type | egrep '^$'|wc -l \s*3 (re) +Separate sections from subsections + + $ hg help config.format | egrep '^ ("|-)|^\s*$' | uniq + "format" + -------- + + "usegeneraldelta" + + "dotencode" + + "usefncache" + + "usestore" + + "profiling" + ----------- + + "format" + + "progress" + ---------- + + "format" + + Last item in help config.*: $ hg help config.`hg help config|grep '^ "'| \ @@ -1026,14 +1240,42 @@ helphook1 helphook2 +help -c should only show debug --debug + + $ hg help -c --debug|egrep debug|wc -l|egrep '^\s*0\s*$' + [1] + +help -c should only show deprecated for -v + + $ hg help -c -v|egrep DEPRECATED|wc -l|egrep '^\s*0\s*$' + [1] + +Test -s / --system + + $ hg help config.files -s windows |grep 'etc/mercurial' | \ + > wc -l | sed -e 's/ //g' + 0 + $ hg help config.files --system unix | grep 'USER' | \ + > wc -l | sed -e 's/ //g' + 0 + Test -e / -c / -k combinations - $ hg help -c progress - abort: no such help topic: progress - (try "hg help --keyword progress") + $ hg help -c|egrep '^[A-Z].*:|^ debug' + Commands: + $ hg help -e|egrep '^[A-Z].*:|^ debug' + Extensions: + $ hg help -k|egrep '^[A-Z].*:|^ debug' + Topics: + Commands: + Extensions: + Extension Commands: + $ hg help -c schemes + abort: no such help topic: schemes + (try "hg help --keyword schemes") [255] - $ hg help -e progress |head -1 - progress extension - show progress bars for some actions (DEPRECATED) + $ hg help -e schemes |head -1 + schemes extension - extend schemes with shortcuts to repository swarms $ hg help -c -k dates |egrep '^(Topics|Extensions|Commands):' Commands: $ hg help -e -k a |egrep '^(Topics|Extensions|Commands):' @@ -1068,16 +1310,14 @@ Commands: - bookmarks create a new bookmark or list existing bookmarks - clone make a copy of an existing repository - debugapplystreamclonebundle apply a stream clone bundle file - debugcreatestreamclonebundle create a stream clone bundle file - paths show aliases for remote repositories - update update working directory (or switch revisions) + bookmarks create a new bookmark or list existing bookmarks + clone make a copy of an existing repository + paths show aliases for remote repositories + update update working directory (or switch revisions) Extensions: - clonebundles advertise pre-generated bundles to seed clones (experimental) + clonebundles advertise pre-generated bundles to seed clones prefixedname matched against word "clone" relink recreates hardlinks between repository clones @@ -1146,7 +1386,7 @@ This paragraph is never omitted (for extension) - This paragraph is omitted, if "hg help" is invoked without "-v" (for + This paragraph is omitted, if 'hg help' is invoked without "-v" (for extension) This paragraph is never omitted, too (for extension) @@ -1167,7 +1407,7 @@ This paragraph is never omitted (for topic). - This paragraph is omitted, if "hg help" is invoked without "-v" (for + This paragraph is omitted, if 'hg help' is invoked without "-v" (for topic) This paragraph is never omitted, too (for topic) @@ -1184,7 +1424,7 @@ (DVCS) can be described as a directed acyclic graph (DAG), consisting of nodes and edges, where nodes correspond to changesets and edges imply a parent -> child relation. This graph can be visualized by - graphical tools such as "hg log --graph". In Mercurial, the DAG is + graphical tools such as 'hg log --graph'. In Mercurial, the DAG is limited by the requirement for children to have at most two parents. @@ -1192,28 +1432,43 @@ "paths" ------- - Assigns symbolic names to repositories. The left side is the symbolic - name, and the right gives the directory or URL that is the location of the - repository. Default paths can be declared by setting the following - entries. + Assigns symbolic names and behavior to repositories. + + Options are symbolic names defining the URL or directory that is the + location of the repository. Example: + + [paths] + my_server = https://example.com/my_repo + local_path = /home/me/repo + + These symbolic names can be used from the command line. To pull from + "my_server": 'hg pull my_server'. To push to "local_path": 'hg push + local_path'. + + Options containing colons (":") denote sub-options that can influence + behavior for that specific path. Example: + + [paths] + my_server = https://example.com/my_path + my_server:pushurl = ssh://example.com/my_path + + The following sub-options can be defined: + + "pushurl" + The URL to use for push operations. If not defined, the location + defined by the path's main entry is used. + + The following special named paths exist: "default" - Directory or URL to use when pulling if no source is specified. - (default: repository from which the current repository was cloned) + The URL or directory to use when no source or remote is specified. + + 'hg clone' will automatically define this path to the location the + repository was cloned from. "default-push" - Optional. Directory or URL to use when pushing if no destination is - specified. - - Custom paths can be defined by assigning the path to a name that later can - be used from the command line. Example: - - [paths] - my_path = http://example.com/path - - To push to the path defined in "my_path" run the command: - - hg push my_path + (deprecated) The URL or directory for the default 'hg push' location. + "default:pushurl" should be used instead. $ hg help glossary.mcguffin abort: help section not found @@ -1239,8 +1494,8 @@ the two file versions, so they can determine the changes made on both branches. - Merge tools are used both for "hg resolve", "hg merge", "hg update", "hg - backout" and in several extensions. + Merge tools are used both for 'hg resolve', 'hg merge', 'hg update', 'hg + backout' and in several extensions. Usually, the merge tool tries to automatically reconcile the files by combining all non-overlapping changes that occurred separately in the two @@ -1483,6 +1738,13 @@ Configuring hgweb </td></tr> <tr><td> + <a href="/help/internals"> + internals + </a> + </td><td> + Technical implementation topics + </td></tr> + <tr><td> <a href="/help/merge-tools"> merge-tools </a> @@ -1560,6 +1822,7 @@ This is the topic to test omit indicating. </td></tr> + <tr><td colspan="2"><h2><a name="main" href="#main">Main Commands</a></h2></td></tr> <tr><td> @@ -1682,6 +1945,8 @@ update working directory (or switch revisions) </td></tr> + + <tr><td colspan="2"><h2><a name="other" href="#other">Other Commands</a></h2></td></tr> <tr><td> @@ -1922,6 +2187,8 @@ </td><td> output version and copyright information </td></tr> + + </table> </div> </div> @@ -1989,15 +2256,17 @@ </p> <p> The files will be added to the repository at the next commit. To - undo an add before that, see "hg forget". + undo an add before that, see 'hg forget'. </p> <p> - If no names are given, add all files to the repository. + If no names are given, add all files to the repository (except + files matching ".hgignore"). </p> <p> - An example showing how new (unknown) files are added - automatically by "hg add": + Examples: </p> + <ul> + <li> New (unknown) files are added automatically by 'hg add': <pre> \$ ls (re) foo.c @@ -2008,6 +2277,19 @@ \$ hg status (re) A foo.c </pre> + <li> Specific files to be added can be specified: + <pre> + \$ ls (re) + bar.c foo.c + \$ hg status (re) + ? bar.c + ? foo.c + \$ hg add bar.c (re) + \$ hg status (re) + A bar.c + ? foo.c + </pre> + </ul> <p> Returns 0 if all files are successfully added. </p> @@ -2151,8 +2433,8 @@ </p> <p> This command schedules the files to be removed at the next commit. - To undo a remove before that, see "hg revert". To undo added - files, see "hg forget". + To undo a remove before that, see 'hg revert'. To undo added + files, see 'hg forget'. </p> <p> -A/--after can be used to remove only files that have already @@ -2164,7 +2446,7 @@ The following table details the behavior of remove for different file states (columns) and option combinations (rows). The file states are Added [A], Clean [C], Modified [M] and Missing [!] - (as reported by "hg status"). The actions are Warn, Remove + (as reported by 'hg status'). The actions are Warn, Remove (from branch) and Delete (from disk): </p> <table> @@ -2195,8 +2477,11 @@ <td>R</td></tr> </table> <p> - Note that remove never deletes files in Added [A] state from the - working directory, not even if option --force is specified. + <b>Note:</b> + </p> + <p> + 'hg remove' never deletes files in Added [A] state from the + working directory, not even if "--force" is specified. </p> <p> Returns 0 on success, 1 if any warnings encountered. @@ -2381,6 +2666,329 @@ </html> +Sub-topic indexes rendered properly + + $ get-with-headers.py 127.0.0.1:$HGPORT "help/internals" + 200 Script output follows + + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"> + <head> + <link rel="icon" href="/static/hgicon.png" type="image/png" /> + <meta name="robots" content="index, nofollow" /> + <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> + + <title>Help: internals</title> + </head> + <body> + + <div class="container"> + <div class="menu"> + <div class="logo"> + <a href="https://mercurial-scm.org/"> + <img src="/static/hglogo.png" alt="mercurial" /></a> + </div> + <ul> + <li><a href="/shortlog">log</a></li> + <li><a href="/graph">graph</a></li> + <li><a href="/tags">tags</a></li> + <li><a href="/bookmarks">bookmarks</a></li> + <li><a href="/branches">branches</a></li> + </ul> + <ul> + <li><a href="/help">help</a></li> + </ul> + </div> + + <div class="main"> + <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2> + <form class="search" action="/log"> + + <p><input name="rev" id="search1" type="text" size="30" /></p> + <div id="hint">Find changesets by keywords (author, files, the commit message), revision + number or hash, or <a href="/help/revsets">revset expression</a>.</div> + </form> + <table class="bigtable"> + <tr><td colspan="2"><h2><a name="main" href="#topics">Topics</a></h2></td></tr> + + <tr><td> + <a href="/help/internals.bundles"> + bundles + </a> + </td><td> + container for exchange of repository data + </td></tr> + <tr><td> + <a href="/help/internals.changegroups"> + changegroups + </a> + </td><td> + representation of revlog data + </td></tr> + <tr><td> + <a href="/help/internals.revlogs"> + revlogs + </a> + </td><td> + revision storage mechanism + </td></tr> + + + + + + </table> + </div> + </div> + + <script type="text/javascript">process_dates()</script> + + + </body> + </html> + + +Sub-topic topics rendered properly + + $ get-with-headers.py 127.0.0.1:$HGPORT "help/internals.changegroups" + 200 Script output follows + + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"> + <head> + <link rel="icon" href="/static/hgicon.png" type="image/png" /> + <meta name="robots" content="index, nofollow" /> + <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> + + <title>Help: internals.changegroups</title> + </head> + <body> + + <div class="container"> + <div class="menu"> + <div class="logo"> + <a href="https://mercurial-scm.org/"> + <img src="/static/hglogo.png" alt="mercurial" /></a> + </div> + <ul> + <li><a href="/shortlog">log</a></li> + <li><a href="/graph">graph</a></li> + <li><a href="/tags">tags</a></li> + <li><a href="/bookmarks">bookmarks</a></li> + <li><a href="/branches">branches</a></li> + </ul> + <ul> + <li class="active"><a href="/help">help</a></li> + </ul> + </div> + + <div class="main"> + <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2> + <h3>Help: internals.changegroups</h3> + + <form class="search" action="/log"> + + <p><input name="rev" id="search1" type="text" size="30" /></p> + <div id="hint">Find changesets by keywords (author, files, the commit message), revision + number or hash, or <a href="/help/revsets">revset expression</a>.</div> + </form> + <div id="doc"> + <h1>representation of revlog data</h1> + <h2>Changegroups</h2> + <p> + Changegroups are representations of repository revlog data, specifically + the changelog, manifest, and filelogs. + </p> + <p> + There are 3 versions of changegroups: "1", "2", and "3". From a + high-level, versions "1" and "2" are almost exactly the same, with + the only difference being a header on entries in the changeset + segment. Version "3" adds support for exchanging treemanifests and + includes revlog flags in the delta header. + </p> + <p> + Changegroups consists of 3 logical segments: + </p> + <pre> + +---------------------------------+ + | | | | + | changeset | manifest | filelogs | + | | | | + +---------------------------------+ + </pre> + <p> + The principle building block of each segment is a *chunk*. A *chunk* + is a framed piece of data: + </p> + <pre> + +---------------------------------------+ + | | | + | length | data | + | (32 bits) | <length> bytes | + | | | + +---------------------------------------+ + </pre> + <p> + Each chunk starts with a 32-bit big-endian signed integer indicating + the length of the raw data that follows. + </p> + <p> + There is a special case chunk that has 0 length ("0x00000000"). We + call this an *empty chunk*. + </p> + <h3>Delta Groups</h3> + <p> + A *delta group* expresses the content of a revlog as a series of deltas, + or patches against previous revisions. + </p> + <p> + Delta groups consist of 0 or more *chunks* followed by the *empty chunk* + to signal the end of the delta group: + </p> + <pre> + +------------------------------------------------------------------------+ + | | | | | | + | chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 | + | (32 bits) | (various) | (32 bits) | (various) | (32 bits) | + | | | | | | + +------------------------------------------------------------+-----------+ + </pre> + <p> + Each *chunk*'s data consists of the following: + </p> + <pre> + +-----------------------------------------+ + | | | | + | delta header | mdiff header | delta | + | (various) | (12 bytes) | (various) | + | | | | + +-----------------------------------------+ + </pre> + <p> + The *length* field is the byte length of the remaining 3 logical pieces + of data. The *delta* is a diff from an existing entry in the changelog. + </p> + <p> + The *delta header* is different between versions "1", "2", and + "3" of the changegroup format. + </p> + <p> + Version 1: + </p> + <pre> + +------------------------------------------------------+ + | | | | | + | node | p1 node | p2 node | link node | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | + | | | | | + +------------------------------------------------------+ + </pre> + <p> + Version 2: + </p> + <pre> + +------------------------------------------------------------------+ + | | | | | | + | node | p1 node | p2 node | base node | link node | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | + | | | | | | + +------------------------------------------------------------------+ + </pre> + <p> + Version 3: + </p> + <pre> + +------------------------------------------------------------------------------+ + | | | | | | | + | node | p1 node | p2 node | base node | link node | flags | + | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) | + | | | | | | | + +------------------------------------------------------------------------------+ + </pre> + <p> + The *mdiff header* consists of 3 32-bit big-endian signed integers + describing offsets at which to apply the following delta content: + </p> + <pre> + +-------------------------------------+ + | | | | + | offset | old length | new length | + | (32 bits) | (32 bits) | (32 bits) | + | | | | + +-------------------------------------+ + </pre> + <p> + In version 1, the delta is always applied against the previous node from + the changegroup or the first parent if this is the first entry in the + changegroup. + </p> + <p> + In version 2, the delta base node is encoded in the entry in the + changegroup. This allows the delta to be expressed against any parent, + which can result in smaller deltas and more efficient encoding of data. + </p> + <h3>Changeset Segment</h3> + <p> + The *changeset segment* consists of a single *delta group* holding + changelog data. It is followed by an *empty chunk* to denote the + boundary to the *manifests segment*. + </p> + <h3>Manifest Segment</h3> + <p> + The *manifest segment* consists of a single *delta group* holding + manifest data. It is followed by an *empty chunk* to denote the boundary + to the *filelogs segment*. + </p> + <h3>Filelogs Segment</h3> + <p> + The *filelogs* segment consists of multiple sub-segments, each + corresponding to an individual file whose data is being described: + </p> + <pre> + +--------------------------------------+ + | | | | | + | filelog0 | filelog1 | filelog2 | ... | + | | | | | + +--------------------------------------+ + </pre> + <p> + In version "3" of the changegroup format, filelogs may include + directory logs when treemanifests are in use. directory logs are + identified by having a trailing '/' on their filename (see below). + </p> + <p> + The final filelog sub-segment is followed by an *empty chunk* to denote + the end of the segment and the overall changegroup. + </p> + <p> + Each filelog sub-segment consists of the following: + </p> + <pre> + +------------------------------------------+ + | | | | + | filename size | filename | delta group | + | (32 bits) | (various) | (various) | + | | | | + +------------------------------------------+ + </pre> + <p> + That is, a *chunk* consisting of the filename (not terminated or padded) + followed by N chunks constituting the *delta group* for this file. + </p> + + </div> + </div> + </div> + + <script type="text/javascript">process_dates()</script> + + + </body> + </html> + + $ killdaemons.py #endif
--- a/tests/test-hgignore.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgignore.t Sun Jan 17 21:40:21 2016 -0600 @@ -55,6 +55,29 @@ ? a.c ? syntax +Ensure that comments work: + + $ touch 'foo#bar' 'quux#' +#if no-windows + $ touch 'baz\#wat' +#endif + $ cat <<'EOF' >> .hgignore + > # full-line comment + > # whitespace-only comment line + > syntax# pattern, no whitespace, then comment + > a.c # pattern, then whitespace, then comment + > baz\\# # escaped comment character + > foo\#b # escaped comment character + > quux\## escaped comment character at end of name + > EOF + $ hg status + A dir/b.o + ? .hgignore + $ rm 'foo#bar' 'quux#' +#if no-windows + $ rm 'baz\#wat' +#endif + Check it does not ignore the current directory '.': $ echo "^\." > .hgignore @@ -143,6 +166,10 @@ $ hg debugignore (?:(?:|.*/)[^/]*(?:/|$)) + $ hg debugignore b.o + b.o is ignored + (ignore rule in $TESTTMP/ignorerepo/.hgignore, line 1: '*') (glob) + $ cd .. Check patterns that match only the directory @@ -168,6 +195,11 @@ ? a.c ? a.o ? syntax + $ hg debugignore a.c + a.c is not ignored + $ hg debugignore dir/c.o + dir/c.o is ignored + (ignore rule in $TESTTMP/ignorerepo/.hgignore, line 2: 'dir/**/c.o') (glob) Check using 'include:' in ignore file @@ -251,3 +283,6 @@ $ hg status | grep file2 [1] + $ hg debugignore dir1/file2 + dir1/file2 is ignored + (ignore rule in dir2/.hgignore, line 1: 'file*2')
--- a/tests/test-hgweb-commands.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgweb-commands.t Sun Jan 17 21:40:21 2016 -0600 @@ -6,6 +6,11 @@ - unbundle, tested in test-push-http - changegroupsubset, tested in test-pull + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + Set up the repo $ hg init test @@ -1892,7 +1897,7 @@ $ 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) + lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch 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 heads @@ -2098,10 +2103,34 @@ capabilities - $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities'; echo +(plain version to check the format) + + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities' | dd ibs=75 count=1 2> /dev/null; 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) + lookup changegroupsubset branchmap pushkey known + +(spread version to check the content) + + $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities' | tr ' ' '\n'; echo + 200 + Script + output + follows + + lookup + changegroupsubset + branchmap + pushkey + known + getbundle + unbundlehash + batch + stream-preferred + streamreqs=generaldelta,revlogv1 + 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 heads
--- a/tests/test-hgweb-diffs.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgweb-diffs.t Sun Jan 17 21:40:21 2016 -0600 @@ -927,7 +927,7 @@ </tr> <tr> <th>parents</th> - <td><a href="/file/0cd96de13884/a">0cd96de13884</a> </td> + <td><a href="/file/d73db4d812ff/a">d73db4d812ff</a> </td> </tr> <tr> <th>children</th>
--- a/tests/test-hgweb-filelog.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgweb-filelog.t Sun Jan 17 21:40:21 2016 -0600 @@ -179,7 +179,7 @@ <li><a href="/help">help</a></li> </ul> <div class="atom-logo"> - <a href="/atom-log/3f41bc784e7e/a" title="subscribe to atom feed"> + <a href="/atom-log/tip/a" title="subscribe to atom feed"> <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" /> </a> </div> @@ -187,7 +187,10 @@ <div class="main"> <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2> - <h3>log a</h3> + <h3> + log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a> + <span class="branchname">a-branch</span> + </h3> <form class="search" action="/log"> @@ -296,7 +299,7 @@ <li><a href="/help">help</a></li> </ul> <div class="atom-logo"> - <a href="/atom-log/3f41bc784e7e/a" title="subscribe to atom feed"> + <a href="/atom-log/tip/a" title="subscribe to atom feed"> <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" /> </a> </div> @@ -304,7 +307,10 @@ <div class="main"> <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2> - <h3>log a</h3> + <h3> + log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a> + <span class="branchname">a-branch</span> + </h3> <form class="search" action="/log"> @@ -413,7 +419,7 @@ <li><a href="/help">help</a></li> </ul> <div class="atom-logo"> - <a href="/atom-log/5ed941583260/a" title="subscribe to atom feed"> + <a href="/atom-log/tip/a" title="subscribe to atom feed"> <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" /> </a> </div> @@ -421,7 +427,10 @@ <div class="main"> <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2> - <h3>log a</h3> + <h3> + log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a> + <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> + </h3> <form class="search" action="/log"> @@ -522,7 +531,7 @@ <li><a href="/help">help</a></li> </ul> <div class="atom-logo"> - <a href="/atom-log/5ed941583260/a" title="subscribe to atom feed"> + <a href="/atom-log/tip/a" title="subscribe to atom feed"> <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" /> </a> </div> @@ -530,7 +539,10 @@ <div class="main"> <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2> - <h3>log a</h3> + <h3> + log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a> + <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> + </h3> <form class="search" action="/log">
--- a/tests/test-hgweb-json.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgweb-json.t Sun Jan 17 21:40:21 2016 -0600 @@ -1062,6 +1062,10 @@ "topic": "hgweb" }, { + "summary": "Technical implementation topics", + "topic": "internals" + }, + { "summary": "Merge Tools", "topic": "merge-tools" },
--- a/tests/test-hgweb-symrev.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgweb-symrev.t Sun Jan 17 21:40:21 2016 -0600 @@ -165,7 +165,8 @@ <li><a href="/comparison/xyzzy/foo?style=paper">comparison</a></li> <li><a href="/annotate/xyzzy/foo?style=paper">annotate</a></li> <li><a href="/raw-file/xyzzy/foo">raw</a></li> - <a href="/atom-log/a7c1559b7bba/foo" title="subscribe to atom feed"> + <a href="/atom-log/tip/foo" title="subscribe to atom feed"> + log foo @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a> <a href="/log/xyzzy/foo?revcount=30&style=paper">less</a> <a href="/log/xyzzy/foo?revcount=120&style=paper">more</a> | <a href="/log/43c799df6e75/foo?style=paper">(0)</a> <a href="/log/tip/foo?style=paper">tip</a> </div> @@ -352,7 +353,8 @@ <li><a href="/comparison/xyzzy/foo?style=coal">comparison</a></li> <li><a href="/annotate/xyzzy/foo?style=coal">annotate</a></li> <li><a href="/raw-file/xyzzy/foo">raw</a></li> - <a href="/atom-log/a7c1559b7bba/foo" title="subscribe to atom feed"> + <a href="/atom-log/tip/foo" title="subscribe to atom feed"> + log foo @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a> <a href="/log/xyzzy/foo?revcount=30&style=coal">less</a> <a href="/log/xyzzy/foo?revcount=120&style=coal">more</a> | <a href="/log/43c799df6e75/foo?style=coal">(0)</a> <a href="/log/tip/foo?style=coal">tip</a> </div> @@ -816,7 +818,7 @@ <a href="/file/43c799df6e75/foo?style=monoblue">file</a> | <a href="/diff/43c799df6e75/foo?style=monoblue">diff</a> | <a href="/annotate/43c799df6e75/foo?style=monoblue">annotate</a> - <a href="/log/43c799df6e75/foo?style=monoblue">(0)</a><a href="/log/tip/foo?style=monoblue">tip</a> + <a href="/log/43c799df6e75/foo?style=monoblue">(0)</a> <a href="/log/tip/foo?style=monoblue">tip</a> $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=monoblue' | egrep $REVLINKS <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
--- a/tests/test-hgwebdir.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hgwebdir.t Sun Jan 17 21:40:21 2016 -0600 @@ -1181,6 +1181,38 @@ </body> </html> + +test listening address/port specified by web-conf (issue4699): + + $ killdaemons.py + $ cat >> paths.conf <<EOF + > [web] + > address = localhost + > port = $HGPORT1 + > EOF + $ hg serve -d --pid-file=hg.pid --web-conf paths.conf \ + > -A access-paths.log -E error-paths-9.log + listening at http://*:$HGPORT1/ (bound to 127.0.0.1:$HGPORT1) (glob) + $ cat hg.pid >> $DAEMON_PIDS + $ get-with-headers.py localhost:$HGPORT1 '?style=raw' + 200 Script output follows + + + +test --port option overrides web.port: + + $ killdaemons.py + $ hg serve -p $HGPORT2 -d -v --pid-file=hg.pid --web-conf paths.conf \ + > -A access-paths.log -E error-paths-10.log + listening at http://*:$HGPORT2/ (bound to 127.0.0.1:$HGPORT2) (glob) + $ cat hg.pid >> $DAEMON_PIDS + $ get-with-headers.py localhost:$HGPORT2 '?style=raw' + 200 Script output follows + + + + + $ killdaemons.py $ cat > collections.conf <<EOF > [collections] > $root=$root @@ -1338,6 +1370,14 @@ $ cat error-paths-8.log +paths errors 9 + + $ cat error-paths-9.log + +paths errors 10 + + $ cat error-paths-10.log + collections errors $ cat error-collections.log
--- a/tests/test-histedit-arguments.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-arguments.t Sun Jan 17 21:40:21 2016 -0600 @@ -64,14 +64,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved Run on a revision not ancestors of the current working directory. -------------------------------------------------------------------- @@ -92,7 +92,6 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg up --quiet $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF @@ -100,7 +99,6 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg up --quiet Test config specified default @@ -110,7 +108,6 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved Run on a revision not descendants of the initial parent -------------------------------------------------------------------- @@ -132,8 +129,8 @@ > EOF 1 files updated, 0 files merged, 0 files removed, 0 files unresolved reverting alpha - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (08d98a8350f3), you may commit or record as needed now. + (hg histedit --continue to resume) [1] $ mv .hg/histedit-state .hg/histedit-state.back @@ -142,7 +139,6 @@ $ mv .hg/histedit-state.back .hg/histedit-state $ hg histedit --continue - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg (glob) $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2:: @ 4 f5ed five @@ -163,8 +159,8 @@ > pick eb57da33312f 2 three > pick 08d98a8350f3 4 five > EOF - abort: missing rules for changeset c8e68270e35a - (do you want to use the drop action?) + hg: parse error: missing rules for changeset c8e68270e35a + (use "drop c8e68270e35a" to discard, see also: "hg help -e histedit.config") [255] Test that extra revisions are detected @@ -175,7 +171,8 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - abort: may not use changesets other than the ones listed + hg: parse error: pick "363035386362" changeset was not a candidate + (only use listed changesets) [255] Test malformed line @@ -186,7 +183,7 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - abort: malformed line "pickeb57da33312f2three" + hg: parse error: malformed line "pickeb57da33312f2three" [255] Test unknown changeset @@ -197,7 +194,7 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - abort: unknown changeset 0123456789ab listed + hg: parse error: unknown changeset 0123456789ab listed [255] Test unknown command @@ -208,7 +205,7 @@ > pick c8e68270e35a 3 four > pick 08d98a8350f3 4 five > EOF - abort: unknown action "coin" + hg: parse error: unknown action "coin" [255] Test duplicated changeset @@ -221,7 +218,18 @@ > pick eb57da33312f 2 three > pick 08d98a8350f3 4 five > EOF - abort: duplicated command for changeset eb57da33312f + hg: parse error: duplicated command for changeset eb57da33312f + [255] + +Test bogus rev +--------------------------------------- + + $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF + > pick eb57da33312f 2 three + > pick 0 + > pick 08d98a8350f3 4 five + > EOF + hg: parse error: invalid changeset 0 [255] Test short version of command @@ -251,9 +259,8 @@ HG: branch 'default' HG: changed alpha 1 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/foo/.hg/strip-backup/*-backup.hg (glob) - saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-23a13bf9-backup.hg (glob) + saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob) $ hg update -q 2 $ echo x > x @@ -288,14 +295,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved Test --continue with --keep @@ -304,8 +311,8 @@ > edit eb57da33312f 2 three > pick f3cfcca30c44 4 x > EOF - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (eb57da33312f), you may commit or record as needed now. + (hg histedit --continue to resume) [1] $ echo edit >> alpha $ hg histedit -q --continue @@ -331,15 +338,15 @@ $ hg histedit . -q --commands - << EOF > edit 8fda0c726bf2 6 x > EOF - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (8fda0c726bf2), you may commit or record as needed now. + (hg histedit --continue to resume) [1] Corrupt histedit state file $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit $ mv ../corrupt-histedit .hg/histedit-state $ hg histedit --abort warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up - abort: No such file or directory: * (glob) + abort: .*(No such file or directory:|The system cannot find the file specified).* (re) [255] Histedit state has been exited $ hg summary -q @@ -347,3 +354,98 @@ commit: 1 added, 1 unknown (new branch head) update: 4 new changesets (update) + $ cd .. + +Set up default base revision tests + + $ hg init defaultbase + $ cd defaultbase + $ touch foo + $ hg -q commit -A -m root + $ echo 1 > foo + $ hg commit -m 'public 1' + $ hg phase --force --public -r . + $ echo 2 > foo + $ hg commit -m 'draft after public' + $ hg -q up -r 1 + $ echo 3 > foo + $ hg commit -m 'head 1 public' + created new head + $ hg phase --force --public -r . + $ echo 4 > foo + $ hg commit -m 'head 1 draft 1' + $ echo 5 > foo + $ hg commit -m 'head 1 draft 2' + $ hg -q up -r 2 + $ echo 6 > foo + $ hg commit -m 'head 2 commit 1' + $ echo 7 > foo + $ hg commit -m 'head 2 commit 2' + $ hg -q up -r 2 + $ echo 8 > foo + $ hg commit -m 'head 3' + created new head + $ hg -q up -r 2 + $ echo 9 > foo + $ hg commit -m 'head 4' + created new head + $ hg merge --tool :local -r 8 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg commit -m 'merge head 3 into head 4' + $ echo 11 > foo + $ hg commit -m 'commit 1 after merge' + $ echo 12 > foo + $ hg commit -m 'commit 2 after merge' + + $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n' + @ 12:8cde254db839 draft commit 2 after merge + | + o 11:6f2f0241f119 draft commit 1 after merge + | + o 10:90506cc76b00 draft merge head 3 into head 4 + |\ + | o 9:f8607a373a97 draft head 4 + | | + o | 8:0da92be05148 draft head 3 + |/ + | o 7:4c35cdf97d5e draft head 2 commit 2 + | | + | o 6:931820154288 draft head 2 commit 1 + |/ + | o 5:8cdc02b9bc63 draft head 1 draft 2 + | | + | o 4:463b8c0d2973 draft head 1 draft 1 + | | + | o 3:23a0c4eefcbf public head 1 public + | | + o | 2:4117331c3abb draft draft after public + |/ + o 1:4426d359ea59 public public 1 + | + o 0:54136a8ddf32 public root + + +Default base revision should stop at public changesets + + $ hg -q up 8cdc02b9bc63 + $ hg histedit --commands - <<EOF + > pick 463b8c0d2973 + > pick 8cdc02b9bc63 + > EOF + +Default base revision should stop at branchpoint + + $ hg -q up 4c35cdf97d5e + $ hg histedit --commands - <<EOF + > pick 931820154288 + > pick 4c35cdf97d5e + > EOF + +Default base revision should stop at merge commit + + $ hg -q up 8cde254db839 + $ hg histedit --commands - <<EOF + > pick 6f2f0241f119 + > pick 8cde254db839 + > EOF
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-histedit-base.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,265 @@ + $ . "$TESTDIR/histedit-helpers.sh" + + $ cat >> $HGRCPATH <<EOF + > [alias] + > tglog = log -G --template "{rev}:{node}:{phase} '{desc}'\n" + > [extensions] + > histedit= + > [experimental] + > histeditng=True + > EOF + +Create repo a: + + $ hg init a + $ cd a + $ hg unbundle "$TESTDIR/bundles/rebase.hg" + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 7 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg tglog + @ 7:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | + | o 6:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + |/| + o | 5:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + | | + | o 4:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + | o 3:32af7686d403cf45b5d95f2d70cebea587ac806a:draft 'D' + | | + | o 2:5fddd98957c8a54a4d436dfe1da9d87f21a1b97b:draft 'C' + | | + | o 1:42ccdea3bb16d28e1848c95fe2e44c000f3f21b1:draft 'B' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + + + +Go to D + $ hg update 3 + 3 files updated, 0 files merged, 2 files removed, 0 files unresolved +edit the history to rebase B onto H + + +Rebase B onto H + $ hg histedit 1 --commands - 2>&1 << EOF | fixbundle + > base 02de42196ebe + > pick 42ccdea3bb16 B + > pick 5fddd98957c8 C + > pick 32af7686d403 D + > EOF + + $ hg tglog + @ 7:0937e82309df47d14176ee15e45dbec5fbdef340:draft 'D' + | + o 6:f778d1cbddac4ab679d9983c9bb92e4c5e09e7fa:draft 'C' + | + o 5:3d41b7cc708545206213a842f96d812d2e73d818:draft 'B' + | + o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | + | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + |/| + o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + | | + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + +Rebase back and drop something + $ hg histedit 5 --commands - 2>&1 << EOF | fixbundle + > base cd010b8cd998 + > pick 3d41b7cc7085 B + > drop f778d1cbddac C + > pick 0937e82309df D + > EOF + + $ hg tglog + @ 6:476cc3e4168da2d036b141f7f7dcff7f8e3fe846:draft 'D' + | + o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + | + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + +Split stack + $ hg histedit 5 --commands - 2>&1 << EOF | fixbundle + > base cd010b8cd998 + > pick d273e35dcdf2 B + > base cd010b8cd998 + > pick 476cc3e4168d D + > EOF + + $ hg tglog + @ 6:d7a6f907a822c4ce6f15662ae45a42aa46d3818a:draft 'D' + | + | o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + |/ + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + +Abort + $ echo x > B + $ hg add B + $ hg commit -m "X" + $ hg tglog + @ 7:591369deedfdcbf57471e894999a70d7f676186d:draft 'X' + | + o 6:d7a6f907a822c4ce6f15662ae45a42aa46d3818a:draft 'D' + | + | o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + |/ + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + + $ hg histedit 6 --commands - 2>&1 << EOF | fixbundle + > base d273e35dcdf2 B + > drop d7a6f907a822 D + > pick 591369deedfd X + > EOF + merging B + warning: conflicts while merging B! (edit, then use 'hg resolve --mark') + Fix up the change (pick 591369deedfd) + (hg histedit --continue to resume) + $ hg histedit --abort | fixbundle + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg tglog + @ 7:591369deedfdcbf57471e894999a70d7f676186d:draft 'X' + | + o 6:d7a6f907a822c4ce6f15662ae45a42aa46d3818a:draft 'D' + | + | o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + |/ + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + +Continue + $ hg histedit 6 --commands - 2>&1 << EOF | fixbundle + > base d273e35dcdf2 B + > drop d7a6f907a822 D + > pick 591369deedfd X + > EOF + merging B + warning: conflicts while merging B! (edit, then use 'hg resolve --mark') + Fix up the change (pick 591369deedfd) + (hg histedit --continue to resume) + $ echo b2 > B + $ hg resolve --mark B + (no more unresolved files) + continue: hg histedit --continue + $ hg histedit --continue | fixbundle + $ hg tglog + @ 6:03772da75548bb42a8f1eacd8c91d0717a147fcd:draft 'X' + | + o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + | + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + + +base on a previously picked changeset + $ echo i > i + $ hg add i + $ hg commit -m "I" + $ echo j > j + $ hg add j + $ hg commit -m "J" + $ hg tglog + @ 8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J' + | + o 7:b2f90fd8aa85db5569e3cfc30cd1d7739546368e:draft 'I' + | + o 6:03772da75548bb42a8f1eacd8c91d0717a147fcd:draft 'X' + | + o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + | + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' + + $ hg histedit 5 --commands - 2>&1 << EOF | fixbundle + > pick d273e35dcdf2 B + > pick 03772da75548 X + > base d273e35dcdf2 B + > pick e8c55b19d366 J + > base d273e35dcdf2 B + > pick b2f90fd8aa85 I + > EOF + hg: parse error: base "643237336533" changeset was not an edited list candidate + (only use listed changesets) + + $ hg --config experimental.histeditng=False histedit 5 --commands - 2>&1 << EOF | fixbundle + > base cd010b8cd998 A + > pick d273e35dcdf2 B + > pick 03772da75548 X + > pick b2f90fd8aa85 I + > pick e8c55b19d366 J + > EOF + hg: parse error: unknown action "base" + + $ hg tglog + @ 8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J' + | + o 7:b2f90fd8aa85db5569e3cfc30cd1d7739546368e:draft 'I' + | + o 6:03772da75548bb42a8f1eacd8c91d0717a147fcd:draft 'X' + | + o 5:d273e35dcdf21a7eb305192ef2e362887cd0a6f8:draft 'B' + | + | o 4:02de42196ebee42ef284b6780a87cdc96e8eaab6:draft 'H' + | | + | | o 3:eea13746799a9e0bfd88f29d3c2e9dc9389f524f:draft 'G' + | |/| + | o | 2:24b6387c8c8cae37178880f3fa95ded3cb1cf785:draft 'F' + |/ / + | o 1:9520eea781bcca16c1e15acc0ba14335a0e8e5ba:draft 'E' + |/ + o 0:cd010b8cd998f3981a5a8115f94f8da4ab506089:draft 'A' +
--- a/tests/test-histedit-bookmark-motion.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-bookmark-motion.t Sun Jan 17 21:40:21 2016 -0600 @@ -70,14 +70,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg histedit 1 --commands - --verbose << EOF | grep histedit > pick 177f92b77385 2 c > drop d2ae7f538514 1 b @@ -131,14 +131,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg histedit 1 --commands - --verbose << EOF | grep histedit > pick b346ab9a313d 1 c > pick cacdfd884a93 3 f
--- a/tests/test-histedit-commute.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-commute.t Sun Jan 17 21:40:21 2016 -0600 @@ -64,20 +64,40 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved edit the history (use a hacky editor to check histedit-last-edit.txt backup) $ EDITED="$TESTTMP/editedhistory" $ cat > $EDITED <<EOF + > edit 177f92b77385 c + > pick e860deea161a e + > pick 652413bf663e f + > pick 055a42cdd887 d + > EOF + $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + Editing (177f92b77385), you may commit or record as needed now. + (hg histedit --continue to resume) + +rules should end up in .hg/histedit-last-edit.txt: + $ cat .hg/histedit-last-edit.txt + edit 177f92b77385 c + pick e860deea161a e + pick 652413bf663e f + pick 055a42cdd887 d + + $ hg histedit --abort + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat > $EDITED <<EOF > pick 177f92b77385 c > pick e860deea161a e > pick 652413bf663e f @@ -85,16 +105,6 @@ > EOF $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - -rules should end up in .hg/histedit-last-edit.txt: - $ cat .hg/histedit-last-edit.txt - pick 177f92b77385 c - pick e860deea161a e - pick 652413bf663e f - pick 055a42cdd887 d log after edit $ hg log --graph @@ -139,9 +149,6 @@ > pick 8ade9693061e f > EOF 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 --graph @ changeset: 5:7eca9b5b1148 @@ -185,10 +192,6 @@ > pick 177f92b77385 c > EOF 0 files updated, 0 files merged, 4 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 --graph @ changeset: 5:38b92f448761 | tag: tip @@ -230,8 +233,6 @@ > pick de71b079d9ce e > EOF 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - 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 --graph @ changeset: 7:803ef1c6fcfd | tag: tip @@ -281,7 +282,8 @@ > pick de71b079d9ce e > pick 38b92f448761 c > EOF - abort: may not use changesets other than the ones listed + hg: parse error: pick "646537316230" changeset was not a candidate + (only use listed changesets) $ hg log --graph @ changeset: 7:803ef1c6fcfd | tag: tip @@ -342,14 +344,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved should also work if a commit message is missing $ BUNDLE="$TESTDIR/missing-comment.hg" @@ -380,7 +382,6 @@ summary: Checked in text file $ hg histedit 0 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. $ cd .. @@ -421,10 +422,8 @@ removing initial-dir/initial-file (glob) 0 files updated, 0 files merged, 1 files removed, 0 files unresolved 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 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/issue4251/.hg/strip-backup/*-backup.hg (glob) - saved backup bundle to $TESTTMP/issue4251/.hg/strip-backup/b0f4233702ca-d99e7186-backup.hg (glob) + saved backup bundle to $TESTTMP/issue4251/.hg/strip-backup/*-backup.hg (glob) $ hg --config diff.git=yes export 0 # HG changeset patch
--- a/tests/test-histedit-drop.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-drop.t Sun Jan 17 21:40:21 2016 -0600 @@ -60,9 +60,6 @@ > pick 055a42cdd887 d > EOF 0 files updated, 0 files merged, 4 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved log after edit $ hg log --graph @@ -151,4 +148,26 @@ summary: a - $ cd .. + $ hg histedit cb9a9f314b8b --commands - 2>&1 << EOF | fixbundle + > pick cb9a9f314b8b a + > pick ee283cb5f2d5 e + > EOF + hg: parse error: missing rules for changeset a4f7421b80f7 + (use "drop a4f7421b80f7" to discard, see also: "hg help -e histedit.config") + $ hg --config histedit.dropmissing=True histedit cb9a9f314b8b --commands - 2>&1 << EOF | fixbundle + > pick cb9a9f314b8b a + > pick ee283cb5f2d5 e + > EOF + 0 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ hg log --graph + @ changeset: 1:e99c679bf03e + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: e + | + o changeset: 0:cb9a9f314b8b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a +
--- a/tests/test-histedit-edit.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-edit.t Sun Jan 17 21:40:21 2016 -0600 @@ -57,6 +57,13 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: a +dirty a file + $ echo a > g + $ hg histedit 177f92b77385 --commands - 2>&1 << EOF + > EOF + abort: uncommitted changes + [255] + $ echo g > g edit the history $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle @@ -67,8 +74,8 @@ > pick 3c6a8ed2ebe8 g > EOF 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (e860deea161a), you may commit or record as needed now. + (hg histedit --continue to resume) edit the plan via the editor $ cat >> $TESTTMP/editplan.sh <<EOF @@ -141,8 +148,6 @@ (use 'hg histedit --continue' or 'hg histedit --abort') [255] $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle - 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 --graph @ changeset: 6:b5f70786f9b0 @@ -193,8 +198,8 @@ > pick b5f70786f9b0 g > EOF 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (1a60820cd1f6), you may commit or record as needed now. + (hg histedit --continue to resume) $ mv .hg/histedit-state .hg/histedit-state.bak $ hg strip -q -r b5f70786f9b0 @@ -235,8 +240,8 @@ > edit b5f70786f9b0 f > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (b5f70786f9b0), you may commit or record as needed now. + (hg histedit --continue to resume) $ hg status A f @@ -262,7 +267,6 @@ HG: user: test HG: branch 'default' HG: added f - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-backup.hg (glob) $ hg status @@ -283,7 +287,6 @@ > EOF $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg status $ hg log --limit 1 changeset: 6:1fd3b2fe7754 @@ -364,9 +367,9 @@ HG: branch 'default' HG: added f ==== + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt abort: pretxncommit.unexpectedabort hook exited with status 1 [255] $ cat .hg/last-message.txt @@ -388,9 +391,9 @@ HG: user: test HG: branch 'default' HG: added f + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt abort: pretxncommit.unexpectedabort hook exited with status 1 [255] @@ -406,7 +409,6 @@ > mess 1fd3b2fe7754 f > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg status $ hg log --limit 1 changeset: 6:62feedb1200e @@ -430,11 +432,10 @@ > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved adding a - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (cb9a9f314b8b), you may commit or record as needed now. + (hg histedit --continue to resume) [1] $ 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 @@ -457,11 +458,22 @@ > 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 + warning: histedit rules saved to: .hg/histedit-last-edit.txt + hg: parse error: 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 + $ cat .hg/histedit-last-edit.txt + fold 0012be4a27ea 2 extend a + + # Edit history between 0012be4a27ea and 0012be4a27ea + # + # Commits are listed from least to most recent + # + # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content + # p, fold = use commit + # d, drop = remove commit from history + # f, fold = use commit, but combine it with the one above + # r, roll = like fold, but discard this commit's description + #
--- a/tests/test-histedit-fold-non-commute.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-fold-non-commute.t Sun Jan 17 21:40:21 2016 -0600 @@ -89,12 +89,14 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (fold 39522b764e3d) + (hg histedit --continue to resume) fix up $ echo 'I can haz no commute' > e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ cat > cat.py <<EOF > import sys > print open(sys.argv[1]).read() @@ -120,19 +122,18 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick 7b4e2f4b7bcd) + (hg histedit --continue to resume) just continue this time $ hg revert -r 'p1()' e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle 7b4e2f4b7bcd: empty changeset - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved log after edit $ hg log --graph @@ -252,28 +253,29 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (roll 39522b764e3d) + (hg histedit --continue to resume) fix up $ echo 'I can haz no commute' > e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed' 2 files updated, 0 files merged, 0 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 merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick 7b4e2f4b7bcd) + (hg histedit --continue to resume) just continue this time $ hg revert -r 'p1()' e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle 7b4e2f4b7bcd: empty changeset - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved log after edit $ hg log --graph
--- a/tests/test-histedit-fold.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-fold.t Sun Jan 17 21:40:21 2016 -0600 @@ -55,12 +55,8 @@ > pick 055a42cdd887 d > EOF 0 files updated, 0 files merged, 4 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 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 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved log after edit $ hg logt --graph @@ -118,9 +114,6 @@ 0 files updated, 0 files merged, 4 files removed, 0 files unresolved 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 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ HGEDITOR=$OLDHGEDITOR @@ -241,14 +234,17 @@ summary: f - $ hg histedit e860deea161a --commands - 2>&1 <<EOF | fixbundle + $ hg --config progress.debug=1 --debug \ + > histedit e860deea161a --commands - 2>&1 <<EOF | \ + > egrep 'editing|unresolved' > pick e860deea161a e > fold a00ad806cb55 f > EOF + editing: pick e860deea161a 4 e 1/2 changes (50.00%) + editing: fold a00ad806cb55 5 f 2/2 changes (100.00%) 0 files updated, 0 files merged, 1 files removed, 0 files unresolved 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 tip after edit $ hg log --rev . @@ -297,7 +293,8 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved merging file warning: conflicts while merging file! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (fold 251d831eeec5) + (hg histedit --continue to resume) [1] There were conflicts, we keep P1 content. This should effectively drop the changes from +6. @@ -309,9 +306,9 @@ $ hg revert -r 'p1()' file $ hg resolve --mark file (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 251d831eeec5: empty changeset - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/*-backup.hg (glob) $ hg logt --graph @ 1:617f94f13c0f +4 @@ -358,7 +355,8 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved merging file warning: conflicts while merging file! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (fold 251d831eeec5) + (hg histedit --continue to resume) [1] $ cat > file << EOF > 1 @@ -369,6 +367,7 @@ > EOF $ hg resolve --mark file (no more unresolved files) + continue: hg histedit --continue $ hg commit -m '+5.2' created new head $ echo 6 >> file @@ -389,7 +388,6 @@ HG: branch 'default' HG: changed file 1 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/fold-with-dropped/.hg/strip-backup/55c8d8dc79ce-4066cd98-backup.hg (glob) saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-a35700fc-backup.hg (glob) $ hg logt -G @@ -449,7 +447,6 @@ reverting b.txt 1 files updated, 0 files merged, 1 files removed, 0 files unresolved 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg logt --follow b.txt 1:cf858d235c76 rename @@ -493,11 +490,8 @@ > pick 6c795aa153cb a > EOF 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 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 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved commit 9599899f62c05f4377548c32bf1c9f1a39634b0c $ hg logt @@ -524,13 +518,13 @@ 2:0e01aeef5fa8 foo1 1:578c7455730c a 0:79b99e9c8e49 b - $ cat > $TESTTMP/editor.sh <<EOF - > echo ran editor >> $TESTTMP/editorlog.txt - > cat \$1 >> $TESTTMP/editorlog.txt - > echo END >> $TESTTMP/editorlog.txt + $ cat > "$TESTTMP/editor.sh" <<EOF + > echo ran editor >> "$TESTTMP/editorlog.txt" + > cat \$1 >> "$TESTTMP/editorlog.txt" + > echo END >> "$TESTTMP/editorlog.txt" > echo merged foos > \$1 > EOF - $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 1 --commands - 2>&1 <<EOF | fixbundle + $ HGEDITOR="sh \"$TESTTMP/editor.sh\"" hg histedit 1 --commands - 2>&1 <<EOF | fixbundle > pick 578c7455730c 1 a > pick 0e01aeef5fa8 2 foo1 > fold b7389cc4d66e 3 foo2 @@ -540,11 +534,9 @@ reverting foo 0 files updated, 0 files merged, 1 files removed, 0 files unresolved 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging foo 0 files updated, 0 files merged, 1 files removed, 0 files unresolved 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 logt 2:e8bedbda72c1 merged foos 1:578c7455730c a
--- a/tests/test-histedit-no-change.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-no-change.t Sun Jan 17 21:40:21 2016 -0600 @@ -91,12 +91,10 @@ | edit e860deea161a 4 e | pick 652413bf663e 5 f 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (e860deea161a), you may commit or record as needed now. + (hg histedit --continue to resume) $ continueediting true "(leaving commit message unaltered)" % finalize changeset editing (leaving commit message unaltered) - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved check state of working copy @@ -144,13 +142,12 @@ | edit e860deea161a 4 e | pick 652413bf663e 5 f 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (055a42cdd887), you may commit or record as needed now. + (hg histedit --continue to resume) $ continueediting true "(leaving commit message unaltered)" % finalize changeset editing (leaving commit message unaltered) - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (e860deea161a), you may commit or record as needed now. + (hg histedit --continue to resume) $ graphlog "log after first edit" % log after first edit @ 6 e5ae3ca2f1ffdbd89ec41ebc273a231f7c3022f2 "d" @@ -187,7 +184,6 @@ hist: 2 remaining (histedit --continue) $ hg histedit --abort 2>&1 | fixbundle - [1] modified files should survive the abort when we've moved away already $ hg st @@ -208,5 +204,15 @@ | @ 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a" +aborting and not changing files can skip mentioning updating (no) files + $ hg up + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg commit --close-branch -m 'closebranch' + $ startediting 1 1 "(not changing anything)" # edit the 3rd of 3 changesets + % start editing the history (not changing anything) + | edit 292aec348d9e 6 closebranch + Editing (292aec348d9e), you may commit or record as needed now. + (hg histedit --continue to resume) + $ hg histedit --abort $ cd ..
--- a/tests/test-histedit-non-commute-abort.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-non-commute-abort.t Sun Jan 17 21:40:21 2016 -0600 @@ -70,15 +70,50 @@ > pick 652413bf663e f > EOF 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick e860deea161a) + (hg histedit --continue to resume) + +insert unsupported advisory merge record + $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x + $ hg debugmergestate + * version 2 records + local: 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758 + other: e860deea161a2f77de56603b340ebbb4536308ae + unrecognized entry: x advisory record + file: e (record type "F", state "u", hash 58e6b3a414a1e090dfc6029add0f3555ccba127f) + local path: e (flags "") + ancestor path: e (node null) + other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f) + $ hg resolve -l + U e +insert unsupported mandatory merge record + $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X + $ hg debugmergestate + * version 2 records + local: 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758 + other: e860deea161a2f77de56603b340ebbb4536308ae + file: e (record type "F", state "u", hash 58e6b3a414a1e090dfc6029add0f3555ccba127f) + local path: e (flags "") + ancestor path: e (node null) + other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f) + unrecognized entry: X mandatory record + $ hg resolve -l + abort: unsupported merge state records: X + (see https://mercurial-scm.org/wiki/MergeStateRecords for more information) + [255] + $ hg resolve -ma + abort: unsupported merge state records: X + (see https://mercurial-scm.org/wiki/MergeStateRecords for more information) + [255] -abort the edit +abort the edit (should clear out merge state) $ hg histedit --abort 2>&1 | fixbundle 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg debugmergestate + no merge state found log after abort $ hg resolve -l
--- a/tests/test-histedit-non-commute.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-non-commute.t Sun Jan 17 21:40:21 2016 -0600 @@ -90,7 +90,8 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick 39522b764e3d) + (hg histedit --continue to resume) abort the edit $ hg histedit --abort 2>&1 | fixbundle @@ -147,17 +148,19 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick 39522b764e3d) + (hg histedit --continue to resume) fix up $ echo 'I can haz no commute' > e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick 7b4e2f4b7bcd) + (hg histedit --continue to resume) This failure is caused by 7b4e2f4b7bcd "e" not rebasing the non commutative former children. @@ -166,10 +169,9 @@ $ hg revert -r 'p1()' e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle 7b4e2f4b7bcd: empty changeset - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved log after edit $ hg log --graph @@ -234,24 +236,25 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (mess 39522b764e3d) + (hg histedit --continue to resume) $ echo 'I can haz no commute' > e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') - Fix up the change and run hg histedit --continue + Fix up the change (pick 7b4e2f4b7bcd) + (hg histedit --continue to resume) second edit also fails, but just continue $ hg revert -r 'p1()' e $ hg resolve --mark e (no more unresolved files) + continue: hg histedit --continue $ hg histedit --continue 2>&1 | fixbundle 7b4e2f4b7bcd: empty changeset - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved post message fix $ hg log --graph
--- a/tests/test-histedit-obsolete.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-obsolete.t Sun Jan 17 21:40:21 2016 -0600 @@ -49,14 +49,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg histedit 1 --commands - --verbose <<EOF | grep histedit > pick 177f92b77385 2 c > drop d2ae7f538514 1 b @@ -109,7 +109,6 @@ > pick cacdfd884a93 8 f > EOF 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 @ 11:c13eb81022ca f | @@ -123,7 +122,6 @@ > pick b346ab9a313d 6 c > pick c13eb81022ca 8 f > EOF - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -139,12 +137,11 @@ > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved adding c - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (b346ab9a313d), you may commit or record as needed now. + (hg histedit --continue to resume) [1] $ echo c >> c $ hg histedit --continue - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -r 'unstable()' 11:c13eb81022ca f (no-eol) @@ -155,6 +152,14 @@ rebasing 11:c13eb81022ca "f" $ hg up tip -q +check that extra has accumulated from histedit and rebase + + $ hg log -T '{extras % "{key}={value}\n"}\n' -r tip + branch=default + histedit_source=cacdfd884a9321ec4e1de275ef3949fa953a1f83 + rebase_source=c13eb81022caa686a369223fe7f926bc4f7db576 + + Test dropping of changeset on the top of the stack ------------------------------------------------------- @@ -168,7 +173,7 @@ $ cd droplast $ hg histedit -r '40db8afa467b' --commands - << EOF > pick 40db8afa467b 10 c - > drop b449568bf7fc 11 f + > drop 947ece25170f 11 f > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -G @@ -191,8 +196,6 @@ > drop 1b3b05f35ff0 13 h > EOF 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - 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 @ 17:ee6544123ab8 c | @@ -218,7 +221,7 @@ $ hg ph -pv '.^' phase changed for 2 changesets $ hg log -G - @ 13:b449568bf7fc (draft) f + @ 13:947ece25170f (draft) f | o 12:40db8afa467b (public) c | @@ -240,17 +243,17 @@ > done $ hg phase --force --secret .~2 $ hg log -G - @ 18:ee118ab9fa44 (secret) k + @ 18:14bda137d5b3 (secret) k | - o 17:3a6c53ee7f3d (secret) j + o 17:c62e7241a4f2 (secret) j | - o 16:b605fb7503f2 (secret) i + o 16:9cd3934e05af (secret) i | - o 15:7395e1ff83bd (draft) h + o 15:ee4a24fc4dfa (draft) h | - o 14:6b70183d2492 (draft) g + o 14:d22905de3528 (draft) g | - o 13:b449568bf7fc (draft) f + o 13:947ece25170f (draft) f | o 12:40db8afa467b (public) c | @@ -268,39 +271,33 @@ $ cp -r base simple-draft $ cd simple-draft - $ hg histedit -r 'b449568bf7fc' --commands - << EOF - > edit b449568bf7fc 11 f - > pick 6b70183d2492 12 g - > pick 7395e1ff83bd 13 h - > pick b605fb7503f2 14 i - > pick 3a6c53ee7f3d 15 j - > pick ee118ab9fa44 16 k + $ hg histedit -r '947ece25170f' --commands - << EOF + > edit 947ece25170f 11 f + > pick d22905de3528 12 g + > pick ee4a24fc4dfa 13 h + > pick 9cd3934e05af 14 i + > pick c62e7241a4f2 15 j + > pick 14bda137d5b3 16 k > EOF 0 files updated, 0 files merged, 6 files removed, 0 files unresolved adding f - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (947ece25170f), you may commit or record as needed now. + (hg histedit --continue to resume) [1] $ echo f >> f $ hg histedit --continue - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 - @ 24:12e89af74238 (secret) k + @ 24:12925f763c90 (secret) k + | + o 23:4545a6e77442 (secret) j | - o 23:636a8687b22e (secret) j + o 22:d947a0798e76 (secret) i | - o 22:ccaf0a38653f (secret) i + o 21:28fb35ae4ebb (draft) h | - o 21:11a89d1c2613 (draft) h + o 20:10b22a5a9645 (draft) g | - o 20:c1dec7ca82ea (draft) g - | - o 19:087281e68428 (draft) f + o 19:c5a1db4a69f5 (draft) f | o 12:40db8afa467b (public) c | @@ -317,39 +314,33 @@ > [phases] > new-commit=secret > EOF - $ hg histedit -r 'b449568bf7fc' --commands - << EOF - > edit b449568bf7fc 11 f - > pick 6b70183d2492 12 g - > pick 7395e1ff83bd 13 h - > pick b605fb7503f2 14 i - > pick 3a6c53ee7f3d 15 j - > pick ee118ab9fa44 16 k + $ hg histedit -r '947ece25170f' --commands - << EOF + > edit 947ece25170f 11 f + > pick d22905de3528 12 g + > pick ee4a24fc4dfa 13 h + > pick 9cd3934e05af 14 i + > pick c62e7241a4f2 15 j + > pick 14bda137d5b3 16 k > EOF 0 files updated, 0 files merged, 6 files removed, 0 files unresolved adding f - Make changes as needed, you may commit or record as needed now. - When you are finished, run hg histedit --continue to resume. + Editing (947ece25170f), you may commit or record as needed now. + (hg histedit --continue to resume) [1] $ echo f >> f $ hg histedit --continue - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 - @ 24:12e89af74238 (secret) k + @ 24:12925f763c90 (secret) k + | + o 23:4545a6e77442 (secret) j | - o 23:636a8687b22e (secret) j + o 22:d947a0798e76 (secret) i | - o 22:ccaf0a38653f (secret) i + o 21:28fb35ae4ebb (draft) h | - o 21:11a89d1c2613 (draft) h + o 20:10b22a5a9645 (draft) g | - o 20:c1dec7ca82ea (draft) g - | - o 19:087281e68428 (draft) f + o 19:c5a1db4a69f5 (draft) f | o 12:40db8afa467b (public) c | @@ -366,32 +357,27 @@ $ cp -r base reorder $ cd reorder - $ hg histedit -r 'b449568bf7fc' --commands - << EOF - > pick b449568bf7fc 11 f - > pick 3a6c53ee7f3d 15 j - > pick 6b70183d2492 12 g - > pick b605fb7503f2 14 i - > pick 7395e1ff83bd 13 h - > pick ee118ab9fa44 16 k + $ hg histedit -r '947ece25170f' --commands - << EOF + > pick 947ece25170f 11 f + > pick c62e7241a4f2 15 j + > pick d22905de3528 12 g + > pick 9cd3934e05af 14 i + > pick ee4a24fc4dfa 13 h + > pick 14bda137d5b3 16 k > EOF 0 files updated, 0 files merged, 5 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 - @ 23:558246857888 (secret) k + @ 23:9e712162b2c1 (secret) k + | + o 22:490861543602 (secret) h | - o 22:28bd44768535 (secret) h + o 21:86aeda50b70d (secret) i | - o 21:d5395202aeb9 (secret) i + o 20:b2fa360bc090 (secret) g | - o 20:21edda8e341b (secret) g + o 19:e10fb4e3eb8e (secret) j | - o 19:5ab64f3a4832 (secret) j - | - o 13:b449568bf7fc (draft) f + o 13:947ece25170f (draft) f | o 12:40db8afa467b (public) c | @@ -413,51 +399,45 @@ > [phases] > new-commit=secret > EOF - $ hg histedit -r 'b449568bf7fc' --commands - << EOF - > pick 7395e1ff83bd 13 h - > fold b449568bf7fc 11 f - > pick 6b70183d2492 12 g - > fold 3a6c53ee7f3d 15 j - > pick b605fb7503f2 14 i - > fold ee118ab9fa44 16 k + $ hg histedit -r '947ece25170f' --commands - << EOF + > pick ee4a24fc4dfa 13 h + > fold 947ece25170f 11 f + > pick d22905de3528 12 g + > fold c62e7241a4f2 15 j + > pick 9cd3934e05af 14 i + > fold 14bda137d5b3 16 k > EOF 0 files updated, 0 files merged, 6 files removed, 0 files unresolved - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 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 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 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 - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved 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 $ hg log -G - @ 27:f9daec13fb98 (secret) i + @ 27:769e8ee8708e (secret) i | - o 24:49807617f46a (secret) g + o 24:3de6dbab1b62 (secret) g | - o 21:050280826e04 (draft) h + o 21:1d51647632b2 (draft) h | o 12:40db8afa467b (public) c | o 0:cb9a9f314b8b (public) a - $ hg co 49807617f46a + $ hg co 3de6dbab1b62 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 f9daec13fb98 + $ hg merge 769e8ee8708e 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 050280826e04 + $ hg histedit 1d51647632b2 abort: cannot edit history that contains merges [255] $ cd ..
--- a/tests/test-histedit-outgoing.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-histedit-outgoing.t Sun Jan 17 21:40:21 2016 -0600 @@ -46,14 +46,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. show the error from unrelated repos @@ -78,14 +78,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. test sensitivity to branch in URL: @@ -102,14 +102,14 @@ # Commits are listed from least to most recent # # Commands: + # + # e, edit = use commit, but stop for amending + # m, mess = edit commit message without changing commit content # p, pick = use commit - # e, edit = use commit, but stop for amending + # d, drop = remove commit from history # f, fold = use commit, but combine it with the one above # r, roll = like fold, but discard this commit's description - # d, drop = remove commit from history - # m, mess = edit commit message without changing commit content # - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved test to check number of roots in outgoing revisions
--- a/tests/test-hook.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-hook.t Sun Jan 17 21:40:21 2016 -0600 @@ -81,10 +81,10 @@ pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a 2:ee9deb46ab31 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob) + created new head txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob) commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b - created new head $ hg merge 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -118,7 +118,7 @@ adding manifests adding file changes added 3 changesets with 2 changes to 2 files - changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) + changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) @@ -326,7 +326,7 @@ adding file changes added 1 changesets with 1 changes to 1 files 4:539e4b31b6dc - pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) + pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_NODE_LAST=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob) transaction abort! rollback completed abort: pretxnchangegroup.forbid1 hook exited with status 1 @@ -563,9 +563,9 @@ foo committing manifest committing changelog + committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708 calling hook commit.auto: hgext_hookext.autohook Automatically installed hook - committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708 $ hg showconfig hooks hooks.commit.auto=<function autohook at *> (glob)
--- a/tests/test-http-bundle1.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-http-bundle1.t Sun Jan 17 21:40:21 2016 -0600 @@ -136,7 +136,7 @@ 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) + changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT1/ (glob) (run 'hg update' to get a working copy) $ cd ..
--- a/tests/test-http.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-http.t Sun Jan 17 21:40:21 2016 -0600 @@ -127,7 +127,7 @@ 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) + changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT1/ (glob) (run 'hg update' to get a working copy) $ cd ..
--- a/tests/test-https.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-https.t Sun Jan 17 21:40:21 2016 -0600 @@ -210,7 +210,7 @@ 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=https://localhost:$HGPORT/ (glob) + changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob) (run 'hg update' to get a working copy) $ cd ..
--- a/tests/test-import.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-import.t Sun Jan 17 21:40:21 2016 -0600 @@ -1735,7 +1735,10 @@ > mercurial.cmdutil.extrapostimport.append('foo') > mercurial.cmdutil.extrapostimportmap['foo'] = postimport > EOF - $ printf "[extensions]\nparseextra=$TESTTMP/parseextra.py" >> $HGRCPATH + $ cat >> $HGRCPATH <<EOF + > [extensions] + > parseextra=$TESTTMP/parseextra.py + > EOF $ hg up -C tip 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat > $TESTTMP/foo.patch <<EOF
--- a/tests/test-inherit-mode.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-inherit-mode.t Sun Jan 17 21:40:21 2016 -0600 @@ -83,6 +83,7 @@ 00660 ./.hg/store/undo 00660 ./.hg/store/undo.backupfiles 00660 ./.hg/store/undo.phaseroots + 00660 ./.hg/undo.backup.dirstate 00660 ./.hg/undo.bookmarks 00660 ./.hg/undo.branch 00660 ./.hg/undo.desc
--- a/tests/test-init.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-init.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,12 @@ This test tries to exercise the ssh functionality with a dummy script +(enable general delta early) + + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + $ checknewrepo() > { > name=$1 @@ -20,6 +27,7 @@ 00changelog.i created dotencode fncache + generaldelta revlogv1 store $ echo this > local/foo @@ -55,6 +63,7 @@ $ hg --config format.usestore=false init old $ checknewrepo old + generaldelta revlogv1 creating repo with format.usefncache=false @@ -63,6 +72,7 @@ $ checknewrepo old2 store created 00changelog.i created + generaldelta revlogv1 store @@ -73,6 +83,18 @@ store created 00changelog.i created fncache + generaldelta + revlogv1 + store + +creating repo with format.dotencode=false + + $ hg --config format.generaldelta=false --config format.usegeneraldelta=false init old4 + $ checknewrepo old4 + store created + 00changelog.i created + dotencode + fncache revlogv1 store @@ -186,6 +208,7 @@ 00changelog.i created dotencode fncache + generaldelta revlogv1 store @@ -203,6 +226,7 @@ 00changelog.i created dotencode fncache + generaldelta revlogv1 store @@ -216,6 +240,7 @@ 00changelog.i created dotencode fncache + generaldelta revlogv1 store
--- a/tests/test-install.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-install.t Sun Jan 17 21:40:21 2016 -0600 @@ -41,3 +41,71 @@ checking commit editor... checking username... no problems detected + +#if test-repo + $ cat >> wixxml.py << EOF + > import os, subprocess, sys + > import xml.etree.ElementTree as ET + > + > # MSYS mangles the path if it expands $TESTDIR + > testdir = os.environ['TESTDIR'] + > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'} + > + > def directory(node, relpath): + > '''generator of files in the xml node, rooted at relpath''' + > dirs = node.findall('./{%(wix)s}Directory' % ns) + > + > for d in dirs: + > for subfile in directory(d, relpath + d.attrib['Name'] + '/'): + > yield subfile + > + > files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns) + > + > for f in files: + > yield relpath + f.attrib['Name'] + > + > def hgdirectory(relpath): + > '''generator of tracked files, rooted at relpath''' + > hgdir = "%s/../mercurial" % (testdir) + > args = ['hg', '--cwd', hgdir, 'files', relpath] + > proc = subprocess.Popen(args, stdout=subprocess.PIPE, + > stderr=subprocess.PIPE) + > output = proc.communicate()[0] + > + > slash = '/' + > for line in output.splitlines(): + > if os.name == 'nt': + > yield line.replace(os.sep, slash) + > else: + > yield line + > + > tracked = [f for f in hgdirectory(sys.argv[1])] + > + > xml = ET.parse("%s/../contrib/wix/%s.wxs" % (testdir, sys.argv[1])) + > root = xml.getroot() + > dir = root.find('.//{%(wix)s}DirectoryRef' % ns) + > + > installed = [f for f in directory(dir, '')] + > + > print('Not installed:') + > for f in sorted(set(tracked) - set(installed)): + > print(' %s' % f) + > + > print('Not tracked:') + > for f in sorted(set(installed) - set(tracked)): + > print(' %s' % f) + > EOF + + $ python wixxml.py help + Not installed: + help/common.txt + help/hg.1.txt + help/hgignore.5.txt + help/hgrc.5.txt + Not tracked: + + $ python wixxml.py templates + Not installed: + Not tracked: + +#endif
--- a/tests/test-issue1175.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-issue1175.t Sun Jan 17 21:40:21 2016 -0600 @@ -78,6 +78,7 @@ $ echo b3 >> b $ hg resolve --mark b (no more unresolved files) + continue: hg graft --continue $ hg graft --continue grafting 1:5974126fad84 "b1" warning: can't find ancestor for 'b' copied from 'a'!
--- a/tests/test-issue1502.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-issue1502.t Sun Jan 17 21:40:21 2016 -0600 @@ -19,8 +19,9 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) - not updating: not a linear update + abort: not updating: not a linear update (merge or update --check to force update) + [255] $ hg -R foo1 book branchy $ hg -R foo1 book
--- a/tests/test-issue672.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-issue672.t Sun Jan 17 21:40:21 2016 -0600 @@ -66,7 +66,7 @@ ancestor: c64f439569a9, local: e327dca35ac8+, remote: 746e9549ea96 preserving 1a for resolve of 1a 1a: local copied/moved from 1 -> m (premerge) - picked tool ':merge' for 1a (binary False symlink False) + picked tool ':merge' for 1a (binary False symlink False changedelete False) merging 1a and 1 to 1a my 1a@e327dca35ac8+ other 1@746e9549ea96 ancestor 1@81f4b099af3d premerge successful @@ -89,7 +89,7 @@ preserving 1 for resolve of 1a removing 1 1a: remote moved from 1 -> m (premerge) - picked tool ':merge' for 1a (binary False symlink False) + picked tool ':merge' for 1a (binary False symlink False changedelete False) merging 1 and 1a to 1a my 1a@746e9549ea96+ other 1a@e327dca35ac8 ancestor 1@81f4b099af3d premerge successful
--- a/tests/test-keyword.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-keyword.t Sun Jan 17 21:40:21 2016 -0600 @@ -141,8 +141,8 @@ committing manifest committing changelog overwriting a expanding keywords + committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9 running hook commit.test: cp a hooktest - committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9 $ hg status ? hooktest $ hg debugrebuildstate @@ -1204,7 +1204,7 @@ +xxxx $ hg shelve -q --name tmp $ hg shelve --list --patch - tmp (*) changes to 'localresolve' (glob) + tmp (*)* changes to: localresolve (glob) diff --git a/a b/a --- a/a @@ -1288,7 +1288,7 @@ Test restricted mode with backout - $ hg backout -q 11 + $ hg backout -q 11 --no-commit $ hg diff a diff -r 01a68de1003a a --- a/a Thu Jan 01 00:00:00 1970 +0000
--- a/tests/test-largefiles-cache.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-largefiles-cache.t Sun Jan 17 21:40:21 2016 -0600 @@ -193,7 +193,7 @@ $ echo corruption > .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 $ hg up -C getting changed largefiles - large: data corruption in $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 with hash 6a7bb2556144babe3899b25e5428123735bb1e27 + large: data corruption in $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 with hash 6a7bb2556144babe3899b25e5428123735bb1e27 (glob) 0 largefiles updated, 0 removed 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg st
--- a/tests/test-largefiles-misc.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-largefiles-misc.t Sun Jan 17 21:40:21 2016 -0600 @@ -509,6 +509,18 @@ $ hg revert anotherlarge $ hg st ? sub/anotherlarge.orig + +Test orig files go where we want them + $ echo moremore >> anotherlarge + $ hg revert anotherlarge -v --config 'ui.origbackuppath=.hg/origbackups' + creating directory: $TESTTMP/addrm2/.hg/origbackups/.hglf/sub (glob) + saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge.orig (glob) + reverting ../.hglf/sub/anotherlarge (glob) + creating directory: $TESTTMP/addrm2/.hg/origbackups/sub (glob) + found 90c622cf65cebe75c5842f9136c459333faf392e in store + found 90c622cf65cebe75c5842f9136c459333faf392e in store + $ ls ../.hg/origbackups/sub + anotherlarge.orig $ cd .. Test glob logging from the root dir
--- a/tests/test-largefiles-update.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-largefiles-update.t Sun Jan 17 21:40:21 2016 -0600 @@ -158,7 +158,15 @@ 0 largefiles updated, 0 removed 0 files updated, 2 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg commit -m '1-2-3 testing' + $ hg commit -m '1-2-3 testing' --config largefiles.usercache=not + large1: largefile 58e24f733a964da346e2407a2bee99d9001184f5 not available from local store + $ hg up -C . --config largefiles.usercache=not + getting changed largefiles + large1: largefile 58e24f733a964da346e2407a2bee99d9001184f5 not available from file:/*/$TESTTMP/repo (glob) + 0 largefiles updated, 0 removed + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg st large1 + ! large1 $ hg rollback -q $ mv 58e24f733a964da346e2407a2bee99d9001184f5 .hg/largefiles/ @@ -590,13 +598,14 @@ $ echo "manually modified before 'hg rebase --continue'" > large1 $ hg resolve -m normal1 (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue --config ui.interactive=True <<EOF > c > EOF rebasing 1:72518492caa6 "#1" rebasing 4:07d6153b5c04 "#4" local changed .hglf/large1 which remote deleted - use (c)hanged version or (d)elete? c + use (c)hanged version, (d)elete, or leave (u)nresolved? c $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]' -e5bb990443d6a92aaf7223813720f7566c9dd05b @@ -622,7 +631,7 @@ Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file .hglf/large1.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue [255] $ hg status -A large1 C large1
--- a/tests/test-lfconvert.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-lfconvert.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,6 +1,8 @@ $ USERCACHE="$TESTTMP/cache"; export USERCACHE $ mkdir "${USERCACHE}" $ cat >> $HGRCPATH <<EOF + > [format] + > usegeneraldelta=yes > [extensions] > largefiles = > share = @@ -97,6 +99,7 @@ $ cat .hg/requires dotencode fncache + generaldelta largefiles revlogv1 store
--- a/tests/test-lrucachedict.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-lrucachedict.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,11 +1,11 @@ from mercurial import util -def printifpresent(d, xs): +def printifpresent(d, xs, name='d'): for x in xs: present = x in d - print "'%s' in d: %s" % (x, present) + print "'%s' in %s: %s" % (x, name, present) if present: - print "d['%s']: %s" % (x, d[x]) + print "%s['%s']: %s" % (name, x, d[x]) def test_lrucachedict(): d = util.lrucachedict(4) @@ -34,5 +34,37 @@ d.clear() printifpresent(d, ['b', 'c', 'd', 'e', 'f']) + # Now test dicts that aren't full. + d = util.lrucachedict(4) + d['a'] = 1 + d['b'] = 2 + d['a'] + d['b'] + printifpresent(d, ['a', 'b']) + + # test copy method + d = util.lrucachedict(4) + d['a'] = 'va3' + d['b'] = 'vb3' + d['c'] = 'vc3' + d['d'] = 'vd3' + + dc = d.copy() + + # all of these should be present + print "\nAll of these should be present:" + printifpresent(dc, ['a', 'b', 'c', 'd'], 'dc') + + # 'a' should be dropped because it was least recently used + print "\nAll of these except 'a' should be present:" + dc['e'] = 've3' + printifpresent(dc, ['a', 'b', 'c', 'd', 'e'], 'dc') + + # contents and order of original dict should remain unchanged + print "\nThese should be in reverse alphabetical order and read 'v?3':" + dc['b'] = 'vb3_new' + for k in list(iter(d)): + print "d['%s']: %s" % (k, d[k]) + if __name__ == '__main__': test_lrucachedict()
--- a/tests/test-lrucachedict.py.out Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-lrucachedict.py.out Sun Jan 17 21:40:21 2016 -0600 @@ -29,3 +29,34 @@ 'd' in d: False 'e' in d: False 'f' in d: False +'a' in d: True +d['a']: 1 +'b' in d: True +d['b']: 2 + +All of these should be present: +'a' in dc: True +dc['a']: va3 +'b' in dc: True +dc['b']: vb3 +'c' in dc: True +dc['c']: vc3 +'d' in dc: True +dc['d']: vd3 + +All of these except 'a' should be present: +'a' in dc: False +'b' in dc: True +dc['b']: vb3 +'c' in dc: True +dc['c']: vc3 +'d' in dc: True +dc['d']: vd3 +'e' in dc: True +dc['e']: ve3 + +These should be in reverse alphabetical order and read 'v?3': +d['d']: vd3 +d['c']: vc3 +d['b']: vb3 +d['a']: va3
--- a/tests/test-manifest.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-manifest.py Sun Jan 17 21:40:21 2016 -0600 @@ -187,7 +187,7 @@ def testCopy(self): m = self.parsemanifest(A_SHORT_MANIFEST) - m['a'] = BIN_HASH_1 + m['a'] = BIN_HASH_1 m2 = m.copy() del m del m2 # make sure we don't double free() anything
--- a/tests/test-manifestv2.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-manifestv2.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,10 @@ Create repo with old manifest + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + $ hg init existing $ cd existing $ echo footext > foo @@ -91,6 +96,6 @@ Check that manifest revlog is smaller than for v1 $ hg debugindex -m - rev offset length base linkrev nodeid p1 p2 - 0 0 81 0 0 57361477c778 000000000000 000000000000 + rev offset length delta linkrev nodeid p1 p2 + 0 0 81 -1 0 57361477c778 000000000000 000000000000 1 81 33 0 1 aeaab5a2ef74 57361477c778 000000000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge-changedelete.t Sun Jan 17 21:40:21 2016 -0600 @@ -0,0 +1,992 @@ +Tests for change/delete conflicts, including: +b5605d88dc27: Make ui.prompt repeat on "unrecognized response" again + (issue897) + +840e2b315c1f: Fix misleading error and prompts during update/merge + (issue556) + +Make sure HGMERGE doesn't interfere with the test + $ unset HGMERGE + + $ status() { + > echo "--- status ---" + > hg st -A file1 file2 file3 + > echo "--- resolve --list ---" + > hg resolve --list file1 file2 file3 + > echo "--- debugmergestate ---" + > hg debugmergestate + > for file in file1 file2 file3; do + > if [ -f $file ]; then + > echo "--- $file ---" + > cat $file + > else + > echo "*** $file does not exist" + > fi + > done + > } + + $ hg init repo + $ cd repo + + $ echo 1 > file1 + $ echo 2 > file2 + $ echo 3 > file3 + $ hg ci -Am 'added files' + adding file1 + adding file2 + adding file3 + + $ hg rm file1 + $ echo changed >> file2 + $ echo changed1 >> file3 + $ hg ci -m 'removed file1, changed file2, changed file3' + + $ hg co 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo changed >> file1 + $ hg rm file2 + $ echo changed2 >> file3 + $ hg ci -m 'changed file1, removed file2, changed file3' + created new head + + +Non-interactive merge: + + $ hg merge -y + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging file3 + warning: conflicts while merging file3! (edit, then use 'hg resolve --mark') + 0 files updated, 0 files merged, 0 files removed, 3 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + + $ status + --- status --- + M file2 + M file3 + C file1 + --- resolve --list --- + U file1 + U file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + <<<<<<< local: 13910f48cf7b - test: changed file1, removed file2, changed file3 + changed2 + ======= + changed1 + >>>>>>> other: 10f9a0a634e8 - test: removed file1, changed file2, changed file3 + + +Interactive merge: + + $ hg co -C + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --config ui.interactive=true <<EOF + > c + > d + > EOF + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? c + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? d + merging file3 + warning: conflicts while merging file3! (edit, then use 'hg resolve --mark') + 0 files updated, 2 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + + $ status + --- status --- + file2: * (glob) + M file3 + C file1 + --- resolve --list --- + R file1 + R file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "r", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + *** file2 does not exist + --- file3 --- + 3 + <<<<<<< local: 13910f48cf7b - test: changed file1, removed file2, changed file3 + changed2 + ======= + changed1 + >>>>>>> other: 10f9a0a634e8 - test: removed file1, changed file2, changed file3 + + +Interactive merge with bad input: + + $ hg co -C + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg merge --config ui.interactive=true <<EOF + > foo + > bar + > d + > baz + > c + > EOF + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? foo + unrecognized response + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? bar + unrecognized response + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? d + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? baz + unrecognized response + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c + merging file3 + warning: conflicts while merging file3! (edit, then use 'hg resolve --mark') + 0 files updated, 1 files merged, 1 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + + $ status + --- status --- + M file2 + M file3 + R file1 + --- resolve --list --- + R file1 + R file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "r", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + *** file1 does not exist + --- file2 --- + 2 + changed + --- file3 --- + 3 + <<<<<<< local: 13910f48cf7b - test: changed file1, removed file2, changed file3 + changed2 + ======= + changed1 + >>>>>>> other: 10f9a0a634e8 - test: removed file1, changed file2, changed file3 + + +Interactive merge with not enough input: + + $ hg co -C + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --config ui.interactive=true <<EOF + > d + > EOF + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? d + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + merging file3 + warning: conflicts while merging file3! (edit, then use 'hg resolve --mark') + 0 files updated, 0 files merged, 1 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + + $ status + --- status --- + M file2 + M file3 + R file1 + --- resolve --list --- + R file1 + U file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + *** file1 does not exist + --- file2 --- + 2 + changed + --- file3 --- + 3 + <<<<<<< local: 13910f48cf7b - test: changed file1, removed file2, changed file3 + changed2 + ======= + changed1 + >>>>>>> other: 10f9a0a634e8 - test: removed file1, changed file2, changed file3 + +Choose local versions of files + + $ hg co -C + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --tool :local + 0 files updated, 3 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ status 2>&1 | tee $TESTTMP/local.status + --- status --- + file2: * (glob) + M file3 + C file1 + --- resolve --list --- + R file1 + R file2 + R file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "r", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "r", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + *** file2 does not exist + --- file3 --- + 3 + changed2 + +Choose other versions of files + + $ hg co -C + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg merge --tool :other + 0 files updated, 2 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ status 2>&1 | tee $TESTTMP/other.status + --- status --- + M file2 + M file3 + R file1 + --- resolve --list --- + R file1 + R file2 + R file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "r", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "r", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + *** file1 does not exist + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed1 + +Fail + + $ hg co -C + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --tool :fail + 0 files updated, 0 files merged, 0 files removed, 3 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ status 2>&1 | tee $TESTTMP/fail.status + --- status --- + M file2 + M file3 + C file1 + --- resolve --list --- + U file1 + U file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed2 + +Force prompts with no input (should be similar to :fail) + + $ hg co -C + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --config ui.interactive=True --tool :prompt + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + no tool found to merge file3 + keep (l)ocal, take (o)ther, or leave (u)nresolved? + 0 files updated, 0 files merged, 0 files removed, 3 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ status 2>&1 | tee $TESTTMP/prompt.status + --- status --- + M file2 + M file3 + C file1 + --- resolve --list --- + U file1 + U file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed2 + $ cmp $TESTTMP/fail.status $TESTTMP/prompt.status || diff -U8 $TESTTMP/fail.status $TESTTMP/prompt.status + + +Force prompts + + $ hg co -C + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --tool :prompt + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + no tool found to merge file3 + keep (l)ocal, take (o)ther, or leave (u)nresolved? u + 0 files updated, 0 files merged, 0 files removed, 3 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ status + --- status --- + M file2 + M file3 + C file1 + --- resolve --list --- + U file1 + U file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed2 + +Choose to merge all files + + $ hg co -C + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge --tool :merge3 + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging file3 + warning: conflicts while merging file3! (edit, then use 'hg resolve --mark') + 0 files updated, 0 files merged, 0 files removed, 3 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ status + --- status --- + M file2 + M file3 + C file1 + --- resolve --list --- + U file1 + U file2 + U file3 + --- debugmergestate --- + * version 2 records + local: 13910f48cf7bdb2a0ba6e24b4900e4fdd5739dd4 + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + file: file3 (record type "F", state "u", hash d5b0a58bc47161b1b8a831084b366f757c4f0b11) + local path: file3 (flags "") + ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) + other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + <<<<<<< local: 13910f48cf7b - test: changed file1, removed file2, changed file3 + changed2 + ||||||| base + ======= + changed1 + >>>>>>> other: 10f9a0a634e8 - test: removed file1, changed file2, changed file3 + +Exercise transitions between local, other, fail and prompt, and make sure the +dirstate stays consistent. (Compare with each other and to the above +invocations.) + + $ testtransitions() { + > # this traversal order covers every transition + > tools="local other prompt local fail other local prompt other fail prompt fail local" + > lasttool="merge3" + > for tool in $tools; do + > echo "=== :$lasttool -> :$tool ===" + > ref="$TESTTMP/$tool.status" + > hg resolve --unmark --all + > hg resolve --tool ":$tool" --all --config ui.interactive=True + > status > "$TESTTMP/compare.status" 2>&1 + > echo '--- diff of status ---' + > if cmp "$TESTTMP/$tool.status" "$TESTTMP/compare.status" || diff -U8 "$TESTTMP/$tool.status" "$TESTTMP/compare.status"; then + > echo '(status identical)' + > fi + > lasttool="$tool" + > echo + > done + > } + + $ testtransitions + === :merge3 -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :local -> :other === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :other -> :prompt === + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + no tool found to merge file3 + keep (l)ocal, take (o)ther, or leave (u)nresolved? + --- diff of status --- + (status identical) + + === :prompt -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :local -> :fail === + --- diff of status --- + (status identical) + + === :fail -> :other === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :other -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :local -> :prompt === + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + no tool found to merge file3 + keep (l)ocal, take (o)ther, or leave (u)nresolved? + --- diff of status --- + (status identical) + + === :prompt -> :other === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :other -> :fail === + --- diff of status --- + (status identical) + + === :fail -> :prompt === + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + no tool found to merge file3 + keep (l)ocal, take (o)ther, or leave (u)nresolved? + --- diff of status --- + (status identical) + + === :prompt -> :fail === + --- diff of status --- + (status identical) + + === :fail -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + + +Non-interactive linear update + + $ hg co -C 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo changed >> file1 + $ hg rm file2 + $ hg update 1 -y + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + 1 files updated, 0 files merged, 0 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ status + --- status --- + A file1 + C file2 + C file3 + --- resolve --list --- + U file1 + U file2 + --- debugmergestate --- + * version 2 records + local: ab57bf49aa276a22d35a473592d4c34b5abc3eff + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed1 + +Choose local versions of files + + $ hg co -C 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo changed >> file1 + $ hg rm file2 + $ hg update 1 --tool :local + 1 files updated, 2 files merged, 0 files removed, 0 files unresolved + $ status 2>&1 | tee $TESTTMP/local.status + --- status --- + file2: * (glob) + A file1 + C file3 + --- resolve --list --- + R file1 + R file2 + --- debugmergestate --- + * version 2 records + local: ab57bf49aa276a22d35a473592d4c34b5abc3eff + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "r", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + --- file1 --- + 1 + changed + *** file2 does not exist + --- file3 --- + 3 + changed1 + +Choose other versions of files + + $ hg co -C 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo changed >> file1 + $ hg rm file2 + $ hg update 1 --tool :other + 1 files updated, 1 files merged, 1 files removed, 0 files unresolved + $ status 2>&1 | tee $TESTTMP/other.status + --- status --- + file1: * (glob) + C file2 + C file3 + --- resolve --list --- + R file1 + R file2 + --- debugmergestate --- + * version 2 records + local: ab57bf49aa276a22d35a473592d4c34b5abc3eff + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "r", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + *** file1 does not exist + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed1 + +Fail + + $ hg co -C 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo changed >> file1 + $ hg rm file2 + $ hg update 1 --tool :fail + 1 files updated, 0 files merged, 0 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ status 2>&1 | tee $TESTTMP/fail.status + --- status --- + A file1 + C file2 + C file3 + --- resolve --list --- + U file1 + U file2 + --- debugmergestate --- + * version 2 records + local: ab57bf49aa276a22d35a473592d4c34b5abc3eff + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed1 + +Force prompts with no input + + $ hg co -C 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo changed >> file1 + $ hg rm file2 + $ hg update 1 --config ui.interactive=True --tool :prompt + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + 1 files updated, 0 files merged, 0 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ status 2>&1 | tee $TESTTMP/prompt.status + --- status --- + A file1 + C file2 + C file3 + --- resolve --list --- + U file1 + U file2 + --- debugmergestate --- + * version 2 records + local: ab57bf49aa276a22d35a473592d4c34b5abc3eff + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed1 + $ cmp $TESTTMP/fail.status $TESTTMP/prompt.status || diff -U8 $TESTTMP/fail.status $TESTTMP/prompt.status + +Choose to merge all files + + $ hg co -C 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo changed >> file1 + $ hg rm file2 + $ hg update 1 --tool :merge3 + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + 1 files updated, 0 files merged, 0 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ status + --- status --- + A file1 + C file2 + C file3 + --- resolve --list --- + U file1 + U file2 + --- debugmergestate --- + * version 2 records + local: ab57bf49aa276a22d35a473592d4c34b5abc3eff + other: 10f9a0a634e82080907e62f075ab119cbc565ea6 + file: file1 (record type "C", state "u", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) + other path: file1 (node null) + file: file2 (record type "C", state "u", hash null) + local path: file2 (flags "") + ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) + other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) + --- file1 --- + 1 + changed + --- file2 --- + 2 + changed + --- file3 --- + 3 + changed1 + +Test transitions between different merge tools + + $ testtransitions + === :merge3 -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :local -> :other === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :other -> :prompt === + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + --- diff of status --- + (status identical) + + === :prompt -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :local -> :fail === + --- diff of status --- + (status identical) + + === :fail -> :other === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :other -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :local -> :prompt === + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + --- diff of status --- + (status identical) + + === :prompt -> :other === + (no more unresolved files) + --- diff of status --- + (status identical) + + === :other -> :fail === + --- diff of status --- + (status identical) + + === :fail -> :prompt === + local changed file1 which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? + remote changed file2 which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? + --- diff of status --- + (status identical) + + === :prompt -> :fail === + --- diff of status --- + (status identical) + + === :fail -> :local === + (no more unresolved files) + --- diff of status --- + (status identical) +
--- a/tests/test-merge-commit.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-commit.t Sun Jan 17 21:40:21 2016 -0600 @@ -73,7 +73,7 @@ ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28 preserving bar for resolve of bar bar: versions differ -> m (premerge) - picked tool ':merge' for bar (binary False symlink False) + picked tool ':merge' for bar (binary False symlink False changedelete False) merging bar my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9 premerge successful @@ -159,7 +159,7 @@ ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0 preserving bar for resolve of bar bar: versions differ -> m (premerge) - picked tool ':merge' for bar (binary False symlink False) + picked tool ':merge' for bar (binary False symlink False changedelete False) merging bar my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9 premerge successful
--- a/tests/test-merge-criss-cross.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-criss-cross.t Sun Jan 17 21:40:21 2016 -0600 @@ -83,11 +83,11 @@ f1: remote is newer -> g getting f1 f2: versions differ -> m (premerge) - picked tool ':dump' for f2 (binary False symlink False) + picked tool ':dump' for f2 (binary False symlink False changedelete False) merging f2 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c f2: versions differ -> m (merge) - picked tool ':dump' for f2 (binary False symlink False) + picked tool ':dump' for f2 (binary False symlink False changedelete False) my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c 1 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
--- a/tests/test-merge-force.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-force.t Sun Jan 17 21:40:21 2016 -0600 @@ -5,7 +5,8 @@ local changeset, and then modified in the working copy to match the remote content, then finally forgotten. - $ hg init + $ hg init repo + $ cd repo Create base changeset @@ -142,55 +143,55 @@ $ hg merge -f --tool internal:merge3 'desc("remote")' local changed content1_missing_content1_content4-tracked which remote deleted - use (c)hanged version or (d)elete? c + use (c)hanged version, (d)elete, or leave (u)nresolved? u local changed content1_missing_content3_content3-tracked which remote deleted - use (c)hanged version or (d)elete? c + use (c)hanged version, (d)elete, or leave (u)nresolved? u local changed content1_missing_content3_content4-tracked which remote deleted - use (c)hanged version or (d)elete? c + use (c)hanged version, (d)elete, or leave (u)nresolved? u local changed content1_missing_missing_content4-tracked which remote deleted - use (c)hanged version or (d)elete? c + use (c)hanged version, (d)elete, or leave (u)nresolved? u remote changed content1_content2_content1_content1-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content1_content2-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content1_content4-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content1_missing-tracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content1_missing-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content2_content1-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content2_content2-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content2_content4-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content2_missing-tracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content2_missing-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content3_content1-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content3_content2-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content3_content3-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content3_content4-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content3_missing-tracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_content3_missing-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_missing_content1-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_missing_content2-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_missing_content4-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_missing_missing-tracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u remote changed content1_content2_missing_missing-untracked which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u merging content1_content2_content1_content4-tracked merging content1_content2_content2_content1-tracked merging content1_content2_content2_content4-tracked @@ -214,7 +215,7 @@ warning: conflicts while merging missing_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') warning: conflicts while merging missing_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') warning: conflicts while merging missing_content2_missing_content4-untracked! (edit, then use 'hg resolve --mark') - 39 files updated, 3 files merged, 8 files removed, 10 files unresolved + 18 files updated, 3 files merged, 8 files removed, 35 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon [1] @@ -226,14 +227,39 @@ odd 'if force and branchmerge and different' case in manifestmerge(). $ hg resolve -l + U content1_content2_content1_content1-untracked + U content1_content2_content1_content2-untracked U content1_content2_content1_content4-tracked + U content1_content2_content1_content4-untracked + U content1_content2_content1_missing-tracked + U content1_content2_content1_missing-untracked R content1_content2_content2_content1-tracked + U content1_content2_content2_content1-untracked + U content1_content2_content2_content2-untracked U content1_content2_content2_content4-tracked + U content1_content2_content2_content4-untracked + U content1_content2_content2_missing-tracked + U content1_content2_content2_missing-untracked R content1_content2_content3_content1-tracked + U content1_content2_content3_content1-untracked + U content1_content2_content3_content2-untracked U content1_content2_content3_content3-tracked + U content1_content2_content3_content3-untracked U content1_content2_content3_content4-tracked + U content1_content2_content3_content4-untracked + U content1_content2_content3_missing-tracked + U content1_content2_content3_missing-untracked R content1_content2_missing_content1-tracked + U content1_content2_missing_content1-untracked + U content1_content2_missing_content2-untracked U content1_content2_missing_content4-tracked + U content1_content2_missing_content4-untracked + U content1_content2_missing_missing-tracked + U content1_content2_missing_missing-untracked + U content1_missing_content1_content4-tracked + U content1_missing_content3_content3-tracked + U content1_missing_content3_content4-tracked + U content1_missing_missing_content4-tracked U missing_content2_content2_content4-tracked U missing_content2_content3_content3-tracked U missing_content2_content3_content4-tracked @@ -260,22 +286,20 @@ missing_missing_content3_missing-tracked becomes removed ('R'), even though the remote side did not touch the file - $ for f in `python $TESTDIR/generate-working-copy-states.py filelist 3` - > do - > echo - > hg status -A $f - > if test -f $f - > then - > cat $f - > else - > echo '<missing>' - > fi - > if test -f ${f}.orig - > then - > echo ${f}.orig: - > cat ${f}.orig - > fi - > done + $ checkstatus() { + > for f in `python $TESTDIR/generate-working-copy-states.py filelist 3` + > do + > echo + > hg status -A $f + > if test -f $f + > then + > cat $f + > else + > echo '<missing>' + > fi + > done + > } + $ checkstatus 2>&1 | tee $TESTTMP/status1 C content1_content1_content1_content1-tracked content1 @@ -357,8 +381,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - content1_content2_content1_content4-tracked.orig: - content4 M content1_content2_content1_content4-untracked content2 @@ -389,8 +411,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - content1_content2_content2_content4-tracked.orig: - content4 M content1_content2_content2_content4-untracked content2 @@ -421,8 +441,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - content1_content2_content3_content3-tracked.orig: - content3 M content1_content2_content3_content3-untracked content2 @@ -435,8 +453,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - content1_content2_content3_content4-tracked.orig: - content4 M content1_content2_content3_content4-untracked content2 @@ -467,8 +483,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - content1_content2_missing_content4-tracked.orig: - content4 M content1_content2_missing_content4-untracked content2 @@ -552,8 +566,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - missing_content2_content2_content4-tracked.orig: - content4 M missing_content2_content2_content4-untracked content2 @@ -577,8 +589,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - missing_content2_content3_content3-tracked.orig: - content3 M missing_content2_content3_content3-untracked content2 @@ -590,8 +600,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - missing_content2_content3_content4-tracked.orig: - content4 M missing_content2_content3_content4-untracked content2 @@ -615,8 +623,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - missing_content2_missing_content4-tracked.orig: - content4 M missing_content2_missing_content4-untracked <<<<<<< local: 0447570f1af6 - test: local @@ -625,8 +631,6 @@ ======= content2 >>>>>>> other: 85100b8c675b - test: remote - missing_content2_missing_content4-untracked.orig: - content4 M missing_content2_missing_missing-tracked content2 @@ -663,3 +667,116 @@ missing_missing_missing_missing-untracked: * (glob) <missing> + + $ for f in `python $TESTDIR/generate-working-copy-states.py filelist 3` + > do + > if test -f ${f}.orig + > then + > echo ${f}.orig: + > cat ${f}.orig + > fi + > done + content1_content2_content1_content4-tracked.orig: + content4 + content1_content2_content2_content4-tracked.orig: + content4 + content1_content2_content3_content3-tracked.orig: + content3 + content1_content2_content3_content4-tracked.orig: + content4 + content1_content2_missing_content4-tracked.orig: + content4 + missing_content2_content2_content4-tracked.orig: + content4 + missing_content2_content3_content3-tracked.orig: + content3 + missing_content2_content3_content4-tracked.orig: + content4 + missing_content2_missing_content4-tracked.orig: + content4 + missing_content2_missing_content4-untracked.orig: + content4 + +Re-resolve and check status + + $ hg resolve --unmark --all + $ hg resolve --all --tool :local + (no more unresolved files) + $ hg resolve --unmark --all + $ hg resolve --all --tool internal:merge3 + remote changed content1_content2_content1_content1-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content1_content2-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_content1_content4-tracked + remote changed content1_content2_content1_content4-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content1_missing-tracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content1_missing-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_content2_content1-tracked + remote changed content1_content2_content2_content1-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content2_content2-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_content2_content4-tracked + remote changed content1_content2_content2_content4-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content2_missing-tracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content2_missing-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_content3_content1-tracked + remote changed content1_content2_content3_content1-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content3_content2-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_content3_content3-tracked + remote changed content1_content2_content3_content3-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_content3_content4-tracked + remote changed content1_content2_content3_content4-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content3_missing-tracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_content3_missing-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_missing_content1-tracked + remote changed content1_content2_missing_content1-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_missing_content2-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + merging content1_content2_missing_content4-tracked + remote changed content1_content2_missing_content4-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_missing_missing-tracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + remote changed content1_content2_missing_missing-untracked which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u + local changed content1_missing_content1_content4-tracked which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + local changed content1_missing_content3_content3-tracked which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + local changed content1_missing_content3_content4-tracked which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + local changed content1_missing_missing_content4-tracked which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u + merging missing_content2_content2_content4-tracked + merging missing_content2_content3_content3-tracked + merging missing_content2_content3_content4-tracked + merging missing_content2_missing_content4-tracked + merging missing_content2_missing_content4-untracked + warning: conflicts while merging content1_content2_content1_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging content1_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging content1_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging content1_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging content1_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging missing_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging missing_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging missing_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging missing_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') + warning: conflicts while merging missing_content2_missing_content4-untracked! (edit, then use 'hg resolve --mark') + [1] + $ checkstatus > $TESTTMP/status2 2>&1 + $ cmp $TESTTMP/status1 $TESTTMP/status2 || diff -U8 $TESTTMP/status1 $TESTTMP/status2
--- a/tests/test-merge-local.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-local.t Sun Jan 17 21:40:21 2016 -0600 @@ -59,6 +59,9 @@ use 'hg resolve' to retry unresolved file merges [1] + $ hg resolve -m + (no more unresolved files) + $ hg co 0 merging zzz1_merge_ok merging zzz2_merge_bad @@ -83,6 +86,9 @@ Local merge with conflicts: + $ hg resolve -m + (no more unresolved files) + $ hg co merging zzz1_merge_ok merging zzz2_merge_bad @@ -91,7 +97,10 @@ use 'hg resolve' to retry unresolved file merges [1] - $ hg co 0 + $ hg resolve -m + (no more unresolved files) + + $ hg co 0 --config 'ui.origbackuppath=.hg/origbackups' merging zzz1_merge_ok merging zzz2_merge_bad warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve --mark') @@ -99,6 +108,10 @@ use 'hg resolve' to retry unresolved file merges [1] +Are orig files from the last commit where we want them? + $ ls .hg/origbackups + zzz2_merge_bad.orig + $ hg diff --nodates | grep "^[+-][^<>]" --- a/zzz1_merge_ok +++ b/zzz1_merge_ok @@ -120,6 +133,9 @@ $ hg revert zzz2_merge_bad + $ hg resolve -m + (no more unresolved files) + $ hg co merging zzz1_merge_ok 4 files updated, 1 files merged, 2 files removed, 0 files unresolved
--- a/tests/test-merge-prompt.t Wed Jan 06 11:01:55 2016 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -Test for -b5605d88dc27: Make ui.prompt repeat on "unrecognized response" again - (issue897) - -840e2b315c1f: Fix misleading error and prompts during update/merge - (issue556) - - $ status() { - > echo "--- status ---" - > hg st -A file1 file2 - > for file in file1 file2; do - > if [ -f $file ]; then - > echo "--- $file ---" - > cat $file - > else - > echo "*** $file does not exist" - > fi - > done - > } - - $ hg init - - $ echo 1 > file1 - $ echo 2 > file2 - $ hg ci -Am 'added file1 and file2' - adding file1 - adding file2 - - $ hg rm file1 - $ echo changed >> file2 - $ hg ci -m 'removed file1, changed file2' - - $ hg co 0 - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ echo changed >> file1 - $ hg rm file2 - $ hg ci -m 'changed file1, removed file2' - created new head - - -Non-interactive merge: - - $ hg merge -y - local changed file1 which remote deleted - use (c)hanged version or (d)elete? c - remote changed file2 which local deleted - use (c)hanged version or leave (d)eleted? c - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - - $ status - --- status --- - M file2 - C file1 - --- file1 --- - 1 - changed - --- file2 --- - 2 - changed - - -Interactive merge: - - $ hg co -C - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - - $ hg merge --config ui.interactive=true <<EOF - > c - > d - > EOF - local changed file1 which remote deleted - use (c)hanged version or (d)elete? c - remote changed file2 which local deleted - use (c)hanged version or leave (d)eleted? d - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - - $ status - --- status --- - file2: * (glob) - C file1 - --- file1 --- - 1 - changed - *** file2 does not exist - - -Interactive merge with bad input: - - $ hg co -C - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ hg merge --config ui.interactive=true <<EOF - > foo - > bar - > d - > baz - > c - > EOF - local changed file1 which remote deleted - use (c)hanged version or (d)elete? foo - unrecognized response - local changed file1 which remote deleted - use (c)hanged version or (d)elete? bar - unrecognized response - local changed file1 which remote deleted - use (c)hanged version or (d)elete? d - remote changed file2 which local deleted - use (c)hanged version or leave (d)eleted? baz - unrecognized response - remote changed file2 which local deleted - use (c)hanged version or leave (d)eleted? c - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - (branch merge, don't forget to commit) - - $ status - --- status --- - M file2 - R file1 - *** file1 does not exist - --- file2 --- - 2 - changed - - -Interactive merge with not enough input: - - $ hg co -C - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - - $ hg merge --config ui.interactive=true <<EOF - > d - > EOF - local changed file1 which remote deleted - use (c)hanged version or (d)elete? d - remote changed file2 which local deleted - use (c)hanged version or leave (d)eleted? abort: response expected - [255] - - $ status - --- status --- - file2: * (glob) - C file1 - --- file1 --- - 1 - changed - *** file2 does not exist -
--- a/tests/test-merge-remove.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-remove.t Sun Jan 17 21:40:21 2016 -0600 @@ -103,10 +103,11 @@ $ hg merge -f remote changed bar which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u merging foo1 and foo to foo1 - 1 files updated, 1 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) + 0 files updated, 1 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] $ cat bar bleh $ hg st
--- a/tests/test-merge-tools.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-tools.t Sun Jan 17 21:40:21 2016 -0600 @@ -50,6 +50,8 @@ > cat f > echo "# hg stat" > hg stat + > echo "# hg resolve --list" + > hg resolve --list > rm -f f.orig > } @@ -82,6 +84,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f simplest hgrc using false for merge: @@ -103,6 +107,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f #if unix-permissions @@ -150,6 +156,8 @@ space # hg stat M f + # hg resolve --list + R f unless lowered on command line: @@ -171,6 +179,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f or false set higher on command line: @@ -192,6 +202,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f or true set to disabled: $ beforemerge @@ -212,6 +224,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f or true.executable not found in PATH: @@ -233,6 +247,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f or true.executable with bogus path: @@ -254,6 +270,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f but true.executable set to cat found in PATH works: @@ -280,6 +298,8 @@ space # hg stat M f + # hg resolve --list + R f and true.executable set to cat with path works: @@ -305,6 +325,8 @@ space # hg stat M f + # hg resolve --list + R f #if unix-permissions @@ -330,6 +352,8 @@ space # hg stat M f + # hg resolve --list + R f #endif @@ -356,6 +380,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f merge-patterns specifies executable not found in PATH and gets warning: @@ -380,6 +406,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f merge-patterns specifies executable with bogus path and gets warning: @@ -404,6 +432,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f ui.merge overrules priority @@ -428,6 +458,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f ui.merge specifies internal:fail: @@ -447,6 +479,8 @@ space # hg stat M f + # hg resolve --list + U f ui.merge specifies :local (without internal prefix): @@ -465,6 +499,8 @@ space # hg stat M f + # hg resolve --list + R f ui.merge specifies internal:other: @@ -483,6 +519,8 @@ space # hg stat M f + # hg resolve --list + R f ui.merge specifies internal:prompt: @@ -493,16 +531,106 @@ true.executable=cat # hg update -C 1 $ hg merge -r 2 --config ui.merge=internal:prompt - no tool found to merge f - keep (l)ocal or take (o)ther? l - 0 files updated, 1 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) + no tool found to merge f + keep (l)ocal, take (o)ther, or leave (u)nresolved? u + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + # hg resolve --list + U f + +ui.merge specifies :prompt, with 'leave unresolved' chosen + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg merge -r 2 --config ui.merge=:prompt --config ui.interactive=True << EOF + > u + > EOF + no tool found to merge f + keep (l)ocal, take (o)ther, or leave (u)nresolved? u + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] $ aftermerge # cat f revision 1 space # hg stat M f + # hg resolve --list + U f + +prompt with EOF + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg merge -r 2 --config ui.merge=internal:prompt --config ui.interactive=true + no tool found to merge f + keep (l)ocal, take (o)ther, or leave (u)nresolved? + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + # hg resolve --list + U f + $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true + no tool found to merge f + keep (l)ocal, take (o)ther, or leave (u)nresolved? + [1] + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + ? f.orig + # hg resolve --list + U f + $ rm f + $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true + no tool found to merge f + keep (l)ocal, take (o)ther, or leave (u)nresolved? + [1] + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + # hg resolve --list + U f + $ hg resolve --all --config ui.merge=internal:prompt + no tool found to merge f + keep (l)ocal, take (o)ther, or leave (u)nresolved? u + [1] + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + ? f.orig + # hg resolve --list + U f ui.merge specifies internal:dump: @@ -527,6 +655,8 @@ ? f.local ? f.orig ? f.other + # hg resolve --list + U f f.base: @@ -568,6 +698,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f Premerge @@ -592,6 +724,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f HGMERGE specifies internal:other but is overruled by --tool=false @@ -615,6 +749,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests @@ -671,6 +807,8 @@ space # hg stat M f + # hg resolve --list + R f update should also have --tool @@ -712,6 +850,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f Default is silent simplemerge: @@ -732,6 +872,8 @@ revision 3 # hg stat M f + # hg resolve --list + R f .premerge=True is same: @@ -752,6 +894,8 @@ revision 3 # hg stat M f + # hg resolve --list + R f .premerge=False executes merge-tool: @@ -778,6 +922,8 @@ space # hg stat M f + # hg resolve --list + R f premerge=keep keeps conflict markers in: @@ -810,6 +956,8 @@ >>>>>>> other: 81448d39c9a0 - test: revision 4 # hg stat M f + # hg resolve --list + R f premerge=keep-merge3 keeps conflict markers with base content: @@ -848,6 +996,8 @@ >>>>>>> other: 81448d39c9a0 - test: revision 4 # hg stat M f + # hg resolve --list + R f Tool execution @@ -886,6 +1036,8 @@ space # hg stat M f + # hg resolve --list + R f Merge with "echo mergeresult > $local": @@ -904,6 +1056,8 @@ mergeresult # hg stat M f + # hg resolve --list + R f - and $local is the file f: @@ -922,6 +1076,8 @@ mergeresult # hg stat M f + # hg resolve --list + R f Merge with "echo mergeresult > $output" - the variable is a bit magic: @@ -940,6 +1096,8 @@ mergeresult # hg stat M f + # hg resolve --list + R f Merge using tool with a path that must be quoted: @@ -969,6 +1127,8 @@ space # hg stat M f + # hg resolve --list + R f Issue3581: Merging a filename that needs to be quoted (This test doesn't work on Windows filesystems even on Linux, so check @@ -1029,6 +1189,8 @@ # hg stat M f ? f.orig + # hg resolve --list + U f #if symlink
--- a/tests/test-merge-types.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge-types.t Sun Jan 17 21:40:21 2016 -0600 @@ -36,7 +36,7 @@ ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c preserving a for resolve of a a: versions differ -> m (premerge) - picked tool ':merge' for a (binary False symlink True) + picked tool ':merge' for a (binary False symlink True changedelete False) merging a my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da warning: internal :merge cannot merge symlinks for a @@ -69,7 +69,7 @@ ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a a: versions differ -> m (premerge) - picked tool ':union' for a (binary False symlink True) + picked tool ':union' for a (binary False symlink True changedelete False) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da warning: internal :union cannot merge symlinks for a @@ -92,7 +92,7 @@ ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a a: versions differ -> m (premerge) - picked tool ':merge3' for a (binary False symlink True) + picked tool ':merge3' for a (binary False symlink True changedelete False) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da warning: internal :merge3 cannot merge symlinks for a @@ -105,6 +105,50 @@ a is an executable file with content: a + $ hg update -C 1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg merge --debug --tool :merge-local + searching for copies back to rev 1 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f + preserving a for resolve of a + a: versions differ -> m (premerge) + picked tool ':merge-local' for a (binary False symlink True changedelete False) + merging a + my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da + warning: internal :merge-local cannot merge symlinks for a + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + + $ tellmeabout a + a is an executable file with content: + a + + $ hg update -C 1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg merge --debug --tool :merge-other + searching for copies back to rev 1 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f + preserving a for resolve of a + a: versions differ -> m (premerge) + picked tool ':merge-other' for a (binary False symlink True changedelete False) + merging a + my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da + warning: internal :merge-other cannot merge symlinks for a + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + + $ tellmeabout a + a is an executable file with content: + a + Update to link without local change should get us a symlink (issue3316): $ hg up -C 0 @@ -126,10 +170,12 @@ preserving a for resolve of a a: versions differ -> m (premerge) (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 - keep (l)ocal or take (o)ther? l - 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + picked tool ':prompt' for a (binary False symlink True changedelete False) + no tool found to merge a + keep (l)ocal, take (o)ther, or leave (u)nresolved? u + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] $ hg diff --git diff --git a/a b/a old mode 120000
--- a/tests/test-merge1.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge1.t Sun Jan 17 21:40:21 2016 -0600 @@ -124,7 +124,111 @@ $ echo This is file b2 > b #endif -merge of b expected +bad config + $ hg merge 1 --config merge.checkunknown=x + abort: merge.checkunknown not valid ('x' is none of 'abort', 'ignore', 'warn') + [255] +this merge should fail + $ hg merge 1 --config merge.checkunknown=abort + b: untracked file differs + abort: untracked files in working directory differ from files in requested revision + [255] + +this merge should warn + $ hg merge 1 --config merge.checkunknown=warn + b: replacing untracked file + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ cat b.orig + This is file b2 + $ hg up --clean 2 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mv b.orig b + +this merge should silently ignore + $ cat b + This is file b2 + $ hg merge 1 --config merge.checkunknown=ignore + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + +merge.checkignored + $ hg up --clean 1 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ cat >> .hgignore << EOF + > remoteignored + > EOF + $ echo This is file localignored3 > localignored + $ echo This is file remoteignored3 > remoteignored + $ hg add .hgignore localignored remoteignored + $ hg commit -m "commit #3" + + $ hg up 2 + 1 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ cat >> .hgignore << EOF + > localignored + > EOF + $ hg add .hgignore + $ hg commit -m "commit #4" + +remote .hgignore shouldn't be used for determining whether a file is ignored + $ echo This is file remoteignored4 > remoteignored + $ hg merge 3 --config merge.checkignored=ignore --config merge.checkunknown=abort + remoteignored: untracked file differs + abort: untracked files in working directory differ from files in requested revision + [255] + $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=ignore + merging .hgignore + merging for .hgignore + 3 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ cat remoteignored + This is file remoteignored3 + $ cat remoteignored.orig + This is file remoteignored4 + $ rm remoteignored.orig + +local .hgignore should be used for that + $ hg up --clean 4 + 1 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ echo This is file localignored4 > localignored +also test other conflicting files to see we output the full set of warnings + $ echo This is file b2 > b + $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=abort + b: untracked file differs + localignored: untracked file differs + abort: untracked files in working directory differ from files in requested revision + [255] + $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=ignore + localignored: untracked file differs + abort: untracked files in working directory differ from files in requested revision + [255] + $ hg merge 3 --config merge.checkignored=warn --config merge.checkunknown=abort + b: untracked file differs + abort: untracked files in working directory differ from files in requested revision + [255] + $ hg merge 3 --config merge.checkignored=warn --config merge.checkunknown=warn + b: replacing untracked file + localignored: replacing untracked file + merging .hgignore + merging for .hgignore + 3 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ cat localignored + This is file localignored3 + $ cat localignored.orig + This is file localignored4 + $ rm localignored.orig + + $ cat b.orig + This is file b2 + $ hg up --clean 2 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mv b.orig b + +this merge of b should work + $ cat b + This is file b2 $ hg merge -f 1 merging b merging for b
--- a/tests/test-merge7.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-merge7.t Sun Jan 17 21:40:21 2016 -0600 @@ -85,11 +85,11 @@ ancestor: 96b70246a118, local: 50c3a7e29886+, remote: 40d11a4173a8 preserving test.txt for resolve of test.txt test.txt: versions differ -> m (premerge) - picked tool ':merge' for test.txt (binary False symlink False) + picked tool ':merge' for test.txt (binary False symlink False changedelete False) merging test.txt my test.txt@50c3a7e29886+ other test.txt@40d11a4173a8 ancestor test.txt@96b70246a118 test.txt: versions differ -> m (merge) - picked tool ':merge' for test.txt (binary False symlink False) + picked tool ':merge' for test.txt (binary False symlink False changedelete False) my test.txt@50c3a7e29886+ other test.txt@40d11a4173a8 ancestor test.txt@96b70246a118 warning: conflicts while merging test.txt! (edit, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
--- a/tests/test-minirst.py.out Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-minirst.py.out Sun Jan 17 21:40:21 2016 -0600 @@ -594,18 +594,18 @@ == roles == 60 column format: ---------------------------------------------------------------------- -Please see "hg add". +Please see 'hg add'. ---------------------------------------------------------------------- 30 column format: ---------------------------------------------------------------------- -Please see "hg add". +Please see 'hg add'. ---------------------------------------------------------------------- html format: ---------------------------------------------------------------------- <p> -Please see "hg add". +Please see 'hg add'. </p> ---------------------------------------------------------------------- @@ -621,7 +621,7 @@ Subsection '''''''''' -Markup: "foo" and "hg help" +Markup: "foo" and 'hg help' --------------------------- ---------------------------------------------------------------------- @@ -636,7 +636,7 @@ Subsection '''''''''' -Markup: "foo" and "hg help" +Markup: "foo" and 'hg help' --------------------------- ---------------------------------------------------------------------- @@ -645,7 +645,7 @@ <h1>Title</h1> <h2>Section</h2> <h3>Subsection</h3> -<h2>Markup: "foo" and "hg help"</h2> +<h2>Markup: "foo" and 'hg help'</h2> ---------------------------------------------------------------------- == admonitions ==
--- a/tests/test-module-imports.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-module-imports.t Sun Jan 17 21:40:21 2016 -0600 @@ -68,6 +68,33 @@ > from .. import parent > EOF + $ touch testpackage/subpackage/foo.py + $ cat > testpackage/subpackage/__init__.py << EOF + > from __future__ import absolute_import + > from . import levelpriority # should not cause cycle + > EOF + + $ cat > testpackage/subpackage/localimport.py << EOF + > from __future__ import absolute_import + > from . import foo + > def bar(): + > # should not cause "higher-level import should come first" + > from .. import unsorted + > # but other errors should be detected + > from .. import more + > import testpackage.subpackage.levelpriority + > EOF + + $ cat > testpackage/importmodulefromsub.py << EOF + > from __future__ import absolute_import + > from .subpackage import foo # not a "direct symbol import" + > EOF + + $ cat > testpackage/importsymbolfromsub.py << EOF + > from __future__ import absolute_import + > from .subpackage import foo, nonmodule + > EOF + $ cat > testpackage/sortedentries.py << EOF > from __future__ import absolute_import > from . import ( @@ -87,20 +114,23 @@ > 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 + testpackage/importalias.py:2: ui module must be "as" aliased to uimod + testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod + testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted + testpackage/importfromrelative.py:2: direct symbol import foo from testpackage.unsorted + testpackage/importsymbolfromsub.py:2: direct symbol import nonmodule from testpackage.subpackage + testpackage/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node + testpackage/multiple.py:2: multiple imported names: os, sys + testpackage/multiplegroups.py:3: multiple "from . import" statements + testpackage/relativestdlib.py:2: relative import of stdlib module + testpackage/requirerelative.py:2: import should be relative: testpackage.unsorted + testpackage/sortedentries.py:2: imports from testpackage not lexically sorted: bar < foo + testpackage/stdafterlocal.py:3: stdlib import follows local import: os + testpackage/subpackage/levelpriority.py:3: higher-level import should come first: testpackage + testpackage/subpackage/localimport.py:7: multiple "from .. import" statements + testpackage/subpackage/localimport.py:8: import should be relative: testpackage.subpackage.levelpriority + testpackage/symbolimport.py:2: direct symbol import foo from testpackage.unsorted + testpackage/unsorted.py:3: imports not lexically sorted: os < sys [1] $ cd "$TESTDIR"/..
--- a/tests/test-mq-qfold.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-mq-qfold.t Sun Jan 17 21:40:21 2016 -0600 @@ -229,9 +229,9 @@ HG: added aa HG: changed a ==== + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 [255]
--- a/tests/test-mq-qnew.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-mq-qnew.t Sun Jan 17 21:40:21 2016 -0600 @@ -299,9 +299,9 @@ HG: branch 'default' HG: no files changed ==== + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt abort: pretxncommit.unexpectedabort hook exited with status 1 [255] $ cat .hg/last-message.txt
--- a/tests/test-mq-qpush-fail.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-mq-qpush-fail.t Sun Jan 17 21:40:21 2016 -0600 @@ -444,7 +444,7 @@ $ hg st a M a $ echo b >> b - $ hg --config mq.keepchanges=1 qpop --force + $ hg --config mq.keepchanges=1 qpop --force --config 'ui.origbackuppath=.hg/origbackups' popping p3 now at: p2 $ hg st b @@ -461,4 +461,10 @@ now at: p2 $ hg st a +test previous qpop (with --force and --config) saved .orig files to where user +wants them + $ ls .hg/origbackups + b.orig + $ rm -rf .hg/origbackups + $ cd ..
--- a/tests/test-mq-qrefresh-replace-log-message.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-mq-qrefresh-replace-log-message.t Sun Jan 17 21:40:21 2016 -0600 @@ -185,9 +185,9 @@ HG: branch 'default' HG: added file2 ==== + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 [255] @@ -223,14 +223,14 @@ M file2 ==== - $ HGEDITOR='sh "$TESTTMP/checkvisibility.sh"' hg qrefresh -e + $ HGEDITOR="sh \"$TESTTMP/checkvisibility.sh\"" hg qrefresh -e ==== 0:25e397dabed2 A file2 ==== + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 [255]
--- a/tests/test-mq.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-mq.t Sun Jan 17 21:40:21 2016 -0600 @@ -25,7 +25,7 @@ Known patches are represented as patch files in the .hg/patches directory. Applied patches are both patch files and changesets. - Common tasks (use "hg help command" for more details): + Common tasks (use 'hg help command' for more details): create new patch qnew import existing patch qimport @@ -49,14 +49,14 @@ will override the [diff] section and always generate git or regular patches, possibly losing data in the second case. - It may be desirable for mq changesets to be kept in the secret phase (see "hg - help phases"), which can be enabled with the following setting: + It may be desirable for mq changesets to be kept in the secret phase (see 'hg + help phases'), which can be enabled with the following setting: [mq] secret = True You will by default be managing a patch queue named "patches". You can create - other, independent patch queues with the "hg qqueue" command. + other, independent patch queues with the 'hg qqueue' command. If the working directory contains uncommitted files, qpush, qpop and qgoto abort immediately. If -f/--force is used, the changes are discarded. Setting: @@ -1394,9 +1394,10 @@ apply force, should discard changes in hello, but not bye - $ hg qpush -f --verbose + $ hg qpush -f --verbose --config 'ui.origbackuppath=.hg/origbackups' applying empty - saving current version of hello.txt as hello.txt.orig + creating directory: $TESTTMP/forcepush/.hg/origbackups (glob) + saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt.orig (glob) patching file hello.txt committing files: hello.txt @@ -1405,7 +1406,6 @@ now at: empty $ hg st M bye.txt - ? hello.txt.orig $ hg diff --config diff.nodates=True diff -r ba252371dbc1 bye.txt --- a/bye.txt @@ -1428,6 +1428,10 @@ +world +universe +test that the previous call to qpush with -f (--force) and --config actually put +the orig files out of the working copy + $ ls .hg/origbackups + hello.txt.orig test popping revisions not in working dir ancestry
--- a/tests/test-notify.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-notify.t Sun Jan 17 21:40:21 2016 -0600 @@ -22,7 +22,7 @@ This extension implements hooks to send email notifications when changesets are sent from or received by the local repository. - First, enable the extension as explained in "hg help extensions", and register + First, enable the extension as explained in 'hg help extensions', and register the hook you want to run. "incoming" and "changegroup" hooks are run when changesets are received, while "outgoing" hooks are for changesets sent to another repository:
--- a/tests/test-obsolete-changeset-exchange.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-obsolete-changeset-exchange.t Sun Jan 17 21:40:21 2016 -0600 @@ -118,19 +118,17 @@ $ cd .. client only pulls down 1 changeset -("all local heads known remotely" may change if the wire protocol discovery -commands ever stop saying they have hidden changesets) $ cd pull-hidden-common-client $ hg pull --debug pulling from $TESTTMP/pull-hidden-common (glob) query 1; heads searching for changes - all local heads known remotely - 3 changesets found + taking quick initial sample + query 2; still undecided: 2, sample size is: 2 + 2 total queries + 1 changesets found list of changesets: - 96ee1d7354c4ad7372047672c36a1f561e3a6a4c - a33779fdfc23063680fc31e9ff637dff6876d3d2 bec0734cd68e84477ba7fc1d13e6cff53ab70129 listing keys for "phase" listing keys for "bookmarks" @@ -141,14 +139,12 @@ bundle2-input-bundle: with-transaction bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported adding changesets - add changeset 96ee1d7354c4 - add changeset a33779fdfc23 add changeset bec0734cd68e adding manifests adding file changes adding foo revisions added 1 changesets with 1 changes to 1 files (+1 heads) - bundle2-input-part: total payload size 1378 + bundle2-input-part: total payload size 474 bundle2-input-part: "listkeys" (params: 1 mandatory) supported bundle2-input-part: "listkeys" (params: 1 mandatory) supported bundle2-input-bundle: 2 parts total
--- a/tests/test-obsolete.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-obsolete.t Sun Jan 17 21:40:21 2016 -0600 @@ -238,6 +238,14 @@ Fixing "bumped" situation We need to create a clone of 5 and add a special marker with a flag + $ hg summary + parent: 5:5601fb93a350 tip + add new_3_c + branch: default + commit: (clean) + update: 1 new changesets, 2 branch heads (merge) + phases: 1 draft + bumped: 1 changesets $ hg up '5^' 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg revert -ar 5 @@ -465,6 +473,14 @@ 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'} $ hg log -r 'obsolete()' 4:94b33453f93b (draft) [ ] add original_d + $ hg summary + parent: 5:cda648ca50f5 tip + add original_e + branch: default + commit: (clean) + update: 1 new changesets, 2 branch heads (merge) + phases: 3 draft + unstable: 1 changesets $ hg log -G -r '::unstable()' @ 5:cda648ca50f5 (draft) [tip ] add original_e | @@ -712,34 +728,40 @@ $ hg init repo-issue3805 $ cd repo-issue3805 + $ echo "base" > base + $ hg ci -Am "base" + adding base $ echo "foo" > foo $ hg ci -Am "A" adding foo $ hg clone . ../other-issue3805 updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo "bar" >> foo $ hg ci --amend $ cd ../other-issue3805 $ hg log -G - @ 0:193e9254ce7e (draft) [tip ] A + @ 1:29f0c6921ddd (draft) [tip ] A + | + o 0:d20a80d4def3 (draft) [ ] base $ hg log -G -R ../repo-issue3805 - @ 2:3816541e5485 (draft) [tip ] A + @ 3:323a9c3ddd91 (draft) [tip ] A + | + o 0:d20a80d4def3 (draft) [ ] base $ hg incoming comparing with $TESTTMP/tmpe/repo-issue3805 (glob) searching for changes - 2:3816541e5485 (draft) [tip ] A + 3:323a9c3ddd91 (draft) [tip ] A $ hg incoming --bundle ../issue3805.hg comparing with $TESTTMP/tmpe/repo-issue3805 (glob) searching for changes - 2:3816541e5485 (draft) [tip ] A + 3:323a9c3ddd91 (draft) [tip ] A $ hg outgoing comparing with $TESTTMP/tmpe/repo-issue3805 (glob) searching for changes - no changes found - [1] + 1:29f0c6921ddd (draft) [tip ] A #if serve @@ -749,12 +771,11 @@ $ hg incoming http://localhost:$HGPORT comparing with http://localhost:$HGPORT/ searching for changes - 1:3816541e5485 (draft) [tip ] A + 2:323a9c3ddd91 (draft) [tip ] A $ hg outgoing http://localhost:$HGPORT comparing with http://localhost:$HGPORT/ searching for changes - no changes found - [1] + 1:29f0c6921ddd (draft) [tip ] A $ killdaemons.py @@ -767,13 +788,13 @@ $ cd .. $ hg init repo-issue3814 $ cd repo-issue3805 - $ hg push -r 3816541e5485 ../repo-issue3814 + $ hg push -r 323a9c3ddd91 ../repo-issue3814 pushing to ../repo-issue3814 searching for changes adding changesets adding manifests adding file changes - added 1 changesets with 1 changes to 1 files + added 2 changesets with 2 changes to 2 files 2 new obsolescence markers $ hg out ../repo-issue3814 comparing with ../repo-issue3814 @@ -783,24 +804,26 @@ Test that a local tag blocks a changeset from being hidden - $ hg tag -l visible -r 0 --hidden + $ hg tag -l visible -r 1 --hidden $ hg log -G - @ 2:3816541e5485 (draft) [tip ] A - - x 0:193e9254ce7e (draft) [visible ] A + @ 3:323a9c3ddd91 (draft) [tip ] A + | + | x 1:29f0c6921ddd (draft) [visible ] A + |/ + o 0:d20a80d4def3 (draft) [ ] base Test that removing a local tag does not cause some commands to fail $ hg tag -l -r tip tiptag $ hg tags - tiptag 2:3816541e5485 - tip 2:3816541e5485 - visible 0:193e9254ce7e + tiptag 3:323a9c3ddd91 + tip 3:323a9c3ddd91 + visible 1:29f0c6921ddd $ hg --config extensions.strip= strip -r tip --no-backup 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg tags - visible 0:193e9254ce7e - tip 0:193e9254ce7e + visible 1:29f0c6921ddd + tip 1:29f0c6921ddd Test bundle overlay onto hidden revision
--- a/tests/test-parseindex2.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-parseindex2.py Sun Jan 17 21:40:21 2016 -0600 @@ -22,7 +22,7 @@ s = 64 cache = None index = [] - nodemap = {nullid: nullrev} + nodemap = {nullid: nullrev} n = off = 0 l = len(data) - s
--- a/tests/test-patchbomb.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-patchbomb.t Sun Jan 17 21:40:21 2016 -0600 @@ -23,6 +23,8 @@ > print l, > EOF $ FILTERBOUNDARY="python `pwd`/prune-blank-after-boundary.py" + $ echo "[format]" >> $HGRCPATH + $ echo "usegeneraldelta=yes" >> $HGRCPATH $ echo "[extensions]" >> $HGRCPATH $ echo "patchbomb=" >> $HGRCPATH @@ -347,21 +349,22 @@ Content-Disposition: attachment; filename="bundle.hg" Content-Transfer-Encoding: base64 - SEcxMEJaaDkxQVkmU1nvR7I3AAAN////lFYQWj1/4HwRkdC/AywIAk0E4pfoSIIIgQCgGEQOcLAA - 2tA1VPyp4mkeoG0EaaPU0GTT1GjRiNPIg9CZGBqZ6UbU9J+KFU09DNUaGgAAAAAANAGgAAAAA1U8 - oGgAADQGgAANNANAAAAAAZipFLz3XoakCEQB3PVPyHJVi1iYkAAKQAZQGpQGZESInRnCFMqLDla2 - Bx3qfRQeA2N4lnzKkAmP8kR2asievLLXXebVU8Vg4iEBqcJNJAxIapSU6SM4888ZAciRG6MYAIEE - SlIBpFisgGkyRjX//TMtfcUAEsGu56+YnE1OlTZmzKm8BSu2rvo4rHAYYaadIFFuTy0LYgIkgLVD - sgVa2F19D1tx9+hgbAygLgQwaIqcDdgA4BjQgIiz/AEP72++llgDKhKducqodGE4B0ETqF3JFOFC - Q70eyNw= - --===*=-- (glob) + SEcyMAAAAA5Db21wcmVzc2lvbj1CWkJaaDkxQVkmU1lCZFwPAAAKf//7nFYSWD1/4H7R09C/I70I + Ak0E4peoSIYIgQCgGUQOcLABGY2hqoAAAaBMTTAAAahgTCZoAAAAAMQaqn5GmapojQ00DEGI/VGJ + kDAJoGTDUAAyM0QaAEqalPTUaMhoyDIDR6IxAGEGmgAehMRhDRsoyB6TYTC8JyLN+jTGqitRAgRJ + b3SRlhd8/+VxlAUqAilLoKPEEyxFQkaEGo+DzItFeNiFAo8NMMweVtvXJFIMhjoKC18DeYwjLKBz + wrMcs86qJrctDNJorwBMuLcqvTVWHh1IlsIaaaYSUIP2IZsogT1+pSSZS+bSTJrgfKsO9go/f0HF + uW4Yr2vXpxDreOgSIAdK/xC8Yay48SLpxIuqc/BZ6rVZCgG21rr0zhCaEgXOTqNaYEvANvg0B0Qo + dgtqAs1FDcZgzYitwJh6ZAG0C4mA7FPrp9b7h0h/A44Xgd+0it1gvF0mFE/CCPwymXS+OisOOCAF + mDUDAC1pBvsXckU4UJBCZFwP + --===============*==-- (glob) with a specific bundle type (binary part must be different) $ hg email --date '1970-1-1 0:3' -n -f quux -t foo \ > -c bar -s test -r tip -b --desc description \ - > --config patchbomb.bundletype=gzip | $FILTERBOUNDARY + > --config patchbomb.bundletype=gzip-v1 | $FILTERBOUNDARY searching for changes 1 changesets found @@ -2679,13 +2682,17 @@ +d +Don't prompt for a CC header. + + $ echo "[email]" >> $HGRCPATH + $ echo "cc=" >> $HGRCPATH + dest#branch URIs: $ hg email --date '1980-1-1 0:1' -n -t foo -s test -o ../t#test comparing with ../t From [test]: test this patch series consists of 1 patches. - Cc: displaying [PATCH] test ... Content-Type: text/plain; charset="us-ascii" @@ -2718,6 +2725,18 @@ +d +Set up a fake sendmail program + + $ cat > pretendmail.sh << 'EOF' + > #!/bin/sh + > echo "$@" + > cat + > EOF + $ chmod +x pretendmail.sh + + $ echo '[email]' >> $HGRCPATH + $ echo "method=`pwd`/pretendmail.sh" >> $HGRCPATH + Test introduction configuration ================================= @@ -2730,18 +2749,18 @@ single rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' | grep "Write the introductory message for the patch series." [1] single rev + flag - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' --intro | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' --intro | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. Multi rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '9::' | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '9::' | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. "never" setting @@ -2751,23 +2770,23 @@ single rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' | grep "Write the introductory message for the patch series." [1] single rev + flag - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' --intro | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' --intro | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. Multi rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '9::' | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '9::' | grep "Write the introductory message for the patch series." [1] Multi rev + flag - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '9::' --intro | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '9::' --intro | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. "always" setting @@ -2777,23 +2796,23 @@ single rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. single rev + flag - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' --intro | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' --intro | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. Multi rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '9::' | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '9::' | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. Multi rev + flag - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '9::' --intro | grep "Write the introductory message for the patch series." + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '9::' --intro | grep "Write the introductory message for the patch series." Write the introductory message for the patch series. bad value setting @@ -2803,15 +2822,13 @@ single rev - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' + $ hg email --date '1980-1-1 0:1' -v -t foo -s test -r '10' From [test]: test this patch series consists of 1 patches. warning: invalid patchbomb.intro value "mpmwearaclownnose" (should be one of always, never, auto) - Cc: - - displaying [PATCH] test ... + -f test foo Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -2842,6 +2859,9 @@ d +d + sending [PATCH] test ... + sending mail: $TESTTMP/t2/pretendmail.sh -f test foo + Test pull url header ================================= @@ -2849,7 +2869,7 @@ $ echo 'intro=auto' >> $HGRCPATH $ echo "publicurl=$TESTTMP/t2" >> $HGRCPATH - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' | grep '^#' + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' | grep '^#' abort: public url $TESTTMP/t2 is missing 3b6f1ec9dde9 (use "hg push $TESTTMP/t2 -r 3b6f1ec9dde9") [1] @@ -2857,7 +2877,7 @@ remote missing $ echo 'publicurl=$TESTTMP/missing' >> $HGRCPATH - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' unable to access public repo: $TESTTMP/missing abort: repository $TESTTMP/missing not found! [255] @@ -2872,7 +2892,7 @@ updating to branch test 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 'publicurl=$TESTTMP/t3' >> $HGRCPATH - $ hg email --date '1980-1-1 0:1' -n -t foo -s test -r '10' + $ hg email --date '1980-1-1 0:1' -t foo -s test -r '10' abort: public url $TESTTMP/t3 is missing 3b6f1ec9dde9 (use "hg push $TESTTMP/t3 -r 3b6f1ec9dde9") [255]
--- a/tests/test-pathencode.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-pathencode.py Sun Jan 17 21:40:21 2016 -0600 @@ -9,9 +9,6 @@ import binascii, itertools, math, os, random, sys, time import collections -if sys.version_info[:2] < (2, 6): - sys.exit(0) - validchars = set(map(chr, range(0, 256))) alphanum = range(ord('A'), ord('Z'))
--- a/tests/test-paths.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-paths.t Sun Jan 17 21:40:21 2016 -0600 @@ -3,6 +3,19 @@ updating to branch default 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd a + +with no paths: + + $ hg paths + $ hg paths unknown + not found! + [1] + $ hg paths -Tjson + [ + ] + +with paths: + $ echo '[paths]' >> .hg/hgrc $ echo 'dupe = ../b#tip' >> .hg/hgrc $ echo 'expand = $SOMETHING/bar' >> .hg/hgrc @@ -42,6 +55,101 @@ [1] $ hg paths -q unknown [1] + +formatter output with paths: + + $ echo 'dupe:pushurl = https://example.com/dupe' >> .hg/hgrc + $ hg paths -Tjson + [ + { + "name": "dupe", + "pushurl": "https://example.com/dupe", + "url": "$TESTTMP/b#tip" + }, + { + "name": "expand", + "url": "$TESTTMP/a/$SOMETHING/bar" + } + ] + $ hg paths -Tjson dupe + [ + { + "name": "dupe", + "pushurl": "https://example.com/dupe", + "url": "$TESTTMP/b#tip" + } + ] + $ hg paths -Tjson -q unknown + [ + ] + [1] + +password should be masked in plain output, but not in machine-readable output: + + $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc + $ hg paths insecure + http://foo:***@example.com/ + $ hg paths -Tjson insecure + [ + { + "name": "insecure", + "url": "http://foo:insecure@example.com/" + } + ] + + $ cd .. + +sub-options for an undeclared path are ignored + + $ hg init suboptions + $ cd suboptions + + $ cat > .hg/hgrc << EOF + > [paths] + > path0 = https://example.com/path0 + > path1:pushurl = https://example.com/path1 + > EOF + $ hg paths + path0 = https://example.com/path0 + +unknown sub-options aren't displayed + + $ cat > .hg/hgrc << EOF + > [paths] + > path0 = https://example.com/path0 + > path0:foo = https://example.com/path1 + > EOF + + $ hg paths + path0 = https://example.com/path0 + +:pushurl must be a URL + + $ cat > .hg/hgrc << EOF + > [paths] + > default = /path/to/nothing + > default:pushurl = /not/a/url + > EOF + + $ hg paths + (paths.default:pushurl not a URL; ignoring) + default = /path/to/nothing + +#fragment is not allowed in :pushurl + + $ cat > .hg/hgrc << EOF + > [paths] + > default = https://example.com/repo + > invalid = https://example.com/repo + > invalid:pushurl = https://example.com/repo#branch + > EOF + + $ hg paths + ("#fragment" in paths.invalid:pushurl not supported; ignoring) + default = https://example.com/repo + invalid = https://example.com/repo + invalid:pushurl = https://example.com/repo + $ cd .. 'file:' disables [paths] entries for clone destination
--- a/tests/test-pull-update.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-pull-update.t Sun Jan 17 21:40:21 2016 -0600 @@ -25,8 +25,9 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) - not updating: not a linear update + abort: not updating: not a linear update (merge or update --check to force update) + [255] $ cd ../tt @@ -39,8 +40,9 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) - not updating: not a linear update + abort: not updating: not a linear update (merge or update --check to force update) + [255] $ HGMERGE=true hg merge merging foo
--- a/tests/test-push-cgi.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-push-cgi.t Sun Jan 17 21:40:21 2016 -0600 @@ -31,7 +31,7 @@ $ . "$TESTDIR/cgienv" $ REQUEST_METHOD="POST"; export REQUEST_METHOD $ CONTENT_TYPE="application/octet-stream"; export CONTENT_TYPE - $ hg bundle --all bundle.hg + $ hg bundle --type v1 --all bundle.hg 1 changesets found $ CONTENT_LENGTH=279; export CONTENT_LENGTH;
--- a/tests/test-push-http-bundle1.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-push-http-bundle1.t Sun Jan 17 21:40:21 2016 -0600 @@ -77,7 +77,7 @@ 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: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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) @@ -93,7 +93,7 @@ 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: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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) @@ -109,7 +109,7 @@ 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: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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)
--- a/tests/test-push-http.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-push-http.t Sun Jan 17 21:40:21 2016 -0600 @@ -68,7 +68,7 @@ remote: adding file changes remote: added 1 changesets with 1 changes to 1 files 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) + remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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) @@ -85,7 +85,7 @@ remote: adding file changes remote: added 1 changesets with 1 changes to 1 files 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) + remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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) @@ -102,7 +102,7 @@ remote: adding file changes remote: added 1 changesets with 1 changes to 1 files 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) + remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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) @@ -123,7 +123,7 @@ 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: prepushkey hook: HG_BUNDLE2=1 HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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 @@ -141,7 +141,7 @@ 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: prepushkey hook: HG_BUNDLE2=1 HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=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 $ hg rollback repository tip rolled back to revision 0 (undo serve)
--- a/tests/test-qrecord.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-qrecord.t Sun Jan 17 21:40:21 2016 -0600 @@ -30,10 +30,10 @@ interactively select changes to commit - If a list of files is omitted, all changes reported by "hg status" will be + If a list of files is omitted, all changes reported by 'hg status' will be candidates for recording. - See "hg help dates" for a list of formats valid for -d/--date. + See 'hg help dates' for a list of formats valid for -d/--date. You will be prompted for whether to record changes to each modified file, and for files with multiple changes, for each change to use. For each @@ -82,7 +82,7 @@ interactively record a new patch - See "hg help qnew" & "hg help record" for more information and usage. + See 'hg help qnew' & 'hg help record' for more information and usage. (some details hidden, use --verbose to show complete help) @@ -114,7 +114,7 @@ interactively record a new patch - See "hg help qnew" & "hg help record" for more information and usage. + See 'hg help qnew' & 'hg help record' for more information and usage. (some details hidden, use --verbose to show complete help) @@ -128,7 +128,7 @@ interactively record a new patch - See "hg help qnew" & "hg help record" for more information and usage. + See 'hg help qnew' & 'hg help record' for more information and usage. options ([+] can be repeated):
--- a/tests/test-rebase-abort.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-abort.t Sun Jan 17 21:40:21 2016 -0600 @@ -68,11 +68,49 @@ unresolved conflicts (see hg resolve, then hg rebase --continue) [1] -Abort: +Insert unsupported advisory merge record: + + $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x + $ hg debugmergestate + * version 2 records + local: 3e046f2ecedb793b97ed32108086edd1a162f8bc + other: 46f0b057b5c061d276b91491c22151f78698abd2 + unrecognized entry: x advisory record + file: common (record type "F", state "u", hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1) + local path: common (flags "") + ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6) + other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5) + $ hg resolve -l + U common + +Insert unsupported mandatory merge record: + + $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X + $ hg debugmergestate + * version 2 records + local: 3e046f2ecedb793b97ed32108086edd1a162f8bc + other: 46f0b057b5c061d276b91491c22151f78698abd2 + file: common (record type "F", state "u", hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1) + local path: common (flags "") + ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6) + other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5) + unrecognized entry: X mandatory record + $ hg resolve -l + abort: unsupported merge state records: X + (see https://mercurial-scm.org/wiki/MergeStateRecords for more information) + [255] + $ hg resolve -ma + abort: unsupported merge state records: X + (see https://mercurial-scm.org/wiki/MergeStateRecords for more information) + [255] + +Abort (should clear out unsupported merge state): $ hg rebase --abort saved backup bundle to $TESTTMP/a/.hg/strip-backup/3e046f2ecedb-6beef7d5-backup.hg (glob) rebase aborted + $ hg debugmergestate + no merge state found $ hg tglog @ 4:draft 'L2'
--- a/tests/test-rebase-bookmarks.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-bookmarks.t Sun Jan 17 21:40:21 2016 -0600 @@ -176,6 +176,7 @@ $ echo 'c' > c $ hg resolve --mark c (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue rebasing 3:3d5fa227f4b5 "C" (Y Z) saved backup bundle to $TESTTMP/a3/.hg/strip-backup/3d5fa227f4b5-c6ea2371-backup.hg (glob)
--- a/tests/test-rebase-check-restore.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-check-restore.t Sun Jan 17 21:40:21 2016 -0600 @@ -78,6 +78,7 @@ $ rm A.orig $ hg resolve -m A (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue already rebased 1:27547f69f254 "B" as 45396c49d53b rebasing 2:965c486023db "C" @@ -134,6 +135,7 @@ $ rm A.orig $ hg resolve -m A (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue rebasing 5:01e6ebbd8272 "F" (tip) saved backup bundle to $TESTTMP/a2/.hg/strip-backup/01e6ebbd8272-6fd3a015-backup.hg (glob)
--- a/tests/test-rebase-conflicts.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-conflicts.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,4 +1,6 @@ $ cat >> $HGRCPATH <<EOF + > [format] + > usegeneraldelta=yes > [extensions] > rebase= > @@ -81,6 +83,7 @@ $ echo 'resolved merge' >common $ hg resolve -m common (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue already rebased 3:3163e20567cc "L1" as 3e046f2ecedb rebasing 4:46f0b057b5c0 "L2" @@ -275,13 +278,19 @@ list of changesets: e31216eec445e44352c5f01588856059466a24c9 2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2 + bundle2-output-bundle: "HG20", (1 params) 1 parts total + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-15f7a814-backup.hg (glob) 3 changesets found list of changesets: 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c 19c888675e133ab5dff84516926a65672eaf04d9 2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf + bundle2-output-bundle: "HG20", 1 parts total + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload adding branch + bundle2-input-bundle: with-transaction + bundle2-input-part: "changegroup" (params: 1 mandatory) supported adding changesets add changeset 4c9fbe56a16f add changeset 19c888675e13 @@ -290,6 +299,8 @@ adding file changes adding f1.txt revisions added 2 changesets with 2 changes to 1 files + bundle2-input-part: total payload size 1713 + bundle2-input-bundle: 0 parts total invalid branchheads cache (served): tip differs rebase completed updating the branch cache
--- a/tests/test-rebase-detach.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-detach.t Sun Jan 17 21:40:21 2016 -0600 @@ -391,6 +391,7 @@ [1] $ hg resolve --all -t internal:local (no more unresolved files) + continue: hg rebase --continue $ hg rebase -c rebasing 8:6215fafa5447 "H2" (tip) note: rebase of 8:6215fafa5447 created no changes to commit
--- a/tests/test-rebase-interruptions.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-interruptions.t Sun Jan 17 21:40:21 2016 -0600 @@ -107,6 +107,7 @@ $ rm A.orig $ hg resolve -m A (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue already rebased 1:27547f69f254 "B" as 45396c49d53b
--- a/tests/test-rebase-mq-skip.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-mq-skip.t Sun Jan 17 21:40:21 2016 -0600 @@ -2,6 +2,8 @@ already has one local mq patch $ cat >> $HGRCPATH <<EOF + > [format] + > usegeneraldelta=yes > [extensions] > rebase= > mq= @@ -68,17 +70,17 @@ $TESTTMP/a/.hg/patches/p0.patch (glob) 2 changesets found uncompressed size of bundle content: - 344 (changelog) - 284 (manifests) - 109 p0 - 109 p1 + 384 (changelog) + 324 (manifests) + 129 p0 + 129 p1 saved backup bundle to $TESTTMP/a/.hg/strip-backup/13a46ce44f60-5da6ecfb-backup.hg (glob) 2 changesets found uncompressed size of bundle content: - 399 (changelog) - 284 (manifests) - 109 p0 - 109 p1 + 439 (changelog) + 324 (manifests) + 129 p0 + 129 p1 adding branch adding changesets adding manifests @@ -153,6 +155,7 @@ $ HGMERGE=internal:local hg resolve --all (no more unresolved files) + continue: hg rebase --continue $ hg rebase --continue already rebased 1:b4bffa6e4776 "r1" (qbase r1) as 057f55ff8f44
--- a/tests/test-rebase-mq.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-mq.t Sun Jan 17 21:40:21 2016 -0600 @@ -70,6 +70,7 @@ $ echo mq1r1 > f $ hg resolve -m f (no more unresolved files) + continue: hg rebase --continue $ hg rebase -c rebasing 2:3504f44bffc0 "P0" (f.patch qbase) rebasing 3:929394423cd3 "P1" (f2.patch qtip tip) @@ -83,6 +84,7 @@ $ echo mq1r1mq2 > f $ hg resolve -m f (no more unresolved files) + continue: hg rebase --continue $ hg rebase -c already rebased 2:3504f44bffc0 "P0" (f.patch qbase) as ebe9914c0d1c rebasing 3:929394423cd3 "P1" (f2.patch qtip)
--- a/tests/test-rebase-newancestor.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-newancestor.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,4 +1,6 @@ $ cat >> $HGRCPATH <<EOF + > [format] + > usegeneraldelta=yes > [extensions] > rebase= > @@ -126,13 +128,15 @@ Full rebase all the way back from branching point: - $ hg rebase -r 'only(dev,default)' -d default + $ hg rebase -r 'only(dev,default)' -d default --config ui.interactive=True << EOF + > c + > EOF rebasing 1:1d1a643d390e "dev: create branch" note: rebase of 1:1d1a643d390e created no changes to commit rebasing 2:ec2c14fb2984 "dev: f-dev stuff" rebasing 4:4b019212aaf6 "dev: merge default" remote changed f-default which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c rebasing 6:9455ee510502 "dev: merge default" saved backup bundle to $TESTTMP/ancestor-merge/.hg/strip-backup/1d1a643d390e-43e9e04b-backup.hg (glob) $ hg tglog @@ -155,11 +159,13 @@ $ cd ../ancestor-merge-2 $ hg phase -fdr0: - $ hg rebase -r 'children(only(dev,default))' -d default + $ hg rebase -r 'children(only(dev,default))' -d default --config ui.interactive=True << EOF + > c + > EOF rebasing 2:ec2c14fb2984 "dev: f-dev stuff" rebasing 4:4b019212aaf6 "dev: merge default" remote changed f-default which local deleted - use (c)hanged version or leave (d)eleted? c + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c rebasing 6:9455ee510502 "dev: merge default" saved backup bundle to $TESTTMP/ancestor-merge-2/.hg/strip-backup/ec2c14fb2984-62d0b222-backup.hg (glob) $ hg tglog @@ -298,15 +304,15 @@ rebase merging completed 1 changesets found uncompressed size of bundle content: - 193 (changelog) - 196 (manifests) - 162 other + 213 (changelog) + 216 (manifests) + 182 other saved backup bundle to $TESTTMP/parentorder/.hg/strip-backup/4c5f12f25ebe-f46990e5-backup.hg (glob) 1 changesets found uncompressed size of bundle content: - 252 (changelog) - 147 (manifests) - 162 other + 272 (changelog) + 167 (manifests) + 182 other adding branch adding changesets adding manifests
--- a/tests/test-rebase-obsolete.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-obsolete.t Sun Jan 17 21:40:21 2016 -0600 @@ -11,7 +11,7 @@ > evolution=createmarkers,allowunstable > [phases] > publish=False - > [extensions]' + > [extensions] > rebase= > EOF @@ -248,6 +248,30 @@ D + $ hg up -qr 'desc(G)' + $ hg graft 4596109a6a4328c398bde3a4a3b6737cfade3003 + grafting 11:4596109a6a43 "D" + $ hg up -qr 'desc(E)' + $ hg rebase -s tip -d . + rebasing 14:0f4c66d0b70f "D" (tip) + $ hg log --style default --debug -r tip + changeset: 15:884f358981b4d32069bb539e0e95d49a35eb81d0 + tag: tip + phase: draft + parent: 4:9520eea781bcca16c1e15acc0ba14335a0e8e5ba + parent: -1:0000000000000000000000000000000000000000 + manifest: 15:648e8ede73ae3e497d093d3a4c8fcc2daa864f42 + user: Nicolas Dumazet <nicdumz.commits@gmail.com> + date: Sat Apr 30 15:24:48 2011 +0200 + files+: D + extra: branch=default + extra: intermediate-source=4596109a6a4328c398bde3a4a3b6737cfade3003 + extra: rebase_source=0f4c66d0b70f8e1ce4aec01f8e95cf24ee923afa + extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a + description: + D + + $ cd .. collapse rebase @@ -635,3 +659,149 @@ $ hg rebase -d 'desc(B2)' note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2" rebasing 5:1a79b7535141 "D" (tip) + $ hg up 4 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "O" > O + $ hg add O + $ hg commit -m O + $ echo "P" > P + $ hg add P + $ hg commit -m P + $ hg log -G + @ 8:8d47583e023f P + | + o 7:360bbaa7d3ce O + | + | o 6:9c48361117de D + | | + o | 4:ff2c4d47b71d C + |/ + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=all + $ hg rebase -d 6 -r "4::" + rebasing 4:ff2c4d47b71d "C" + note: not rebasing 7:360bbaa7d3ce "O", it has no successor + rebasing 8:8d47583e023f "P" (tip) + +If all the changeset to be rebased are obsolete and present in the destination, we +should display a friendly error message + + $ hg log -G + @ 10:121d9e3bc4c6 P + | + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + + $ hg up 9 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "non-relevant change" > nonrelevant + $ hg add nonrelevant + $ hg commit -m nonrelevant + created new head + $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=all + $ hg rebase -r . -d 10 + abort: all requested changesets have equivalents or were marked as obsolete + (to force the rebase, set the config experimental.rebaseskipobsolete to False) + [255] + +If a rebase is going to create divergence, it should abort + + $ hg log -G + @ 11:f44da1f4954c nonrelevant + | + | o 10:121d9e3bc4c6 P + |/ + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + + $ hg up 9 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "john" > doe + $ hg add doe + $ hg commit -m "john doe" + created new head + $ hg up 10 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "foo" > bar + $ hg add bar + $ hg commit --amend -m "10'" + $ hg up 10 --hidden + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "bar" > foo + $ hg add foo + $ hg commit -m "bar foo" + $ hg log -G + @ 15:73568ab6879d bar foo + | + | o 14:77d874d096a2 10' + | | + | | o 12:3eb461388009 john doe + | |/ + x | 10:121d9e3bc4c6 P + |/ + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + $ hg summary + parent: 15:73568ab6879d tip + bar foo + branch: default + commit: (clean) + update: 2 new changesets, 3 branch heads (merge) + phases: 8 draft + unstable: 1 changesets + $ hg rebase -s 10 -d 12 + abort: this rebase will cause divergence + (to force the rebase please set rebase.allowdivergence=True) + [255] + $ hg log -G + @ 15:73568ab6879d bar foo + | + | o 14:77d874d096a2 10' + | | + | | o 12:3eb461388009 john doe + | |/ + x | 10:121d9e3bc4c6 P + |/ + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + +With rebase.allowdivergence=True, rebase can create divergence + + $ hg rebase -s 10 -d 12 --config rebase.allowdivergence=True + rebasing 10:121d9e3bc4c6 "P" + rebasing 15:73568ab6879d "bar foo" (tip) + $ hg summary + parent: 17:61bd55f69bc4 tip + bar foo + branch: default + commit: (clean) + update: 1 new changesets, 2 branch heads (merge) + phases: 8 draft + divergent: 2 changesets +
--- a/tests/test-rebase-parameters.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebase-parameters.t Sun Jan 17 21:40:21 2016 -0600 @@ -484,6 +484,7 @@ $ hg resolve -m c2 (no more unresolved files) + continue: hg rebase --continue $ hg rebase -c --tool internal:fail rebasing 2:e4e3f3546619 "c2b" (tip) note: rebase of 2:e4e3f3546619 created no changes to commit
--- a/tests/test-rebuildstate.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rebuildstate.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,34 @@ + + $ cat > adddrop.py <<EOF + > from mercurial import cmdutil + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > @command('debugadddrop', + > [('', 'drop', False, 'drop file from dirstate', 'FILE'), + > ('', 'normal-lookup', False, 'add file to dirstate', 'FILE')], + > 'hg debugadddrop') + > def debugadddrop(ui, repo, *pats, **opts): + > '''Add or drop unnamed arguments to or from the dirstate''' + > drop = opts.get('drop') + > nl = opts.get('normal_lookup') + > if nl and drop: + > raise error.Abort('drop and normal-lookup are mutually exclusive') + > wlock = repo.wlock() + > try: + > for file in pats: + > if opts.get('normal_lookup'): + > repo.dirstate.normallookup(file) + > else: + > repo.dirstate.drop(file) + > + > repo.dirstate.write(repo.currenttransaction()) + > finally: + > wlock.release() + > EOF + + $ echo "[extensions]" >> $HGRCPATH + $ echo "debugadddrop=`pwd`/adddrop.py" >> $HGRCPATH + basic test for hg debugrebuildstate $ hg init repo @@ -20,6 +51,15 @@ n 644 -1 set bar n 644 -1 set foo + $ hg debugadddrop --normal-lookup file1 file2 + $ hg debugadddrop --drop bar + $ hg debugadddrop --drop + $ hg debugstate --nodates + n 0 -1 unset file1 + n 0 -1 unset file2 + n 644 -1 set foo + $ hg debugrebuildstate + status $ hg st -A @@ -27,4 +67,59 @@ ? baz C foo - $ cd .. +Test debugdirstate --minimal where a file is not in parent manifest +but in the dirstate + $ touch foo bar qux + $ hg add qux + $ hg remove bar + $ hg status -A + A qux + R bar + ? baz + C foo + $ hg debugadddrop --normal-lookup baz + $ hg debugdirstate --nodates + r 0 0 * bar (glob) + n 0 -1 * baz (glob) + n 644 0 * foo (glob) + a 0 -1 * qux (glob) + $ hg debugrebuilddirstate --minimal + $ hg debugdirstate --nodates + r 0 0 * bar (glob) + n 644 0 * foo (glob) + a 0 -1 * qux (glob) + $ hg status -A + A qux + R bar + ? baz + C foo + +Test debugdirstate --minimal where file is in the parent manifest but not the +dirstate + $ hg manifest + bar + foo + $ hg status -A + A qux + R bar + ? baz + C foo + $ hg debugdirstate --nodates + r 0 0 * bar (glob) + n 644 0 * foo (glob) + a 0 -1 * qux (glob) + $ hg debugadddrop --drop foo + $ hg debugdirstate --nodates + r 0 0 * bar (glob) + a 0 -1 * qux (glob) + $ hg debugrebuilddirstate --minimal + $ hg debugdirstate --nodates + r 0 0 * bar (glob) + n 644 -1 * foo (glob) + a 0 -1 * qux (glob) + $ hg status -A + A qux + R bar + ? baz + C foo +
--- a/tests/test-record.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-record.t Sun Jan 17 21:40:21 2016 -0600 @@ -17,10 +17,10 @@ interactively select changes to commit - If a list of files is omitted, all changes reported by "hg status" will be + If a list of files is omitted, all changes reported by 'hg status' will be candidates for recording. - See "hg help dates" for a list of formats valid for -d/--date. + See 'hg help dates' for a list of formats valid for -d/--date. You will be prompted for whether to record changes to each modified file, and for files with multiple changes, for each change to use. For each
--- a/tests/test-rename-dir-merge.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rename-dir-merge.t Sun Jan 17 21:40:21 2016 -0600 @@ -231,3 +231,63 @@ R a/f $ cd .. + +Test renames to separate directories + + $ hg init a + $ cd a + $ mkdir a + $ touch a/s + $ touch a/t + $ hg ci -Am0 + adding a/s + adding a/t + +Add more files + + $ touch a/s2 + $ touch a/t2 + $ hg ci -Am1 + adding a/s2 + adding a/t2 + +Do moves on a branch + + $ hg up 0 + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ mkdir s + $ mkdir t + $ hg mv a/s s + $ hg mv a/t t + $ hg ci -Am2 + created new head + $ hg st --copies --change . + A s/s + a/s + A t/t + a/t + R a/s + R a/t + +Merge shouldn't move s2, t2 + + $ hg merge + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg st --copies + M a/s2 + M a/t2 + +Try the merge in the other direction. It may or may not be appropriate for +status to list copies here. + + $ hg up -C 1 + 4 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg merge + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg st --copies + M s/s + M t/t + R a/s + R a/t
--- a/tests/test-rename-merge1.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rename-merge1.t Sun Jan 17 21:40:21 2016 -0600 @@ -36,18 +36,18 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: af1939970a1c, local: 044f8520aeeb+, remote: 85c198ef2f6c + note: possible conflict - a2 was renamed multiple times to: + c2 + b2 preserving a for resolve of b removing a b2: remote created -> g getting b2 b: remote moved from a -> m (premerge) - picked tool ':merge' for b (binary False symlink False) + picked tool ':merge' for b (binary False symlink False changedelete False) merging a and b to b my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c premerge successful - note: possible conflict - a2 was renamed multiple times to: - c2 - b2 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -177,10 +177,10 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 19d7f95df299, local: 0084274f6b67+, remote: 5d32493049f0 + note: possible conflict - file was deleted and renamed to: + newfile newfile: remote created -> g getting newfile - note: possible conflict - file was deleted and renamed to: - newfile 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg status
--- a/tests/test-rename-merge2.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rename-merge2.t Sun Jan 17 21:40:21 2016 -0600 @@ -90,16 +90,16 @@ preserving rev for resolve of rev a: remote unchanged -> k b: remote copied from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging a and b to b my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337 premerge successful rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -130,16 +130,16 @@ a: remote is newer -> g getting a b: local copied/moved from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b and a to b my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -169,16 +169,16 @@ preserving rev for resolve of rev removing a b: remote moved from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging a and b to b my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337 premerge successful rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -206,16 +206,16 @@ preserving b for resolve of b preserving rev for resolve of rev b: local copied/moved from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b and a to b my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -244,11 +244,11 @@ b: remote created -> g getting b rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -275,11 +275,11 @@ ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 97c705ade336 preserving rev for resolve of rev rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -310,11 +310,11 @@ b: remote created -> g getting b rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -340,11 +340,11 @@ ancestor: 924404dff337, local: 02963e448370+, remote: 97c705ade336 preserving rev for resolve of rev rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -368,20 +368,20 @@ preserving b for resolve of b preserving rev for resolve of rev b: both renamed from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337 b: both renamed from a -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -411,21 +411,21 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 02963e448370+, remote: fe905ef2c33e + note: possible conflict - a was renamed multiple times to: + b + c preserving rev for resolve of rev c: remote created -> g getting c rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 - note: possible conflict - a was renamed multiple times to: - b - c 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) -------------- @@ -447,20 +447,20 @@ preserving b for resolve of b preserving rev for resolve of rev b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -487,20 +487,20 @@ a: other deleted -> r removing a b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -526,20 +526,20 @@ a: remote is newer -> g getting a b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -566,20 +566,20 @@ a: other deleted -> r removing a b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -605,20 +605,20 @@ a: remote is newer -> g getting a b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -644,20 +644,20 @@ preserving rev for resolve of rev a: remote unchanged -> k b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -679,35 +679,36 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 02963e448370+, remote: 8dbce441892a - remote changed a which local deleted - use (c)hanged version or leave (d)eleted? c preserving b for resolve of b preserving rev for resolve of rev - a: prompt recreating -> g - getting a + a: prompt deleted/changed -> m (premerge) + picked tool ':prompt' for a (binary False symlink False changedelete True) + remote changed a which local deleted + use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@02963e448370+ other b@8dbce441892a ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 - 1 files updated, 2 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) + 0 files updated, 2 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon -------------- M a M b + abort: unresolved merge conflicts (see "hg help resolve") -------------- $ tm "up a b" "nm a b" " " "19 merge b no ancestor, prompt remove a" @@ -721,34 +722,37 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 0b76e65c8289+, remote: bdb19105162a - local changed a which remote deleted - use (c)hanged version or (d)elete? c + preserving a for resolve of a preserving b for resolve of b preserving rev for resolve of rev - a: prompt keep -> a + a: prompt changed/deleted -> m (premerge) + picked tool ':prompt' for a (binary False symlink False changedelete True) + local changed a which remote deleted + use (c)hanged version, (d)elete, or leave (u)nresolved? u b: both created -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b my b@0b76e65c8289+ other b@bdb19105162a ancestor b@000000000000 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337 b: both created -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 - 0 files updated, 2 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) + 0 files updated, 2 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon -------------- M b C a + abort: unresolved merge conflicts (see "hg help resolve") -------------- $ tm "up a " "um a b" " " "20 merge a and b to b, remove a" @@ -769,20 +773,20 @@ preserving rev for resolve of rev removing a b: remote moved from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging a and b to b my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337 b: remote moved from a -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -810,20 +814,20 @@ preserving b for resolve of b preserving rev for resolve of rev b: local copied/moved from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b and a to b my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337 rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337 b: local copied/moved from a -> m (merge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) 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 (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -858,16 +862,16 @@ c: remote created -> g getting c b: local copied/moved from a -> m (premerge) - picked tool 'python ../merge' for b (binary False symlink False) + picked tool 'python ../merge' for b (binary False symlink False changedelete False) merging b and a to b my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337 premerge successful rev: versions differ -> m (premerge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) merging rev my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337 rev: versions differ -> m (merge) - picked tool 'python ../merge' for rev (binary False symlink False) + picked tool 'python ../merge' for rev (binary False symlink False changedelete False) my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337 launching merge tool: python ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 @@ -922,7 +926,7 @@ $ mkdir 7 8 $ echo m > 7/f $ echo m > 8/f - $ hg merge -f --tool internal:dump -v --debug -r2 | sed '/^ 0\/f: both created -> m/,$d' 2> /dev/null + $ hg merge -f --tool internal:dump -v --debug -r2 | sed '/^resolving manifests/,$d' 2> /dev/null searching for copies back to rev 1 unmatched files in local: 5/g @@ -940,24 +944,6 @@ src: '5/f' -> dst: '5/g' * src: '6/f' -> dst: '6/g' * checking for directory renames - resolving manifests - branchmerge: True, force: True, partial: False - ancestor: e6cb3cf11019, local: ec44bf929ab5+, remote: c62e34d0b898 - remote changed 8/f which local deleted - use (c)hanged version or leave (d)eleted? c - preserving 0/f for resolve of 0/f - preserving 1/g for resolve of 1/g - preserving 2/f for resolve of 2/f - preserving 3/f for resolve of 3/f - preserving 3/f for resolve of 3/g - preserving 4/f for resolve of 4/g - preserving 5/f for resolve of 5/f - preserving 5/g for resolve of 5/g - preserving 6/g for resolve of 6/g - preserving 7/f for resolve of 7/f - removing 4/f - 8/f: prompt recreating -> g - getting 8/f $ hg mani 0/f 1/g
--- a/tests/test-resolve.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-resolve.t Sun Jan 17 21:40:21 2016 -0600 @@ -66,7 +66,7 @@ > def markdriver(ui, repo, *pats, **opts): > wlock = repo.wlock() > try: - > ms = merge.mergestate(repo) + > ms = merge.mergestate.read(repo) > m = scmutil.match(repo[None], pats, opts) > for f in ms: > if not m(f): @@ -154,6 +154,26 @@ abort: resolve command not applicable when not merging [255] +can not update or merge when there are unresolved conflicts + + $ hg up -qC 0 + $ echo quux >> file1 + $ hg up 1 + merging file1 + warning: conflicts while merging file1! (edit, then use 'hg resolve --mark') + 1 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ hg up 0 + abort: outstanding merge conflicts + [255] + $ hg merge 2 + abort: outstanding merge conflicts + [255] + $ hg merge --force 2 + abort: outstanding merge conflicts + [255] + set up conflict-free merge $ hg up -qC 3 @@ -197,6 +217,18 @@ $ cat file2.orig foo baz + +.orig files should exists where specified + $ hg resolve --all --verbose --config 'ui.origbackuppath=.hg/origbackups' + merging file1 + creating directory: $TESTTMP/repo/.hg/origbackups (glob) + merging file2 + warning: conflicts while merging file1! (edit, then use 'hg resolve --mark') + warning: conflicts while merging file2! (edit, then use 'hg resolve --mark') + [1] + $ ls .hg/origbackups + file1.orig + file2.orig $ grep '<<<' file1 > /dev/null $ grep '<<<' file2 > /dev/null @@ -209,22 +241,7 @@ test .orig behavior with resolve - $ echo resolve > file - $ hg resolve -q file1 --tool 'f --dump $TESTTMP/repo/file1.orig' - */file1~base*: (glob) - >>> - foo - <<< - */file1~other*: (glob) - >>> - foo - bar - <<< - $TESTTMP/repo/file1: (glob) - >>> - foo - baz - <<< + $ hg resolve -q file1 --tool "sh -c 'f --dump \"$TESTTMP/repo/file1.orig\"'" $TESTTMP/repo/file1.orig: (glob) >>> foo @@ -238,9 +255,69 @@ $ cat file1 resolved +insert unsupported advisory merge record + + $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x + $ hg debugmergestate + * version 2 records + local: 57653b9f834a4493f7240b0681efcb9ae7cab745 + other: dc77451844e37f03f5c559e3b8529b2b48d381d1 + unrecognized entry: x advisory record + file: file1 (record type "F", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd) + other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d) + file: file2 (record type "F", state "u", hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523) + local path: file2 (flags "") + ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd) + other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d) + $ hg resolve -l + R file1 + U file2 + +insert unsupported mandatory merge record + + $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X + $ hg debugmergestate + * version 2 records + local: 57653b9f834a4493f7240b0681efcb9ae7cab745 + other: dc77451844e37f03f5c559e3b8529b2b48d381d1 + file: file1 (record type "F", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390) + local path: file1 (flags "") + ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd) + other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d) + file: file2 (record type "F", state "u", hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523) + local path: file2 (flags "") + ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd) + other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d) + unrecognized entry: X mandatory record + $ hg resolve -l + abort: unsupported merge state records: X + (see https://mercurial-scm.org/wiki/MergeStateRecords for more information) + [255] + $ hg resolve -ma + abort: unsupported merge state records: X + (see https://mercurial-scm.org/wiki/MergeStateRecords for more information) + [255] + $ hg summary + parent: 2:57653b9f834a + append baz to files + parent: 1:dc77451844e3 + append bar to files + branch: default + warning: merge state has unsupported record types: X + commit: 2 modified, 2 unknown (merge) + update: 2 new changesets (update) + phases: 5 draft + +update --clean shouldn't abort on unsupported records + + $ hg up -qC 1 + $ hg debugmergestate + no merge state found + test crashed merge with empty mergestate - $ hg up -qC 1 $ mkdir .hg/merge $ touch .hg/merge/state
--- a/tests/test-revert.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-revert.t Sun Jan 17 21:40:21 2016 -0600 @@ -86,6 +86,16 @@ saving current version of e as e.orig reverting e +Test creation of backup (.orig) file in configured file location +---------------------------------------------------------------- + + $ echo z > e + $ hg revert --all -v --config 'ui.origbackuppath=.hg/origbackups' + creating directory: $TESTTMP/repo/.hg/origbackups (glob) + saving current version of e as $TESTTMP/repo/.hg/origbackups/e.orig (glob) + reverting e + $ rm -rf .hg/origbackups + revert on clean file (no change) --------------------------------
--- a/tests/test-revlog-ancestry.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-revlog-ancestry.py Sun Jan 17 21:40:21 2016 -0600 @@ -17,10 +17,10 @@ commit(name, time) def update(rev): - merge.update(repo, rev, False, True, False) + merge.update(repo, rev, False, True) def merge_(rev): - merge.update(repo, rev, True, False, False) + merge.update(repo, rev, True, False) if __name__ == '__main__': addcommit("A", 0) @@ -82,4 +82,3 @@ print '\nDescendants of 5 and 4' for r in repo.changelog.descendants([5, 4]): print r, -
--- a/tests/test-revset.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-revset.t Sun Jan 17 21:40:21 2016 -0600 @@ -1163,6 +1163,12 @@ 0 -1 +test ',' in `_list` + $ log '0,1' + hg: parse error: can't use a list in this context + (see hg help "revsets.x or y") + [255] + test that chained `or` operations make balanced addsets $ try '0:1|1:2|2:3|3:4|4:5' @@ -1476,11 +1482,11 @@ Bogus function gets suggestions $ log 'add()' hg: parse error: unknown identifier: add - (did you mean 'adds'?) + (did you mean adds?) [255] $ log 'added()' hg: parse error: unknown identifier: added - (did you mean 'adds'?) + (did you mean adds?) [255] $ log 'remo()' hg: parse error: unknown identifier: remo @@ -1493,7 +1499,7 @@ 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'?) + (did you mean matching?) [255] Undocumented functions aren't suggested as similar either @@ -2183,3 +2189,43 @@ [255] $ cd .. + +Test registrar.delayregistrar via revset.extpredicate + +'extpredicate' decorator shouldn't register any functions until +'setup()' on it. + + $ cd repo + + $ cat <<EOF > $TESTTMP/custompredicate.py + > from mercurial import revset + > + > revsetpredicate = revset.extpredicate() + > + > @revsetpredicate('custom1()') + > def custom1(repo, subset, x): + > return revset.baseset([1]) + > @revsetpredicate('custom2()') + > def custom2(repo, subset, x): + > return revset.baseset([2]) + > + > def uisetup(ui): + > if ui.configbool('custompredicate', 'enabled'): + > revsetpredicate.setup() + > EOF + $ cat <<EOF > .hg/hgrc + > [extensions] + > custompredicate = $TESTTMP/custompredicate.py + > EOF + + $ hg debugrevspec "custom1()" + hg: parse error: unknown identifier: custom1 + [255] + $ hg debugrevspec "custom2()" + hg: parse error: unknown identifier: custom2 + [255] + $ hg debugrevspec "custom1() or custom2()" --config custompredicate.enabled=true + 1 + 2 + + $ cd ..
--- a/tests/test-rollback.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-rollback.t Sun Jan 17 21:40:21 2016 -0600 @@ -113,9 +113,9 @@ > echo "another precious commit message" > "$1" > __EOF__ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg --config hooks.pretxncommit=false commit 2>&1 + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt abort: pretxncommit hook exited with status * (glob) [255] $ cat .hg/last-message.txt
--- a/tests/test-run-tests.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-run-tests.t Sun Jan 17 21:40:21 2016 -0600 @@ -7,13 +7,20 @@ $ unset HGTEST_PORT $ unset HGTEST_SHELL -Smoke test +Smoke test with install ============ - $ run-tests.py $HGTEST_RUN_TESTS_PURE + $ run-tests.py $HGTEST_RUN_TESTS_PURE -l # Ran 0 tests, 0 skipped, 0 warned, 0 failed. +Define a helper to avoid the install step +============= + $ rt() + > { + > run-tests.py --with-hg=`which hg` "$@" + > } + a succesful test ======================= @@ -26,7 +33,7 @@ > nor this (?) > EOF - $ run-tests.py --with-hg=`which hg` + $ rt . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. @@ -44,7 +51,7 @@ >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None - $ run-tests.py --with-hg=`which hg` + $ rt --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -73,7 +80,7 @@ [1] test --xunit support - $ run-tests.py --with-hg=`which hg` --xunit=xunit.xml + $ rt --xunit=xunit.xml --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -129,7 +136,7 @@ test for --retest ==================== - $ run-tests.py --with-hg=`which hg` --retest + $ rt --retest --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -152,18 +159,18 @@ successful - $ run-tests.py --with-hg=`which hg` test-success.t + $ rt test-success.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. success w/ keyword - $ run-tests.py --with-hg=`which hg` -k xyzzy + $ rt -k xyzzy . # Ran 2 tests, 1 skipped, 0 warned, 0 failed. failed - $ run-tests.py --with-hg=`which hg` test-failure.t + $ rt test-failure.t --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -182,7 +189,7 @@ [1] failure w/ keyword - $ run-tests.py --with-hg=`which hg` -k rataxes + $ rt -k rataxes --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -202,36 +209,45 @@ Verify that when a process fails to start we show a useful message ================================================================== -NOTE: there is currently a bug where this shows "2 failed" even though -it's actually the same test being reported for failure twice. $ cat > test-serve-fail.t <<EOF > $ echo 'abort: child process failed to start blah' > EOF - $ run-tests.py --with-hg=`which hg` test-serve-fail.t + $ rt test-serve-fail.t ERROR: test-serve-fail.t output changed ! - ERROR: test-serve-fail.t output changed - ! Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob) - Failed test-serve-fail.t: output changed - # Ran 1 tests, 0 skipped, 0 warned, 2 failed. + # Ran 1 tests, 0 skipped, 0 warned, 1 failed. python hash seed: * (glob) [1] $ rm test-serve-fail.t +Verify that we can try other ports +=================================== + $ hg init inuse + $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid + $ cat blocks.pid >> $DAEMON_PIDS + $ cat > test-serve-inuse.t <<EOF + > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid + > $ cat hg.pid >> \$DAEMON_PIDS + > EOF + $ rt test-serve-inuse.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + $ rm test-serve-inuse.t + Running In Debug Mode ====================== - $ run-tests.py --with-hg=`which hg` --debug 2>&1 | grep -v pwd + $ rt --debug 2>&1 | grep -v pwd + echo *SALT* 0 0 (glob) *SALT* 0 0 (glob) + echo babar babar + echo *SALT* 4 0 (glob) *SALT* 4 0 (glob) - .+ echo *SALT* 0 0 (glob) + *+ echo *SALT* 0 0 (glob) *SALT* 0 0 (glob) + echo babar babar @@ -250,7 +266,7 @@ (duplicate the failing test to get predictable output) $ cp test-failure.t test-failure-copy.t - $ run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t -n + $ rt --jobs 2 test-failure*.t -n !! Failed test-failure*.t: output changed (glob) Failed test-failure*.t: output changed (glob) @@ -262,7 +278,7 @@ >>> f = open('test-nothing.t', 'w') >>> f.write('foo\n' * 1024) and None >>> f.write(' $ sleep 1') and None - $ run-tests.py --with-hg=`which hg` --jobs 2 --first + $ rt --jobs 2 --first --- $TESTTMP/test-failure*.t (glob) +++ $TESTTMP/test-failure*.t.err (glob) @@ -292,7 +308,7 @@ Refuse the fix - $ echo 'n' | run-tests.py --with-hg=`which hg` -i + $ echo 'n' | rt -i --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -318,7 +334,7 @@ Interactive with custom view - $ echo 'n' | run-tests.py --with-hg=`which hg` -i --view echo + $ echo 'n' | rt -i --view echo $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob) Accept this change? [n]* (glob) ERROR: test-failure.t output changed @@ -330,7 +346,7 @@ View the fix - $ echo 'y' | run-tests.py --with-hg=`which hg` --view echo + $ echo 'y' | rt --view echo $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob) ERROR: test-failure.t output changed @@ -348,8 +364,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' | run-tests.py --with-hg=`which hg` -i 2>&1 | \ - > sed -e 's,(glob)$,&<,g' + $ echo 'y' | rt -i 2>&1 --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -361,9 +376,9 @@ this test is still more bytes than success. $ echo 'saved backup bundle to $TESTTMP/foo.hg' - saved backup bundle to $TESTTMP/foo.hg - + saved backup bundle to $TESTTMP/foo.hg (glob)< + + saved backup bundle to $TESTTMP/foo.hg* (glob) $ echo 'saved backup bundle to $TESTTMP/foo.hg' - saved backup bundle to $TESTTMP/foo.hg (glob)< + saved backup bundle to $TESTTMP/foo.hg* (glob) $ echo 'saved backup bundle to $TESTTMP/foo.hg' Accept this change? [n] .. # Ran 2 tests, 0 skipped, 0 warned, 0 failed. @@ -386,7 +401,7 @@ No Diff =============== - $ run-tests.py --with-hg=`which hg` --nodiff + $ rt --nodiff !. Failed test-failure.t: output changed # Ran 2 tests, 0 skipped, 0 warned, 1 failed. @@ -394,17 +409,42 @@ [1] test --tmpdir support - $ run-tests.py --with-hg=`which hg` --tmpdir=$TESTTMP/keep test-success.t + $ rt --tmpdir=$TESTTMP/keep test-success.t Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t (glob) Keeping threadtmp dir: $TESTTMP/keep/child1 (glob) . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. +timeouts +======== + $ cat > test-timeout.t <<EOF + > $ sleep 2 + > $ echo pass + > pass + > EOF + > echo '#require slow' > test-slow-timeout.t + > cat test-timeout.t >> test-slow-timeout.t + $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t + st + Skipped test-slow-timeout.t: missing feature: allow slow tests + Failed test-timeout.t: timed out + # Ran 1 tests, 1 skipped, 0 warned, 1 failed. + python hash seed: * (glob) + [1] + $ rt --timeout=1 --slowtimeout=3 \ + > test-timeout.t test-slow-timeout.t --allow-slow-tests + .t + Failed test-timeout.t: timed out + # Ran 2 tests, 0 skipped, 0 warned, 1 failed. + python hash seed: * (glob) + [1] + $ rm test-timeout.t test-slow-timeout.t + test for --time ================== - $ run-tests.py --with-hg=`which hg` test-success.t --time + $ rt test-success.t --time . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. # Producing time report @@ -414,7 +454,7 @@ test for --time with --job enabled ==================================== - $ run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2 + $ rt test-success.t --time --jobs 2 . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. # Producing time report @@ -427,24 +467,24 @@ > $ echo xyzzy > #require false > EOF - $ run-tests.py --with-hg=`which hg` --nodiff + $ rt --nodiff !.s - Skipped test-skip.t: skipped + Skipped test-skip.t: missing feature: nail clipper Failed test-failure.t: output changed # Ran 2 tests, 1 skipped, 0 warned, 1 failed. python hash seed: * (glob) [1] - $ run-tests.py --with-hg=`which hg` --keyword xyzzy + $ rt --keyword xyzzy .s - Skipped test-skip.t: skipped + Skipped test-skip.t: missing feature: nail clipper # Ran 2 tests, 2 skipped, 0 warned, 0 failed. Skips with xml - $ run-tests.py --with-hg=`which hg` --keyword xyzzy \ + $ rt --keyword xyzzy \ > --xunit=xunit.xml .s - Skipped test-skip.t: skipped + Skipped test-skip.t: missing feature: nail clipper # Ran 2 tests, 2 skipped, 0 warned, 0 failed. $ cat xunit.xml <?xml version="1.0" encoding="utf-8"?> @@ -454,7 +494,7 @@ Missing skips or blacklisted skips don't count as executed: $ echo test-failure.t > blacklist - $ run-tests.py --with-hg=`which hg` --blacklist=blacklist \ + $ rt --blacklist=blacklist \ > test-failure.t test-bogus.t ss Skipped test-bogus.t: Doesn't exist @@ -466,7 +506,7 @@ test for --json ================== - $ run-tests.py --with-hg=`which hg` --json + $ rt --json --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -479,7 +519,7 @@ ERROR: test-failure.t output changed !.s - Skipped test-skip.t: skipped + Skipped test-skip.t: missing feature: nail clipper Failed test-failure.t: output changed # Ran 2 tests, 1 skipped, 0 warned, 1 failed. python hash seed: * (glob) @@ -490,6 +530,7 @@ "test-failure.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "diff": "---.+\+\+\+.+", ? (re) "end": "\s*[\d\.]{4,5}", ? (re) "result": "failure", ? (re) "start": "\s*[\d\.]{4,5}", ? (re) @@ -498,6 +539,7 @@ "test-skip.t": { "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "diff": "", ? (re) "end": "\s*[\d\.]{4,5}", ? (re) "result": "skip", ? (re) "start": "\s*[\d\.]{4,5}", ? (re) @@ -506,6 +548,7 @@ "test-success.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "diff": "", ? (re) "end": "\s*[\d\.]{4,5}", ? (re) "result": "success", ? (re) "start": "\s*[\d\.]{4,5}", ? (re) @@ -516,7 +559,7 @@ 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 + $ echo y | rt --json -i --- $TESTTMP/test-failure.t +++ $TESTTMP/test-failure.t.err @@ -527,7 +570,7 @@ 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 + Skipped test-skip.t: missing feature: nail clipper # Ran 2 tests, 1 skipped, 0 warned, 0 failed. $ cat report.json @@ -535,6 +578,7 @@ "test-failure.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "diff": "", ? (re) "end": "\s*[\d\.]{4,5}", ? (re) "result": "success", ? (re) "start": "\s*[\d\.]{4,5}", ? (re) @@ -543,6 +587,7 @@ "test-skip.t": { "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "diff": "", ? (re) "end": "\s*[\d\.]{4,5}", ? (re) "result": "skip", ? (re) "start": "\s*[\d\.]{4,5}", ? (re) @@ -551,6 +596,7 @@ "test-success.t": [\{] (re) "csys": "\s*[\d\.]{4,5}", ? (re) "cuser": "\s*[\d\.]{4,5}", ? (re) + "diff": "", ? (re) "end": "\s*[\d\.]{4,5}", ? (re) "result": "success", ? (re) "start": "\s*[\d\.]{4,5}", ? (re) @@ -568,7 +614,7 @@ > foo * \ (glob) > EOF - $ run-tests.py --with-hg=`which hg` test-glob-backslash.t + $ rt test-glob-backslash.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. @@ -588,7 +634,7 @@ > $ echo foo > foo > EOF - $ run-tests.py $HGTEST_RUN_TESTS_PURE test-hghave.t + $ rt $HGTEST_RUN_TESTS_PURE test-hghave.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. @@ -600,14 +646,18 @@ > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime) > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime) > + > #if windows + > $ test "\$TESTDIR" = "$TESTTMP\anothertests" + > #else > $ test "\$TESTDIR" = "$TESTTMP"/anothertests + > #endif > $ 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 $HGTEST_RUN_TESTS_PURE test-runtestdir.t + $ rt $HGTEST_RUN_TESTS_PURE test-runtestdir.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. @@ -624,7 +674,7 @@ > $ custom-command.sh > hello world > EOF - $ run-tests.py $HGTEST_RUN_TESTS_PURE test-testdir-path.t + $ rt $HGTEST_RUN_TESTS_PURE test-testdir-path.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed. @@ -636,10 +686,10 @@ > $ echo pass > pass > EOF - $ run-tests.py $HGTEST_RUN_TESTS_PURE test-very-slow-test.t + $ rt $HGTEST_RUN_TESTS_PURE test-very-slow-test.t s - Skipped test-very-slow-test.t: skipped + Skipped test-very-slow-test.t: missing feature: allow slow tests # Ran 0 tests, 1 skipped, 0 warned, 0 failed. - $ run-tests.py $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t + $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t . # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
--- a/tests/test-shelve.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-shelve.t Sun Jan 17 21:40:21 2016 -0600 @@ -54,6 +54,7 @@ -A --addremove mark new/missing files as added/removed before shelving + -u --unknown Store unknown files in the shelve --cleanup delete all shelved changes --date DATE shelve with the specified commit date -d --delete delete the named shelved change(s) @@ -140,11 +141,11 @@ ensure that our shelved changes exist $ hg shelve -l - default-01 (*)* changes to '[mq]: second.patch' (glob) - default (*)* changes to '[mq]: second.patch' (glob) + default-01 (*)* changes to: [mq]: second.patch (glob) + default (*)* changes to: [mq]: second.patch (glob) $ hg shelve -l -p default - default (*)* changes to '[mq]: second.patch' (glob) + default (*)* changes to: [mq]: second.patch (glob) diff --git a/a/a b/a/a --- a/a/a @@ -177,7 +178,7 @@ unshelving change 'default-01' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 4:4702e8911fe0 "changes to '[mq]: second.patch'" (tip) + rebasing 4:32c69314e062 "changes to: [mq]: second.patch" (tip) merging a/a $ hg revert --all -q @@ -299,7 +300,7 @@ unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 5:4702e8911fe0 "changes to '[mq]: second.patch'" (tip) + rebasing 5:32c69314e062 "changes to: [mq]: second.patch" (tip) merging a/a warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') @@ -329,7 +330,7 @@ c +======= +a - +>>>>>>> source: 4702e8911fe0 - shelve: changes to '[mq]: second.patch' + +>>>>>>> source: 32c69314e062 - shelve: changes to: [mq]: second.patch diff --git a/b/b b/b.rename/b rename from b/b rename to b.rename/b @@ -395,6 +396,7 @@ $ hg revert -r . a/a $ hg resolve -m a/a (no more unresolved files) + continue: hg unshelve --continue $ hg commit -m 'commit while unshelve in progress' abort: unshelve already in progress @@ -402,7 +404,7 @@ [255] $ hg unshelve -c - rebasing 5:4702e8911fe0 "changes to '[mq]: second.patch'" (tip) + rebasing 5:32c69314e062 "changes to: [mq]: second.patch" (tip) unshelve of 'default' complete ensure the repo is as we hope @@ -469,13 +471,36 @@ if we resolve a conflict while unshelving, the unshelve should succeed + $ hg unshelve --tool :merge-other --keep + unshelving change 'default' + temporarily committing pending changes (restore with 'hg unshelve --abort') + rebasing shelved changes + rebasing 6:2f694dd83a13 "changes to: second" (tip) + merging a/a + $ hg parents -q + 4:33f7f61e6c5e + $ hg shelve -l + default (*)* changes to: second (glob) + $ hg status + M a/a + A foo/foo + $ cat a/a + a + c + a + $ cat > a/a << EOF + > a + > c + > x + > EOF + $ HGMERGE=true hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 6:c5e6910e7601 "changes to 'second'" (tip) + rebasing 6:2f694dd83a13 "changes to: second" (tip) merging a/a - note: rebase of 6:c5e6910e7601 created no changes to commit + note: rebase of 6:2f694dd83a13 created no changes to commit $ hg parents -q 4:33f7f61e6c5e $ hg shelve -l @@ -492,11 +517,11 @@ shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg shelve --list - default (*) changes to 'create conflict' (glob) - $ hg unshelve --keep + default (*)* changes to: create conflict (glob) + $ hg unshelve -k unshelving change 'default' $ hg shelve --list - default (*) changes to 'create conflict' (glob) + default (*)* changes to: create conflict (glob) $ hg shelve --cleanup $ hg shelve --list @@ -531,7 +556,7 @@ shelved as test 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg --config extensions.mq=! shelve --list - test (*) changes to 'create conflict' (glob) + test (*)* changes to: create conflict (glob) $ hg bookmark * test 4:33f7f61e6c5e $ hg --config extensions.mq=! unshelve @@ -564,7 +589,7 @@ $ hg unshelve unshelving change 'default' rebasing shelved changes - rebasing 4:b8fefe789ed0 "changes to 'xyz'" (tip) + rebasing 4:82a0d7d6ba61 "changes to: xyz" (tip) $ hg status M z @@ -591,7 +616,7 @@ $ hg unshelve unshelving change 'default' rebasing shelved changes - rebasing 3:0cae6656c016 "changes to 'c'" (tip) + rebasing 3:958bcbd1776e "changes to: c" (tip) $ hg status A d @@ -605,7 +630,7 @@ $ hg unshelve unshelving change 'default' rebasing shelved changes - rebasing 3:be58f65f55fb "changes to 'b'" (tip) + rebasing 3:013284d9655e "changes to: b" (tip) $ hg status A d @@ -688,7 +713,7 @@ shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -G --template '{rev} {desc|firstline} {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()' - o 4 changes to 'commit stuff' shelve@localhost + o 4 changes to: commit stuff shelve@localhost | $ hg log -G --template '{rev} {desc|firstline} {author}' @ 3 commit stuff test @@ -703,13 +728,13 @@ unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 5:23b29cada8ba "changes to 'commit stuff'" (tip) + rebasing 5:81152db69da7 "changes to: commit stuff" (tip) merging f warning: conflicts while merging f! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}' - @ 5 changes to 'commit stuff' shelve@localhost 1970-01-01 00:00 +0000 + @ 5 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000 | | @ 4 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000 |/ @@ -727,10 +752,11 @@ g ======= f - >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff' + >>>>>>> source: 81152db69da7 - shelve: changes to: commit stuff $ cat f.orig g - $ hg unshelve --abort + $ hg unshelve --abort -t false + tool option will be ignored rebase aborted unshelve of 'default' aborted $ hg st @@ -742,7 +768,7 @@ unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 5:23b29cada8ba "changes to 'commit stuff'" (tip) + rebasing 5:81152db69da7 "changes to: commit stuff" (tip) $ hg st M a A f @@ -758,7 +784,7 @@ $ hg unshelve unshelving change 'default' rebasing shelved changes - rebasing 5:23b29cada8ba "changes to 'commit stuff'" (tip) + rebasing 5:81152db69da7 "changes to: commit stuff" (tip) merging f warning: conflicts while merging f! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') @@ -771,7 +797,7 @@ g ======= f - >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff' + >>>>>>> source: 81152db69da7 - shelve: changes to: commit stuff $ cat f.orig g $ hg unshelve --abort @@ -801,7 +827,7 @@ $ hg unshelve unshelving change 'default' rebasing shelved changes - rebasing 5:4b555fdb4e96 "changes to 'second'" (tip) + rebasing 5:e42a7da90865 "changes to: second" (tip) merging a/a warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') @@ -816,9 +842,10 @@ reverting a/a (glob) $ hg resolve -m a/a (no more unresolved files) + continue: hg unshelve --continue $ hg unshelve -c - rebasing 5:4b555fdb4e96 "changes to 'second'" (tip) - note: rebase of 5:4b555fdb4e96 created no changes to commit + rebasing 5:e42a7da90865 "changes to: second" (tip) + note: rebase of 5:e42a7da90865 created no changes to commit unshelve of 'default' complete $ hg bookmark * test 4:33f7f61e6c5e @@ -911,7 +938,7 @@ unshelving change 'test' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 6:65b5d1c34c34 "changes to 'create conflict'" (tip) + rebasing 6:96a1354f65f6 "changes to: create conflict" (tip) merging a/a $ hg bookmark * test 4:33f7f61e6c5e @@ -945,7 +972,7 @@ abort: --stat expects a single shelf [255] $ hg shelve --patch default - default (* ago) changes to 'create conflict' (glob) + default (*)* changes to: create conflict (glob) diff --git a/shelf-patch-a b/shelf-patch-a new file mode 100644 @@ -954,7 +981,7 @@ @@ -0,0 +1,1 @@ +patch a $ hg shelve --stat default - default (* ago) changes to 'create conflict' (glob) + default (*)* changes to: create conflict (glob) shelf-patch-a | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ hg shelve --patch nonexistentshelf @@ -971,7 +998,7 @@ no general delta - $ hg clone --pull repo bundle1 --config format.generaldelta=0 + $ hg clone --pull repo bundle1 --config format.usegeneraldelta=0 requesting all changes adding changesets adding manifests @@ -986,12 +1013,12 @@ shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg debugbundle .hg/shelved/*.hg - 7e30d8ac6f23cfc84330fd7e698730374615d21a + 45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d $ cd .. with general delta - $ hg clone --pull repo bundle2 --config format.generaldelta=1 + $ hg clone --pull repo bundle2 --config format.usegeneraldelta=1 requesting all changes adding changesets adding manifests @@ -1008,7 +1035,7 @@ $ hg debugbundle .hg/shelved/*.hg Stream params: {'Compression': 'BZ'} changegroup -- "{'version': '02'}" - 7e30d8ac6f23cfc84330fd7e698730374615d21a + 45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d $ cd .. Test visibility of in-memory changes inside transaction to external hook @@ -1062,13 +1089,13 @@ $ hg unshelve --keep default temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 7:fcbb97608399 "changes to 'create conflict'" (tip) + rebasing 7:206bf5d4f922 "changes to: create conflict" (tip) ==== preupdate: VISIBLE 6:66b86db80ee4 ACTUAL 5:703117a2acfb ==== ==== preupdate: - VISIBLE 8:cb2a4e59c2d5 + VISIBLE 8:a0e04704317e ACTUAL 5:703117a2acfb ==== ==== preupdate: @@ -1107,10 +1134,10 @@ $ hg unshelve --keep default temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes - rebasing 7:fcbb97608399 "changes to 'create conflict'" (tip) + rebasing 7:206bf5d4f922 "changes to: create conflict" (tip) ==== update: VISIBLE 6:66b86db80ee4 - VISIBLE 7:fcbb97608399 + VISIBLE 7:206bf5d4f922 ACTUAL 5:703117a2acfb ==== ==== update: @@ -1135,7 +1162,7 @@ $ cd .. -test Abort unshelve always gets user out of the unshelved state +test .orig files go where the user wants them to --------------------------------------------------------------- $ hg init salvage $ cd salvage @@ -1144,15 +1171,21 @@ $ echo '' > root $ hg shelve -q $ echo 'contADDent' > root - $ hg unshelve -q + $ hg unshelve -q --config 'ui.origbackuppath=.hg/origbackups' warning: conflicts while merging root! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] + $ ls .hg/origbackups + root.orig + $ rm -rf .hg/origbackups + +test Abort unshelve always gets user out of the unshelved state +--------------------------------------------------------------- Wreak havoc on the unshelve process $ rm .hg/unshelverebasestate $ hg unshelve --abort unshelve of 'default' aborted - abort: No such file or directory + abort: (No such file or directory|The system cannot find the file specified) (re) [255] Can the user leave the current state? $ hg up -C . @@ -1213,3 +1246,71 @@ test 4:33f7f61e6c5e $ cd .. + +Shelve and unshelve unknown files. For the purposes of unshelve, a shelved +unknown file is the same as a shelved added file, except that it will be in +unknown state after unshelve if and only if it was either absent or unknown +before the unshelve operation. + + $ hg init unknowns + $ cd unknowns + +The simplest case is if I simply have an unknown file that I shelve and unshelve + + $ echo unknown > unknown + $ hg status + ? unknown + $ hg shelve --unknown + shelved as default + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg status + $ hg unshelve + unshelving change 'default' + $ hg status + ? unknown + $ rm unknown + +If I shelve, add the file, and unshelve, does it stay added? + + $ echo unknown > unknown + $ hg shelve -u + shelved as default + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg status + $ touch unknown + $ hg add unknown + $ hg status + A unknown + $ hg unshelve + unshelving change 'default' + temporarily committing pending changes (restore with 'hg unshelve --abort') + rebasing shelved changes + rebasing 1:098df96e7410 "(changes in empty repository)" (tip) + merging unknown + $ hg status + A unknown + $ hg forget unknown + $ rm unknown + +And if I shelve, commit, then unshelve, does it become modified? + + $ echo unknown > unknown + $ hg shelve -u + shelved as default + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg status + $ touch unknown + $ hg add unknown + $ hg commit -qm "Add unknown" + $ hg status + $ hg unshelve + unshelving change 'default' + rebasing shelved changes + rebasing 1:098df96e7410 "(changes in empty repository)" (tip) + merging unknown + $ hg status + M unknown + $ hg remove --force unknown + $ hg commit -qm "Remove unknown" + + $ cd ..
--- a/tests/test-ssh-bundle1.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-ssh-bundle1.t Sun Jan 17 21:40:21 2016 -0600 @@ -5,6 +5,8 @@ > [experimental] > # This test is dedicated to interaction through old bundle > bundle2-exp = False + > [format] # temporary settings + > usegeneraldelta=yes > EOF @@ -460,8 +462,8 @@ 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: 371 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 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 @@ -490,7 +492,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_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=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 @@ -500,7 +502,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_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=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 @@ -508,7 +510,7 @@ 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_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=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 remote hook failure is attributed to remote @@ -522,11 +524,11 @@ $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc - $ hg -q --config ui.ssh="python '$TESTDIR/dummyssh'" clone ssh://user@dummy/remote hookout + $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout $ cd hookout $ touch hookfailure $ hg -q commit -A -m 'remote hook failure' - $ hg --config ui.ssh="python '$TESTDIR/dummyssh'" push + $ hg --config ui.ssh="python $TESTDIR/dummyssh" push pushing to ssh://user@dummy/remote searching for changes remote: adding changesets
--- a/tests/test-ssh.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-ssh.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,6 +1,11 @@ This test tries to exercise the ssh functionality with a dummy script + $ cat <<EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF + creating 'remote' repo $ hg init remote @@ -449,8 +454,8 @@ 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: 371 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 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 @@ -482,7 +487,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_BUNDLE2=1 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_NODE_LAST=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 @@ -492,7 +497,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_BUNDLE2=1 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_NODE_LAST=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 @@ -500,7 +505,7 @@ 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_BUNDLE2=1 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_NODE_LAST=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 remote hook failure is attributed to remote @@ -514,11 +519,11 @@ $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc - $ hg -q --config ui.ssh="python '$TESTDIR/dummyssh'" clone ssh://user@dummy/remote hookout + $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout $ cd hookout $ touch hookfailure $ hg -q commit -A -m 'remote hook failure' - $ hg --config ui.ssh="python '$TESTDIR/dummyssh'" push + $ hg --config ui.ssh="python $TESTDIR/dummyssh" push pushing to ssh://user@dummy/remote searching for changes remote: adding changesets
--- a/tests/test-static-http.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-static-http.t Sun Jan 17 21:40:21 2016 -0600 @@ -68,7 +68,7 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files - changegroup hook: HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT/remote (glob) + changegroup hook: HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_NODE_LAST=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT/remote (glob) (run 'hg update' to get a working copy) trying to push
--- a/tests/test-status.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-status.t Sun Jan 17 21:40:21 2016 -0600 @@ -362,6 +362,45 @@ $ cd .. +hg status with --rev and reverted changes: + + $ hg init reverted-changes-repo + $ cd reverted-changes-repo + $ echo a > file + $ hg add file + $ hg ci -m a + $ echo b > file + $ hg ci -m b + +reverted file should appear clean + + $ hg revert -r 0 . + reverting file + $ hg status -A --rev 0 + C file + +#if execbit +reverted file with changed flag should appear modified + + $ chmod +x file + $ hg status -A --rev 0 + M file + + $ hg revert -r 0 . + reverting file + +reverted and committed file with changed flag should appear modified + + $ hg co -C . + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ chmod +x file + $ hg ci -m 'change flag' + $ hg status -A --rev 1 --rev 2 + M file + $ hg diff -r 1 -r 2 + +#endif + hg status of binary file starting with '\1\n', a separator for metadata: $ hg init repo5
--- a/tests/test-strip.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-strip.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,5 @@ + $ echo "[format]" >> $HGRCPATH + $ echo "usegeneraldelta=yes" >> $HGRCPATH $ echo "[extensions]" >> $HGRCPATH $ echo "strip=" >> $HGRCPATH @@ -208,7 +210,9 @@ summary: b $ hg debugbundle .hg/strip-backup/* - 264128213d290d868c54642d13aeaa3675551a78 + Stream params: {'Compression': 'BZ'} + changegroup -- "{'version': '02'}" + 264128213d290d868c54642d13aeaa3675551a78 $ hg pull .hg/strip-backup/* pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg searching for changes @@ -548,12 +552,19 @@ $ echo b > b $ echo d > d $ hg strip --keep tip - saved backup bundle to $TESTTMP/test/.hg/strip-backup/57e364c8a475-4cfed93c-backup.hg (glob) + saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob) $ hg status M b ! bar ? c ? d + +... after updating the dirstate + $ hg add c + $ hg commit -mc + $ hg rm c + $ hg commit -mc + $ hg strip --keep '.^' -q $ cd .. stripping many nodes on a complex graph (issue3299) @@ -569,11 +580,15 @@ $ cd .. $ hg init bookmarks $ cd bookmarks - $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b' + $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f' $ hg bookmark -r 'a' 'todelete' $ hg bookmark -r 'b' 'B' $ hg bookmark -r 'b' 'nostrip' $ hg bookmark -r 'c' 'delete' + $ hg bookmark -r 'd' 'multipledelete1' + $ hg bookmark -r 'e' 'multipledelete2' + $ hg bookmark -r 'f' 'singlenode1' + $ hg bookmark -r 'f' 'singlenode2' $ hg up -C todelete 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (activating bookmark todelete) @@ -593,6 +608,36 @@ $ hg bookmarks B 9:ff43616e5d0f delete 6:2702dd0c91e7 + multipledelete1 11:e46a4836065c + multipledelete2 12:b4594d867745 + singlenode1 13:43227190fef8 + singlenode2 13:43227190fef8 + $ hg strip -B multipledelete1 -B multipledelete2 + saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/e46a4836065c-89ec65c2-backup.hg (glob) + bookmark 'multipledelete1' deleted + bookmark 'multipledelete2' deleted + $ hg id -ir e46a4836065c + abort: unknown revision 'e46a4836065c'! + [255] + $ hg id -ir b4594d867745 + abort: unknown revision 'b4594d867745'! + [255] + $ hg strip -B singlenode1 -B singlenode2 + saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/43227190fef8-8da858f2-backup.hg (glob) + bookmark 'singlenode1' deleted + bookmark 'singlenode2' deleted + $ hg id -ir 43227190fef8 + abort: unknown revision '43227190fef8'! + [255] + $ hg strip -B unknownbookmark + abort: bookmark 'unknownbookmark' not found + [255] + $ hg strip -B unknownbookmark1 -B unknownbookmark2 + abort: bookmark 'unknownbookmark1,unknownbookmark2' not found + [255] + $ hg strip -B delete -B unknownbookmark + abort: bookmark 'unknownbookmark' not found + [255] $ hg strip -B delete saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob) bookmark 'delete' deleted @@ -622,14 +667,14 @@ options ([+] can be repeated): - -r --rev REV [+] strip specified revision (optional, can specify revisions - without this option) - -f --force force removal of changesets, discard uncommitted changes - (no backup) - --no-backup no backups - -k --keep do not modify working directory during strip - -B --bookmark VALUE remove revs only reachable from given bookmark - --mq operate on patch repository + -r --rev REV [+] strip specified revision (optional, can specify + revisions without this option) + -f --force force removal of changesets, discard uncommitted + changes (no backup) + --no-backup no backups + -k --keep do not modify working directory during strip + -B --bookmark VALUE [+] remove revs only reachable from given bookmark + --mq operate on patch repository (use "hg strip -h" to show more help) [255] @@ -751,6 +796,8 @@ list of changesets: 6625a516847449b6f0fa3737b9ba56e9f0f3032c d8db9d1372214336d2b5570f20ee468d2c72fa8b + bundle2-output-bundle: "HG20", (1 params) 1 parts total + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload 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
--- a/tests/test-subrepo-git.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-subrepo-git.t Sun Jan 17 21:40:21 2016 -0600 @@ -875,6 +875,16 @@ $ hg status --subrepos ? s/barfoo +revert moves orig files to the right place + $ echo 'bloop' > s/foobar + $ hg revert --all --verbose --config 'ui.origbackuppath=.hg/origbackups' + reverting subrepo ../gitroot + creating directory: $TESTTMP/tc/.hg/origbackups (glob) + saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar.orig (glob) + $ ls .hg/origbackups + foobar.orig + $ rm -rf .hg/origbackups + show file at specific revision $ cat > s/foobar << EOF > woop woop
--- a/tests/test-subrepo.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-subrepo.t Sun Jan 17 21:40:21 2016 -0600 @@ -297,11 +297,11 @@ ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198 preserving t for resolve of t t: versions differ -> m (premerge) - picked tool ':merge' for t (binary False symlink False) + picked tool ':merge' for t (binary False symlink False changedelete False) merging t my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a t: versions differ -> m (merge) - picked tool ':merge' for t (binary False symlink False) + picked tool ':merge' for t (binary False symlink False changedelete False) my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a warning: conflicts while merging t! (edit, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 1 files unresolved @@ -671,7 +671,7 @@ backout calls revert internally with minimal opts, which should not raise KeyError - $ hg backout ".^" + $ hg backout ".^" --no-commit 0 files updated, 0 files merged, 0 files removed, 0 files unresolved changeset c373c8102e68 backed out, don't forget to commit.
--- a/tests/test-tag.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-tag.t Sun Jan 17 21:40:21 2016 -0600 @@ -272,9 +272,9 @@ HG: branch 'tag-and-branch-same-name' HG: changed .hgtags ==== + note: commit message saved in .hg/last-message.txt transaction abort! rollback completed - note: commit message saved in .hg/last-message.txt abort: pretxncommit.unexpectedabort hook exited with status 1 [255] $ cat .hg/last-message.txt
--- a/tests/test-template-engine.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-template-engine.t Sun Jan 17 21:40:21 2016 -0600 @@ -44,4 +44,17 @@ 0 97e5f848f0936960273bbf75be6388cd0350a32b -1 0000000000000000000000000000000000000000 -1 0000000000000000000000000000000000000000 -1 0000000000000000000000000000000000000000 +Fuzzing the unicode escaper to ensure it produces valid data + +#if hypothesis + + >>> from hypothesishelpers import * + >>> import mercurial.templatefilters as tf + >>> import json + >>> @check(st.text().map(lambda s: s.encode('utf-8'))) + ... def testtfescapeproducesvalidjson(text): + ... json.loads('"' + tf.jsonescape(text) + '"') + +#endif + $ cd ..
--- a/tests/test-tools.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-tools.t Sun Jan 17 21:40:21 2016 -0600 @@ -43,7 +43,13 @@ foo: mode=644 #endif +#if no-windows $ python $TESTDIR/seq.py 10 > bar +#else +Convert CRLF -> LF for consistency + $ python $TESTDIR/seq.py 10 | sed "s/$//" > bar +#endif + #if unix-permissions symlink $ chmod +x bar $ f bar --newer foo --mode --type --size --dump --links --bytes 7
--- a/tests/test-transplant.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-transplant.t Sun Jan 17 21:40:21 2016 -0600 @@ -7,9 +7,26 @@ $ hg init t $ cd t + $ hg transplant + abort: no source URL, branch revision, or revision list provided + [255] + $ hg transplant --continue --all + abort: --continue is incompatible with --branch, --all and --merge + [255] + $ hg transplant --all tip + abort: --all requires a branch revision + [255] + $ hg transplant --all --branch default tip + abort: --all is incompatible with a revision list + [255] $ echo r1 > r1 $ hg ci -Amr1 -d'0 0' adding r1 + $ hg co -q null + $ hg transplant tip + abort: no revision checked out + [255] + $ hg up -q $ echo r2 > r2 $ hg ci -Amr2 -d'1 0' adding r2 @@ -20,6 +37,18 @@ $ hg ci -Amb1 -d '0 0' adding b1 created new head + $ hg merge 1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg transplant 1 + abort: outstanding uncommitted merges + [255] + $ hg up -qC tip + $ echo b0 > b1 + $ hg transplant 1 + abort: outstanding local changes + [255] + $ hg up -qC tip $ echo b2 > b2 $ hg ci -Amb2 -d '1 0' adding b2 @@ -37,6 +66,9 @@ $ hg clone . ../rebase updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg init ../emptydest + $ cd ../emptydest + $ hg transplant --source=../t > /dev/null $ cd ../rebase $ hg up -C 1 @@ -68,6 +100,8 @@ 5 1:d11e3596cc1a b1 6 b2 7 b3 + $ hg log -r 'transplanted(head())' --template '{rev} {parents} {desc}\n' + 7 b3 $ hg help revsets | grep transplanted "transplanted([set])" Transplanted changesets in set, or all transplanted changesets. @@ -368,7 +402,7 @@ Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file foo.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue [255] transplant -c shouldn't use an old changeset @@ -376,14 +410,29 @@ $ hg up -C 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ rm added + $ hg transplant --continue + abort: no transplant to continue + [255] $ hg transplant 1 applying 46ae92138f3c patching file foo Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file foo.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue [255] + $ cp .hg/transplant/journal .hg/transplant/journal.orig + $ cat .hg/transplant/journal + # User test + # Date 0 0 + # Node ID 46ae92138f3ce0249f6789650403286ead052b6d + # Parent e8643552fde58f57515e19c4b373a57c96e62af3 + foo2 + $ grep -v 'Date' .hg/transplant/journal.orig > .hg/transplant/journal + $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e + abort: filter corrupted changeset (no user or date) + [255] + $ cp .hg/transplant/journal.orig .hg/transplant/journal $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e HGEDITFORM=transplant.normal 46ae92138f3c transplanted as 9159dada197d @@ -413,7 +462,11 @@ Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file baz.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue + [255] + $ hg transplant 1:3 + abort: transplant in progress + (use 'hg transplant --continue' or 'hg update' to abort) [255] $ echo fixed > baz $ hg transplant --continue @@ -460,7 +513,7 @@ Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file a.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue [255] $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e HGEDITFORM=transplant.merge @@ -506,6 +559,23 @@ o 0:17ab29e464c6 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF + > ? + > x + > q + > EOF + 0:17ab29e464c6 + apply changeset? [ynmpcq?]: ? + y: yes, transplant this changeset + n: no, skip this changeset + m: merge at this changeset + p: show patch + c: commit selected changesets + q: quit and cancel transplant + ?: ? (show this help) + apply changeset? [ynmpcq?]: x + unrecognized response + apply changeset? [ynmpcq?]: q + $ hg transplant -q --config ui.interactive=true -s ../t <<EOF > p > y > n @@ -612,7 +682,7 @@ file b1 already exists 1 out of 1 hunks FAILED -- saving rejects to file b1.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue [255] $ cd .. @@ -738,6 +808,9 @@ $ hg init merge2b $ cd merge2b + $ hg transplant -s ../merge2a --parent tip tip + abort: be9f9b39483f is not a parent of be9f9b39483f + [255] $ hg transplant -s ../merge2a --parent 0 tip applying be9f9b39483f be9f9b39483f transplanted to 9959e51f94d1 @@ -778,7 +851,7 @@ file b already exists 1 out of 1 hunks FAILED -- saving rejects to file b.rej patch failed to apply - abort: fix up the merge and run hg transplant --continue + abort: fix up the working directory and run hg transplant --continue [255] $ hg status ? b.rej @@ -854,14 +927,14 @@ > [extensions] > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py > fakepatchtime = $TESTDIR/fakepatchtime.py - > abort = $TESTTMP/abort.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 + abort: fix up the working directory and run hg transplant --continue [255] $ cat >> .hg/hgrc <<EOF > [hooks]
--- a/tests/test-treemanifest.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-treemanifest.t Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,7 @@ + $ cat << EOF >> $HGRCPATH + > [format] + > usegeneraldelta=yes + > EOF Set up repo @@ -28,7 +32,7 @@ $ hg debugdata -m 1 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) - dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc) + dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc) e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) $ hg debugdata --dir dir1 0 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) @@ -118,13 +122,13 @@ $ 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 + rev offset length delta linkrev nodeid p1 p2 + 0 0 54 -1 1 8b3ffd73f901 000000000000 000000000000 + 1 54 68 0 2 68e9d057c5a8 8b3ffd73f901 000000000000 + 2 122 12 1 4 4698198d2624 68e9d057c5a8 000000000000 + 3 134 55 1 5 44844058ccce 68e9d057c5a8 000000000000 + 4 189 55 1 6 bf3d9b744927 68e9d057c5a8 000000000000 + 5 244 55 4 7 dde7c0af2a03 bf3d9b744927 44844058ccce 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.) @@ -250,13 +254,13 @@ 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 + rev offset length delta linkrev nodeid p1 p2 + 0 0 80 -1 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 + 2 163 89 0 2 5d9b9da231a2 40536115ed9e 000000000000 + 3 252 83 2 3 d17d663cbd8a 5d9b9da231a2 f3376063c255 + 4 335 124 1 4 51e32a8c60ee f3376063c255 000000000000 + 5 459 126 2 5 cc5baa78b230 5d9b9da231a2 f3376063c255 Status across flat/tree boundary should work @@ -270,16 +274,16 @@ 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 + rev offset length delta linkrev nodeid p1 p2 + 0 0 127 -1 4 064927a0648a 000000000000 000000000000 + 1 127 111 0 5 25ecb8cb8618 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 + rev offset length delta linkrev nodeid p1 p2 + 0 0 127 -1 4 064927a0648a 000000000000 000000000000 + 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000 + 2 238 55 1 6 5b16163a30c6 25ecb8cb8618 000000000000 Create deeper repo with tree manifests. @@ -384,3 +388,57 @@ $ mv oldmf2 .hg/store/meta/b/foo $ mv oldmf3 .hg/store/meta/b/bar/orange +Add some more changes to the deep repo + $ echo narf >> b/bar/fruits.txt + $ hg ci -m narf + $ echo troz >> b/bar/orange/fly/gnat.py + $ hg ci -m troz + +Test cloning a treemanifest repo over http. + $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log + $ cat hg.pid >> $DAEMON_PIDS + $ cd .. +We can clone even with the knob turned off and we'll get a treemanifest repo. + $ hg clone --config experimental.treemanifest=False \ + > --config experimental.changegroup3=True \ + > http://localhost:$HGPORT deepclone + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 10 changes to 8 files + updating to branch default + 8 files updated, 0 files merged, 0 files removed, 0 files unresolved +No server errors. + $ cat deeprepo/errors.log +requires got updated to include treemanifest + $ cat deepclone/.hg/requires | grep treemanifest + treemanifest +Tree manifest revlogs exist. + $ find deepclone/.hg/store/meta | sort + deepclone/.hg/store/meta + deepclone/.hg/store/meta/a + deepclone/.hg/store/meta/a/00manifest.i + deepclone/.hg/store/meta/b + deepclone/.hg/store/meta/b/00manifest.i + deepclone/.hg/store/meta/b/bar + deepclone/.hg/store/meta/b/bar/00manifest.i + deepclone/.hg/store/meta/b/bar/orange + deepclone/.hg/store/meta/b/bar/orange/00manifest.i + deepclone/.hg/store/meta/b/bar/orange/fly + deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i + deepclone/.hg/store/meta/b/foo + deepclone/.hg/store/meta/b/foo/00manifest.i + deepclone/.hg/store/meta/b/foo/apple + deepclone/.hg/store/meta/b/foo/apple/00manifest.i + deepclone/.hg/store/meta/b/foo/apple/bees + deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i +Verify passes. + $ cd deepclone + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 8 files, 3 changesets, 10 total revisions + $ cd ..
--- a/tests/test-up-local-change.t Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-up-local-change.t Sun Jan 17 21:40:21 2016 -0600 @@ -50,11 +50,11 @@ b: remote created -> g getting b a: versions differ -> m (premerge) - picked tool 'true' for a (binary False symlink False) + picked tool 'true' for a (binary False symlink False changedelete False) merging a my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a a: versions differ -> m (merge) - picked tool 'true' for a (binary False symlink False) + picked tool 'true' for a (binary False symlink False changedelete False) my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a launching merge tool: true *$TESTTMP/r2/a* * * (glob) merge tool returned: 0 @@ -74,11 +74,11 @@ b: other deleted -> r removing b a: versions differ -> m (premerge) - picked tool 'true' for a (binary False symlink False) + picked tool 'true' for a (binary False symlink False changedelete False) merging a my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb a: versions differ -> m (merge) - picked tool 'true' for a (binary False symlink False) + picked tool 'true' for a (binary False symlink False changedelete False) my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb launching merge tool: true *$TESTTMP/r2/a* * * (glob) merge tool returned: 0 @@ -106,11 +106,11 @@ b: remote created -> g getting b a: versions differ -> m (premerge) - picked tool 'true' for a (binary False symlink False) + picked tool 'true' for a (binary False symlink False changedelete False) merging a my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a a: versions differ -> m (merge) - picked tool 'true' for a (binary False symlink False) + picked tool 'true' for a (binary False symlink False changedelete False) my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a launching merge tool: true *$TESTTMP/r2/a* * * (glob) merge tool returned: 0
--- a/tests/test-walkrepo.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-walkrepo.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,9 +1,20 @@ +from __future__ import absolute_import + import os -from mercurial import hg, ui -from mercurial.scmutil import walkrepos -from mercurial.util import checklink -from os import mkdir, chdir -from os.path import join as pjoin + +from mercurial import ( + hg, + scmutil, + ui, + util, +) + +chdir = os.chdir +mkdir = os.mkdir +pjoin = os.path.join + +walkrepos = scmutil.walkrepos +checklink = util.checklink u = ui.ui() sym = checklink('.')
--- a/tests/test-wireproto.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/test-wireproto.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,3 +1,5 @@ +from __future__ import absolute_import + from mercurial import wireproto class proto(object):
--- a/tests/tinyproxy.py Wed Jan 06 11:01:55 2016 -0800 +++ b/tests/tinyproxy.py Sun Jan 17 21:40:21 2016 -0600 @@ -1,5 +1,7 @@ #!/usr/bin/env python +from __future__ import absolute_import + __doc__ = """Tiny HTTP Proxy. This module implements GET, HEAD, POST, PUT and DELETE methods @@ -12,7 +14,12 @@ __version__ = "0.2.1" -import BaseHTTPServer, select, socket, SocketServer, urlparse, os +import BaseHTTPServer +import os +import select +import socket +import SocketServer +import urlparse class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler): __base = BaseHTTPServer.BaseHTTPRequestHandler @@ -22,7 +29,7 @@ rbufsize = 0 # self.rfile Be unbuffered def handle(self): - (ip, port) = self.client_address + (ip, port) = self.client_address allowed = getattr(self, 'allowed_clients', None) if allowed is not None and ip not in allowed: self.raw_requestline = self.rfile.readline()