Mercurial > hg
changeset 21751:c5699c2d690b
merge with stable
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Fri, 13 Jun 2014 17:42:04 -0500 |
parents | fecead61d222 (diff) 4ab287c2d337 (current diff) |
children | b7baef94a333 |
files | tests/test-subrepo-git.t |
diffstat | 157 files changed, 6868 insertions(+), 3772 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Fri Jun 13 14:17:14 2014 -0500 +++ b/.hgignore Fri Jun 13 17:42:04 2014 -0500 @@ -26,6 +26,7 @@ build contrib/hgsh/hgsh dist +packages doc/common.txt doc/*.[0-9] doc/*.[0-9].txt
--- a/Makefile Fri Jun 13 14:17:14 2014 -0500 +++ b/Makefile Fri Jun 13 17:42:04 2014 -0500 @@ -132,6 +132,37 @@ msgmerge --no-location --update $@.tmp $^ mv -f $@.tmp $@ +# Packaging targets + +osx: + @which -s bdist_mpkg || \ + (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false) + bdist_mpkg setup.py + mkdir -p packages/osx + rm -rf dist/mercurial-*.mpkg + mv dist/mercurial*macosx*.zip packages/osx + +fedora: + mkdir -p packages/fedora + contrib/buildrpm + cp rpmbuild/RPMS/*/* packages/fedora + cp rpmbuild/SRPMS/* packages/fedora + rm -rf rpmbuild + +docker-fedora: + mkdir -p packages/fedora + contrib/dockerrpm fedora + +centos6: + mkdir -p packages/centos6 + contrib/buildrpm + cp rpmbuild/RPMS/*/* packages/centos6 + cp rpmbuild/SRPMS/* packages/centos6 + +docker-centos6: + mkdir -p packages/centos6 + contrib/dockerrpm centos6 + .PHONY: help all local build doc clean install install-bin install-doc \ install-home install-home-bin install-home-doc dist dist-notests tests \ - update-pot + update-pot fedora docker-fedora
--- a/contrib/bash_completion Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/bash_completion Fri Jun 13 17:42:04 2014 -0500 @@ -629,7 +629,7 @@ _hg_cmd_shelve() { - if [[ "$prev" = @(-d|--delete) ]]; then + if [[ "$prev" = @(-d|--delete|-l|--list) ]]; then _hg_shelves else _hg_status "mard"
--- a/contrib/buildrpm Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/buildrpm Fri Jun 13 17:42:04 2014 -0500 @@ -1,16 +1,13 @@ -#!/bin/sh +#!/bin/sh -e # -# Build a Mercurial RPM in place. +# Build a Mercurial RPM from the current repo # # Tested on -# - Fedora 8 (with docutils 0.5) -# - Fedora 11 -# - OpenSuse 11.2 +# - Fedora 20 +# - CentOS 5 +# - centOS 6 cd "`dirname $0`/.." -HG="$PWD/hg" -PYTHONPATH="$PWD/mercurial/pure" -export PYTHONPATH specfile=contrib/mercurial.spec if [ ! -f $specfile ]; then @@ -23,21 +20,17 @@ exit 1 fi -if $HG id -i | grep '+$' > /dev/null 2>&1; then - echo -n "Your local changes will NOT be in the RPM. Continue [y/n] ? " - read answer - if echo $answer | grep -iv '^y'; then - exit - fi -fi +# build local hg and use it +python setup.py build_py -c -d . +HG="$PWD/hg" +PYTHONPATH="$PWD/mercurial/pure" +export PYTHONPATH rpmdir="$PWD/rpmbuild" rm -rf $rpmdir mkdir -p $rpmdir/SOURCES $rpmdir/SPECS $rpmdir/RPMS $rpmdir/SRPMS $rpmdir/BUILD -# make setup.py build the version string -python setup.py build_py -c -d . hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'` if echo $hgversion | grep -- '-' > /dev/null 2>&1; then @@ -50,8 +43,8 @@ release='0' fi -$HG archive -t tgz $rpmdir/SOURCES/mercurial-$version.tar.gz -rpmspec=$rpmdir/SPECS/mercurial-$version.spec +$HG archive -t tgz $rpmdir/SOURCES/mercurial-$version-$release.tar.gz +rpmspec=$rpmdir/SPECS/mercurial.spec sed -e "s,^Version:.*,Version: $version," \ -e "s,^Release:.*,Release: $release," \
--- a/contrib/check-code.py Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/check-code.py Fri Jun 13 17:42:04 2014 -0500 @@ -367,16 +367,28 @@ [] ] +webtemplatefilters = [] + +webtemplatepats = [ + [], + [ + (r'{desc(\|(?!websub|firstline)[^\|]*)+}', + 'follow desc keyword with either firstline or websub'), + ] +] + checks = [ - ('python', r'.*\.(py|cgi)$', pyfilters, pypats), - ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats), - ('c', r'.*\.[ch]$', cfilters, cpats), - ('unified test', r'.*\.t$', utestfilters, utestpats), - ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters, - inrevlogpats), - ('layering violation ui in util', r'mercurial/util\.py', pyfilters, + ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats), + ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats), + ('c', r'.*\.[ch]$', '', cfilters, cpats), + ('unified test', r'.*\.t$', '', utestfilters, utestpats), + ('layering violation repo in revlog', r'mercurial/revlog\.py', '', + pyfilters, inrevlogpats), + ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters, inutilpats), - ('txt', r'.*\.txt$', txtfilters, txtpats), + ('txt', r'.*\.txt$', '', txtfilters, txtpats), + ('web template', r'mercurial/templates/.*\.tmpl', '', + webtemplatefilters, webtemplatepats), ] def _preparepats(): @@ -392,7 +404,7 @@ p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p) pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:] - filters = c[2] + filters = c[3] for i, flt in enumerate(filters): filters[i] = re.compile(flt[0]), flt[1] _preparepats() @@ -446,22 +458,24 @@ """ blamecache = None result = True - for name, match, filters, pats in checks: + + try: + fp = open(f) + except IOError, e: + print "Skipping %s, %s" % (f, str(e).split(':', 1)[0]) + return result + pre = post = fp.read() + fp.close() + + for name, match, magic, filters, pats in checks: if debug: print name, f fc = 0 - if not re.match(match, f): + if not (re.match(match, f) or (magic and re.search(magic, f))): if debug: print "Skipping %s for %s it doesn't match %s" % ( name, match, f) continue - try: - fp = open(f) - except IOError, e: - print "Skipping %s, %s" % (f, str(e).split(':', 1)[0]) - continue - pre = post = fp.read() - fp.close() if "no-" "check-code" in pre: print "Skipping %s it has no-" "check-code" % f return "Skip" # skip checking this file
--- a/contrib/debugshell.py Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/debugshell.py Fri Jun 13 17:42:04 2014 -0500 @@ -4,6 +4,10 @@ import sys import mercurial import code +from mercurial import cmdutil + +cmdtable = {} +command = cmdutil.command(cmdtable) def pdb(ui, repo, msg, **opts): objects = { @@ -24,6 +28,7 @@ IPython.embed() +@command('debugshell|dbsh', []) def debugshell(ui, repo, **opts): bannermsg = "loaded repo : %s\n" \ "using source: %s" % (repo.root, @@ -47,7 +52,3 @@ debugger = 'pdb' getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts) - -cmdtable = { - "debugshell|dbsh": (debugshell, []) -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/docker/centos6 Fri Jun 13 17:42:04 2014 -0500 @@ -0,0 +1,7 @@ +FROM centos +RUN yum install -y gcc +RUN yum install -y python-devel python-docutils +RUN yum install -y make +RUN yum install -y rpm-build +RUN yum install -y gettext +RUN yum install -y tar
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/docker/fedora Fri Jun 13 17:42:04 2014 -0500 @@ -0,0 +1,6 @@ +FROM fedora +RUN yum install -y gcc +RUN yum install -y python-devel python-docutils +RUN yum install -y make +RUN yum install -y rpm-build +RUN yum install -y gettext
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dockerrpm Fri Jun 13 17:42:04 2014 -0500 @@ -0,0 +1,14 @@ +#!/bin/bash + +BUILDDIR=$(dirname $0) +ROOTDIR=$(cd $BUILDDIR/..; pwd) + +if which docker.io >> /dev/null ; then + DOCKER=docker.io +elif which docker >> /dev/null ; then + DOCKER=docker +fi + +$DOCKER build --tag "hg-dockerrpm-$1" - < $BUILDDIR/docker/$1 +$DOCKER run --rm -v $ROOTDIR:/hg "hg-dockerrpm-$1" bash -c \ + "cp -a hg hg-build; cd hg-build; make clean local $1; cp packages/$1/* /hg/packages/$1/"
--- a/contrib/hgfixes/fix_bytes.py Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/hgfixes/fix_bytes.py Fri Jun 13 17:42:04 2014 -0500 @@ -12,10 +12,10 @@ # XXX: Implementing a blacklist in 2to3 turned out to be more troublesome than # blacklisting some modules inside the fixers. So, this is what I came with. -blacklist = ['mercurial/demandimport.py', +blacklist = ('mercurial/demandimport.py', 'mercurial/py3kcompat.py', # valid python 3 already 'mercurial/i18n.py', - ] + ) def isdocstring(node): def isclassorfunction(ancestor): @@ -83,7 +83,8 @@ PATTERN = 'STRING' def transform(self, node, results): - if self.filename in blacklist: + # The filename may be prefixed with a build directory. + if self.filename.endswith(blacklist): return if node.type == token.STRING: if _re.match(node.value):
--- a/contrib/mercurial.spec Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/mercurial.spec Fri Jun 13 17:42:04 2014 -0500 @@ -1,3 +1,6 @@ +%global emacs_lispdir %{_datadir}/emacs/site-lisp +%global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))') + Summary: A fast, lightweight Source Control Management system Name: mercurial Version: snapshot @@ -5,33 +8,21 @@ License: GPLv2+ Group: Development/Tools URL: http://mercurial.selenic.com/ -Source0: http://mercurial.selenic.com/release/%{name}-%{version}.tar.gz +Source0: %{name}-%{version}-%{release}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root -# From the README: -# -# Note: some distributions fails to include bits of distutils by -# default, you'll need python-dev to install. You'll also need a C -# compiler and a 3-way merge tool like merge, tkdiff, or kdiff3. -# -# python-devel provides an adequate python-dev. The merge tool is a -# run-time dependency. -# BuildRequires: python >= 2.4, python-devel, make, gcc, python-docutils >= 0.5, gettext Provides: hg = %{version}-%{release} Requires: python >= 2.4 # The hgk extension uses the wish tcl interpreter, but we don't enforce it #Requires: tk -%define pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))') -%define emacs_lispdir %{_datadir}/emacs/site-lisp - %description Mercurial is a fast, lightweight source control management system designed for efficient handling of very large distributed projects. %prep -%setup -q +%setup -q -n mercurial-%{version}-%{release} %build make all @@ -40,8 +31,8 @@ rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir} -install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir} -install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir} +install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/ +install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/ bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d mkdir -p $bash_completion_dir @@ -52,8 +43,8 @@ install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir} -install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir} -install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir} +install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/ +install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/ mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/mercurial/hgrc.d install -m 644 contrib/mergetools.hgrc $RPM_BUILD_ROOT%{_sysconfdir}/mercurial/hgrc.d/mergetools.rc
--- a/contrib/mergetools.hgrc Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/mergetools.hgrc Fri Jun 13 17:42:04 2014 -0500 @@ -71,6 +71,12 @@ ecmerge.gui=True ecmerge.diffargs=$parent $child --mode=diff2 --title1='$plabel1' --title2='$clabel' +# editmerge is a small script shipped in contrib. +# It needs this config otherwise it behaves the same as internal:local +editmerge.args=$output +editmerge.check=changed +editmerge.premerge=keep + filemerge.executable=/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge filemerge.args=-left $other -right $local -ancestor $base -merge $output filemerge.gui=True
--- a/contrib/revsetbenchmarks.py Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/revsetbenchmarks.py Fri Jun 13 17:42:04 2014 -0500 @@ -14,7 +14,12 @@ # to compare performance. import sys +import os from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE +# cannot use argparse, python 2.7 only +from optparse import OptionParser + + def check_output(*args, **kwargs): kwargs.setdefault('stderr', PIPE) @@ -33,15 +38,19 @@ print >> sys.stderr, 'update to revision %s failed, aborting' % rev sys.exit(exc.returncode) -def perf(revset): +def perf(revset, target=None): """run benchmark for this very revset""" try: - output = check_output(['./hg', - '--config', - 'extensions.perf=contrib/perf.py', - 'perfrevset', - revset], - stderr=STDOUT) + cmd = ['./hg', + '--config', + 'extensions.perf=' + + os.path.join(contribdir, 'perf.py'), + 'perfrevset', + revset] + if target is not None: + cmd.append('-R') + cmd.append(target) + output = check_output(cmd, stderr=STDOUT) output = output.lstrip('!') # remove useless ! in this context return output.strip() except CalledProcessError, exc: @@ -65,12 +74,26 @@ return [r for r in out.split() if r] +parser = OptionParser(usage="usage: %prog [options] <revs>") +parser.add_option("-f", "--file", + help="read revset from FILE", metavar="FILE") +parser.add_option("-R", "--repo", + help="run benchmark on REPO", metavar="REPO") -target_rev = sys.argv[1] +(options, args) = parser.parse_args() + +if len(sys.argv) < 2: + parser.print_help() + sys.exit(255) + +# the directory where both this script and the perf.py extension live. +contribdir = os.path.dirname(__file__) + +target_rev = args[0] revsetsfile = sys.stdin -if len(sys.argv) > 2: - revsetsfile = open(sys.argv[2]) +if options.file: + revsetsfile = open(options.file) revsets = [l.strip() for l in revsetsfile] @@ -95,7 +118,7 @@ res = [] results.append(res) for idx, rset in enumerate(revsets): - data = perf(rset) + data = perf(rset, target=options.repo) res.append(data) print "%i)" % idx, data sys.stdout.flush()
--- a/contrib/revsetbenchmarks.txt Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/revsetbenchmarks.txt Fri Jun 13 17:42:04 2014 -0500 @@ -2,11 +2,13 @@ draft() ::tip draft() and ::tip +::tip and draft() 0::tip roots(0::tip) author(lmoscovicz) author(mpm) author(lmoscovicz) or author(mpm) +author(mpm) or author(lmoscovicz) tip:0 max(tip:0) min(0:tip) @@ -18,3 +20,4 @@ :10000 and public() draft() :10000 and draft() +max(::(tip~20) - obsolete())
--- a/contrib/synthrepo.py Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/synthrepo.py Fri Jun 13 17:42:04 2014 -0500 @@ -334,7 +334,7 @@ for __ in xrange(add): lines.insert(random.randint(0, len(lines)), makeline()) path = fctx.path() - changes[path] = context.memfilectx(path, + changes[path] = context.memfilectx(repo, path, '\n'.join(lines) + '\n') for __ in xrange(pick(filesremoved)): path = random.choice(mfk) @@ -354,7 +354,7 @@ path = '/'.join(filter(None, path)) data = '\n'.join(makeline() for __ in xrange(pick(linesinfilesadded))) + '\n' - changes[path] = context.memfilectx(path, data) + changes[path] = context.memfilectx(repo, path, data) def filectxfn(repo, memctx, path): data = changes[path] if data is None:
--- a/contrib/vim/hgcommand.vim Fri Jun 13 14:17:14 2014 -0500 +++ b/contrib/vim/hgcommand.vim Fri Jun 13 17:42:04 2014 -0500 @@ -105,7 +105,7 @@ let fileName=<SID>HGResolveLink(a:fileName) let newCwd=fnamemodify(fileName, ':h') if strlen(newCwd) > 0 - execute 'cd' escape(newCwd, ' ') + try | execute 'cd' escape(newCwd, ' ') | catch | | endtry endif return oldCwd endfunction @@ -396,7 +396,7 @@ return returnExpression finally - execute 'cd' escape(oldCwd, ' ') + try | execute 'cd' escape(oldCwd, ' ') | catch | | endtry endtry endfunction
--- a/hgext/children.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/children.py Fri Jun 13 17:42:04 2014 -0500 @@ -18,8 +18,15 @@ from mercurial.commands import templateopts from mercurial.i18n import _ +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' +@command('children', + [('r', 'rev', '', + _('show children of the specified revision'), _('REV')), + ] + templateopts, + _('hg children [-r REV] [FILE]')) def children(ui, repo, file_=None, **opts): """show the children of the given or working directory revision @@ -40,13 +47,4 @@ displayer.show(cctx) displayer.close() -cmdtable = { - "children": - (children, - [('r', 'rev', '', - _('show children of the specified revision'), _('REV')), - ] + templateopts, - _('hg children [-r REV] [FILE]')), -} - commands.inferrepo += " children"
--- a/hgext/churn.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/churn.py Fri Jun 13 17:42:04 2014 -0500 @@ -14,6 +14,8 @@ import os import time, datetime +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' def maketemplater(ui, repo, tmpl): @@ -88,6 +90,21 @@ return rate +@command('churn', + [('r', 'rev', [], + _('count rate for the specified revision or range'), _('REV')), + ('d', 'date', '', + _('count rate for revisions matching date spec'), _('DATE')), + ('t', 'template', '{author|email}', + _('template to group changesets'), _('TEMPLATE')), + ('f', 'dateformat', '', + _('strftime-compatible format for grouping by date'), _('FORMAT')), + ('c', 'changesets', False, _('count rate by number of changesets')), + ('s', 'sort', False, _('sort by key (default: sort by count)')), + ('', 'diffstat', False, _('display added/removed lines separately')), + ('', 'aliases', '', _('file with email aliases'), _('FILE')), + ] + commands.walkopts, + _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")) def churn(ui, repo, *pats, **opts): '''histogram of changes to the repository @@ -181,25 +198,4 @@ for name, count in rate: ui.write(format(name, count)) - -cmdtable = { - "churn": - (churn, - [('r', 'rev', [], - _('count rate for the specified revision or range'), _('REV')), - ('d', 'date', '', - _('count rate for revisions matching date spec'), _('DATE')), - ('t', 'template', '{author|email}', - _('template to group changesets'), _('TEMPLATE')), - ('f', 'dateformat', '', - _('strftime-compatible format for grouping by date'), _('FORMAT')), - ('c', 'changesets', False, _('count rate by number of changesets')), - ('s', 'sort', False, _('sort by key (default: sort by count)')), - ('', 'diffstat', False, _('display added/removed lines separately')), - ('', 'aliases', '', - _('file with email aliases'), _('FILE')), - ] + commands.walkopts, - _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")), -} - commands.inferrepo += " churn"
--- a/hgext/color.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/color.py Fri Jun 13 17:42:04 2014 -0500 @@ -111,10 +111,12 @@ import os -from mercurial import commands, dispatch, extensions, ui as uimod, util +from mercurial import cmdutil, commands, dispatch, extensions, ui as uimod, util from mercurial import templater, error from mercurial.i18n import _ +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' # start and stop parameters for effects @@ -166,7 +168,7 @@ def _modesetup(ui, coloropt): global _terminfo_params - auto = coloropt == 'auto' + auto = (coloropt == 'auto') always = not auto and util.parsebool(coloropt) if not always and not auto: return None @@ -440,6 +442,7 @@ _("when to colorize (boolean, always, auto, or never)"), _('TYPE'))) +@command('debugcolor', [], 'hg debugcolor') def debugcolor(ui, repo, **opts): global _styles _styles = {} @@ -579,8 +582,3 @@ finally: # Explicitly reset original attributes _kernel32.SetConsoleTextAttribute(stdout, origattr) - -cmdtable = { - 'debugcolor': - (debugcolor, [], ('hg debugcolor')) -}
--- a/hgext/convert/__init__.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/convert/__init__.py Fri Jun 13 17:42:04 2014 -0500 @@ -10,13 +10,34 @@ import convcmd import cvsps import subversion -from mercurial import commands, templatekw +from mercurial import cmdutil, commands, templatekw from mercurial.i18n import _ +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' # Commands definition was moved elsewhere to ease demandload job. +@command('convert', + [('', 'authors', '', + _('username mapping filename (DEPRECATED, use --authormap instead)'), + _('FILE')), + ('s', 'source-type', '', _('source repository type'), _('TYPE')), + ('d', 'dest-type', '', _('destination repository type'), _('TYPE')), + ('r', 'rev', '', _('import up to source revision REV'), _('REV')), + ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')), + ('', 'filemap', '', _('remap file names using contents of file'), + _('FILE')), + ('', 'splicemap', '', _('splice synthesized history into place'), + _('FILE')), + ('', 'branchmap', '', _('change branch names while converting'), + _('FILE')), + ('', 'branchsort', None, _('try to sort changesets by branches')), + ('', 'datesort', None, _('try to sort changesets by date')), + ('', 'sourcesort', None, _('preserve source changesets order')), + ('', 'closesort', None, _('try to reorder closed revisions'))], + _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')) def convert(ui, src, dest=None, revmapfile=None, **opts): """convert a foreign SCM repository to a Mercurial one. @@ -282,9 +303,28 @@ """ return convcmd.convert(ui, src, dest, revmapfile, **opts) +@command('debugsvnlog', [], 'hg debugsvnlog') def debugsvnlog(ui, **opts): return subversion.debugsvnlog(ui, **opts) +@command('debugcvsps', + [ + # Main options shared with cvsps-2.1 + ('b', 'branches', [], _('only return changes on specified branches')), + ('p', 'prefix', '', _('prefix to remove from file names')), + ('r', 'revisions', [], + _('only return changes after or between specified tags')), + ('u', 'update-cache', None, _("update cvs log cache")), + ('x', 'new-cache', None, _("create new cvs log cache")), + ('z', 'fuzz', 60, _('set commit time fuzz in seconds')), + ('', 'root', '', _('specify cvsroot')), + # Options specific to builtin cvsps + ('', 'parents', '', _('show parent changesets')), + ('', 'ancestors', '', _('show current changeset in ancestor branches')), + # Options that are ignored for compatibility with cvsps-2.1 + ('A', 'cvs-direct', None, _('ignored for compatibility')), + ], + _('hg debugcvsps [OPTION]... [PATH]...')) def debugcvsps(ui, *args, **opts): '''create changeset information from CVS @@ -300,57 +340,6 @@ commands.norepo += " convert debugsvnlog debugcvsps" -cmdtable = { - "convert": - (convert, - [('', 'authors', '', - _('username mapping filename (DEPRECATED, use --authormap instead)'), - _('FILE')), - ('s', 'source-type', '', - _('source repository type'), _('TYPE')), - ('d', 'dest-type', '', - _('destination repository type'), _('TYPE')), - ('r', 'rev', '', - _('import up to source revision REV'), _('REV')), - ('A', 'authormap', '', - _('remap usernames using this file'), _('FILE')), - ('', 'filemap', '', - _('remap file names using contents of file'), _('FILE')), - ('', 'splicemap', '', - _('splice synthesized history into place'), _('FILE')), - ('', 'branchmap', '', - _('change branch names while converting'), _('FILE')), - ('', 'branchsort', None, _('try to sort changesets by branches')), - ('', 'datesort', None, _('try to sort changesets by date')), - ('', 'sourcesort', None, _('preserve source changesets order')), - ('', 'closesort', None, _('try to reorder closed revisions'))], - _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')), - "debugsvnlog": - (debugsvnlog, - [], - 'hg debugsvnlog'), - "debugcvsps": - (debugcvsps, - [ - # Main options shared with cvsps-2.1 - ('b', 'branches', [], _('only return changes on specified branches')), - ('p', 'prefix', '', _('prefix to remove from file names')), - ('r', 'revisions', [], - _('only return changes after or between specified tags')), - ('u', 'update-cache', None, _("update cvs log cache")), - ('x', 'new-cache', None, _("create new cvs log cache")), - ('z', 'fuzz', 60, _('set commit time fuzz in seconds')), - ('', 'root', '', _('specify cvsroot')), - # Options specific to builtin cvsps - ('', 'parents', '', _('show parent changesets')), - ('', 'ancestors', '', - _('show current changeset in ancestor branches')), - # Options that are ignored for compatibility with cvsps-2.1 - ('A', 'cvs-direct', None, _('ignored for compatibility')), - ], - _('hg debugcvsps [OPTION]... [PATH]...')), -} - def kwconverted(ctx, name): rev = ctx.extra().get('convert_revision', '') if rev.startswith('svn:'):
--- a/hgext/convert/common.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/convert/common.py Fri Jun 13 17:42:04 2014 -0500 @@ -260,8 +260,15 @@ """ pass - def hascommit(self, rev): - """Return True if the sink contains rev""" + def hascommitfrommap(self, rev): + """Return False if a rev mentioned in a filemap is known to not be + present.""" + raise NotImplementedError + + def hascommitforsplicemap(self, rev): + """This method is for the special needs for splicemap handling and not + for general use. Returns True if the sink contains rev, aborts on some + special cases.""" raise NotImplementedError class commandline(object):
--- a/hgext/convert/convcmd.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/convert/convcmd.py Fri Jun 13 17:42:04 2014 -0500 @@ -173,8 +173,12 @@ parents = {} while visit: n = visit.pop(0) - if n in known or n in self.map: + if n in known: continue + if n in self.map: + m = self.map[n] + if m == SKIPREV or self.dest.hascommitfrommap(m): + continue known.add(n) self.ui.progress(_('scanning'), len(known), unit=_('revisions')) commit = self.cachecommit(n) @@ -193,7 +197,7 @@ """ for c in sorted(splicemap): if c not in parents: - if not self.dest.hascommit(self.map.get(c, c)): + if not self.dest.hascommitforsplicemap(self.map.get(c, c)): # Could be in source but not converted during this run self.ui.warn(_('splice map revision %s is not being ' 'converted, ignoring\n') % c) @@ -201,7 +205,7 @@ pc = [] for p in splicemap[c]: # We do not have to wait for nodes already in dest. - if self.dest.hascommit(self.map.get(p, p)): + if self.dest.hascommitforsplicemap(self.map.get(p, p)): continue # Parent is not in dest and not being converted, not good if p not in parents:
--- a/hgext/convert/git.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/convert/git.py Fri Jun 13 17:42:04 2014 -0500 @@ -46,6 +46,18 @@ del os.environ['GIT_DIR'] else: os.environ['GIT_DIR'] = prevgitdir + + def gitpipe(self, s): + prevgitdir = os.environ.get('GIT_DIR') + os.environ['GIT_DIR'] = self.path + try: + return util.popen3(s) + finally: + if prevgitdir is None: + del os.environ['GIT_DIR'] + else: + os.environ['GIT_DIR'] = prevgitdir + else: def gitopen(self, s, err=None): if err == subprocess.PIPE: @@ -56,6 +68,9 @@ else: return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb') + def gitpipe(self, s): + return util.popen3('GIT_DIR=%s %s' % (self.path, s)) + def popen_with_stderr(self, s): p = subprocess.Popen(s, shell=True, bufsize=-1, close_fds=util.closefds, @@ -84,6 +99,12 @@ self.path = path self.submodules = [] + self.catfilepipe = self.gitpipe('git cat-file --batch') + + def after(self): + for f in self.catfilepipe: + f.close() + def getheads(self): if not self.rev: heads, ret = self.gitread('git rev-parse --branches --remotes') @@ -98,9 +119,17 @@ def catfile(self, rev, type): if rev == hex(nullid): raise IOError - data, ret = self.gitread("git cat-file %s %s" % (type, rev)) - if ret: + self.catfilepipe[0].write(rev+'\n') + self.catfilepipe[0].flush() + info = self.catfilepipe[1].readline().split() + if info[1] != type: raise util.Abort(_('cannot read %r object at %s') % (type, rev)) + size = int(info[2]) + data = self.catfilepipe[1].read(size) + if len(data) < size: + raise util.Abort(_('cannot read %r object at %s: %s') % (type, rev)) + # read the trailing newline + self.catfilepipe[1].read(1) return data def getfile(self, name, rev):
--- a/hgext/convert/hg.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/convert/hg.py Fri Jun 13 17:42:04 2014 -0500 @@ -136,8 +136,8 @@ data, mode = source.getfile(f, v) if f == '.hgtags': data = self._rewritetags(source, revmap, data) - return context.memfilectx(f, data, 'l' in mode, 'x' in mode, - copies.get(f)) + return context.memfilectx(self.repo, f, data, 'l' in mode, + 'x' in mode, copies.get(f)) pl = [] for p in parents: @@ -229,7 +229,7 @@ data = "".join(newlines) def getfilectx(repo, memctx, f): - return context.memfilectx(f, data, False, False, None) + return context.memfilectx(repo, f, data, False, False, None) self.ui.status(_("updating tags\n")) date = "%s 0" % int(time.mktime(time.gmtime())) @@ -253,7 +253,11 @@ destmarks[bookmark] = bin(updatedbookmark[bookmark]) destmarks.write() - def hascommit(self, rev): + def hascommitfrommap(self, rev): + # the exact semantics of clonebranches is unclear so we can't say no + return rev in self.repo or self.clonebranches + + def hascommitforsplicemap(self, rev): if rev not in self.repo and self.clonebranches: raise util.Abort(_('revision %s not found in destination ' 'repository (lookups with clonebranches=true ' @@ -394,7 +398,9 @@ sortkey=ctx.rev()) def gettags(self): - tags = [t for t in self.repo.tagslist() if t[0] != 'tip'] + # This will get written to .hgtags, filter non global tags out. + tags = [t for t in self.repo.tagslist() + if self.repo.tagtype(t[0]) == 'global'] return dict([(name, hex(node)) for name, node in tags if self.keep(node)])
--- a/hgext/convert/subversion.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/convert/subversion.py Fri Jun 13 17:42:04 2014 -0500 @@ -1300,7 +1300,12 @@ self.ui.warn(_('writing Subversion tags is not yet implemented\n')) return None, None - def hascommit(self, rev): + def hascommitfrommap(self, rev): + # We trust that revisions referenced in a map still is present + # TODO: implement something better if necessary and feasible + return True + + def hascommitforsplicemap(self, rev): # This is not correct as one can convert to an existing subversion # repository and childmap would not list all revisions. Too bad. if rev in self.childmap:
--- a/hgext/extdiff.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/extdiff.py Fri Jun 13 17:42:04 2014 -0500 @@ -63,9 +63,11 @@ from mercurial.i18n import _ from mercurial.node import short, nullid -from mercurial import scmutil, scmutil, util, commands, encoding +from mercurial import cmdutil, scmutil, scmutil, util, commands, encoding import os, shlex, shutil, tempfile, re +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' def snapshot(ui, repo, files, node, tmproot): @@ -238,6 +240,15 @@ ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot) +@command('extdiff', + [('p', 'program', '', + _('comparison program to run'), _('CMD')), + ('o', 'option', [], + _('pass option to comparison program'), _('OPT')), + ('r', 'rev', [], _('revision'), _('REV')), + ('c', 'change', '', _('change made by revision'), _('REV')), + ] + commands.walkopts, + _('hg extdiff [OPT]... [FILE]...')) def extdiff(ui, repo, *pats, **opts): '''use external program to diff repository (or selected files) @@ -262,21 +273,6 @@ option = option or ['-Npru'] return dodiff(ui, repo, program, option, pats, opts) -cmdtable = { - "extdiff": - (extdiff, - [('p', 'program', '', - _('comparison program to run'), _('CMD')), - ('o', 'option', [], - _('pass option to comparison program'), _('OPT')), - ('r', 'rev', [], - _('revision'), _('REV')), - ('c', 'change', '', - _('change made by revision'), _('REV')), - ] + commands.walkopts, - _('hg extdiff [OPT]... [FILE]...')), - } - def uisetup(ui): for cmd, path in ui.configitems('extdiff'): if cmd.startswith('cmd.'):
--- a/hgext/factotum.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/factotum.py Fri Jun 13 17:42:04 2014 -0500 @@ -52,6 +52,8 @@ ERRMAX = 128 +_executable = _mountpoint = _service = None + def auth_getkey(self, params): if not self.ui.interactive(): raise util.Abort(_('factotum not interactive'))
--- a/hgext/fetch.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/fetch.py Fri Jun 13 17:42:04 2014 -0500 @@ -12,8 +12,18 @@ from mercurial import commands, cmdutil, hg, util, error from mercurial.lock import release +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' +@command('fetch', + [('r', 'rev', [], + _('a specific revision you would like to pull'), _('REV')), + ('e', 'edit', None, _('edit commit message')), + ('', 'force-editor', None, _('edit commit message (DEPRECATED)')), + ('', 'switch-parent', None, _('switch parents when merging')), + ] + commands.commitopts + commands.commitopts2 + commands.remoteopts, + _('hg fetch [SOURCE]')) def fetch(ui, repo, source='default', **opts): '''pull changes from a remote repository, merge new changes if needed. @@ -132,10 +142,9 @@ message = (cmdutil.logmessage(ui, opts) or ('Automated merge with %s' % util.removeauth(other.url()))) - editor = cmdutil.commiteditor - if opts.get('force_editor') or opts.get('edit'): - editor = cmdutil.commitforceeditor - n = repo.commit(message, opts['user'], opts['date'], editor=editor) + editopt = opts.get('edit') or opts.get('force_editor') + n = repo.commit(message, opts['user'], opts['date'], + editor=cmdutil.getcommiteditor(edit=editopt)) ui.status(_('new changeset %d:%s merges remote changes ' 'with local\n') % (repo.changelog.rev(n), short(n))) @@ -144,15 +153,3 @@ finally: release(lock, wlock) - -cmdtable = { - 'fetch': - (fetch, - [('r', 'rev', [], - _('a specific revision you would like to pull'), _('REV')), - ('e', 'edit', None, _('edit commit message')), - ('', 'force-editor', None, _('edit commit message (DEPRECATED)')), - ('', 'switch-parent', None, _('switch parents when merging')), - ] + commands.commitopts + commands.commitopts2 + commands.remoteopts, - _('hg fetch [SOURCE]')), -}
--- a/hgext/gpg.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/gpg.py Fri Jun 13 17:42:04 2014 -0500 @@ -204,6 +204,7 @@ _('the key id to sign with'), _('ID')), ('m', 'message', '', _('commit message'), _('TEXT')), + ('e', 'edit', False, _('invoke editor on commit messages')), ] + commands.commitopts2, _('hg sign [OPTION]... [REV]...')) def sign(ui, repo, *revs, **opts): @@ -276,7 +277,8 @@ % hgnode.short(n) for n in nodes]) try: - repo.commit(message, opts['user'], opts['date'], match=msigs) + repo.commit(message, opts['user'], opts['date'], match=msigs, + editor=cmdutil.getcommiteditor(**opts)) except ValueError, inst: raise util.Abort(str(inst))
--- a/hgext/hgk.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/hgk.py Fri Jun 13 17:42:04 2014 -0500 @@ -35,12 +35,22 @@ ''' import os -from mercurial import commands, util, patch, revlog, scmutil +from mercurial import cmdutil, commands, util, patch, revlog, scmutil from mercurial.node import nullid, nullrev, short from mercurial.i18n import _ +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' +@command('debug-diff-tree', + [('p', 'patch', None, _('generate patch')), + ('r', 'recursive', None, _('recursive')), + ('P', 'pretty', None, _('pretty')), + ('s', 'stdin', None, _('stdin')), + ('C', 'copy', None, _('detect copies')), + ('S', 'search', "", _('search'))], + ('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')) def difftree(ui, repo, node1=None, node2=None, *files, **opts): """diff trees from two commits""" def __difftree(repo, node1, node2, files=[]): @@ -125,6 +135,7 @@ if prefix: ui.write('\0') +@command('debug-merge-base', [], _('hg debug-merge-base REV REV')) def base(ui, repo, node1, node2): """output common ancestor information""" node1 = repo.lookup(node1) @@ -132,6 +143,9 @@ n = repo.changelog.ancestor(node1, node2) ui.write(short(n) + "\n") +@command('debug-cat-file', + [('s', 'stdin', None, _('stdin'))], + _('hg debug-cat-file [OPTION]... TYPE FILE')) def catfile(ui, repo, type=None, r=None, **opts): """cat a specific revision""" # in stdin mode, every line except the commit is prefixed with two @@ -276,6 +290,9 @@ break count += 1 +@command('debug-rev-parse', + [('', 'default', '', _('ignored'))], + _('hg debug-rev-parse REV')) def revparse(ui, repo, *revs, **opts): """parse given revisions""" def revstr(rev): @@ -292,6 +309,12 @@ # git rev-list tries to order things by date, and has the ability to stop # at a given commit without walking the whole repo. TODO add the stop # parameter +@command('debug-rev-list', + [('H', 'header', None, _('header')), + ('t', 'topo-order', None, _('topo-order')), + ('p', 'parents', None, _('parents')), + ('n', 'max-count', 0, _('max-count'))], + ('hg debug-rev-list [OPTION]... REV...')) def revlist(ui, repo, *revs, **opts): """print revisions""" if opts['header']: @@ -301,6 +324,7 @@ copy = [x for x in revs] revtree(ui, copy, repo, full, opts['max_count'], opts['parents']) +@command('debug-config', [], _('hg debug-config')) def config(ui, repo, **opts): """print extension options""" def writeopt(name, value): @@ -309,6 +333,10 @@ writeopt('vdiff', ui.config('hgk', 'vdiff', '')) +@command('view', + [('l', 'limit', '', + _('limit number of changes displayed'), _('NUM'))], + _('hg view [-l LIMIT] [REVRANGE]')) def view(ui, repo, *etc, **opts): "start interactive history viewer" os.chdir(repo.root) @@ -317,40 +345,4 @@ ui.debug("running %s\n" % cmd) util.system(cmd) -cmdtable = { - "^view": - (view, - [('l', 'limit', '', - _('limit number of changes displayed'), _('NUM'))], - _('hg view [-l LIMIT] [REVRANGE]')), - "debug-diff-tree": - (difftree, - [('p', 'patch', None, _('generate patch')), - ('r', 'recursive', None, _('recursive')), - ('P', 'pretty', None, _('pretty')), - ('s', 'stdin', None, _('stdin')), - ('C', 'copy', None, _('detect copies')), - ('S', 'search', "", _('search'))], - _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')), - "debug-cat-file": - (catfile, - [('s', 'stdin', None, _('stdin'))], - _('hg debug-cat-file [OPTION]... TYPE FILE')), - "debug-config": - (config, [], _('hg debug-config')), - "debug-merge-base": - (base, [], _('hg debug-merge-base REV REV')), - "debug-rev-parse": - (revparse, - [('', 'default', '', _('ignored'))], - _('hg debug-rev-parse REV')), - "debug-rev-list": - (revlist, - [('H', 'header', None, _('header')), - ('t', 'topo-order', None, _('topo-order')), - ('p', 'parents', None, _('parents')), - ('n', 'max-count', 0, _('max-count'))], - _('hg debug-rev-list [OPTION]... REV...')), -} - commands.inferrepo += " debug-diff-tree debug-cat-file"
--- a/hgext/histedit.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/histedit.py Fri Jun 13 17:42:04 2014 -0500 @@ -275,7 +275,8 @@ if path in headmf: fctx = last[path] flags = fctx.flags() - mctx = context.memfilectx(fctx.path(), fctx.data(), + mctx = context.memfilectx(repo, + fctx.path(), fctx.data(), islink='l' in flags, isexec='x' in flags, copied=copied.get(path)) @@ -298,9 +299,8 @@ filectxfn=filectxfn, user=user, date=date, - extra=extra) - new._text = cmdutil.commitforceeditor(repo, new, []) - repo.savecommitmessage(new.description()) + extra=extra, + editor=cmdutil.getcommiteditor(edit=True)) return repo.commitctx(new) def pick(ui, repo, ctx, ha, opts): @@ -402,12 +402,11 @@ if stats and stats[3] > 0: raise error.InterventionRequired( _('Fix up the change and run hg histedit --continue')) - message = oldctx.description() + '\n' - message = ui.edit(message, ui.username()) - repo.savecommitmessage(message) + message = oldctx.description() commit = commitfuncfor(repo, oldctx) new = commit(text=message, user=oldctx.user(), date=oldctx.date(), - extra=oldctx.extra()) + extra=oldctx.extra(), + editor=cmdutil.getcommiteditor(edit=True)) newctx = repo[new] if oldctx.node() != newctx.node(): return newctx, [(oldctx.node(), (new,))] @@ -682,11 +681,9 @@ if action in ('f', 'fold'): message = 'fold-temp-revision %s' % currentnode else: - message = ctx.description() + '\n' - if action in ('e', 'edit', 'm', 'mess'): - editor = cmdutil.commitforceeditor - else: - editor = False + message = ctx.description() + editopt = action in ('e', 'edit', 'm', 'mess') + editor = cmdutil.getcommiteditor(edit=editopt) commit = commitfuncfor(repo, ctx) new = commit(text=message, user=ctx.user(), date=ctx.date(), extra=ctx.extra(),
--- a/hgext/largefiles/lfcommands.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/largefiles/lfcommands.py Fri Jun 13 17:42:04 2014 -0500 @@ -21,6 +21,18 @@ # -- Commands ---------------------------------------------------------- +cmdtable = {} +command = cmdutil.command(cmdtable) + +commands.inferrepo += " lfconvert" + +@command('lfconvert', + [('s', 'size', '', + _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'), + ('', 'to-normal', False, + _('convert from a largefiles repo to a normal repo')), + ], + _('hg lfconvert SOURCE DEST [FILE ...]')) def lfconvert(ui, src, dest, *pats, **opts): '''convert a normal repository to a largefiles repository @@ -160,10 +172,10 @@ finally: if fd: fd.close() - return context.memfilectx(f, data, 'l' in fctx.flags(), + return context.memfilectx(repo, f, data, 'l' in fctx.flags(), 'x' in fctx.flags(), renamed) else: - return _getnormalcontext(repo.ui, ctx, f, revmap) + return _getnormalcontext(repo, ctx, f, revmap) dstfiles = [] for file in files: @@ -243,10 +255,11 @@ # doesn't change after rename or copy renamed = lfutil.standin(renamed[0]) - return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in - fctx.flags(), 'x' in fctx.flags(), renamed) + return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n', + 'l' in fctx.flags(), 'x' in fctx.flags(), + renamed) else: - return _getnormalcontext(repo.ui, ctx, f, revmap) + return _getnormalcontext(repo, ctx, f, revmap) # Commit _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap) @@ -281,7 +294,7 @@ return parents # Get memfilectx for a normal file -def _getnormalcontext(ui, ctx, f, revmap): +def _getnormalcontext(repo, ctx, f, revmap): try: fctx = ctx.filectx(f) except error.LookupError: @@ -292,8 +305,8 @@ data = fctx.data() if f == '.hgtags': - data = _converttags (ui, revmap, data) - return context.memfilectx(f, data, 'l' in fctx.flags(), + data = _converttags (repo.ui, revmap, data) + return context.memfilectx(repo, f, data, 'l' in fctx.flags(), 'x' in fctx.flags(), renamed) # Remap tag data using a revision map @@ -519,6 +532,10 @@ finally: wlock.release() +@command('lfpull', + [('r', 'rev', [], _('pull largefiles for these revisions')) + ] + commands.remoteopts, + _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]')) def lfpull(ui, repo, source="default", **opts): """pull largefiles for the specified revisions from the specified source @@ -553,24 +570,3 @@ (cached, missing) = cachelfiles(ui, repo, rev) numcached += len(cached) ui.status(_("%d largefiles cached\n") % numcached) - -# -- hg commands declarations ------------------------------------------------ - -cmdtable = { - 'lfconvert': (lfconvert, - [('s', 'size', '', - _('minimum size (MB) for files to be converted ' - 'as largefiles'), - 'SIZE'), - ('', 'to-normal', False, - _('convert from a largefiles repo to a normal repo')), - ], - _('hg lfconvert SOURCE DEST [FILE ...]')), - 'lfpull': (lfpull, - [('r', 'rev', [], _('pull largefiles for these revisions')) - ] + commands.remoteopts, - _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]') - ), - } - -commands.inferrepo += " lfconvert"
--- a/hgext/largefiles/overrides.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/largefiles/overrides.py Fri Jun 13 17:42:04 2014 -0500 @@ -282,6 +282,8 @@ standin = lfutil.standin(m._files[i]) if standin in repo[ctx.node()]: m._files[i] = standin + elif m._files[i] not in repo[ctx.node()]: + m._files.append(standin) pats.add(standin) m._fmap = set(m._files) @@ -408,14 +410,13 @@ if overwrite: return actions - removes = set(a[0] for a in actions if a[1] == 'r') - processed = [] + removes = set(a[0] for a in actions['r']) - for action in actions: - f, m, args, msg = action - + newglist = [] + for action in actions['g']: + f, args, msg = action splitstandin = f and lfutil.splitstandin(f) - if (m == "g" and splitstandin is not None and + if (splitstandin is not None and splitstandin in p1 and splitstandin not in removes): # Case 1: normal file in the working copy, largefile in # the second parent @@ -425,12 +426,11 @@ 'use (l)argefile or keep (n)ormal file?' '$$ &Largefile $$ &Normal file') % lfile if repo.ui.promptchoice(msg, 0) == 0: - processed.append((lfile, "r", None, msg)) - processed.append((standin, "g", (p2.flags(standin),), msg)) + actions['r'].append((lfile, None, msg)) + newglist.append((standin, (p2.flags(standin),), msg)) else: - processed.append((standin, "r", None, msg)) - elif (m == "g" and - lfutil.standin(f) in p1 and lfutil.standin(f) not in removes): + actions['r'].append((standin, None, msg)) + elif lfutil.standin(f) in p1 and lfutil.standin(f) not in removes: # Case 2: largefile in the working copy, normal file in # the second parent standin = lfutil.standin(f) @@ -439,20 +439,23 @@ 'keep (l)argefile or use (n)ormal file?' '$$ &Largefile $$ &Normal file') % lfile if repo.ui.promptchoice(msg, 0) == 0: - processed.append((lfile, "r", None, msg)) + actions['r'].append((lfile, None, msg)) else: - processed.append((standin, "r", None, msg)) - processed.append((lfile, "g", (p2.flags(lfile),), msg)) + actions['r'].append((standin, None, msg)) + newglist.append((lfile, (p2.flags(lfile),), msg)) else: - processed.append(action) + newglist.append(action) - return processed + newglist.sort() + actions['g'] = newglist + + return actions # Override filemerge to prompt the user about how they wish to merge # largefiles. This will handle identical edits without prompting the user. -def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca): +def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None): if not lfutil.isstandin(orig): - return origfn(repo, mynode, orig, fcd, fco, fca) + return origfn(repo, mynode, orig, fcd, fco, fca, labels=labels) ahash = fca.data().strip().lower() dhash = fcd.data().strip().lower()
--- a/hgext/mq.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/mq.py Fri Jun 13 17:42:04 2014 -0500 @@ -1026,6 +1026,7 @@ msg: a string or a no-argument function returning a string """ msg = opts.get('msg') + edit = opts.get('edit') user = opts.get('user') date = opts.get('date') if date: @@ -1078,12 +1079,25 @@ p.write("# User " + user + "\n") if date: p.write("# Date %s %s\n\n" % date) - if util.safehasattr(msg, '__call__'): - msg = msg() - repo.savecommitmessage(msg) - commitmsg = msg and msg or ("[mq]: %s" % patchfn) + + defaultmsg = "[mq]: %s" % patchfn + editor = cmdutil.getcommiteditor() + if edit: + def finishdesc(desc): + if desc.rstrip(): + return desc + else: + return defaultmsg + # i18n: this message is shown in editor with "HG: " prefix + extramsg = _('Leave message empty to use default message.') + editor = cmdutil.getcommiteditor(finishdesc=finishdesc, + extramsg=extramsg) + commitmsg = msg + else: + commitmsg = msg or defaultmsg + n = newcommit(repo, None, commitmsg, user, date, match=match, - force=True) + force=True, editor=editor) if n is None: raise util.Abort(_("repo commit failed")) try: @@ -1092,8 +1106,9 @@ self.parseseries() self.seriesdirty = True self.applieddirty = True - if msg: - msg = msg + "\n\n" + nctx = repo[n] + if nctx.description() != defaultmsg.rstrip(): + msg = nctx.description() + "\n\n" p.write(msg) if commitfiles: parent = self.qparents(repo, n) @@ -1471,6 +1486,7 @@ self.ui.write(_("no patches applied\n")) return 1 msg = opts.get('msg', '').rstrip() + edit = opts.get('edit') newuser = opts.get('user') newdate = opts.get('date') if newdate: @@ -1495,8 +1511,6 @@ ph = patchheader(self.join(patchfn), self.plainmode) diffopts = self.diffopts({'git': opts.get('git')}, patchfn) - if msg: - ph.setmessage(msg) if newuser: ph.setuser(newuser) if newdate: @@ -1506,10 +1520,6 @@ # only commit new patch when write is complete patchf = self.opener(patchfn, 'w', atomictemp=True) - comments = str(ph) - if comments: - patchf.write(comments) - # update the dirstate in place, strip off the qtip commit # and then commit. # @@ -1629,14 +1639,6 @@ for f in forget: repo.dirstate.drop(f) - if not msg: - if not ph.message: - message = "[mq]: %s\n" % patchfn - else: - message = "\n".join(ph.message) - else: - message = msg - user = ph.user or changes[1] oldphase = repo[top].phase() @@ -1653,16 +1655,41 @@ try: # might be nice to attempt to roll back strip after this + defaultmsg = "[mq]: %s" % patchfn + editor = cmdutil.getcommiteditor() + if edit: + def finishdesc(desc): + if desc.rstrip(): + ph.setmessage(desc) + return desc + return defaultmsg + # i18n: this message is shown in editor with "HG: " prefix + extramsg = _('Leave message empty to use default message.') + editor = cmdutil.getcommiteditor(finishdesc=finishdesc, + extramsg=extramsg) + message = msg or "\n".join(ph.message) + elif not msg: + if not ph.message: + message = defaultmsg + else: + message = "\n".join(ph.message) + else: + message = msg + ph.setmessage(msg) + # Ensure we create a new changeset in the same phase than # the old one. n = newcommit(repo, oldphase, message, user, ph.date, - match=match, force=True) + 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() @@ -2417,14 +2444,8 @@ Returns 0 on successful creation of a new patch. """ msg = cmdutil.logmessage(ui, opts) - def getmsg(): - return ui.edit(msg, opts.get('user') or ui.username()) q = repo.mq opts['msg'] = msg - if opts.get('edit'): - opts['msg'] = getmsg - else: - opts['msg'] = msg setupheaderopts(ui, opts) q.new(repo, patch, *args, **opts) q.savedirty() @@ -2468,17 +2489,6 @@ """ q = repo.mq message = cmdutil.logmessage(ui, opts) - if opts.get('edit'): - if not q.applied: - ui.write(_("no patches applied\n")) - return 1 - if message: - raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) - patch = q.applied[-1].name - ph = patchheader(q.join(patch), q.plainmode) - message = ui.edit('\n'.join(ph.message), ph.user or ui.username()) - # We don't want to lose the patch message if qrefresh fails (issue2062) - repo.savecommitmessage(message) setupheaderopts(ui, opts) wlock = repo.wlock() try: @@ -2536,9 +2546,6 @@ q.checklocalchanges(repo) message = cmdutil.logmessage(ui, opts) - if opts.get('edit'): - if message: - raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) parent = q.lookup('qtip') patches = [] @@ -2564,7 +2571,7 @@ if not message: ph = patchheader(q.join(parent), q.plainmode) - message, user = ph.message, ph.user + message = ph.message for msg in messages: if msg: if message: @@ -2572,14 +2579,10 @@ message.extend(msg) message = '\n'.join(message) - if opts.get('edit'): - message = ui.edit(message, user or ui.username()) - repo.savecommitmessage(message) - diffopts = q.patchopts(q.diffopts(), *patches) wlock = repo.wlock() try: - q.refresh(repo, msg=message, git=diffopts.git) + q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit')) q.delete(repo, patches, opts) q.savedirty() finally:
--- a/hgext/pager.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/pager.py Fri Jun 13 17:42:04 2014 -0500 @@ -39,12 +39,20 @@ If pager.attend is present, pager.ignore will be ignored. +Lastly, you can enable and disable paging for individual commands with +the attend-<command> option. This setting takes precedence over +existing attend and ignore options and defaults:: + + [pager] + attend-cat = false + 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. + ''' import atexit, sys, os, signal, subprocess, errno, shlex @@ -116,25 +124,37 @@ def pagecmd(orig, ui, options, cmd, cmdfunc): p = ui.config("pager", "pager", os.environ.get("PAGER")) + usepager = False + always = util.parsebool(options['pager']) + auto = options['pager'] == 'auto' - if p: + if not p: + pass + elif always: + usepager = True + elif not auto: + usepager = False + else: attend = ui.configlist('pager', 'attend', attended) - auto = options['pager'] == 'auto' - always = util.parsebool(options['pager']) - + ignore = ui.configlist('pager', 'ignore') cmds, _ = cmdutil.findcmd(cmd, commands.table) - ignore = ui.configlist('pager', 'ignore') for cmd in cmds: - if (always or auto and - (cmd in attend or - (cmd not in ignore and not attend))): - ui.setconfig('ui', 'formatted', ui.formatted(), 'pager') - ui.setconfig('ui', 'interactive', False, 'pager') - if util.safehasattr(signal, "SIGPIPE"): - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - _runpager(ui, p) + 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') + if util.safehasattr(signal, "SIGPIPE"): + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + _runpager(ui, p) return orig(ui, options, cmd, cmdfunc) extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
--- a/hgext/patchbomb.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/patchbomb.py Fri Jun 13 17:42:04 2014 -0500 @@ -149,6 +149,8 @@ subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj) msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) msg['X-Mercurial-Node'] = node + msg['X-Mercurial-Series-Index'] = '%i' % idx + msg['X-Mercurial-Series-Total'] = '%i' % total return msg, subj, ds emailopts = [ @@ -507,9 +509,13 @@ sender_addr = email.Utils.parseaddr(sender)[1] sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) sendmail = None + firstpatch = None for i, (m, subj, ds) in enumerate(msgs): try: m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) + if not firstpatch: + firstpatch = m['Message-Id'] + m['X-Mercurial-Series-Id'] = firstpatch except TypeError: m['Message-Id'] = genmsgid('patchbomb') if parent:
--- a/hgext/rebase.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/rebase.py Fri Jun 13 17:42:04 2014 -0500 @@ -138,9 +138,7 @@ skipped = set() targetancestors = set() - editor = None - if opts.get('edit'): - editor = cmdutil.commitforceeditor + editor = cmdutil.getcommiteditor(**opts) lock = wlock = None try: @@ -376,7 +374,7 @@ for rebased in state: if rebased not in skipped and state[rebased] > nullmerge: commitmsg += '\n* %s' % repo[rebased].description() - editor = cmdutil.commitforceeditor + editor = cmdutil.getcommiteditor(edit=True) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extrafn=extrafn, editor=editor) for oldrev in state.iterkeys(): @@ -533,7 +531,8 @@ repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base])) # When collapsing in-place, the parent is the common ancestor, we # have to allow merging with it. - return merge.update(repo, rev, True, True, False, base, collapse) + return merge.update(repo, rev, True, True, False, base, collapse, + labels=['dest', 'source']) def nearestrebased(repo, rev, state): """return the nearest ancestors of rev in the rebase result"""
--- a/hgext/record.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/record.py Fri Jun 13 17:42:04 2014 -0500 @@ -459,6 +459,8 @@ # backup all changed files dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts) +# This command registration is replaced during uisetup(). +@command('qrecord', [], _('hg qrecord [OPTION]... PATCH [FILE]...')) def qrecord(ui, repo, patch, *pats, **opts): '''interactively record a new patch @@ -637,10 +639,6 @@ finally: ui.write = oldwrite -cmdtable["qrecord"] = \ - (qrecord, [], # placeholder until mq is available - _('hg qrecord [OPTION]... PATCH [FILE]...')) - def uisetup(ui): try: mq = extensions.find('mq')
--- a/hgext/relink.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/relink.py Fri Jun 13 17:42:04 2014 -0500 @@ -7,12 +7,15 @@ """recreates hardlinks between repository clones""" -from mercurial import hg, util +from mercurial import cmdutil, hg, util from mercurial.i18n import _ import os, stat +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' +@command('relink', [], _('[ORIGIN]')) def relink(ui, repo, origin=None, **opts): """recreate hardlinks between two repositories @@ -178,11 +181,3 @@ ui.status(_('relinked %d files (%s reclaimed)\n') % (relinked, util.bytecount(savedbytes))) - -cmdtable = { - 'relink': ( - relink, - [], - _('[ORIGIN]') - ) -}
--- a/hgext/share.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/share.py Fri Jun 13 17:42:04 2014 -0500 @@ -6,10 +6,15 @@ '''share a common history between several working directories''' from mercurial.i18n import _ -from mercurial import hg, commands, util +from mercurial import cmdutil, hg, commands, util +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' +@command('share', + [('U', 'noupdate', None, _('do not create a working copy'))], + _('[-U] SOURCE [DEST]')) def share(ui, source, dest=None, noupdate=False): """create a new shared repository @@ -30,6 +35,7 @@ return hg.share(ui, source, dest, not noupdate) +@command('unshare', [], '') def unshare(ui, repo): """convert a shared repository to a normal one @@ -61,15 +67,4 @@ # update store, spath, sopener and sjoin of repo repo.unfiltered().__init__(repo.baseui, repo.root) -cmdtable = { - "share": - (share, - [('U', 'noupdate', None, _('do not create a working copy'))], - _('[-U] SOURCE [DEST]')), - "unshare": - (unshare, - [], - ''), -} - commands.norepo += " share"
--- a/hgext/transplant.py Fri Jun 13 14:17:14 2014 -0500 +++ b/hgext/transplant.py Fri Jun 13 17:42:04 2014 -0500 @@ -80,13 +80,13 @@ self.dirty = True class transplanter(object): - def __init__(self, ui, repo): + def __init__(self, ui, repo, opts): self.ui = ui self.path = repo.join('transplant') self.opener = scmutil.opener(self.path) self.transplants = transplants(self.path, 'transplants', opener=self.opener) - self.editor = None + self.editor = cmdutil.getcommiteditor(**opts) def applied(self, repo, node, parent): '''returns True if a node is already an ancestor of parent @@ -599,9 +599,7 @@ if not opts.get('filter'): opts['filter'] = ui.config('transplant', 'filter') - tp = transplanter(ui, repo) - if opts.get('edit'): - tp.editor = cmdutil.commitforceeditor + tp = transplanter(ui, repo, opts) cmdutil.checkunfinished(repo) p1, p2 = repo.dirstate.parents()
--- a/mercurial/bundle2.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/bundle2.py Fri Jun 13 17:42:04 2014 -0500 @@ -113,6 +113,8 @@ Mandatory parameters comes first, then the advisory ones. + Each parameter's key MUST be unique within the part. + :payload: payload is a series of `<chunksize><chunkdata>`. @@ -144,6 +146,7 @@ import struct import urllib import string +import pushkey import changegroup, error from i18n import _ @@ -170,18 +173,14 @@ """ return '>'+('BB'*nbparams) -class UnknownPartError(KeyError): - """error raised when no handler is found for a Mandatory part""" - pass - parthandlermapping = {} -def parthandler(parttype): +def parthandler(parttype, params=()): """decorator that register a function as a bundle2 part handler eg:: - @parthandler('myparttype') + @parthandler('myparttype', ('mandatory', 'param', 'handled')) def myparttypehandler(...): '''process a part of type "my part".''' ... @@ -190,6 +189,7 @@ lparttype = parttype.lower() # enforce lower case matching. assert lparttype not in parthandlermapping parthandlermapping[lparttype] = func + func.params = frozenset(params) return func return _decorator @@ -295,18 +295,25 @@ # part key are matched lower case key = parttype.lower() try: - handler = parthandlermapping[key] + handler = parthandlermapping.get(key) + if handler is None: + raise error.BundleValueError(parttype=key) op.ui.debug('found a handler for part %r\n' % parttype) - except KeyError: + unknownparams = part.mandatorykeys - handler.params + if unknownparams: + unknownparams = list(unknownparams) + unknownparams.sort() + raise error.BundleValueError(parttype=key, + params=unknownparams) + except error.BundleValueError, exc: if key != parttype: # mandatory parts - # todo: - # - use a more precise exception - raise UnknownPartError(key) - op.ui.debug('ignoring unknown advisory part %r\n' % key) + raise + op.ui.debug('ignoring unsupported advisory part %s\n' % exc) # consuming the part part.read() continue + # handler is called outside the above try block so that we don't # risk catching KeyErrors from anything other than the # parthandlermapping lookup (any KeyError raised by handler() @@ -321,11 +328,8 @@ if output is not None: output = op.ui.popbuffer() if output: - outpart = bundlepart('b2x:output', - advisoryparams=[('in-reply-to', - str(part.id))], - data=output) - op.reply.addpart(outpart) + outpart = op.reply.newpart('b2x:output', data=output) + outpart.addparam('in-reply-to', str(part.id), mandatory=False) part.read() except Exception, exc: if part is not None: @@ -381,7 +385,7 @@ class bundle20(object): """represent an outgoing bundle2 container - Use the `addparam` method to add stream level parameter. and `addpart` to + Use the `addparam` method to add stream level parameter. and `newpart` to populate it. Then call `getchunks` to retrieve all the binary chunks of data that compose the bundle2 container.""" @@ -391,6 +395,7 @@ self._parts = [] self.capabilities = dict(capabilities) + # methods used to defines the bundle2 content def addparam(self, name, value=None): """add a stream level parameter""" if not name: @@ -407,6 +412,20 @@ part.id = len(self._parts) # very cheap counter self._parts.append(part) + def newpart(self, typeid, *args, **kwargs): + """create a new part and add it to the containers + + As the part is directly added to the containers. For now, this means + that any failure to properly initialize the part after calling + ``newpart`` should result in a failure of the whole bundling process. + + You can still fall back to manually create and add if you need better + control.""" + part = bundlepart(typeid, *args, **kwargs) + self.addpart(part) + return part + + # methods used to generate the bundle2 stream def getchunks(self): self.ui.debug('start emission of %s stream\n' % _magicstring) yield _magicstring @@ -505,7 +524,7 @@ if name[0].islower(): self.ui.debug("ignoring unknown parameter %r\n" % name) else: - raise KeyError(name) + raise error.BundleValueError(params=(name,)) def iterparts(self): @@ -536,17 +555,71 @@ The part `type` is used to route the part to the application level handler. + + The part payload is contained in ``part.data``. It could be raw bytes or a + generator of byte chunks. + + You can add parameters to the part using the ``addparam`` method. + Parameters can be either mandatory (default) or advisory. Remote side + should be able to safely ignore the advisory ones. + + Both data and parameters cannot be modified after the generation has begun. """ def __init__(self, parttype, mandatoryparams=(), advisoryparams=(), data=''): self.id = None self.type = parttype - self.data = data - self.mandatoryparams = mandatoryparams - self.advisoryparams = advisoryparams + self._data = data + self._mandatoryparams = list(mandatoryparams) + self._advisoryparams = list(advisoryparams) + # checking for duplicated entries + self._seenparams = set() + for pname, __ in self._mandatoryparams + self._advisoryparams: + if pname in self._seenparams: + raise RuntimeError('duplicated params: %s' % pname) + self._seenparams.add(pname) + # status of the part's generation: + # - None: not started, + # - False: currently generated, + # - True: generation done. + self._generated = None + + # methods used to defines the part content + def __setdata(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): + # make it an immutable tuple to force people through ``addparam`` + return tuple(self._mandatoryparams) + + @property + def advisoryparams(self): + # make it an immutable tuple to force people through ``addparam`` + return tuple(self._advisoryparams) + + def addparam(self, name, value='', mandatory=True): + if self._generated is not None: + raise error.ReadOnlyPartError('part is being generated') + if name in self._seenparams: + raise ValueError('duplicated params: %s' % name) + self._seenparams.add(name) + params = self._advisoryparams + if mandatory: + params = self._mandatoryparams + params.append((name, value)) + + # methods used to generates the bundle2 stream def getchunks(self): + if self._generated is not None: + raise RuntimeError('part can only be consumed once') + self._generated = False #### header ## parttype header = [_pack(_fparttypesize, len(self.type)), @@ -584,6 +657,7 @@ yield chunk # end of payload yield _pack(_fpayloadsize, 0) + self._generated = True def _payloadchunks(self): """yield chunks of a the part payload @@ -616,6 +690,8 @@ self.type = None self.mandatoryparams = None self.advisoryparams = None + self.params = None + self.mandatorykeys = () self._payloadstream = None self._readheader() @@ -633,6 +709,16 @@ data = self._fromheader(struct.calcsize(format)) return _unpack(format, data) + def _initparams(self, mandatoryparams, advisoryparams): + """internal function to setup all logic related parameters""" + # make it read only to prevent people touching it by mistake. + self.mandatoryparams = tuple(mandatoryparams) + self.advisoryparams = tuple(advisoryparams) + # user friendly UI + self.params = dict(self.mandatoryparams) + self.params.update(dict(self.advisoryparams)) + self.mandatorykeys = frozenset(p[0] for p in mandatoryparams) + def _readheader(self): """read the header and setup the object""" typesize = self._unpackheader(_fparttypesize)[0] @@ -659,8 +745,7 @@ advparams = [] for key, value in advsizes: advparams.append((self._fromheader(key), self._fromheader(value))) - self.mandatoryparams = manparams - self.advisoryparams = advparams + self._initparams(manparams, advparams) ## part payload def payloadchunks(): payloadsize = self._unpack(_fpayloadsize)[0] @@ -685,6 +770,13 @@ self.consumed = True return data +def bundle2caps(remote): + """return the bundlecapabilities of a peer as dict""" + raw = remote.capable('bundle2-exp') + if not raw and raw != '': + return {} + capsblob = urllib.unquote(remote.capable('bundle2-exp')) + return decodecaps(capsblob) @parthandler('b2x:changegroup') def handlechangegroup(op, inpart): @@ -705,17 +797,16 @@ if op.reply is not None: # This is definitly not the final form of this # return. But one need to start somewhere. - part = bundlepart('b2x:reply:changegroup', (), - [('in-reply-to', str(inpart.id)), - ('return', '%i' % ret)]) - op.reply.addpart(part) + part = op.reply.newpart('b2x:reply:changegroup') + part.addparam('in-reply-to', str(inpart.id), mandatory=False) + part.addparam('return', '%i' % ret, mandatory=False) assert not inpart.read() -@parthandler('b2x:reply:changegroup') +@parthandler('b2x:reply:changegroup', ('return', 'in-reply-to')) def handlechangegroup(op, inpart): - p = dict(inpart.advisoryparams) - ret = int(p['return']) - op.records.add('changegroup', {'return': ret}, int(p['in-reply-to'])) + ret = int(inpart.params['return']) + replyto = int(inpart.params['in-reply-to']) + op.records.add('changegroup', {'return': ret}, replyto) @parthandler('b2x:check:heads') def handlechangegroup(op, inpart): @@ -748,21 +839,58 @@ if op.reply is None: op.reply = bundle20(op.ui, caps) -@parthandler('b2x:error:abort') +@parthandler('b2x:error:abort', ('message', 'hint')) def handlereplycaps(op, inpart): """Used to transmit abort error over the wire""" - manargs = dict(inpart.mandatoryparams) - advargs = dict(inpart.advisoryparams) - raise util.Abort(manargs['message'], hint=advargs.get('hint')) + raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint')) -@parthandler('b2x:error:unknownpart') +@parthandler('b2x:error:unsupportedcontent', ('parttype', 'params')) def handlereplycaps(op, inpart): - """Used to transmit unknown part error over the wire""" - manargs = dict(inpart.mandatoryparams) - raise UnknownPartError(manargs['parttype']) + """Used to transmit unknown content error over the wire""" + kwargs = {} + parttype = inpart.params.get('parttype') + if parttype is not None: + kwargs['parttype'] = parttype + params = inpart.params.get('params') + if params is not None: + kwargs['params'] = params.split('\0') -@parthandler('b2x:error:pushraced') + raise error.BundleValueError(**kwargs) + +@parthandler('b2x:error:pushraced', ('message',)) def handlereplycaps(op, inpart): """Used to transmit push race error over the wire""" - manargs = dict(inpart.mandatoryparams) - raise error.ResponseError(_('push failed:'), manargs['message']) + raise error.ResponseError(_('push failed:'), inpart.params['message']) + +@parthandler('b2x:listkeys', ('namespace',)) +def handlelistkeys(op, inpart): + """retrieve pushkey namespace content stored in a bundle2""" + namespace = inpart.params['namespace'] + r = pushkey.decodekeys(inpart.read()) + op.records.add('listkeys', (namespace, r)) + +@parthandler('b2x:pushkey', ('namespace', 'key', 'old', 'new')) +def handlepushkey(op, inpart): + """process a pushkey request""" + dec = pushkey.decode + namespace = dec(inpart.params['namespace']) + key = dec(inpart.params['key']) + old = dec(inpart.params['old']) + new = dec(inpart.params['new']) + ret = op.repo.pushkey(namespace, key, old, new) + record = {'namespace': namespace, + 'key': key, + 'old': old, + 'new': new} + op.records.add('pushkey', record) + if op.reply is not None: + rpart = op.reply.newpart('b2x:reply:pushkey') + rpart.addparam('in-reply-to', str(inpart.id), mandatory=False) + rpart.addparam('return', '%i' % ret, mandatory=False) + +@parthandler('b2x:reply:pushkey', ('return', 'in-reply-to')) +def handlepushkeyreply(op, inpart): + """retrieve the result of a pushkey request""" + ret = int(inpart.params['return']) + partid = int(inpart.params['in-reply-to']) + op.records.add('pushkey', {'return': ret}, partid)
--- a/mercurial/changegroup.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/changegroup.py Fri Jun 13 17:42:04 2014 -0500 @@ -493,6 +493,25 @@ bundler = bundle10(repo, bundlecaps) return getsubset(repo, outgoing, bundler, source) +def _computeoutgoing(repo, heads, common): + """Computes which revs are outgoing given a set of common + and a set of heads. + + This is a separate function so extensions can have access to + the logic. + + Returns a discovery.outgoing object. + """ + cl = repo.changelog + if common: + hasnode = cl.hasnode + common = [n for n in common if hasnode(n)] + else: + common = [nullid] + if not heads: + heads = cl.heads() + return discovery.outgoing(cl, common, heads) + def getbundle(repo, source, heads=None, common=None, bundlecaps=None): """Like changegroupsubset, but returns the set difference between the ancestors of heads and the ancestors common. @@ -502,15 +521,7 @@ The nodes in common might not all be known locally due to the way the current discovery protocol works. """ - cl = repo.changelog - if common: - hasnode = cl.hasnode - common = [n for n in common if hasnode(n)] - else: - common = [nullid] - if not heads: - heads = cl.heads() - outgoing = discovery.outgoing(cl, common, heads) + outgoing = _computeoutgoing(repo, heads, common) return getlocalbundle(repo, source, outgoing, bundlecaps=bundlecaps) def changegroup(repo, basenodes, source):
--- a/mercurial/cmdutil.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/cmdutil.py Fri Jun 13 17:42:04 2014 -0500 @@ -109,6 +109,30 @@ (logfile, inst.strerror)) return message +def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts): + """get appropriate commit message editor according to '--edit' option + + 'finishdesc' is a function to be called with edited commit message + (= 'description' of the new changeset) just after editing, but + before checking empty-ness. It should return actual text to be + stored into history. This allows to change description before + storing. + + 'extramsg' is a extra message to be shown in the editor instead of + 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL + is automatically added. + + 'getcommiteditor' returns 'commitforceeditor' regardless of + 'edit', if one of 'finishdesc' or 'extramsg' is specified, because + they are specific for usage in MQ. + """ + if edit or finishdesc or extramsg: + return lambda r, c, s: commitforceeditor(r, c, s, + finishdesc=finishdesc, + extramsg=extramsg) + else: + return commiteditor + def loglimit(opts): """get the log limit according to option -l/--limit""" limit = opts.get('limit') @@ -562,16 +586,16 @@ tmpname, message, user, date, branch, nodeid, p1, p2 = \ patch.extract(ui, hunk) - editor = commiteditor - if opts.get('edit'): - editor = commitforceeditor + editor = getcommiteditor(**opts) update = not opts.get('bypass') strip = opts["strip"] sim = float(opts.get('similarity') or 0) if not tmpname: - return (None, None) + return (None, None, False) msg = _('applied to working directory') + rejects = False + try: cmdline_message = logmessage(ui, opts) if cmdline_message: @@ -617,9 +641,17 @@ if opts.get('exact') or opts.get('import_branch'): repo.dirstate.setbranch(branch or 'default') + partial = opts.get('partial', False) files = set() - patch.patch(ui, repo, tmpname, strip=strip, files=files, - eolmode=None, similarity=sim / 100.0) + try: + patch.patch(ui, repo, tmpname, strip=strip, files=files, + eolmode=None, similarity=sim / 100.0) + except patch.PatchError, e: + if not partial: + raise util.Abort(str(e)) + if partial: + rejects = True + files = list(files) if opts.get('no_commit'): if message: @@ -634,7 +666,7 @@ m = scmutil.matchfiles(repo, files or []) n = repo.commit(message, opts.get('user') or user, opts.get('date') or date, match=m, - editor=editor) + editor=editor, force=partial) else: if opts.get('exact') or opts.get('import_branch'): branch = branch or 'default' @@ -653,8 +685,7 @@ opts.get('user') or user, opts.get('date') or date, branch, files, store, - editor=commiteditor) - repo.savecommitmessage(memctx.description()) + editor=getcommiteditor()) n = memctx.commit() finally: store.close() @@ -663,7 +694,7 @@ if n: # i18n: refers to a short changeset id msg = _('created %s') % short(n) - return (msg, n) + return (msg, n, rejects) finally: os.unlink(tmpname) @@ -2026,7 +2057,8 @@ try: fctx = ctx[path] flags = fctx.flags() - mctx = context.memfilectx(fctx.path(), fctx.data(), + mctx = context.memfilectx(repo, + fctx.path(), fctx.data(), islink='l' in flags, isexec='x' in flags, copied=copied.get(path)) @@ -2045,12 +2077,10 @@ user = opts.get('user') or old.user() date = opts.get('date') or old.date() - editmsg = False + editor = getcommiteditor(**opts) if not message: - editmsg = True + editor = getcommiteditor(edit=True) message = old.description() - elif opts.get('edit'): - editmsg = True pureextra = extra.copy() extra['amend_source'] = old.hex() @@ -2062,10 +2092,8 @@ filectxfn=filectxfn, user=user, date=date, - extra=extra) - if editmsg: - new._text = commitforceeditor(repo, new, []) - repo.savecommitmessage(new.description()) + extra=extra, + editor=editor) newdesc = changelog.stripdesc(new.description()) if ((not node) @@ -2130,7 +2158,7 @@ return ctx.description() return commitforceeditor(repo, ctx, subs) -def commitforceeditor(repo, ctx, subs): +def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None): edittext = [] modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() if ctx.description(): @@ -2139,7 +2167,10 @@ edittext.append("") # Empty line between message and comments. edittext.append(_("HG: Enter commit message." " Lines beginning with 'HG:' are removed.")) - edittext.append(_("HG: Leave message empty to abort commit.")) + if extramsg: + edittext.append("HG: %s" % extramsg) + else: + edittext.append(_("HG: Leave message empty to abort commit.")) edittext.append("HG: --") edittext.append(_("HG: user: %s") % ctx.user()) if ctx.p2(): @@ -2162,6 +2193,8 @@ text = re.sub("(?m)^HG:.*(\n|$)", "", text) os.chdir(olddir) + if finishdesc: + text = finishdesc(text) if not text.strip(): raise util.Abort(_("empty commit message")) @@ -2218,6 +2251,8 @@ node = ctx.node() mf = ctx.manifest() + if node == p2: + parent = p2 if node == parent: pmf = mf else: @@ -2227,18 +2262,22 @@ # so have to walk both. do not print errors if files exist in one # but not other. + # `names` is a mapping for all elements in working copy and target revision + # The mapping is in the form: + # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>) names = {} wlock = repo.wlock() try: - # walk dirstate. + ## filling of the `names` mapping + # walk dirstate to fill `names` m = scmutil.match(repo[None], pats, opts) m.bad = lambda x, y: False for abs in repo.walk(m): names[abs] = m.rel(abs), m.exact(abs) - # walk target manifest. + # walk target manifest to fill `names` def badfn(path, msg): if path in names: @@ -2259,11 +2298,13 @@ # get the list of subrepos that must be reverted targetsubs = sorted(s for s in ctx.substate if m(s)) + + # Find status of all file in `names`. (Against working directory parent) m = scmutil.matchfiles(repo, names) - changes = repo.status(match=m)[:4] + changes = repo.status(node1=parent, match=m)[:4] modified, added, removed, deleted = map(set, changes) - # if f is a rename, also revert the source + # if f is a rename, update `names` to also revert the source cwd = repo.getcwd() for f in added: src = repo.dirstate.copied(f) @@ -2271,15 +2312,19 @@ removed.add(src) names[src] = (repo.pathto(src, cwd), True) + ## computation of the action to performs on `names` content. + def removeforget(abs): if repo.dirstate[abs] == 'a': return _('forgetting %s\n') return _('removing %s\n') - revert = ([], _('reverting %s\n')) - add = ([], _('adding %s\n')) - remove = ([], removeforget) - undelete = ([], _('undeleting %s\n')) + # action to be actually performed by revert + # (<list of file>, message>) tuple + actions = {'revert': ([], _('reverting %s\n')), + 'add': ([], _('adding %s\n')), + 'remove': ([], removeforget), + 'undelete': ([], _('undeleting %s\n'))} disptable = ( # dispatch table: @@ -2288,14 +2333,20 @@ # action if not in target manifest # make backup if in target manifest # make backup if not in target manifest - (modified, revert, remove, True, True), - (added, revert, remove, True, False), - (removed, undelete, None, True, False), - (deleted, revert, remove, False, False), + (modified, (actions['revert'], True), + (actions['remove'], True)), + (added, (actions['revert'], True), + (actions['remove'], False)), + (removed, (actions['undelete'], True), + (None, False)), + (deleted, (actions['revert'], False), + (actions['remove'], False)), ) for abs, (rel, exact) in sorted(names.items()): + # hash on file in target manifest (or None if missing from target) mfentry = mf.get(abs) + # target file to be touch on disk (relative to cwd) target = repo.wjoin(abs) def handle(xlist, dobackup): xlist[0].append(abs) @@ -2312,27 +2363,35 @@ if not isinstance(msg, basestring): msg = msg(abs) ui.status(msg % rel) - for table, hitlist, misslist, backuphit, backupmiss in disptable: + # search the entry in the dispatch table. + # if the file is in any of this sets, it was touched in the working + # directory parent and we are sure it needs to be reverted. + for table, hit, miss in disptable: if abs not in table: continue # file has changed in dirstate if mfentry: - handle(hitlist, backuphit) - elif misslist is not None: - handle(misslist, backupmiss) + handle(*hit) + elif miss[0] is not None: + handle(*miss) break else: + # Not touched in current dirstate. + + # file is unknown in parent, restore older version or ignore. if abs not in repo.dirstate: if mfentry: - handle(add, True) + handle(actions['add'], True) elif exact: ui.warn(_('file not managed: %s\n') % rel) continue - # file has not changed in dirstate + + # parent is target, no changes mean no changes if node == parent: if exact: ui.warn(_('no changes needed to %s\n') % rel) continue + # no change in dirstate but parent and target may differ if pmf is None: # only need parent manifest in this unlikely case, # so do not read by default @@ -2342,11 +2401,12 @@ # manifests, do nothing if (pmf[abs] != mfentry or pmf.flags(abs) != mf.flags(abs)): - handle(revert, False) + handle(actions['revert'], False) else: - handle(remove, False) + handle(actions['remove'], False) + if not opts.get('dry_run'): - _performrevert(repo, parents, ctx, revert, add, remove, undelete) + _performrevert(repo, parents, ctx, actions) if targetsubs: # Revert the subrepos on the revert list @@ -2355,8 +2415,8 @@ finally: wlock.release() -def _performrevert(repo, parents, ctx, revert, add, remove, undelete): - """function that actually perform all the action computed for revert +def _performrevert(repo, parents, ctx, actions): + """function that actually perform all the actions computed for revert This is an independent function to let extension to plug in and react to the imminent revert. @@ -2370,7 +2430,7 @@ repo.wwrite(f, fc.data(), fc.flags()) audit_path = pathutil.pathauditor(repo.root) - for f in remove[0]: + for f in actions['remove'][0]: if repo.dirstate[f] == 'a': repo.dirstate.drop(f) continue @@ -2390,25 +2450,25 @@ normal = repo.dirstate.normallookup else: normal = repo.dirstate.normal - for f in revert[0]: + for f in actions['revert'][0]: checkout(f) if normal: normal(f) - for f in add[0]: + for f in actions['add'][0]: checkout(f) repo.dirstate.add(f) normal = repo.dirstate.normallookup if node == parent and p2 == nullid: normal = repo.dirstate.normal - for f in undelete[0]: + for f in actions['undelete'][0]: checkout(f) normal(f) copied = copies.pathcopies(repo[parent], ctx) - for f in add[0] + undelete[0] + revert[0]: + for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]: if f in copied: repo.dirstate.copy(copied[f], f)
--- a/mercurial/commands.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/commands.py Fri Jun 13 17:42:04 2014 -0500 @@ -386,6 +386,7 @@ ('', '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, **opts): @@ -487,13 +488,12 @@ cmdutil.revert(ui, repo, rctx, repo.dirstate.parents()) - e = cmdutil.commiteditor - if not opts['message'] and not opts['logfile']: - # we don't translate commit messages - opts['message'] = "Backed out changeset %s" % short(node) - e = cmdutil.commitforceeditor - def commitfunc(ui, repo, message, match, opts): + e = cmdutil.getcommiteditor(**opts) + if not message: + # we don't translate commit messages + message = "Backed out changeset %s" % short(node) + e = cmdutil.getcommiteditor(edit=True) return repo.commit(message, opts.get('user'), opts.get('date'), match, editor=e) newnode = cmdutil.commit(ui, repo, commitfunc, [], opts) @@ -1347,8 +1347,6 @@ Returns 0 on success, 1 if nothing changed. """ - forceeditor = opts.get('edit') - if opts.get('subrepos'): if opts.get('amend'): raise util.Abort(_('cannot amend with --subrepos')) @@ -1410,10 +1408,6 @@ bookmarks.setcurrent(repo, bm) newmarks.write() else: - e = cmdutil.commiteditor - if forceeditor: - e = cmdutil.commitforceeditor - def commitfunc(ui, repo, message, match, opts): try: if opts.get('secret'): @@ -1423,7 +1417,9 @@ 'commit') return repo.commit(message, opts.get('user'), opts.get('date'), - match, editor=e, extra=extra) + match, + editor=cmdutil.getcommiteditor(**opts), + extra=extra) finally: ui.setconfig('phases', 'new-commit', oldcommitphase, 'commit') repo.baseui.setconfig('phases', 'new-commit', oldcommitphase, @@ -1686,17 +1682,17 @@ ml[id * linesperrev] += " r%i" % id mergedtext = "\n".join(ml) files.append(fn) - fctxs[fn] = context.memfilectx(fn, mergedtext) + fctxs[fn] = context.memfilectx(repo, fn, mergedtext) if overwritten_file: fn = "of" files.append(fn) - fctxs[fn] = context.memfilectx(fn, "r%i\n" % id) + fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id) if new_file: fn = "nf%i" % id files.append(fn) - fctxs[fn] = context.memfilectx(fn, "r%i\n" % id) + fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id) if len(ps) > 1: if not p2: p2 = repo[ps[1]] @@ -3077,9 +3073,7 @@ if not opts.get('date') and opts.get('currentdate'): opts['date'] = "%d %d" % util.makedate() - editor = None - if opts.get('edit'): - editor = cmdutil.commitforceeditor + editor = cmdutil.getcommiteditor(**opts) cont = False if opts['continue']: @@ -3186,7 +3180,8 @@ repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'graft') stats = mergemod.update(repo, ctx.node(), True, True, False, - ctx.p1().node()) + ctx.p1().node(), + labels=['local', 'graft']) finally: repo.ui.setconfig('ui', 'forcemerge', '', 'graft') # report any conflicts @@ -3692,6 +3687,8 @@ _("don't commit, just update the working directory")), ('', 'bypass', None, _("apply patch without touching the working directory")), + ('', 'partial', None, + _('commit even if some hunks fail')), ('', 'exact', None, _('apply patch to the nodes from which it was generated')), ('', 'import-branch', None, @@ -3733,6 +3730,16 @@ 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. + 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. @@ -3758,7 +3765,7 @@ hg import --exact proposed-fix.patch - Returns 0 on success. + Returns 0 on success, 1 on partial success (see --partial). """ if not patch1: @@ -3790,6 +3797,7 @@ base = opts["base"] wlock = lock = tr = None msgs = [] + ret = 0 try: @@ -3811,8 +3819,9 @@ haspatch = False for hunk in patch.split(patchfile): - (msg, node) = cmdutil.tryimportone(ui, repo, hunk, parents, - opts, msgs, hg.clean) + (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk, + parents, opts, + msgs, hg.clean) if msg: haspatch = True ui.note(msg + '\n') @@ -3820,6 +3829,12 @@ 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 util.Abort(_('%s: no diffs found') % patchurl) @@ -3828,6 +3843,7 @@ tr.close() if msgs: repo.savecommitmessage('\n* * *\n'.join(msgs)) + return ret except: # re-raises # wlock.release() indirectly calls dirstate.write(): since # we're crashing, we do not want to change the working dir @@ -4930,46 +4946,66 @@ wlock = repo.wlock() try: ms = mergemod.mergestate(repo) + + if not ms.active() and not show: + raise util.Abort( + _('resolve command not applicable when not merging')) + m = scmutil.match(repo[None], pats, opts) ret = 0 + didwork = False for f in ms: - if m(f): - if show: - if nostatus: - ui.write("%s\n" % f) - else: - ui.write("%s %s\n" % (ms[f].upper(), f), - label='resolve.' + - {'u': 'unresolved', 'r': 'resolved'}[ms[f]]) - elif mark: - ms.mark(f, "r") - elif unmark: - ms.mark(f, "u") + if not m(f): + continue + + didwork = True + + if show: + if nostatus: + ui.write("%s\n" % f) else: - wctx = repo[None] - - # backup pre-resolve (merge uses .orig for its own purposes) - a = repo.wjoin(f) - util.copyfile(a, a + ".resolve") - - try: - # resolve file - ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), - 'resolve') - if ms.resolve(f, wctx): - ret = 1 - finally: - ui.setconfig('ui', 'forcemerge', '', 'resolve') - ms.commit() - - # replace filemerge's .orig file with our resolve file - util.rename(a + ".resolve", a + ".orig") + ui.write("%s %s\n" % (ms[f].upper(), f), + label='resolve.' + + {'u': 'unresolved', 'r': 'resolved'}[ms[f]]) + elif mark: + ms.mark(f, "r") + elif unmark: + ms.mark(f, "u") + else: + wctx = repo[None] + + # backup pre-resolve (merge uses .orig for its own purposes) + a = repo.wjoin(f) + util.copyfile(a, a + ".resolve") + + try: + # resolve file + ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), + 'resolve') + if ms.resolve(f, wctx): + ret = 1 + finally: + ui.setconfig('ui', 'forcemerge', '', 'resolve') + ms.commit() + + # replace filemerge's .orig file with our resolve file + util.rename(a + ".resolve", a + ".orig") ms.commit() + + if not didwork and pats: + ui.warn(_("arguments do not match paths that need resolving\n")) + finally: wlock.release() + # Nudge users into finishing an unfinished operation. We don't print + # this with the list/show operation because we want list/show to remain + # machine readable. + if not list(ms.unresolved()) and not show: + ui.status(_('no more unresolved files\n')) + return ret @command('revert', @@ -5689,16 +5725,15 @@ if date: date = util.parsedate(date) - if opts.get('edit'): - message = ui.edit(message, ui.username()) - repo.savecommitmessage(message) + editor = cmdutil.getcommiteditor(**opts) # don't allow tagging the null rev if (not opts.get('remove') and scmutil.revsingle(repo, rev_).rev() == nullrev): raise util.Abort(_("cannot tag null revision")) - repo.tag(names, r, message, opts.get('local'), opts.get('user'), date) + repo.tag(names, r, message, opts.get('local'), opts.get('user'), date, + editor=editor) finally: release(lock, wlock) @@ -5791,9 +5826,11 @@ ('c', 'check', None, _('update across branches if no uncommitted changes')), ('d', 'date', '', _('tipmost revision matching date'), _('DATE')), - ('r', 'rev', '', _('revision'), _('REV'))], + ('r', 'rev', '', _('revision'), _('REV')) + ] + mergetoolopts, _('[-c] [-C] [-d DATE] [[-r] REV]')) -def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False): +def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False, + tool=None): """update working directory (or switch revisions) Update the repository's working directory to the specified @@ -5874,6 +5911,8 @@ rev = repo[repo[None].branch()].rev() mergemod._checkunknown(repo, repo[None], repo[rev]) + repo.ui.setconfig('ui', 'forcemerge', tool, 'update') + if clean: ret = hg.clean(repo, rev) else: @@ -5884,7 +5923,11 @@ ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent) elif brev in repo._bookmarks: bookmarks.setcurrent(repo, brev) + ui.status(_("(activating bookmark %s)\n") % brev) elif brev: + if repo._bookmarkcurrent: + ui.status(_("(leaving bookmark %s)\n") % + repo._bookmarkcurrent) bookmarks.unsetcurrent(repo) return ret
--- a/mercurial/context.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/context.py Fri Jun 13 17:42:04 2014 -0500 @@ -63,10 +63,81 @@ for f in sorted(self._manifest): yield f + def _manifestmatches(self, match, s): + """generate a new manifest filtered by the match argument + + This method is for internal use only and mainly exists to provide an + object oriented way for other contexts to customize the manifest + generation. + """ + mf = self.manifest().copy() + if match.always(): + return mf + for fn in mf.keys(): + if not match(fn): + del mf[fn] + return mf + + def _matchstatus(self, other, s, match, listignored, listclean, + listunknown): + """return match.always if match is none + + This internal method provides a way for child objects to override the + match operator. + """ + return match or matchmod.always(self._repo.root, self._repo.getcwd()) + + def _prestatus(self, other, s, match, listignored, listclean, listunknown): + """provide a hook to allow child objects to preprocess status results + + For example, this allows other contexts, such as workingctx, to query + the dirstate before comparing the manifests. + """ + # load earliest manifest first for caching reasons + if self.rev() < other.rev(): + self.manifest() + return s + + def _poststatus(self, other, s, match, listignored, listclean, listunknown): + """provide a hook to allow child objects to postprocess status results + + For example, this allows other contexts, such as workingctx, to filter + suspect symlinks in the case of FAT32 and NTFS filesytems. + """ + return s + + def _buildstatus(self, other, s, match, listignored, listclean, + listunknown): + """build a status with respect to another context""" + mf1 = other._manifestmatches(match, s) + mf2 = self._manifestmatches(match, s) + + modified, added, clean = [], [], [] + deleted, unknown, ignored = s[3], [], [] + withflags = mf1.withflags() | mf2.withflags() + for fn, mf2node in mf2.iteritems(): + if fn in mf1: + if (fn not in deleted and + ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or + (mf1[fn] != mf2node and + (mf2node or self[fn].cmp(other[fn]))))): + modified.append(fn) + elif listclean: + clean.append(fn) + del mf1[fn] + elif fn not in deleted: + added.append(fn) + removed = mf1.keys() + + return [modified, added, removed, deleted, unknown, ignored, clean] + @propertycache def substate(self): return subrepo.state(self, self._repo.ui) + def subrev(self, subpath): + return self.substate[subpath][1] + def rev(self): return self._rev def node(self): @@ -198,19 +269,81 @@ def dirty(self): return False + def status(self, other=None, match=None, listignored=False, + listclean=False, listunknown=False, listsubrepos=False): + """return status of files between two nodes or node and working + directory. + + If other is None, compare this node with working directory. + + returns (modified, added, removed, deleted, unknown, ignored, clean) + """ + + ctx1 = self + ctx2 = self._repo[other] + + # This next code block is, admittedly, fragile logic that tests for + # reversing the contexts and wouldn't need to exist if it weren't for + # the fast (and common) code path of comparing the working directory + # with its first parent. + # + # What we're aiming for here is the ability to call: + # + # workingctx.status(parentctx) + # + # If we always built the manifest for each context and compared those, + # then we'd be done. But the special case of the above call means we + # just copy the manifest of the parent. + reversed = False + if (not isinstance(ctx1, changectx) + and isinstance(ctx2, changectx)): + reversed = True + ctx1, ctx2 = ctx2, ctx1 + + r = [[], [], [], [], [], [], []] + match = ctx2._matchstatus(ctx1, r, match, listignored, listclean, + listunknown) + r = ctx2._prestatus(ctx1, r, match, listignored, listclean, listunknown) + r = ctx2._buildstatus(ctx1, r, match, listignored, listclean, + listunknown) + r = ctx2._poststatus(ctx1, r, match, listignored, listclean, + listunknown) + + if reversed: + r[1], r[2], r[3], r[4] = r[2], r[1], r[4], r[3] + + 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) + + for l in r: + l.sort() + + # we return a tuple to signify that this list isn't changing + return tuple(r) + + def makememctx(repo, parents, text, user, date, branch, files, store, editor=None): def getfilectx(repo, memctx, path): data, (islink, isexec), copied = store.getfile(path) - return memfilectx(path, data, islink=islink, isexec=isexec, - copied=copied) + return memfilectx(repo, path, data, islink=islink, isexec=isexec, + copied=copied, memctx=memctx) extra = {} if branch: extra['branch'] = encoding.fromlocal(branch) ctx = memctx(repo, parents, text, files, getfilectx, user, - date, extra) - if editor: - ctx._text = editor(repo, ctx, []) + date, extra, editor) return ctx class changectx(basectx): @@ -823,14 +956,7 @@ if user: self._user = user if changes: - self._status = list(changes[:4]) - self._unknown = changes[4] - self._ignored = changes[5] - self._clean = changes[6] - else: - self._unknown = None - self._ignored = None - self._clean = None + self._status = changes self._extra = {} if extra: @@ -891,7 +1017,7 @@ @propertycache def _manifest(self): - """generate a manifest corresponding to the working directory""" + """generate a manifest corresponding to the values in self._status""" man = self._parents[0].manifest().copy() if len(self._parents) > 1: @@ -905,7 +1031,7 @@ copied = self._repo.dirstate.copies() ff = self._flagfunc - modified, added, removed, deleted = self._status + modified, added, removed, deleted = self._status[:4] for i, l in (("a", added), ("m", modified)): for f in l: orig = copied.get(f, f) @@ -923,7 +1049,7 @@ @propertycache def _status(self): - return self._repo.status()[:4] + return self._repo.status() @propertycache def _user(self): @@ -933,21 +1059,8 @@ def _date(self): return util.makedate() - def status(self, ignored=False, clean=False, unknown=False): - """Explicit status query - Unless this method is used to query the working copy status, the - _status property will implicitly read the status using its default - arguments.""" - stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown) - self._unknown = self._ignored = self._clean = None - if unknown: - self._unknown = stat[4] - if ignored: - self._ignored = stat[5] - if clean: - self._clean = stat[6] - self._status = stat[:4] - return stat + def subrev(self, subpath): + return None def user(self): return self._user or self._repo.ui.username() @@ -967,14 +1080,11 @@ def deleted(self): return self._status[3] def unknown(self): - assert self._unknown is not None # must call status first - return self._unknown + return self._status[4] def ignored(self): - assert self._ignored is not None # must call status first - return self._ignored + return self._status[5] def clean(self): - assert self._clean is not None # must call status first - return self._clean + return self._status[6] def branch(self): return encoding.tolocal(self._extra['branch']) def closesbranch(self): @@ -1180,6 +1290,170 @@ finally: wlock.release() + def _filtersuspectsymlink(self, files): + if not files or self._repo.dirstate._checklink: + return files + + # Symlink placeholders may get non-symlink-like contents + # via user error or dereferencing by NFS or Samba servers, + # so we filter out any placeholders that don't look like a + # symlink + sane = [] + for f in files: + if self.flags(f) == 'l': + d = self[f].data() + if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d): + self._repo.ui.debug('ignoring suspect symlink placeholder' + ' "%s"\n' % f) + continue + sane.append(f) + return sane + + def _checklookup(self, files): + # check for any possibly clean files + if not files: + return [], [] + + modified = [] + fixup = [] + pctx = self._parents[0] + # do a full compare of any files that might have changed + for f in sorted(files): + if (f not in pctx or self.flags(f) != pctx.flags(f) + or pctx[f].cmp(self[f])): + modified.append(f) + else: + fixup.append(f) + + # update dirstate for files that are actually clean + if fixup: + try: + # updating the dirstate is optional + # so we don't wait on the lock + normal = self._repo.dirstate.normal + wlock = self._repo.wlock(False) + try: + for f in fixup: + normal(f) + finally: + wlock.release() + except error.LockError: + pass + return modified, fixup + + def _manifestmatches(self, match, s): + """Slow path for workingctx + + The fast path is when we compare the working directory to its parent + which means this function is comparing with a non-parent; therefore we + need to build a manifest and return what matches. + """ + mf = self._repo['.']._manifestmatches(match, s) + modified, added, removed = s[0:3] + for f in modified + added: + mf[f] = None + mf.set(f, self.flags(f)) + for f in removed: + if f in mf: + del mf[f] + return mf + + def _prestatus(self, other, s, match, listignored, listclean, listunknown): + """override the parent hook with a dirstate query + + We use this prestatus hook to populate the status with information from + the dirstate. + """ + # doesn't need to call super; if that changes, be aware that super + # calls self.manifest which would slow down the common case of calling + # status against a workingctx's parent + return self._dirstatestatus(match, listignored, listclean, listunknown) + + def _poststatus(self, other, s, match, listignored, listclean, listunknown): + """override the parent hook with a filter for suspect symlinks + + We use this poststatus hook to filter out symlinks that might have + accidentally ended up with the entire contents of the file they are + susposed to be linking to. + """ + s[0] = self._filtersuspectsymlink(s[0]) + self._status = s[:] + return s + + def _dirstatestatus(self, match=None, ignored=False, clean=False, + unknown=False): + '''Gets the status from the dirstate -- internal use only.''' + listignored, listclean, listunknown = ignored, clean, unknown + match = match or matchmod.always(self._repo.root, self._repo.getcwd()) + subrepos = [] + if '.hgsub' in self: + subrepos = sorted(self.substate) + s = self._repo.dirstate.status(match, subrepos, listignored, + listclean, listunknown) + cmp, modified, added, removed, deleted, unknown, ignored, clean = s + + # check for any possibly clean files + if cmp: + modified2, fixup = self._checklookup(cmp) + modified += modified2 + + # update dirstate for files that are actually clean + if fixup and listclean: + clean += fixup + + return [modified, added, removed, deleted, unknown, ignored, clean] + + def _buildstatus(self, other, s, match, listignored, listclean, + listunknown): + """build a status with respect to another context + + This includes logic for maintaining the fast path of status when + comparing the working directory against its parent, which is to skip + building a new manifest if self (working directory) is not comparing + against its parent (repo['.']). + """ + if other != self._repo['.']: + s = super(workingctx, self)._buildstatus(other, s, match, + listignored, listclean, + listunknown) + return s + + def _matchstatus(self, other, s, match, listignored, listclean, + listunknown): + """override the match method with a filter for directory patterns + + We use inheritance to customize the match.bad method only in cases of + workingctx since it belongs only to the working directory when + comparing against the parent changeset. + + If we aren't comparing against the working directory's parent, then we + just use the default match object sent to us. + """ + superself = super(workingctx, self) + match = superself._matchstatus(other, s, match, listignored, listclean, + listunknown) + if other != self._repo['.']: + def bad(f, msg): + # 'f' may be a directory pattern from 'match.files()', + # so 'f not in ctx1' is not enough + if f not in other and f not in other.dirs(): + self._repo.ui.warn('%s: %s\n' % + (self._repo.dirstate.pathto(f), msg)) + match.bad = bad + return match + + def status(self, other='.', match=None, listignored=False, + listclean=False, listunknown=False, listsubrepos=False): + # yet to be determined: what to do if 'other' is a 'workingctx' or a + # 'memctx'? + s = super(workingctx, self).status(other, match, listignored, listclean, + listunknown, listsubrepos) + # calling 'super' subtly reveresed the contexts, so we flip the results + # (s[1] is 'added' and s[2] is 'removed') + s = list(s) + s[1], s[2] = s[2], s[1] + return tuple(s) + class committablefilectx(basefilectx): """A committablefilectx provides common functionality for a file context that wants the ability to commit, e.g. workingfilectx or memfilectx.""" @@ -1259,7 +1533,7 @@ # invert comparison to reuse the same code path return fctx.cmp(self) -class memctx(object): +class memctx(committablectx): """Use memctx to perform in-memory commits via localrepo.commitctx(). Revision information is supplied at initialization time while @@ -1287,13 +1561,10 @@ is a dictionary of metadata or is left empty. """ def __init__(self, repo, parents, text, files, filectxfn, user=None, - date=None, extra=None): - self._repo = repo + date=None, extra=None, editor=False): + super(memctx, self).__init__(repo, text, user, date, extra) self._rev = None self._node = None - self._text = text - self._date = date and util.parsedate(date) or util.makedate() - self._user = user parents = [(p or nullid) for p in parents] p1, p2 = parents self._parents = [changectx(self._repo, p) for p in (p1, p2)] @@ -1305,55 +1576,9 @@ if self._extra.get('branch', '') == '': self._extra['branch'] = 'default' - def __str__(self): - return str(self._parents[0]) + "+" - - def __int__(self): - return self._rev - - def __nonzero__(self): - return True - - def __getitem__(self, key): - return self.filectx(key) - - def p1(self): - return self._parents[0] - def p2(self): - return self._parents[1] - - def user(self): - return self._user or self._repo.ui.username() - def date(self): - return self._date - def description(self): - return self._text - def files(self): - return self.modified() - def modified(self): - return self._status[0] - def added(self): - return self._status[1] - def removed(self): - return self._status[2] - def deleted(self): - return self._status[3] - def unknown(self): - return self._status[4] - def ignored(self): - return self._status[5] - def clean(self): - return self._status[6] - def branch(self): - return encoding.tolocal(self._extra['branch']) - def extra(self): - return self._extra - def flags(self, f): - return self[f].flags() - - def parents(self): - """return contexts for each parent changeset""" - return self._parents + if editor: + self._text = editor(self._repo, self, []) + self._repo.savecommitmessage(self._text) def filectx(self, path, filelog=None): """get a file context from the working directory""" @@ -1363,12 +1588,13 @@ """commit context to the repo""" return self._repo.commitctx(self) -class memfilectx(object): +class memfilectx(committablefilectx): """memfilectx represents an in-memory file to commit. - See memctx for more details. + See memctx and commitablefilectx for more details. """ - def __init__(self, path, data, islink=False, isexec=False, copied=None): + def __init__(self, repo, path, data, islink=False, + isexec=False, copied=None, memctx=None): """ path is the normalized file path relative to repository root. data is the file content as a string. @@ -1376,21 +1602,17 @@ isexec is True if the file is executable. copied is the source file path if current file was copied in the revision being committed, or None.""" - self._path = path + super(memfilectx, self).__init__(repo, path, None, memctx) self._data = data self._flags = (islink and 'l' or '') + (isexec and 'x' or '') self._copied = None if copied: self._copied = (copied, nullid) - def __nonzero__(self): - return True - def __str__(self): - return "%s@%s" % (self.path(), self._changectx) - def path(self): - return self._path def data(self): return self._data + def size(self): + return len(self.data()) def flags(self): return self._flags def isexec(self):
--- a/mercurial/demandimport.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/demandimport.py Fri Jun 13 17:42:04 2014 -0500 @@ -24,13 +24,17 @@ b = __import__(a) ''' -import __builtin__, os +import __builtin__, os, sys _origimport = __import__ nothing = object() try: - _origimport(__builtin__.__name__, {}, {}, None, -1) + # Python 3 doesn't have relative imports nor level -1. + level = -1 + if sys.version_info[0] >= 3: + level = 0 + _origimport(__builtin__.__name__, {}, {}, None, level) except TypeError: # no level argument def _import(name, globals, locals, fromlist, level): "call _origimport with no level argument" @@ -55,7 +59,7 @@ class _demandmod(object): """module demand-loader and proxy""" - def __init__(self, name, globals, locals, level=-1): + def __init__(self, name, globals, locals, level=level): if '.' in name: head, rest = name.split('.', 1) after = [rest] @@ -105,7 +109,7 @@ self._load() setattr(self._module, attr, val) -def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1): +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 return _hgextimport(_import, name, globals, locals, fromlist, level)
--- a/mercurial/discovery.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/discovery.py Fri Jun 13 17:42:04 2014 -0500 @@ -341,6 +341,10 @@ if branch not in ('default', None): error = _("push creates new remote head %s " "on branch '%s'!") % (short(dhs[0]), branch) + elif repo[dhs[0]].bookmarks(): + error = _("push creates new remote head %s " + "with bookmark '%s'!") % ( + short(dhs[0]), repo[dhs[0]].bookmarks()[0]) else: error = _("push creates new remote head %s!" ) % short(dhs[0])
--- a/mercurial/error.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/error.py Fri Jun 13 17:42:04 2014 -0500 @@ -98,3 +98,22 @@ class PushRaced(RuntimeError): """An exception raised during unbundling that indicate a push race""" +# bundle2 related errors +class BundleValueError(ValueError): + """error raised when bundle2 cannot be processed""" + + def __init__(self, parttype=None, params=()): + self.parttype = parttype + self.params = params + if self.parttype is None: + msg = 'Stream Parameter' + else: + msg = parttype + if self.params: + msg = '%s - %s' % (msg, ', '.join(self.params)) + ValueError.__init__(self, msg) + +class ReadOnlyPartError(RuntimeError): + """error raised when code tries to alter a part being generated""" + pass +
--- a/mercurial/exchange.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/exchange.py Fri Jun 13 17:42:04 2014 -0500 @@ -9,7 +9,7 @@ from node import hex, nullid import errno, urllib import util, scmutil, changegroup, base85, error -import discovery, phases, obsolete, bookmarks, bundle2 +import discovery, phases, obsolete, bookmarks, bundle2, pushkey def readbundle(ui, fh, fname, vfs=None): header = changegroup.readexactly(fh, 4) @@ -208,30 +208,25 @@ The only currently supported type of data is changegroup but this will evolve in the future.""" - # Send known head to the server for race detection. - capsblob = urllib.unquote(pushop.remote.capable('bundle2-exp')) - caps = bundle2.decodecaps(capsblob) - bundler = bundle2.bundle20(pushop.ui, caps) + bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote)) # create reply capability capsblob = bundle2.encodecaps(pushop.repo.bundle2caps) - bundler.addpart(bundle2.bundlepart('b2x:replycaps', data=capsblob)) + bundler.newpart('b2x:replycaps', data=capsblob) + # Send known heads to the server for race detection. if not pushop.force: - part = bundle2.bundlepart('B2X:CHECK:HEADS', - data=iter(pushop.remoteheads)) - bundler.addpart(part) + bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads)) extrainfo = _pushbundle2extraparts(pushop, bundler) # add the changegroup bundle cg = changegroup.getlocalbundle(pushop.repo, 'push', pushop.outgoing) - cgpart = bundle2.bundlepart('B2X:CHANGEGROUP', data=cg.getchunks()) - bundler.addpart(cgpart) + cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg.getchunks()) stream = util.chunkbuffer(bundler.getchunks()) try: reply = pushop.remote.unbundle(stream, ['force'], 'push') - except bundle2.UnknownPartError, exc: + except error.BundleValueError, exc: raise util.Abort('missing support for %s' % exc) try: op = bundle2.processbundle(pushop.repo, reply) - except bundle2.UnknownPartError, exc: + except error.BundleValueError, exc: raise util.Abort('missing support for %s' % exc) cgreplies = op.records.getreplies(cgpart.id) assert len(cgreplies['changegroup']) == 1 @@ -330,37 +325,6 @@ """synchronise phase information locally and remotely""" unfi = pushop.repo.unfiltered() cheads = pushop.commonheads - if pushop.ret: - # push succeed, synchronize target of the push - cheads = pushop.outgoing.missingheads - elif pushop.revs is None: - # All out push fails. synchronize all common - cheads = pushop.outgoing.commonheads - else: - # I want cheads = heads(::missingheads and ::commonheads) - # (missingheads is revs with secret changeset filtered out) - # - # This can be expressed as: - # cheads = ( (missingheads and ::commonheads) - # + (commonheads and ::missingheads))" - # ) - # - # while trying to push we already computed the following: - # common = (::commonheads) - # missing = ((commonheads::missingheads) - commonheads) - # - # We can pick: - # * missingheads part of common (::commonheads) - common = set(pushop.outgoing.common) - nm = pushop.repo.changelog.nodemap - cheads = [node for node in pushop.revs if nm[node] in common] - # and - # * commonheads parents on missing - revset = unfi.set('%ln and parents(roots(%ln))', - pushop.outgoing.commonheads, - pushop.outgoing.missing) - cheads.extend(c.node() for c in revset) - pushop.commonheads = cheads # even when we don't push, exchanging phase data is useful remotephases = pushop.remote.listkeys('phases') if (pushop.ui.configbool('ui', '_usedassubrepo', False) @@ -395,16 +359,54 @@ # Get the list of all revs draft on remote by public here. # XXX Beware that revset break if droots is not strictly # XXX root we may want to ensure it is but it is costly - outdated = unfi.set('heads((%ln::%ln) and public())', - droots, cheads) - for newremotehead in outdated: - r = pushop.remote.pushkey('phases', - newremotehead.hex(), - str(phases.draft), - str(phases.public)) - if not r: - pushop.ui.warn(_('updating %s to public failed!\n') - % newremotehead) + outdated = unfi.set('heads((%ln::%ln) and public())', + droots, cheads) + + b2caps = bundle2.bundle2caps(pushop.remote) + if 'b2x:pushkey' in b2caps: + # server supports bundle2, let's do a batched push through it + # + # This will eventually be unified with the changesets bundle2 push + bundler = bundle2.bundle20(pushop.ui, b2caps) + capsblob = bundle2.encodecaps(pushop.repo.bundle2caps) + bundler.newpart('b2x:replycaps', data=capsblob) + part2node = [] + enc = pushkey.encode + for newremotehead in outdated: + part = bundler.newpart('b2x:pushkey') + part.addparam('namespace', enc('phases')) + part.addparam('key', enc(newremotehead.hex())) + part.addparam('old', enc(str(phases.draft))) + part.addparam('new', enc(str(phases.public))) + part2node.append((part.id, newremotehead)) + stream = util.chunkbuffer(bundler.getchunks()) + try: + reply = pushop.remote.unbundle(stream, ['force'], 'push') + op = bundle2.processbundle(pushop.repo, reply) + except error.BundleValueError, exc: + raise util.Abort('missing support for %s' % exc) + for partid, node in part2node: + partrep = op.records.getreplies(partid) + results = partrep['pushkey'] + assert len(results) <= 1 + msg = None + if not results: + msg = _('server ignored update of %s to public!\n') % node + elif not int(results[0]['return']): + msg = _('updating %s to public failed!\n') % node + if msg is not None: + pushop.ui.warn(msg) + + else: + # fallback to independant pushkey command + for newremotehead in outdated: + r = pushop.remote.pushkey('phases', + newremotehead.hex(), + str(phases.draft), + str(phases.public)) + if not r: + pushop.ui.warn(_('updating %s to public failed!\n') + % newremotehead) def _localphasemove(pushop, nodes, phase=phases.public): """move <nodes> to <phase> in the local source repo""" @@ -568,14 +570,15 @@ """pull data using bundle2 For now, the only supported data are changegroup.""" - kwargs = {'bundlecaps': set(['HG2X'])} - capsblob = bundle2.encodecaps(pullop.repo.bundle2caps) - kwargs['bundlecaps'].add('bundle2=' + urllib.quote(capsblob)) + remotecaps = bundle2.bundle2caps(pullop.remote) + kwargs = {'bundlecaps': caps20to10(pullop.repo)} # pulling changegroup pullop.todosteps.remove('changegroup') kwargs['common'] = pullop.common kwargs['heads'] = pullop.heads or pullop.rheads + if 'b2x:listkeys' in remotecaps: + kwargs['listkeys'] = ['phase'] if not pullop.fetch: pullop.repo.ui.status(_("no changes found\n")) pullop.cgresult = 0 @@ -588,13 +591,18 @@ bundle = pullop.remote.getbundle('pull', **kwargs) try: op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction) - except bundle2.UnknownPartError, exc: + except error.BundleValueError, exc: raise util.Abort('missing support for %s' % exc) if pullop.fetch: assert len(op.records['changegroup']) == 1 pullop.cgresult = op.records['changegroup'][0]['return'] + # processing phases change + for namespace, value in op.records['listkeys']: + if namespace == 'phases': + _pullapplyphases(pullop, value) + def _pullbundle2extraprepare(pullop, kwargs): """hook function so that extensions can extend the getbundle call""" pass @@ -624,8 +632,8 @@ cg = pullop.remote.changegroup(pullop.fetch, 'pull') elif not pullop.remote.capable('changegroupsubset'): raise util.Abort(_("partial pull cannot be done because " - "other repository doesn't support " - "changegroupsubset.")) + "other repository doesn't support " + "changegroupsubset.")) else: cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull') pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull', @@ -633,8 +641,12 @@ def _pullphase(pullop): # Get remote phases data from remote + remotephases = pullop.remote.listkeys('phases') + _pullapplyphases(pullop, remotephases) + +def _pullapplyphases(pullop, remotephases): + """apply phase movement from observed remote state""" pullop.todosteps.remove('phases') - remotephases = pullop.remote.listkeys('phases') publishing = bool(remotephases.get('publishing', False)) if remotephases and not publishing: # remote is new and unpublishing @@ -672,6 +684,13 @@ pullop.repo.invalidatevolatilesets() return tr +def caps20to10(repo): + """return a set with appropriate options to use bundle20 during getbundle""" + caps = set(['HG2X']) + capsblob = bundle2.encodecaps(repo.bundle2caps) + caps.add('bundle2=' + urllib.quote(capsblob)) + return caps + def getbundle(repo, source, heads=None, common=None, bundlecaps=None, **kwargs): """return a full bundle (with potentially multiple kind of parts) @@ -691,6 +710,9 @@ cg = changegroup.getbundle(repo, source, heads=heads, common=common, bundlecaps=bundlecaps) if bundlecaps is None or 'HG2X' not in bundlecaps: + if kwargs: + raise ValueError(_('unsupported getbundle arguments: %s') + % ', '.join(sorted(kwargs.keys()))) return cg # very crude first implementation, # the bundle API will change and the generation will be done lazily. @@ -701,8 +723,13 @@ b2caps.update(bundle2.decodecaps(blob)) bundler = bundle2.bundle20(repo.ui, b2caps) if cg: - part = bundle2.bundlepart('b2x:changegroup', data=cg.getchunks()) - bundler.addpart(part) + bundler.newpart('b2x:changegroup', data=cg.getchunks()) + listkeys = kwargs.get('listkeys', ()) + for namespace in listkeys: + part = bundler.newpart('b2x:listkeys') + part.addparam('namespace', namespace) + keys = repo.listkeys(namespace).items() + part.data = pushkey.encodekeys(keys) _getbundleextrapart(bundler, repo, source, heads=heads, common=common, bundlecaps=bundlecaps, **kwargs) return util.chunkbuffer(bundler.getchunks())
--- a/mercurial/filemerge.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/filemerge.py Fri Jun 13 17:42:04 2014 -0500 @@ -7,7 +7,7 @@ from node import short from i18n import _ -import util, simplemerge, match, error +import util, simplemerge, match, error, templater, templatekw import os, tempfile, re, filecmp def _toolstr(ui, tool, part, default=""): @@ -169,7 +169,7 @@ used to resolve these conflicts.""" return 1 -def _premerge(repo, toolconf, files): +def _premerge(repo, toolconf, files, labels=None): tool, toolpath, binary, symlink = toolconf if symlink: return 1 @@ -190,7 +190,7 @@ (tool, premerge, _valid)) if premerge: - r = simplemerge.simplemerge(ui, a, b, c, quiet=True) + r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels) if not r: ui.debug(" premerge successful\n") return 0 @@ -201,7 +201,7 @@ @internaltool('merge', True, _("merging %s incomplete! " "(edit conflicts, then use 'hg resolve --mark')\n")) -def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files): +def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): """ Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in @@ -211,19 +211,18 @@ repo.ui.warn(_('warning: internal:merge cannot merge symlinks ' 'for %s\n') % fcd.path()) return False, 1 - - r = _premerge(repo, toolconf, files) + r = _premerge(repo, toolconf, files, labels=labels) if r: a, b, c, back = files ui = repo.ui - r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other']) + r = simplemerge.simplemerge(ui, a, b, c, label=labels) return True, r return False, 0 @internaltool('dump', True) -def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files): +def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): """ Creates three versions of the files to merge, containing the contents of local, other and base. These files can then be used to @@ -231,7 +230,7 @@ ``a.txt``, these files will accordingly be named ``a.txt.local``, ``a.txt.other`` and ``a.txt.base`` and they will be placed in the same directory as ``a.txt``.""" - r = _premerge(repo, toolconf, files) + r = _premerge(repo, toolconf, files, labels=labels) if r: a, b, c, back = files @@ -242,8 +241,8 @@ repo.wwrite(fd + ".base", fca.data(), fca.flags()) return False, r -def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files): - r = _premerge(repo, toolconf, files) +def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): + r = _premerge(repo, toolconf, files, labels=labels) if r: tool, toolpath, binary, symlink = toolconf a, b, c, back = files @@ -270,7 +269,58 @@ return True, r return False, 0 -def filemerge(repo, mynode, orig, fcd, fco, fca): +def _formatconflictmarker(repo, ctx, template, label, pad): + """Applies the given template to the ctx, prefixed by the label. + + Pad is the minimum width of the label prefix, so that multiple markers + can have aligned templated parts. + """ + if ctx.node() is None: + ctx = ctx.p1() + + props = templatekw.keywords.copy() + props['templ'] = template + props['ctx'] = ctx + props['repo'] = repo + templateresult = template('conflictmarker', **props) + + label = ('%s:' % label).ljust(pad + 1) + mark = '%s %s' % (label, templater.stringify(templateresult)) + + # The <<< marks add 8 to the length, and '...' adds three, so max + # length of the actual marker is 69. + maxlength = 80 - 8 - 3 + if len(mark) > maxlength: + mark = mark[:maxlength] + '...' + return mark + +_defaultconflictmarker = ('{node|short} ' + + '{ifeq(tags, "tip", "", "{tags} ")}' + + '{if(bookmarks, "{bookmarks} ")}' + + '{ifeq(branch, "default", "", "{branch} ")}' + + '- {author|user}: {desc|firstline}') + +_defaultconflictlabels = ['local', 'other'] + +def _formatlabels(repo, fcd, fco, labels): + """Formats the given labels using the conflict marker template. + + Returns a list of formatted labels. + """ + cd = fcd.changectx() + co = fco.changectx() + + ui = repo.ui + template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker) + template = templater.parsestring(template, quoted=False) + tmpl = templater.templater(None, cache={ 'conflictmarker' : template }) + + pad = max(len(labels[0]), len(labels[1])) + + return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad), + _formatconflictmarker(repo, co, tmpl, labels[1], pad)] + +def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None): """perform a 3-way merge in the working directory mynode = parent node before merge @@ -327,8 +377,17 @@ ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) + markerstyle = ui.config('ui', 'mergemarkers', 'detailed') + if markerstyle == 'basic': + formattedlabels = _defaultconflictlabels + else: + if not labels: + labels = _defaultconflictlabels + + formattedlabels = _formatlabels(repo, fcd, fco, labels) + needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf, - (a, b, c, back)) + (a, b, c, back), labels=formattedlabels) if not needcheck: if r: if onfailure:
--- a/mercurial/help.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/help.py Fri Jun 13 17:42:04 2014 -0500 @@ -481,8 +481,11 @@ rst.append('%s:\n\n' % title) rst.extend(minirst.maketable(sorted(matches[t]), 1)) rst.append('\n') + if not rst: + msg = _('no matches') + hint = _('try "hg help" for a list of topics') + raise util.Abort(msg, hint=hint) elif name and name != 'shortlist': - i = None if unknowncmd: queries = (helpextcmd,) elif opts.get('extension'): @@ -494,12 +497,16 @@ for f in queries: try: rst = f(name) - i = None break - except error.UnknownCommand, inst: - i = inst - if i: - raise i + except error.UnknownCommand: + pass + else: + if unknowncmd: + raise error.UnknownCommand(name) + else: + msg = _('no such help topic: %s') % name + hint = _('try "hg help --keyword %s"') % name + raise util.Abort(msg, hint=hint) else: # program name if not ui.quiet:
--- a/mercurial/help/config.txt Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/help/config.txt Fri Jun 13 17:42:04 2014 -0500 @@ -807,7 +807,9 @@ --------------- This section configures external merge tools to use for file-level -merges. +merges. This section has likely been preconfigured at install time. +Use :hg:`config merge-tools` to check the existing configuration. +Also see :hg:`help merge-tools` for more details. Example ``~/.hgrc``:: @@ -819,6 +821,9 @@ # Give higher priority kdiff3.priority = 1 + # Changing the priority of preconfigured tool + vimdiff.priority = 0 + # Define new tool myHtmlTool.args = -m $local $other $base $output myHtmlTool.regkey = Software\FooSoftware\HtmlMerge @@ -838,7 +843,13 @@ ``args`` The arguments to pass to the tool executable. You can refer to the files being merged as well as the output file through these - variables: ``$base``, ``$local``, ``$other``, ``$output``. + variables: ``$base``, ``$local``, ``$other``, ``$output``. The meaning + of ``$local`` and ``$other`` can vary depending on which action is being + performed. During and update or merge, ``$local`` represents the original + state of the file, while ``$other`` represents the commit you are updating + to or the commit you are merging with. During a rebase ``$local`` + represents the destination of the rebase, and ``$other`` represents the + commit being rebased. Default: ``$local $base $other`` ``premerge`` @@ -1203,6 +1214,20 @@ For more information on merge tools see :hg:`help merge-tools`. For configuring merge tools see the ``[merge-tools]`` section. +``mergemarkers`` + Sets the merge conflict marker label styling. The default ``detailed`` + style uses the ``mergemarkertemplate`` setting to style the labels. + The ``basic`` style just uses 'local' and 'other' as the marker label. + One of ``basic`` or ``detailed``. + Default is ``detailed``. + +``mergemarkertemplate`` + The template used to print the commit description next to each conflict + marker during merge conflicts. See :hg:`help templates` for the template + format. + Defaults to showing the hash, tags, branches, bookmarks, author, and + the first line of the commit description. + ``portablefilenames`` Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``. Default is ``warn``.
--- a/mercurial/hg.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/hg.py Fri Jun 13 17:42:04 2014 -0500 @@ -483,7 +483,8 @@ 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, None, + labels=['working copy', 'destination']) def update(repo, node): """update the working directory to node, merging linear changes"""
--- a/mercurial/i18n.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/i18n.py Fri Jun 13 17:42:04 2014 -0500 @@ -36,7 +36,11 @@ if message is None: return message - paragraphs = message.split('\n\n') + if type(message) is unicode: + # goofy unicode docstrings in test + paragraphs = message.split(u'\n\n') + else: + paragraphs = [p.decode("ascii") for p in message.split('\n\n')] # Be careful not to translate the empty string -- it holds the # meta data of the .po file. u = u'\n\n'.join([p and t.ugettext(p) or '' for p in paragraphs])
--- a/mercurial/localrepo.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/localrepo.py Fri Jun 13 17:42:04 2014 -0500 @@ -180,7 +180,9 @@ requirements = ['revlogv1'] filtername = None - bundle2caps = {'HG2X': ()} + bundle2caps = {'HG2X': (), + 'b2x:listkeys': (), + 'b2x:pushkey': ()} # a list of (ui, featureset) functions. # only functions defined in module of enabled extensions are invoked @@ -479,7 +481,8 @@ return hook.hook(self.ui, self, name, throw, **args) @unfilteredmethod - def _tag(self, names, node, message, local, user, date, extra={}): + def _tag(self, names, node, message, local, user, date, extra={}, + editor=False): if isinstance(names, str): names = (names,) @@ -539,14 +542,15 @@ self[None].add(['.hgtags']) m = matchmod.exact(self.root, '', ['.hgtags']) - tagnode = self.commit(message, user, date, extra=extra, match=m) + tagnode = self.commit(message, user, date, extra=extra, match=m, + editor=editor) for name in names: self.hook('tag', node=hex(node), tag=name, local=local) return tagnode - def tag(self, names, node, message, local, user, date): + def tag(self, names, node, message, local, user, date, editor=False): '''tag a revision with one or more symbolic names. names is a list of strings or, when adding a single tag, names may be a @@ -574,7 +578,7 @@ '(please commit .hgtags manually)')) self.tags() # instantiate the cache - self._tag(names, node, message, local, user, date) + self._tag(names, node, message, local, user, date, editor=editor) @filteredpropertycache def _tagscache(self): @@ -855,7 +859,8 @@ # abort here if the journal already exists if self.svfs.exists("journal"): raise error.RepoError( - _("abandoned transaction found - run hg recover")) + _("abandoned transaction found"), + hint=_("run 'hg recover' to clean up transaction")) def onclose(): self.store.write(self._transref()) @@ -1501,149 +1506,9 @@ def status(self, node1='.', node2=None, match=None, ignored=False, clean=False, unknown=False, listsubrepos=False): - """return status of files between two nodes or node and working - directory. - - If node1 is None, use the first dirstate parent instead. - If node2 is None, compare node1 with working directory. - """ - - def mfmatches(ctx): - mf = ctx.manifest().copy() - if match.always(): - return mf - for fn in mf.keys(): - if not match(fn): - del mf[fn] - return mf - - ctx1 = self[node1] - ctx2 = self[node2] - - working = ctx2.rev() is None - parentworking = working and ctx1 == self['.'] - match = match or matchmod.always(self.root, self.getcwd()) - listignored, listclean, listunknown = ignored, clean, unknown - - # load earliest manifest first for caching reasons - if not working and ctx2.rev() < ctx1.rev(): - ctx2.manifest() - - if not parentworking: - def bad(f, msg): - # 'f' may be a directory pattern from 'match.files()', - # so 'f not in ctx1' is not enough - if f not in ctx1 and f not in ctx1.dirs(): - self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg)) - match.bad = bad - - if working: # we need to scan the working dir - subrepos = [] - if '.hgsub' in self.dirstate: - subrepos = sorted(ctx2.substate) - s = self.dirstate.status(match, subrepos, listignored, - listclean, listunknown) - cmp, modified, added, removed, deleted, unknown, ignored, clean = s - - # check for any possibly clean files - if parentworking and cmp: - fixup = [] - # do a full compare of any files that might have changed - for f in sorted(cmp): - if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f) - or ctx1[f].cmp(ctx2[f])): - modified.append(f) - else: - fixup.append(f) - - # update dirstate for files that are actually clean - if fixup: - if listclean: - clean += fixup - - try: - # updating the dirstate is optional - # so we don't wait on the lock - wlock = self.wlock(False) - try: - for f in fixup: - self.dirstate.normal(f) - finally: - wlock.release() - except error.LockError: - pass - - if not parentworking: - mf1 = mfmatches(ctx1) - if working: - # we are comparing working dir against non-parent - # generate a pseudo-manifest for the working dir - mf2 = mfmatches(self['.']) - for f in cmp + modified + added: - mf2[f] = None - mf2.set(f, ctx2.flags(f)) - for f in removed: - if f in mf2: - del mf2[f] - else: - # we are comparing two revisions - deleted, unknown, ignored = [], [], [] - mf2 = mfmatches(ctx2) - - modified, added, clean = [], [], [] - withflags = mf1.withflags() | mf2.withflags() - for fn, mf2node in mf2.iteritems(): - if fn in mf1: - if (fn not in deleted and - ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or - (mf1[fn] != mf2node and - (mf2node or ctx1[fn].cmp(ctx2[fn]))))): - modified.append(fn) - elif listclean: - clean.append(fn) - del mf1[fn] - elif fn not in deleted: - added.append(fn) - removed = mf1.keys() - - if working and modified and not self.dirstate._checklink: - # Symlink placeholders may get non-symlink-like contents - # via user error or dereferencing by NFS or Samba servers, - # so we filter out any placeholders that don't look like a - # symlink - sane = [] - for f in modified: - if ctx2.flags(f) == 'l': - d = ctx2[f].data() - if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d): - self.ui.debug('ignoring suspect symlink placeholder' - ' "%s"\n' % f) - continue - sane.append(f) - modified = sane - - r = modified, added, removed, deleted, unknown, ignored, clean - - if listsubrepos: - for subpath, sub in scmutil.itersubrepos(ctx1, ctx2): - if working: - rev2 = None - else: - rev2 = ctx2.substate[subpath][1] - 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.ui.status(_("skipping missing subrepository: %s\n") - % subpath) - - for l in r: - l.sort() - return r + '''a convenience method that calls node1.status(node2)''' + return self[node1].status(node2, match, ignored, clean, unknown, + listsubrepos) def heads(self, start=None): heads = self.changelog.heads(start)
--- a/mercurial/merge.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/merge.py Fri Jun 13 17:42:04 2014 -0500 @@ -55,6 +55,8 @@ def reset(self, node=None, other=None): self._state = {} + self._local = None + self._other = None if node: self._local = node self._other = other @@ -68,6 +70,8 @@ of on disk file. """ self._state = {} + self._local = None + self._other = None records = self._readrecords() for rtype, record in records: if rtype == 'L': @@ -171,6 +175,18 @@ raise return records + def active(self): + """Whether mergestate is active. + + Returns True if there appears to be mergestate. This is a rough proxy + for "is a merge in progress." + """ + # Check local variables before looking at filesystem for performance + # reasons. + return bool(self._local) or bool(self._state) or \ + self._repo.opener.exists(self.statepathv1) or \ + self._repo.opener.exists(self.statepathv2) + def commit(self): """Write current state on disk (if necessary)""" if self._dirty: @@ -232,10 +248,7 @@ return self._state[dfile][0] def __iter__(self): - l = self._state.keys() - l.sort() - for f in l: - yield f + return iter(sorted(self._state)) def files(self): return self._state.keys() @@ -244,7 +257,14 @@ self._state[dfile][0] = state self._dirty = True - def resolve(self, dfile, wctx): + def unresolved(self): + """Obtain the paths of unresolved files.""" + + for f, entry in self._state.items(): + if entry[0] == 'u': + yield f + + def resolve(self, dfile, wctx, labels=None): """rerun merge process for file path `dfile`""" if self[dfile] == 'r': return 0 @@ -267,7 +287,8 @@ f = self._repo.opener("merge/" + hash) self._repo.wwrite(dfile, f.read(), flags) f.close() - r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca) + r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca, + labels=labels) if r is None: # no real conflict del self._state[dfile] @@ -310,62 +331,44 @@ as removed. """ - actions = [] - state = branchmerge and 'r' or 'f' + ractions = [] + factions = xactions = [] + if branchmerge: + xactions = ractions for f in wctx.deleted(): if f not in mctx: - actions.append((f, state, None, "forget deleted")) + xactions.append((f, None, "forget deleted")) if not branchmerge: for f in wctx.removed(): if f not in mctx: - actions.append((f, "f", None, "forget removed")) + factions.append((f, None, "forget removed")) - return actions + return ractions, factions def _checkcollision(repo, wmf, actions): # build provisional merged manifest up pmmf = set(wmf) - def addop(f, args): - pmmf.add(f) - def removeop(f, args): - pmmf.discard(f) - def nop(f, args): - pass - - def renamemoveop(f, args): - f2, flags = args - pmmf.discard(f2) - pmmf.add(f) - def renamegetop(f, args): - f2, flags = args - pmmf.add(f) - def mergeop(f, args): - f1, f2, fa, move, anc = args - if move: - pmmf.discard(f1) - pmmf.add(f) - - opmap = { - "a": addop, - "dm": renamemoveop, - "dg": renamegetop, - "dr": nop, - "e": nop, - "k": nop, - "f": addop, # untracked file should be kept in working directory - "g": addop, - "m": mergeop, - "r": removeop, - "rd": nop, - "cd": addop, - "dc": addop, - } - for f, m, args, msg in actions: - op = opmap.get(m) - assert op, m - op(f, args) + if actions: + # k, dr, e and rd are no-op + for m in 'a', 'f', 'g', 'cd', 'dc': + for f, args, msg in actions[m]: + pmmf.add(f) + for f, args, msg in actions['r']: + pmmf.discard(f) + for f, args, msg in actions['dm']: + f2, flags = args + pmmf.discard(f2) + pmmf.add(f) + for f, args, msg in actions['dg']: + f2, flags = args + pmmf.add(f) + for f, args, msg in actions['m']: + f1, f2, fa, move, anc = args + if move: + pmmf.discard(f1) + pmmf.add(f) # check case-folding collision in provisional merged manifest foldmap = {} @@ -386,7 +389,8 @@ acceptremote = accept the incoming changes without prompting """ - actions, copy, movewithdir = [], {}, {} + actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split()) + copy, movewithdir = {}, {} # manifests fetched in order are going to be faster, so prime the caches [x.manifest() for x in @@ -396,9 +400,9 @@ ret = copies.mergecopies(repo, wctx, p2, pa) copy, movewithdir, diverge, renamedelete = ret for of, fl in diverge.iteritems(): - actions.append((of, "dr", (fl,), "divergent renames")) + actions['dr'].append((of, (fl,), "divergent renames")) for of, fl in renamedelete.iteritems(): - actions.append((of, "rd", (fl,), "rename and delete")) + actions['rd'].append((of, (fl,), "rename and delete")) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" @@ -450,50 +454,50 @@ fla = ma.flags(fa) nol = 'l' not in fl1 + fl2 + fla if n2 == a and fl2 == fla: - actions.append((f, "k", (), "keep")) # remote unchanged + actions['k'].append((f, (), "keep")) # remote unchanged elif n1 == a and fl1 == fla: # local unchanged - use remote if n1 == n2: # optimization: keep local content - actions.append((f, "e", (fl2,), "update permissions")) + actions['e'].append((f, (fl2,), "update permissions")) else: - actions.append((f, "g", (fl2,), "remote is newer")) + actions['g'].append((f, (fl2,), "remote is newer")) elif nol and n2 == a: # remote only changed 'x' - actions.append((f, "e", (fl2,), "update permissions")) + actions['e'].append((f, (fl2,), "update permissions")) elif nol and n1 == a: # local only changed 'x' - actions.append((f, "g", (fl1,), "remote is newer")) + actions['g'].append((f, (fl1,), "remote is newer")) else: # both changed something - actions.append((f, "m", (f, f, fa, False, pa.node()), + actions['m'].append((f, (f, f, fa, False, pa.node()), "versions differ")) elif f in copied: # files we'll deal with on m2 side pass elif n1 and f in movewithdir: # directory rename, move local f2 = movewithdir[f] - actions.append((f2, "dm", (f, fl1), + actions['dm'].append((f2, (f, fl1), "remote directory rename - move from " + f)) elif n1 and f in copy: f2 = copy[f] - actions.append((f, "m", (f, f2, f2, False, pa.node()), + actions['m'].append((f, (f, f2, f2, False, pa.node()), "local copied/moved from " + f2)) elif n1 and f in ma: # clean, a different, no remote if n1 != ma[f]: if acceptremote: - actions.append((f, "r", None, "remote delete")) + actions['r'].append((f, None, "remote delete")) else: - actions.append((f, "cd", None, "prompt changed/deleted")) + actions['cd'].append((f, None, "prompt changed/deleted")) elif n1[20:] == "a": # added, no remote - actions.append((f, "f", None, "remote deleted")) + actions['f'].append((f, None, "remote deleted")) else: - actions.append((f, "r", None, "other deleted")) + actions['r'].append((f, None, "other deleted")) elif n2 and f in movewithdir: f2 = movewithdir[f] - actions.append((f2, "dg", (f, fl2), + actions['dg'].append((f2, (f, fl2), "local directory rename - get from " + f)) elif n2 and f in copy: f2 = copy[f] if f2 in m2: - actions.append((f, "m", (f2, f, f2, False, pa.node()), + actions['m'].append((f, (f2, f, f2, False, pa.node()), "remote copied from " + f2)) else: - actions.append((f, "m", (f2, f, f2, True, pa.node()), + actions['m'].append((f, (f2, f, f2, True, pa.node()), "remote moved from " + f2)) elif n2 and f not in ma: # local unknown, remote created: the logic is described by the @@ -509,17 +513,17 @@ # Checking whether the files are different is expensive, so we # don't do that when we can avoid it. if force and not branchmerge: - actions.append((f, "g", (fl2,), "remote created")) + actions['g'].append((f, (fl2,), "remote created")) else: different = _checkunknownfile(repo, wctx, p2, f) if force and branchmerge and different: # FIXME: This is wrong - f is not in ma ... - actions.append((f, "m", (f, f, f, False, pa.node()), + actions['m'].append((f, (f, f, f, False, pa.node()), "remote differs from untracked local")) elif not force and different: aborts.append((f, "ud")) else: - actions.append((f, "g", (fl2,), "remote created")) + actions['g'].append((f, (fl2,), "remote created")) elif n2 and n2 != ma[f]: different = _checkunknownfile(repo, wctx, p2, f) if not force and different: @@ -527,10 +531,10 @@ else: # if different: old untracked f may be overwritten and lost if acceptremote: - actions.append((f, "g", (m2.flags(f),), + actions['g'].append((f, (m2.flags(f),), "remote recreating")) else: - actions.append((f, "dc", (m2.flags(f),), + actions['dc'].append((f, (m2.flags(f),), "prompt deleted/changed")) for f, m in sorted(aborts): @@ -545,44 +549,32 @@ # check collision between files only in p2 for clean update if (not branchmerge and (force or not wctx.dirty(missing=True, branch=False))): - _checkcollision(repo, m2, []) + _checkcollision(repo, m2, None) else: _checkcollision(repo, m1, actions) return actions -def actionkey(a): - return a[1] in "rf" and -1 or 0, a - -def getremove(repo, mctx, overwrite, args): - """apply usually-non-interactive updates to the working directory - - mctx is the context to be merged into the working copy +def batchremove(repo, actions): + """apply removes to the working directory yields tuples for progress updates """ verbose = repo.ui.verbose unlink = util.unlinkpath wjoin = repo.wjoin - fctx = mctx.filectx - wwrite = repo.wwrite audit = repo.wopener.audit i = 0 - for arg in args: - f = arg[0] - if arg[1] == 'r': - if verbose: - repo.ui.note(_("removing %s\n") % f) - audit(f) - try: - unlink(wjoin(f), ignoremissing=True) - except OSError, inst: - repo.ui.warn(_("update failed to remove %s: %s!\n") % - (f, inst.strerror)) - else: - if verbose: - repo.ui.note(_("getting %s\n") % f) - wwrite(f, fctx(f).data(), arg[2][0]) + for f, args, msg in actions: + repo.ui.debug(" %s: %s -> r\n" % (f, msg)) + if verbose: + repo.ui.note(_("removing %s\n") % f) + audit(f) + try: + unlink(wjoin(f), ignoremissing=True) + except OSError, inst: + repo.ui.warn(_("update failed to remove %s: %s!\n") % + (f, inst.strerror)) if i == 100: yield i, f i = 0 @@ -590,7 +582,30 @@ if i > 0: yield i, f -def applyupdates(repo, actions, wctx, mctx, overwrite): +def batchget(repo, mctx, actions): + """apply gets to the working directory + + mctx is the context to get from + + yields tuples for progress updates + """ + verbose = repo.ui.verbose + fctx = mctx.filectx + wwrite = repo.wwrite + i = 0 + for f, args, 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 i == 100: + yield i, f + i = 0 + i += 1 + if i > 0: + yield i, f + +def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None): """apply the merge action list to the working directory wctx is the working copy context @@ -604,29 +619,30 @@ ms = mergestate(repo) ms.reset(wctx.p1().node(), mctx.node()) moves = [] - actions.sort(key=actionkey) + for m, l in actions.items(): + l.sort() # prescan for merges - for a in actions: - f, m, args, msg = a - repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) - if m == "m": # merge - 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] - actx = repo[anc] - if fa in actx: - fca = actx[fa] - else: - fca = repo.filectx(f1, fileid=nullrev) - ms.add(fcl, fco, fca, f) - if f1 != f and move: - moves.append(f1) + for f, args, msg in actions['m']: + 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] + actx = repo[anc] + if fa in actx: + fca = actx[fa] + else: + fca = repo.filectx(f1, fileid=nullrev) + ms.add(fcl, fco, fca, f) + if f1 != f and move: + moves.append(f1) audit = repo.wopener.audit + _updating = _('updating') + _files = _('files') + progress = repo.ui.progress # remove renamed files after safely stored for f in moves: @@ -635,86 +651,120 @@ audit(f) util.unlinkpath(repo.wjoin(f)) - numupdates = len([a for a in actions if a[1] != 'k']) - workeractions = [a for a in actions if a[1] in 'gr'] - updateactions = [a for a in workeractions if a[1] == 'g'] - updated = len(updateactions) - removeactions = [a for a in workeractions if a[1] == 'r'] - removed = len(removeactions) - actions = [a for a in actions if a[1] not in 'grk'] + numupdates = sum(len(l) for m, l in actions.items() if m != 'k') - hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate'] - if hgsub and hgsub[0] == 'r': + if [a for a in actions['r'] if a[0] == '.hgsubstate']: subrepo.submerge(repo, wctx, mctx, wctx, overwrite) + # remove in parallel (must come first) z = 0 - prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite), - removeactions) + prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r']) for i, item in prog: z += i - repo.ui.progress(_('updating'), z, item=item, total=numupdates, - unit=_('files')) - prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite), - updateactions) + progress(_updating, z, item=item, total=numupdates, unit=_files) + removed = len(actions['r']) + + # get in parallel + prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g']) for i, item in prog: z += i - repo.ui.progress(_('updating'), z, item=item, total=numupdates, - unit=_('files')) + progress(_updating, z, item=item, total=numupdates, unit=_files) + updated = len(actions['g']) - if hgsub and hgsub[0] == 'g': + if [a for a in actions['g'] if a[0] == '.hgsubstate']: subrepo.submerge(repo, wctx, mctx, wctx, overwrite) - _updating = _('updating') - _files = _('files') - progress = repo.ui.progress + # forget (manifest only, just log it) (must come first) + for f, args, msg in actions['f']: + repo.ui.debug(" %s: %s -> f\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + + # re-add (manifest only, just log it) + for f, args, msg in actions['a']: + repo.ui.debug(" %s: %s -> a\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)) + # no progress - for i, a in enumerate(actions): - f, m, args, msg = a - progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files) - if m == "m": # merge - f1, f2, fa, move, anc = args - if f == '.hgsubstate': # subrepo states need updating - subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), - overwrite) - continue - audit(f) - r = ms.resolve(f, wctx) - if r is not None and r > 0: - unresolved += 1 + # merge + for f, args, msg in actions['m']: + repo.ui.debug(" %s: %s -> m\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + f1, f2, fa, move, anc = args + if f == '.hgsubstate': # subrepo states need updating + subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), + overwrite) + continue + audit(f) + 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: - if r is None: - updated += 1 - else: - merged += 1 - elif m == "dm": # directory rename, move local - f0, flags = args - repo.ui.note(_("moving %s to %s\n") % (f0, f)) - audit(f) - repo.wwrite(f, wctx.filectx(f0).data(), flags) - util.unlinkpath(repo.wjoin(f0)) - updated += 1 - elif m == "dg": # local directory rename, get - f0, flags = args - repo.ui.note(_("getting %s to %s\n") % (f0, f)) - repo.wwrite(f, mctx.filectx(f0).data(), flags) - updated += 1 - elif m == "dr": # divergent renames - fl, = args - repo.ui.warn(_("note: possible conflict - %s was renamed " - "multiple times to:\n") % f) - for nf in fl: - repo.ui.warn(" %s\n" % nf) - elif m == "rd": # rename and delete - fl, = args - repo.ui.warn(_("note: possible conflict - %s was deleted " - "and renamed to:\n") % f) - for nf in fl: - repo.ui.warn(" %s\n" % nf) - elif m == "e": # exec - flags, = args - audit(f) - util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags) - updated += 1 + merged += 1 + + # directory rename, move local + for f, args, msg in actions['dm']: + repo.ui.debug(" %s: %s -> dm\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + f0, flags = args + repo.ui.note(_("moving %s to %s\n") % (f0, f)) + audit(f) + repo.wwrite(f, wctx.filectx(f0).data(), flags) + util.unlinkpath(repo.wjoin(f0)) + updated += 1 + + # local directory rename, get + for f, args, msg in actions['dg']: + repo.ui.debug(" %s: %s -> dg\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + f0, flags = args + repo.ui.note(_("getting %s to %s\n") % (f0, f)) + repo.wwrite(f, mctx.filectx(f0).data(), flags) + updated += 1 + + # divergent renames + for f, args, msg in actions['dr']: + repo.ui.debug(" %s: %s -> dr\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + fl, = args + repo.ui.warn(_("note: possible conflict - %s was renamed " + "multiple times to:\n") % f) + for nf in fl: + repo.ui.warn(" %s\n" % nf) + + # rename and delete + for f, args, msg in actions['rd']: + repo.ui.debug(" %s: %s -> rd\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + fl, = args + repo.ui.warn(_("note: possible conflict - %s was deleted " + "and renamed to:\n") % f) + for nf in fl: + repo.ui.warn(" %s\n" % nf) + + # exec + for f, args, msg in actions['e']: + repo.ui.debug(" %s: %s -> e\n" % (f, msg)) + z += 1 + progress(_updating, z, item=f, total=numupdates, unit=_files) + flags, = args + audit(f) + util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags) + updated += 1 + ms.commit() progress(_updating, None, total=numupdates, unit=_files) @@ -735,163 +785,173 @@ (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors))) # Call for bids - fbids = {} # mapping filename to list af action bids + fbids = {} # mapping filename to bids (action method to list af actions) for ancestor in ancestors: repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor) actions = manifestmerge(repo, wctx, mctx, ancestor, branchmerge, force, partial, acceptremote, followcopies) - for a in sorted(actions): - repo.ui.debug(' %s: %s\n' % (a[0], a[1])) - f = a[0] - if f in fbids: - fbids[f].append(a) - else: - fbids[f] = [a] + for m, l in sorted(actions.items()): + for a in l: + f, args, msg = a + repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m)) + if f in fbids: + d = fbids[f] + if m in d: + d[m].append(a) + else: + d[m] = [a] + else: + fbids[f] = {m: [a]} # Pick the best bid for each file repo.ui.note(_('\nauction for merging merge bids\n')) - actions = [] - for f, bidsl in sorted(fbids.items()): + actions = dict((m, []) for m in actions.keys()) + for f, bids in sorted(fbids.items()): + # bids is a mapping from action method to list af actions # Consensus? - a0 = bidsl[0] - if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1 - repo.ui.note(" %s: consensus for %s\n" % (f, a0[1])) - actions.append(a0) - continue - # Group bids by kind of action - bids = {} - for a in bidsl: - m = a[1] - if m in bids: - bids[m].append(a) - else: - bids[m] = [a] + if len(bids) == 1: # all bids are the same kind of method + m, l = bids.items()[0] + if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1 + repo.ui.note(" %s: consensus for %s\n" % (f, m)) + actions[m].append(l[0]) + continue # If keep is an option, just do it. if "k" in bids: repo.ui.note(" %s: picking 'keep' action\n" % f) - actions.append(bids["k"][0]) + actions['k'].append(bids["k"][0]) continue - # If all gets agree [how could they not?], just do it. + # If there are gets and they all agree [how could they not?], do it. if "g" in bids: ga0 = bids["g"][0] if util.all(a == ga0 for a in bids["g"][1:]): repo.ui.note(" %s: picking 'get' action\n" % f) - actions.append(ga0) + actions['g'].append(ga0) continue # TODO: Consider other simple actions such as mode changes # Handle inefficient democrazy. repo.ui.note(_(' %s: multiple bids for merge action:\n') % f) - for _f, m, args, msg in bidsl: - repo.ui.note(' %s -> %s\n' % (msg, m)) + for m, l in sorted(bids.items()): + for _f, args, msg in l: + repo.ui.note(' %s -> %s\n' % (msg, m)) # Pick random action. TODO: Instead, prompt user when resolving - a0 = bidsl[0] + m, l = bids.items()[0] repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') % - (f, a0[1])) - actions.append(a0) + (f, m)) + actions[m].append(l[0]) continue repo.ui.note(_('end of auction\n\n')) - # Filter out prompts. - newactions, prompts = [], [] - for a in actions: - if a[1] in ("cd", "dc"): - prompts.append(a) + # Prompt and create actions. TODO: Move this towards resolve phase. + for f, args, msg in actions['cd']: + 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")) else: - newactions.append(a) - # Prompt and create actions. TODO: Move this towards resolve phase. - for f, m, args, msg in sorted(prompts): - if m == "cd": - if repo.ui.promptchoice( - _("local changed %s which remote deleted\n" - "use (c)hanged version or (d)elete?" - "$$ &Changed $$ &Delete") % f, 0): - newactions.append((f, "r", None, "prompt delete")) - else: - newactions.append((f, "a", None, "prompt keep")) - elif m == "dc": - flags, = args - if repo.ui.promptchoice( - _("remote changed %s which local deleted\n" - "use (c)hanged version or leave (d)eleted?" - "$$ &Changed $$ &Deleted") % f, 0) == 0: - newactions.append((f, "g", (flags,), "prompt recreating")) - else: assert False, m + actions['a'].append((f, None, "prompt keep")) + del actions['cd'][:] + + for f, args, msg in actions['dc']: + flags, = args + 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'][:] if wctx.rev() is None: - newactions += _forgetremoved(wctx, mctx, branchmerge) + ractions, factions = _forgetremoved(wctx, mctx, branchmerge) + actions['r'].extend(ractions) + actions['f'].extend(factions) - return newactions + return actions def recordupdates(repo, actions, branchmerge): "record merge actions to the dirstate" - - for a in actions: - f, m, args, msg = a - if m == "r": # remove - if branchmerge: - repo.dirstate.remove(f) - else: - repo.dirstate.drop(f) - elif m == "a": # re-add - if not branchmerge: - repo.dirstate.add(f) - elif m == "f": # forget + # remove (must come first) + for f, args, msg in actions['r']: + if branchmerge: + repo.dirstate.remove(f) + else: repo.dirstate.drop(f) - elif m == "e": # exec change - repo.dirstate.normallookup(f) - elif m == "k": # keep - pass - elif m == "g": # get - if branchmerge: - repo.dirstate.otherparent(f) - else: - repo.dirstate.normal(f) - elif m == "m": # merge - f1, f2, fa, move, anc = args - if branchmerge: - # We've done a branch merge, mark this file as merged - # so that we properly record the merger later - repo.dirstate.merge(f) - if f1 != f2: # copy/rename - if move: - repo.dirstate.remove(f1) - if f1 != f: - repo.dirstate.copy(f1, f) - else: - repo.dirstate.copy(f2, f) - else: - # We've update-merged a locally modified file, so - # we set the dirstate to emulate a normal checkout - # of that file some time in the past. Thus our - # merge will appear as a normal local file - # modification. - if f2 == f: # file not locally copied/moved - repo.dirstate.normallookup(f) + + # forget (must come first) + for f, args, msg in actions['f']: + repo.dirstate.drop(f) + + # re-add + for f, args, msg in actions['a']: + if not branchmerge: + repo.dirstate.add(f) + + # exec change + for f, args, msg in actions['e']: + repo.dirstate.normallookup(f) + + # keep + for f, args, msg in actions['k']: + pass + + # get + for f, args, msg in actions['g']: + if branchmerge: + repo.dirstate.otherparent(f) + else: + repo.dirstate.normal(f) + + # merge + for f, args, msg in actions['m']: + f1, f2, fa, move, anc = args + if branchmerge: + # We've done a branch merge, mark this file as merged + # so that we properly record the merger later + repo.dirstate.merge(f) + if f1 != f2: # copy/rename if move: - repo.dirstate.drop(f1) - elif m == "dm": # directory rename, move local - f0, flag = args - if f0 not in repo.dirstate: - # untracked file moved - continue - if branchmerge: - repo.dirstate.add(f) - repo.dirstate.remove(f0) - repo.dirstate.copy(f0, f) - else: - repo.dirstate.normal(f) - repo.dirstate.drop(f0) - elif m == "dg": # directory rename, get - f0, flag = args - if branchmerge: - repo.dirstate.add(f) - repo.dirstate.copy(f0, f) - else: - repo.dirstate.normal(f) + repo.dirstate.remove(f1) + if f1 != f: + repo.dirstate.copy(f1, f) + else: + repo.dirstate.copy(f2, f) + else: + # We've update-merged a locally modified file, so + # we set the dirstate to emulate a normal checkout + # of that file some time in the past. Thus our + # merge will appear as a normal local file + # modification. + if f2 == f: # file not locally copied/moved + repo.dirstate.normallookup(f) + if move: + repo.dirstate.drop(f1) + + # directory rename, move local + for f, args, msg in actions['dm']: + f0, flag = args + if f0 not in repo.dirstate: + # untracked file moved + continue + if branchmerge: + repo.dirstate.add(f) + repo.dirstate.remove(f0) + repo.dirstate.copy(f0, f) + else: + repo.dirstate.normal(f) + repo.dirstate.drop(f0) + + # directory rename, get + for f, args, msg in actions['dg']: + f0, flag = args + if branchmerge: + repo.dirstate.add(f) + repo.dirstate.copy(f0, f) + else: + repo.dirstate.normal(f) def update(repo, node, branchmerge, force, partial, ancestor=None, - mergeancestor=False): + mergeancestor=False, labels=None): """ Perform a merge between the working directory and the given node @@ -1071,7 +1131,7 @@ # note that we're in the middle of an update repo.vfs.write('updatestate', p2.hex()) - stats = applyupdates(repo, actions, wc, p2, overwrite) + stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels) if not partial: repo.setparents(fp1, fp2)
--- a/mercurial/minirst.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/minirst.py Fri Jun 13 17:42:04 2014 -0500 @@ -56,7 +56,7 @@ # on strings in local encoding causes invalid byte sequences. utext = text.decode(encoding.encoding) for f, t in substs: - utext = utext.replace(f, t) + utext = utext.replace(f.decode("ascii"), t.decode("ascii")) return utext.encode(encoding.encoding) _blockre = re.compile(r"\n(?:\s*\n)+")
--- a/mercurial/patch.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/patch.py Fri Jun 13 17:42:04 2014 -0500 @@ -417,12 +417,12 @@ return os.path.join(self.opener.base, f) def getfile(self, fname): - path = self._join(fname) - if os.path.islink(path): - return (os.readlink(path), (True, False)) + if self.opener.islink(fname): + return (self.opener.readlink(fname), (True, False)) + isexec = False try: - isexec = os.lstat(path).st_mode & 0100 != 0 + isexec = self.opener.lstat(fname).st_mode & 0100 != 0 except OSError, e: if e.errno != errno.ENOENT: raise @@ -431,17 +431,17 @@ def setfile(self, fname, data, mode, copysource): islink, isexec = mode if data is None: - util.setflags(self._join(fname), islink, isexec) + self.opener.setflags(fname, islink, isexec) return if islink: self.opener.symlink(data, fname) else: self.opener.write(fname, data) if isexec: - util.setflags(self._join(fname), False, True) + self.opener.setflags(fname, False, True) def unlink(self, fname): - util.unlinkpath(self._join(fname), ignoremissing=True) + self.opener.unlinkpath(fname, ignoremissing=True) def writerej(self, fname, failed, total, lines): fname = fname + ".rej" @@ -453,7 +453,7 @@ fp.close() def exists(self, fname): - return os.path.lexists(self._join(fname)) + return self.opener.lexists(fname) class workingbackend(fsbackend): def __init__(self, ui, repo, similarity): @@ -1521,14 +1521,11 @@ patcher = ui.config('ui', 'patch') if files is None: files = set() - try: - if patcher: - return _externalpatch(ui, repo, patcher, patchname, strip, - files, similarity) - return internalpatch(ui, repo, patchname, strip, files, eolmode, - similarity) - except PatchError, err: - raise util.Abort(str(err)) + if patcher: + return _externalpatch(ui, repo, patcher, patchname, strip, + files, similarity) + return internalpatch(ui, repo, patchname, strip, files, eolmode, + similarity) def changedfiles(ui, repo, patchpath, strip=1): backend = fsbackend(ui, repo.root)
--- a/mercurial/pushkey.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/pushkey.py Fri Jun 13 17:42:04 2014 -0500 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import bookmarks, phases, obsolete +import bookmarks, phases, obsolete, encoding def _nslist(repo): n = {} @@ -37,3 +37,18 @@ lk = _get(namespace)[1] return lk(repo) +encode = encoding.fromlocal + +decode = encoding.tolocal + +def encodekeys(keys): + """encode the content of a pushkey namespace for exchange over the wire""" + return '\n'.join(['%s\t%s' % (encode(k), encode(v)) for k, v in keys]) + +def decodekeys(data): + """decode the content of a pushkey namespace from exchange over the wire""" + result = {} + for l in data.splitlines(): + k, v = l.split('\t') + result[decode(k)] = decode(v) + return result
--- a/mercurial/py3kcompat.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/py3kcompat.py Fri Jun 13 17:42:04 2014 -0500 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import os, builtins +import builtins from numbers import Number @@ -52,13 +52,6 @@ return ret.encode('utf-8', 'surrogateescape') builtins.bytesformatter = bytesformatter -# Create bytes equivalents for os.environ values -for key in list(os.environ.keys()): - # UTF-8 is fine for us - bkey = key.encode('utf-8', 'surrogateescape') - bvalue = os.environ[key].encode('utf-8', 'surrogateescape') - os.environ[bkey] = bvalue - origord = builtins.ord def fakeord(char): if isinstance(char, int):
--- a/mercurial/revset.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/revset.py Fri Jun 13 17:42:04 2014 -0500 @@ -2764,10 +2764,6 @@ if self._start < self._end: self.reverse() - def _contained(self, rev): - return (rev <= self._start and rev > self._end) or (rev >= self._start - and rev < self._end) - def __iter__(self): if self._start <= self._end: iterrange = xrange(self._start, self._end) @@ -2825,7 +2821,7 @@ start = self._start end = self._end for rev in self._hiddenrevs: - if (end < rev <= start) or (start <= rev and rev < end): + if (end < rev <= start) or (start <= rev < end): count += 1 return abs(self._end - self._start) - count
--- a/mercurial/scmutil.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/scmutil.py Fri Jun 13 17:42:04 2014 -0500 @@ -178,6 +178,9 @@ def islink(self, path=None): return os.path.islink(self.join(path)) + def lexists(self, path=None): + return os.path.lexists(self.join(path)) + def lstat(self, path=None): return os.lstat(self.join(path)) @@ -223,6 +226,9 @@ def unlink(self, path=None): return util.unlink(self.join(path)) + def unlinkpath(self, path=None, ignoremissing=False): + return util.unlinkpath(self.join(path), ignoremissing) + def utime(self, path=None, t=None): return os.utime(self.join(path), t)
--- a/mercurial/simplemerge.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/simplemerge.py Fri Jun 13 17:42:04 2014 -0500 @@ -416,11 +416,11 @@ name_a = local name_b = other labels = opts.get('label', []) - if labels: - name_a = labels.pop(0) - if labels: - name_b = labels.pop(0) - if labels: + if len(labels) > 0: + name_a = labels[0] + if len(labels) > 1: + name_b = labels[1] + if len(labels) > 2: raise util.Abort(_("can only specify two labels.")) try:
--- a/mercurial/subrepo.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/subrepo.py Fri Jun 13 17:42:04 2014 -0500 @@ -205,12 +205,13 @@ sm[s] = r else: debug(s, "both sides changed") + srepo = wctx.sub(s) option = repo.ui.promptchoice( _(' subrepository %s diverged (local revision: %s, ' 'remote revision: %s)\n' '(M)erge, keep (l)ocal or keep (r)emote?' '$$ &Merge $$ &Local $$ &Remote') - % (s, l[1][:12], r[1][:12]), 0) + % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0) if option == 0: wctx.sub(s).merge(r) sm[s] = l @@ -502,6 +503,9 @@ % (substate[0], substate[2])) return [] + def shortid(self, revid): + return revid + class hgsubrepo(abstractsubrepo): def __init__(self, ctx, path, state): self._path = path @@ -867,6 +871,9 @@ pats = [] cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts) + def shortid(self, revid): + return revid[:12] + class svnsubrepo(abstractsubrepo): def __init__(self, ctx, path, state): self._path = path @@ -1563,6 +1570,9 @@ deleted = unknown = ignored = clean = [] return modified, added, removed, deleted, unknown, ignored, clean + def shortid(self, revid): + return revid[:7] + types = { 'hg': hgsubrepo, 'svn': svnsubrepo,
--- a/mercurial/templates/atom/changelogentry.tmpl Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/templates/atom/changelogentry.tmpl Fri Jun 13 17:42:04 2014 -0500 @@ -32,7 +32,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>{desc|strip|escape|addbreaks|nonempty}</td> + <td>{desc|strip|escape|websub|addbreaks|nonempty}</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th>
--- a/mercurial/templates/rss/changelogentry.tmpl Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/templates/rss/changelogentry.tmpl Fri Jun 13 17:42:04 2014 -0500 @@ -27,7 +27,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>{desc|strip|escape|addbreaks|nonempty}</td> + <td>{desc|strip|escape|websub|addbreaks|nonempty}</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th>
--- a/mercurial/templates/rss/filelogentry.tmpl Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/templates/rss/filelogentry.tmpl Fri Jun 13 17:42:04 2014 -0500 @@ -1,7 +1,7 @@ <item> <title>{desc|strip|firstline|strip|escape}</title> <link>{urlbase}{url|urlescape}log{node|short}/{file|urlescape}</link> - <description><![CDATA[{desc|strip|escape|addbreaks|nonempty}]]></description> + <description><![CDATA[{desc|strip|escape|websub|addbreaks|nonempty}]]></description> <author>{author|obfuscate}</author> <pubDate>{date|rfc822date}</pubDate> </item>
--- a/mercurial/wireproto.py Fri Jun 13 14:17:14 2014 -0500 +++ b/mercurial/wireproto.py Fri Jun 13 17:42:04 2014 -0500 @@ -8,7 +8,7 @@ import urllib, tempfile, os, sys from i18n import _ from node import bin, hex -import changegroup as changegroupmod, bundle2 +import changegroup as changegroupmod, bundle2, pushkey as pushkeymod import peer, error, encoding, util, store, exchange @@ -190,6 +190,21 @@ .replace(':,', ',') .replace('::', ':')) +# mapping of options accepted by getbundle and their types +# +# Meant to be extended by extensions. It is extensions responsibility to ensure +# such options are properly processed in exchange.getbundle. +# +# supported types are: +# +# :nodes: list of binary nodes +# :csv: list of comma-separated values +# :plain: string with no transformation needed. +gboptsmap = {'heads': 'nodes', + 'common': 'nodes', + 'bundlecaps': 'csv', + 'listkeys': 'csv'} + # client side class wirepeer(peer.peerrepository): @@ -303,11 +318,7 @@ self.ui.debug('preparing listkeys for "%s"\n' % namespace) yield {'namespace': encoding.fromlocal(namespace)}, f d = f.value - r = {} - for l in d.splitlines(): - k, v = l.split('\t') - r[encoding.tolocal(k)] = encoding.tolocal(v) - yield r + yield pushkeymod.decodekeys(d) def stream_out(self): return self._callstream('stream_out') @@ -325,18 +336,25 @@ bases=bases, heads=heads) return changegroupmod.unbundle10(f, 'UN') - def getbundle(self, source, heads=None, common=None, bundlecaps=None, - **kwargs): + def getbundle(self, source, **kwargs): self.requirecap('getbundle', _('look up remote changes')) opts = {} - if heads is not None: - opts['heads'] = encodelist(heads) - if common is not None: - opts['common'] = encodelist(common) - if bundlecaps is not None: - opts['bundlecaps'] = ','.join(bundlecaps) - opts.update(kwargs) + for key, value in kwargs.iteritems(): + if value is None: + continue + keytype = gboptsmap.get(key) + if keytype is None: + assert False, 'unexpected' + elif keytype == 'nodes': + value = encodelist(value) + elif keytype == 'csv': + value = ','.join(value) + elif keytype != 'plain': + raise KeyError('unknown getbundle option type %s' + % keytype) + opts[key] = value f = self._callcompressable("getbundle", **opts) + bundlecaps = kwargs.get('bundlecaps') if bundlecaps is not None and 'HG2X' in bundlecaps: return bundle2.unbundle20(self.ui, f) else: @@ -489,7 +507,7 @@ opts[k] = others[k] del others[k] if others: - sys.stderr.write("abort: %s got unexpected arguments %s\n" + sys.stderr.write("warning: %s ignored unexpected arguments %s\n" % (cmd, ",".join(others))) return opts @@ -627,12 +645,16 @@ @wireprotocommand('getbundle', '*') def getbundle(repo, proto, others): - opts = options('getbundle', gboptslist, others) + opts = options('getbundle', gboptsmap.keys(), others) for k, v in opts.iteritems(): - if k in ('heads', 'common'): + keytype = gboptsmap[k] + if keytype == 'nodes': opts[k] = decodelist(v) - elif k == 'bundlecaps': + elif keytype == 'csv': opts[k] = set(v.split(',')) + elif keytype != 'plain': + raise KeyError('unknown getbundle option type %s' + % keytype) cg = exchange.getbundle(repo, 'serve', **opts) return streamres(proto.groupchunks(cg)) @@ -655,9 +677,7 @@ @wireprotocommand('listkeys', 'namespace') def listkeys(repo, proto, namespace): d = repo.listkeys(encoding.tolocal(namespace)).items() - t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v)) - for k, v in d]) - return t + return pushkeymod.encodekeys(d) @wireprotocommand('lookup', 'key') def lookup(repo, proto, key): @@ -809,11 +829,13 @@ finally: fp.close() os.unlink(tempname) - except bundle2.UnknownPartError, exc: + except error.BundleValueError, exc: bundler = bundle2.bundle20(repo.ui) - part = bundle2.bundlepart('B2X:ERROR:UNKNOWNPART', - [('parttype', str(exc))]) - bundler.addpart(part) + errpart = bundler.newpart('B2X:ERROR:UNSUPPORTEDCONTENT') + if exc.parttype is not None: + errpart.addparam('parttype', exc.parttype) + if exc.params: + errpart.addparam('params', '\0'.join(exc.params)) return streamres(bundler.getchunks()) except util.Abort, inst: # The old code we moved used sys.stderr directly. @@ -835,9 +857,7 @@ except error.PushRaced, exc: if getattr(exc, 'duringunbundle2', False): bundler = bundle2.bundle20(repo.ui) - part = bundle2.bundlepart('B2X:ERROR:PUSHRACED', - [('message', str(exc))]) - bundler.addpart(part) + bundler.newpart('B2X:ERROR:PUSHRACED', [('message', str(exc))]) return streamres(bundler.getchunks()) else: return pusherr(str(exc))
--- a/setup.py Fri Jun 13 14:17:14 2014 -0500 +++ b/setup.py Fri Jun 13 17:42:04 2014 -0500 @@ -513,7 +513,7 @@ version = version[0] xcode4 = (version.startswith('Xcode') and StrictVersion(version.split()[1]) >= StrictVersion('4.0')) - xcode51 = re.match(r'^Xcode\s+5\.1\.', version) is not None + xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None else: # xcodebuild returns empty on OS X Lion with XCode 4.3 not # installed, but instead with only command-line tools. Assume
--- a/tests/autodiff.py Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/autodiff.py Fri Jun 13 17:42:04 2014 -0500 @@ -1,8 +1,14 @@ # Extension dedicated to test patch.diff() upgrade modes # # -from mercurial import scmutil, patch, util +from mercurial import cmdutil, scmutil, patch, util +cmdtable = {} +command = cmdutil.command(cmdtable) + +@command('autodiff', + [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)')], + '[OPTION]... [FILE]...') def autodiff(ui, repo, *pats, **opts): diffopts = patch.diffopts(ui, opts) git = opts.get('git', 'no') @@ -36,11 +42,3 @@ ui.write(chunk) for fn in sorted(brokenfiles): ui.write(('data lost for: %s\n' % fn)) - -cmdtable = { - "autodiff": - (autodiff, - [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)'), - ], - '[OPTION]... [FILE]...'), -}
--- a/tests/filterpyflakes.py Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/filterpyflakes.py Fri Jun 13 17:42:04 2014 -0500 @@ -29,12 +29,15 @@ for line in sys.stdin: # We whitelist tests (see more messages in pyflakes.messages) pats = [ - r"imported but unused", - r"local variable '.*' is assigned to but never used", - r"unable to detect undefined names", + (r"imported but unused", None), + (r"local variable '.*' is assigned to but never used", None), + (r"unable to detect undefined names", None), + (r"undefined name '.*'", + r"undefined name '(WindowsError|memoryview)'") ] - for msgtype, pat in enumerate(pats): - if re.search(pat, line): + + for msgtype, (pat, excl) in enumerate(pats): + if re.search(pat, line) and (not excl or not re.search(excl, line)): break # pattern matches else: continue # no pattern matched, next line @@ -49,3 +52,7 @@ for msgtype, line in sorted(lines, key=makekey): sys.stdout.write(line) print + +# self test of "undefined name" detection for other than 'memoryview' +if False: + print undefinedname
--- a/tests/run-tests.py Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/run-tests.py Fri Jun 13 17:42:04 2014 -0500 @@ -57,6 +57,7 @@ import threading import killdaemons as killmod import Queue as queue +import unittest processlock = threading.Lock() @@ -92,18 +93,12 @@ return p -# reserved exit code to skip test (used by hghave) -SKIPPED_STATUS = 80 -SKIPPED_PREFIX = 'skipped: ' -FAILED_PREFIX = 'hghave check failed: ' PYTHON = sys.executable.replace('\\', '/') IMPL_PATH = 'PYTHONPATH' if 'java' in sys.platform: IMPL_PATH = 'JYTHONPATH' -requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip", - "gunzip", "bunzip2", "sed"] -createdfiles = [] +TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None defaults = { 'jobs': ('HGTEST_JOBS', 1), @@ -134,6 +129,7 @@ return entries def getparser(): + """Obtain the OptionParser used by the CLI.""" parser = optparse.OptionParser("%prog [options] [tests]") # keep these sorted @@ -214,6 +210,7 @@ return parser def parseargs(args, parser): + """Parse arguments with our OptionParser and validate results.""" (options, args) = parser.parse_args(args) # jython is always pure @@ -285,47 +282,33 @@ shutil.copy(src, dst) os.remove(src) -def parsehghaveoutput(lines): - '''Parse hghave log lines. - Return tuple of lists (missing, failed): - * the missing/unknown features - * the features for which existence check failed''' - missing = [] - failed = [] - for line in lines: - if line.startswith(SKIPPED_PREFIX): - line = line.splitlines()[0] - missing.append(line[len(SKIPPED_PREFIX):]) - elif line.startswith(FAILED_PREFIX): - line = line.splitlines()[0] - failed.append(line[len(FAILED_PREFIX):]) - - return missing, failed - -def showdiff(expected, output, ref, err): - print +def getdiff(expected, output, ref, err): servefail = False + lines = [] for line in difflib.unified_diff(expected, output, ref, err): - sys.stdout.write(line) + if line.startswith('+++') or line.startswith('---'): + if line.endswith(' \n'): + line = line[:-2] + '\n' + lines.append(line) if not servefail and line.startswith( '+ abort: child process failed to start'): servefail = True - return {'servefail': servefail} + return servefail, lines verbose = False def vlog(*msg): - if verbose is not False: - iolock.acquire() - if verbose: - print verbose, - for m in msg: - print m, - print - sys.stdout.flush() - iolock.release() + """Log only when in verbose mode.""" + if verbose is False: + return + + return log(*msg) def log(*msg): + """Log something to stdout. + + Arguments are strings to print. + """ iolock.acquire() if verbose: print verbose, @@ -335,80 +318,6 @@ sys.stdout.flush() iolock.release() -def findprogram(program): - """Search PATH for a executable program""" - for p in os.environ.get('PATH', os.defpath).split(os.pathsep): - name = os.path.join(p, program) - if os.name == 'nt' or os.access(name, os.X_OK): - return name - return None - -def createhgrc(path, options): - # create a fresh hgrc - hgrc = open(path, 'w') - hgrc.write('[ui]\n') - hgrc.write('slash = True\n') - hgrc.write('interactive = False\n') - hgrc.write('[defaults]\n') - hgrc.write('backout = -d "0 0"\n') - hgrc.write('commit = -d "0 0"\n') - hgrc.write('shelve = --date "0 0"\n') - hgrc.write('tag = -d "0 0"\n') - if options.extra_config_opt: - for opt in options.extra_config_opt: - section, key = opt.split('.', 1) - assert '=' in key, ('extra config opt %s must ' - 'have an = for assignment' % opt) - hgrc.write('[%s]\n%s\n' % (section, key)) - hgrc.close() - -def createenv(options, testtmp, threadtmp, port): - env = os.environ.copy() - env['TESTTMP'] = testtmp - env['HOME'] = testtmp - env["HGPORT"] = str(port) - env["HGPORT1"] = str(port + 1) - env["HGPORT2"] = str(port + 2) - env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc') - env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids') - env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' - env["HGMERGE"] = "internal:merge" - env["HGUSER"] = "test" - env["HGENCODING"] = "ascii" - env["HGENCODINGMODE"] = "strict" - - # Reset some environment variables to well-known values so that - # the tests produce repeatable output. - env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C' - env['TZ'] = 'GMT' - env["EMAIL"] = "Foo Bar <foo.bar@example.com>" - env['COLUMNS'] = '80' - env['TERM'] = 'xterm' - - for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' + - 'NO_PROXY').split(): - if k in env: - del env[k] - - # unset env related to hooks - for k in env.keys(): - if k.startswith('HG_'): - del env[k] - - return env - -def checktools(): - # Before we go any further, check for pre-requisite tools - # stuff from coreutils (cat, rm, etc) are not tested - for p in requiredtools: - if os.name == 'nt' and not p.endswith('.exe'): - p += '.exe' - found = findprogram(p) - if found: - vlog("# Found prerequisite", p, "at", found) - else: - print "WARNING: Did not find prerequisite tool: "+p - def terminate(proc): """Terminate subprocess (with fallback for Python versions < 2.6)""" vlog('# Terminating process %d' % proc.pid) @@ -421,264 +330,409 @@ return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog) -def cleanup(options): - if not options.keep_tmpdir: - vlog("# Cleaning up HGTMP", HGTMP) - shutil.rmtree(HGTMP, True) - for f in createdfiles: - try: - os.remove(f) - except OSError: - pass +class Test(unittest.TestCase): + """Encapsulates a single, runnable test. + + While this class conforms to the unittest.TestCase API, it differs in that + instances need to be instantiated manually. (Typically, unittest.TestCase + classes are instantiated automatically by scanning modules.) + """ + + # Status code reserved for skipped tests (used by hghave). + SKIPPED_STATUS = 80 + + def __init__(self, path, tmpdir, keeptmpdir=False, + debug=False, + timeout=defaults['timeout'], + startport=defaults['port'], extraconfigopts=None, + py3kwarnings=False, shell=None): + """Create a test from parameters. -def usecorrectpython(): - # some tests run python interpreter. they must use same - # interpreter we use or bad things will happen. - pyexename = sys.platform == 'win32' and 'python.exe' or 'python' - if getattr(os, 'symlink', None): - vlog("# Making python executable in test path a symlink to '%s'" % - sys.executable) - mypython = os.path.join(TMPBINDIR, pyexename) - try: - if os.readlink(mypython) == sys.executable: - return - os.unlink(mypython) - except OSError, err: - if err.errno != errno.ENOENT: - raise - if findprogram(pyexename) != sys.executable: - try: - os.symlink(sys.executable, mypython) - createdfiles.append(mypython) - except OSError, err: - # child processes may race, which is harmless - if err.errno != errno.EEXIST: - raise - else: - exedir, exename = os.path.split(sys.executable) - vlog("# Modifying search path to find %s as %s in '%s'" % - (exename, pyexename, exedir)) - path = os.environ['PATH'].split(os.pathsep) - while exedir in path: - path.remove(exedir) - os.environ['PATH'] = os.pathsep.join([exedir] + path) - if not findprogram(pyexename): - print "WARNING: Cannot find %s in search path" % pyexename + path is the full path to the file defining the test. + + tmpdir is the main temporary directory to use for this test. + + keeptmpdir determines whether to keep the test's temporary directory + after execution. It defaults to removal (False). -def installhg(options): - vlog("# Performing temporary installation of HG") - installerrs = os.path.join("tests", "install.err") - compiler = '' - if options.compiler: - compiler = '--compiler ' + options.compiler - pure = options.pure and "--pure" or "" - py3 = '' - if sys.version_info[0] == 3: - py3 = '--c2to3' + debug mode will make the test execute verbosely, with unfiltered + output. + + timeout controls the maximum run time of the test. It is ignored when + debug is True. + + 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 + responsibility to allocate a non-overlapping port range to Test + instances. - # Run installer in hg root - script = os.path.realpath(sys.argv[0]) - hgroot = os.path.dirname(os.path.dirname(script)) - os.chdir(hgroot) - nohome = '--home=""' - if os.name == 'nt': - # The --home="" trick works only on OS where os.sep == '/' - # because of a distutils convert_path() fast-path. Avoid it at - # least on Windows for now, deal with .pydistutils.cfg bugs - # when they happen. - nohome = '' - cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all' - ' build %(compiler)s --build-base="%(base)s"' - ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"' - ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' - % {'exe': sys.executable, 'py3': py3, 'pure': pure, - 'compiler': compiler, 'base': os.path.join(HGTMP, "build"), - 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR, - 'nohome': nohome, 'logfile': installerrs}) - vlog("# Running", cmd) - if os.system(cmd) == 0: - if not options.verbose: - os.remove(installerrs) - else: - f = open(installerrs) - for line in f: - print line, - f.close() - sys.exit(1) - os.chdir(TESTDIR) + extraconfigopts is an iterable of extra hgrc config options. Values + must have the form "key=value" (something understood by hgrc). Values + of the form "foo.key=value" will result in "[foo] key=value". + + py3kwarnings enables Py3k warnings. + + shell is the shell to execute tests in. + """ + + self.path = path + self.name = os.path.basename(path) + self._testdir = os.path.dirname(path) + self.errpath = os.path.join(self._testdir, '%s.err' % self.name) - usecorrectpython() - - if options.py3k_warnings and not options.anycoverage: - vlog("# Updating hg command to enable Py3k Warnings switch") - f = open(os.path.join(BINDIR, 'hg'), 'r') - lines = [line.rstrip() for line in f] - lines[0] += ' -3' - f.close() - f = open(os.path.join(BINDIR, 'hg'), 'w') - for line in lines: - f.write(line + '\n') - f.close() + self._threadtmp = tmpdir + self._keeptmpdir = keeptmpdir + self._debug = debug + self._timeout = timeout + self._startport = startport + self._extraconfigopts = extraconfigopts or [] + self._py3kwarnings = py3kwarnings + self._shell = shell - hgbat = os.path.join(BINDIR, 'hg.bat') - if os.path.isfile(hgbat): - # hg.bat expects to be put in bin/scripts while run-tests.py - # installation layout put it in bin/ directly. Fix it - f = open(hgbat, 'rb') - data = f.read() - f.close() - if '"%~dp0..\python" "%~dp0hg" %*' in data: - data = data.replace('"%~dp0..\python" "%~dp0hg" %*', - '"%~dp0python" "%~dp0hg" %*') - f = open(hgbat, 'wb') - f.write(data) + self._aborted = False + self._daemonpids = [] + self._finished = None + self._ret = None + self._out = None + self._skipped = None + self._testtmp = None + + # If we're not in --debug mode and reference output file exists, + # check test output against it. + if debug: + self._refout = None # to match "out is None" + elif os.path.exists(self.refpath): + f = open(self.refpath, 'r') + self._refout = f.read().splitlines(True) f.close() else: - print 'WARNING: cannot fix hg.bat reference to python.exe' + self._refout = [] + + def __str__(self): + return self.name + + def shortDescription(self): + return self.name - if options.anycoverage: - custom = os.path.join(TESTDIR, 'sitecustomize.py') - target = os.path.join(PYTHONDIR, 'sitecustomize.py') - vlog('# Installing coverage trigger to %s' % target) - shutil.copyfile(custom, target) - rc = os.path.join(TESTDIR, '.coveragerc') - vlog('# Installing coverage rc to %s' % rc) - os.environ['COVERAGE_PROCESS_START'] = rc - fn = os.path.join(INST, '..', '.coverage') - os.environ['COVERAGE_FILE'] = fn + def setUp(self): + """Tasks to perform before run().""" + self._finished = False + self._ret = None + self._out = None + self._skipped = None + + try: + os.mkdir(self._threadtmp) + except OSError, e: + if e.errno != errno.EEXIST: + raise + + self._testtmp = os.path.join(self._threadtmp, + os.path.basename(self.path)) + os.mkdir(self._testtmp) + + # Remove any previous output files. + if os.path.exists(self.errpath): + os.remove(self.errpath) -def outputtimes(options): - vlog('# Producing time report') - times.sort(key=lambda t: (t[1], t[0]), reverse=True) - cols = '%7.3f %s' - print '\n%-7s %s' % ('Time', 'Test') - for test, timetaken in times: - print cols % (timetaken, test) + def run(self, result): + """Run this test and report results against a TestResult instance.""" + # This function is extremely similar to unittest.TestCase.run(). Once + # we require Python 2.7 (or at least its version of unittest), this + # function can largely go away. + self._result = result + result.startTest(self) + try: + try: + self.setUp() + except (KeyboardInterrupt, SystemExit): + self._aborted = True + raise + except Exception: + result.addError(self, sys.exc_info()) + return -def outputcoverage(options): - - vlog('# Producing coverage report') - os.chdir(PYTHONDIR) + success = False + try: + self.runTest() + except KeyboardInterrupt: + self._aborted = True + raise + except SkipTest, e: + result.addSkip(self, str(e)) + except IgnoreTest, e: + result.addIgnore(self, str(e)) + except WarnTest, e: + result.addWarn(self, str(e)) + except self.failureException, e: + # This differs from unittest in that we don't capture + # the stack trace. This is for historical reasons and + # this decision could be revisted in the future, + # especially for PythonTest instances. + result.addFailure(self, str(e)) + except Exception: + result.addError(self, sys.exc_info()) + else: + success = True - def covrun(*args): - cmd = 'coverage %s' % ' '.join(args) - vlog('# Running: %s' % cmd) - os.system(cmd) + try: + self.tearDown() + except (KeyboardInterrupt, SystemExit): + self._aborted = True + raise + except Exception: + result.addError(self, sys.exc_info()) + success = False - covrun('-c') - omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR]) - covrun('-i', '-r', '"--omit=%s"' % omit) # report - if options.htmlcov: - htmldir = os.path.join(TESTDIR, 'htmlcov') - covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit) - if options.annotate: - adir = os.path.join(TESTDIR, 'annotated') - if not os.path.isdir(adir): - os.mkdir(adir) - covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) + if success: + result.addSuccess(self) + finally: + result.stopTest(self, interrupted=self._aborted) + + def runTest(self): + """Run this test instance. + + This will return a tuple describing the result of the test. + """ + replacements = self._getreplacements() + env = self._getenv() + self._daemonpids.append(env['DAEMON_PIDS']) + self._createhgrc(env['HGRCPATH']) + + vlog('# Test', self.name) + + ret, out = self._run(replacements, env) + self._finished = True + self._ret = ret + self._out = out + + def describe(ret): + if ret < 0: + return 'killed by signal: %d' % -ret + return 'returned error code %d' % ret + + self._skipped = False + + if ret == self.SKIPPED_STATUS: + if out is None: # Debug mode, nothing to parse. + missing = ['unknown'] + failed = None + else: + missing, failed = TTest.parsehghaveoutput(out) + + if not missing: + missing = ['irrelevant'] -def pytest(test, wd, options, replacements, env): - py3kswitch = options.py3k_warnings and ' -3' or '' - cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) - vlog("# Running", cmd) - if os.name == 'nt': - replacements.append((r'\r\n', '\n')) - return run(cmd, wd, options, replacements, env) + if failed: + self.fail('hg have failed checking for %s' % failed[-1]) + else: + self._skipped = True + raise SkipTest(missing[-1]) + elif ret == 'timeout': + self.fail('timed out') + elif ret is False: + raise WarnTest('no result code from test') + elif out != self._refout: + # Diff generation may rely on written .err file. + if (ret != 0 or out != self._refout) and not self._skipped \ + and not self._debug: + f = open(self.errpath, 'wb') + for line in out: + f.write(line) + f.close() + + # The result object handles diff calculation for us. + self._result.addOutputMismatch(self, ret, out, self._refout) -needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search -escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub -escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256)) -escapemap.update({'\\': '\\\\', '\r': r'\r'}) -def escapef(m): - return escapemap[m.group(0)] -def stringescape(s): - return escapesub(escapef, s) + if ret: + msg = 'output changed and ' + describe(ret) + else: + msg = 'output changed' + + self.fail(msg) + elif ret: + self.fail(describe(ret)) -def rematch(el, l): - try: - # use \Z to ensure that the regex matches to the end of the string - if os.name == 'nt': - return re.match(el + r'\r?\n\Z', l) - return re.match(el + r'\n\Z', l) - except re.error: - # el is an invalid regex - return False + def tearDown(self): + """Tasks to perform after run().""" + for entry in self._daemonpids: + killdaemons(entry) + self._daemonpids = [] + + if not self._keeptmpdir: + shutil.rmtree(self._testtmp, True) + shutil.rmtree(self._threadtmp, True) + + if (self._ret != 0 or self._out != self._refout) and not self._skipped \ + and not self._debug and self._out: + f = open(self.errpath, 'wb') + for line in self._out: + f.write(line) + f.close() -def globmatch(el, l): - # The only supported special characters are * and ? plus / which also - # matches \ on windows. Escaping of these characters is supported. - if el + '\n' == l: - if os.altsep: - # matching on "/" is not needed for this line - return '-glob' - return True - i, n = 0, len(el) - res = '' - while i < n: - c = el[i] - i += 1 - if c == '\\' and el[i] in '*?\\/': - res += el[i - 1:i + 1] - i += 1 - elif c == '*': - res += '.*' - elif c == '?': - res += '.' - elif c == '/' and os.altsep: - res += '[/\\\\]' + vlog("# Ret was:", self._ret) + + def _run(self, replacements, env): + # This should be implemented in child classes to run tests. + raise SkipTest('unknown test type') + + def abort(self): + """Terminate execution of this test.""" + self._aborted = True + + def _getreplacements(self): + """Obtain a mapping of text replacements to apply to test output. + + Test output needs to be normalized so it can be compared to expected + output. This function defines how some of that normalization will + occur. + """ + r = [ + (r':%s\b' % self._startport, ':$HGPORT'), + (r':%s\b' % (self._startport + 1), ':$HGPORT1'), + (r':%s\b' % (self._startport + 2), ':$HGPORT2'), + ] + + if os.name == 'nt': + r.append( + (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or + c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c + for c in self._testtmp), '$TESTTMP')) else: - res += re.escape(c) - return rematch(res, l) + r.append((re.escape(self._testtmp), '$TESTTMP')) + + return r + + def _getenv(self): + """Obtain environment variables to use during test execution.""" + env = os.environ.copy() + env['TESTTMP'] = self._testtmp + env['HOME'] = self._testtmp + env["HGPORT"] = str(self._startport) + env["HGPORT1"] = str(self._startport + 1) + env["HGPORT2"] = str(self._startport + 2) + env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc') + env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids') + env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' + env["HGMERGE"] = "internal:merge" + env["HGUSER"] = "test" + env["HGENCODING"] = "ascii" + env["HGENCODINGMODE"] = "strict" + + # Reset some environment variables to well-known values so that + # the tests produce repeatable output. + env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C' + env['TZ'] = 'GMT' + env["EMAIL"] = "Foo Bar <foo.bar@example.com>" + env['COLUMNS'] = '80' + env['TERM'] = 'xterm' + + for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' + + 'NO_PROXY').split(): + if k in env: + del env[k] + + # unset env related to hooks + for k in env.keys(): + if k.startswith('HG_'): + del env[k] + + return env -def linematch(el, l): - if el == l: # perfect match (fast) - return True - if el: - if el.endswith(" (esc)\n"): - el = el[:-7].decode('string-escape') + '\n' - if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l: - return True - if el.endswith(" (re)\n"): - return rematch(el[:-6], l) - if el.endswith(" (glob)\n"): - return globmatch(el[:-8], l) - if os.altsep and l.replace('\\', '/') == el: - return '+glob' - return False + def _createhgrc(self, path): + """Create an hgrc file for this test.""" + hgrc = open(path, 'w') + hgrc.write('[ui]\n') + hgrc.write('slash = True\n') + hgrc.write('interactive = False\n') + hgrc.write('[defaults]\n') + hgrc.write('backout = -d "0 0"\n') + hgrc.write('commit = -d "0 0"\n') + hgrc.write('shelve = --date "0 0"\n') + hgrc.write('tag = -d "0 0"\n') + for opt in self._extraconfigopts: + section, key = opt.split('.', 1) + assert '=' in key, ('extra config opt %s must ' + 'have an = for assignment' % opt) + hgrc.write('[%s]\n%s\n' % (section, key)) + hgrc.close() + + def fail(self, msg): + # unittest differentiates between errored and failed. + # Failed is denoted by AssertionError (by default at least). + raise AssertionError(msg) + +class PythonTest(Test): + """A Python-based test.""" + + @property + def refpath(self): + return os.path.join(self._testdir, '%s.out' % self.name) + + def _run(self, replacements, env): + py3kswitch = self._py3kwarnings and ' -3' or '' + cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path) + vlog("# Running", cmd) + if os.name == 'nt': + replacements.append((r'\r\n', '\n')) + result = run(cmd, self._testtmp, replacements, env, + debug=self._debug, timeout=self._timeout) + if self._aborted: + raise KeyboardInterrupt() + + return result + +class TTest(Test): + """A "t test" is a test backed by a .t file.""" -def tsttest(test, wd, options, replacements, env): - # We generate a shell script which outputs unique markers to line - # up script results with our source. These markers include input - # line number and the last return code - salt = "SALT" + str(time.time()) - def addsalt(line, inpython): - if inpython: - script.append('%s %d 0\n' % (salt, line)) - else: - script.append('echo %s %s $?\n' % (salt, line)) + SKIPPED_PREFIX = 'skipped: ' + FAILED_PREFIX = 'hghave check failed: ' + NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search + + ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub + ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)) + ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'}) + + @property + def refpath(self): + return os.path.join(self._testdir, self.name) + + def _run(self, replacements, env): + f = open(self.path) + lines = f.readlines() + f.close() + + salt, script, after, expected = self._parsetest(lines) - # After we run the shell script, we re-unify the script output - # with non-active parts of the source, with synchronization by our - # SALT line number markers. The after table contains the - # non-active components, ordered by line number - after = {} - pos = prepos = -1 + # Write out the generated script. + fname = '%s.sh' % self._testtmp + f = open(fname, 'w') + for l in script: + f.write(l) + f.close() - # Expected shell script output - expected = {} + cmd = '%s "%s"' % (self._shell, fname) + vlog("# Running", cmd) + + exitcode, output = run(cmd, self._testtmp, replacements, env, + debug=self._debug, timeout=self._timeout) - # We keep track of whether or not we're in a Python block so we - # can generate the surrounding doctest magic - inpython = False + if self._aborted: + raise KeyboardInterrupt() + + # Do not merge output if skipped. Return hghave message instead. + # Similarly, with --debug, output is None. + if exitcode == self.SKIPPED_STATUS or output is None: + return exitcode, output - # True or False when in a true or false conditional section - skipping = None + return self._processoutput(exitcode, output, salt, after, expected) - def hghave(reqs): - # TODO: do something smarter when all other uses of hghave is gone - tdir = TESTDIR.replace('\\', '/') + def _hghave(self, reqs): + # TODO do something smarter when all other uses of hghave are gone. + tdir = self._testdir.replace('\\', '/') proc = Popen4('%s -c "%s/hghave %s"' % - (options.shell, tdir, ' '.join(reqs)), wd, 0) + (self._shell, tdir, ' '.join(reqs)), + self._testtmp, 0) stdout, stderr = proc.communicate() ret = proc.wait() if wifexited(ret): @@ -686,172 +740,271 @@ if ret == 2: print stdout sys.exit(1) + return ret == 0 - f = open(test) - t = f.readlines() - f.close() + def _parsetest(self, lines): + # We generate a shell script which outputs unique markers to line + # up script results with our source. These markers include input + # line number and the last return code. + salt = "SALT" + str(time.time()) + def addsalt(line, inpython): + if inpython: + script.append('%s %d 0\n' % (salt, line)) + else: + script.append('echo %s %s $?\n' % (salt, line)) + + script = [] + + # After we run the shell script, we re-unify the script output + # with non-active parts of the source, with synchronization by our + # SALT line number markers. The after table contains the non-active + # components, ordered by line number. + after = {} + + # Expected shell script output. + expected = {} + + pos = prepos = -1 + + # True or False when in a true or false conditional section + skipping = None + + # We keep track of whether or not we're in a Python block so we + # can generate the surrounding doctest magic. + inpython = False + + if self._debug: + script.append('set -x\n') + if os.getenv('MSYSTEM'): + script.append('alias pwd="pwd -W"\n') - script = [] - if options.debug: - script.append('set -x\n') - if os.getenv('MSYSTEM'): - script.append('alias pwd="pwd -W"\n') - n = 0 - for n, l in enumerate(t): - if not l.endswith('\n'): - l += '\n' - if l.startswith('#if'): - lsplit = l.split() - if len(lsplit) < 2 or lsplit[0] != '#if': - after.setdefault(pos, []).append(' !!! invalid #if\n') - if skipping is not None: - after.setdefault(pos, []).append(' !!! nested #if\n') - skipping = not hghave(lsplit[1:]) - after.setdefault(pos, []).append(l) - elif l.startswith('#else'): - if skipping is None: - after.setdefault(pos, []).append(' !!! missing #if\n') - skipping = not skipping - after.setdefault(pos, []).append(l) - elif l.startswith('#endif'): - if skipping is None: - after.setdefault(pos, []).append(' !!! missing #if\n') - skipping = None - after.setdefault(pos, []).append(l) - elif skipping: - after.setdefault(pos, []).append(l) - elif l.startswith(' >>> '): # python inlines - after.setdefault(pos, []).append(l) - prepos = pos - pos = n - if not inpython: - # we've just entered a Python block, add the header - inpython = True - addsalt(prepos, False) # make sure we report the exit code - script.append('%s -m heredoctest <<EOF\n' % PYTHON) - addsalt(n, True) - script.append(l[2:]) - elif l.startswith(' ... '): # python inlines - after.setdefault(prepos, []).append(l) - script.append(l[2:]) - elif l.startswith(' $ '): # commands - if inpython: - script.append("EOF\n") - inpython = False - after.setdefault(pos, []).append(l) - prepos = pos - pos = n - addsalt(n, False) - cmd = l[4:].split() - if len(cmd) == 2 and cmd[0] == 'cd': - l = ' $ cd %s || exit 1\n' % cmd[1] - script.append(l[4:]) - elif l.startswith(' > '): # continuations - after.setdefault(prepos, []).append(l) - script.append(l[4:]) - elif l.startswith(' '): # results - # queue up a list of expected results - expected.setdefault(pos, []).append(l[2:]) - else: - if inpython: - script.append("EOF\n") - inpython = False - # non-command/result - queue up for merged output - after.setdefault(pos, []).append(l) + for n, l in enumerate(lines): + if not l.endswith('\n'): + l += '\n' + if l.startswith('#if'): + lsplit = l.split() + if len(lsplit) < 2 or lsplit[0] != '#if': + after.setdefault(pos, []).append(' !!! invalid #if\n') + if skipping is not None: + after.setdefault(pos, []).append(' !!! nested #if\n') + skipping = not self._hghave(lsplit[1:]) + after.setdefault(pos, []).append(l) + elif l.startswith('#else'): + if skipping is None: + after.setdefault(pos, []).append(' !!! missing #if\n') + skipping = not skipping + after.setdefault(pos, []).append(l) + elif l.startswith('#endif'): + if skipping is None: + after.setdefault(pos, []).append(' !!! missing #if\n') + skipping = None + after.setdefault(pos, []).append(l) + elif skipping: + after.setdefault(pos, []).append(l) + elif l.startswith(' >>> '): # python inlines + after.setdefault(pos, []).append(l) + prepos = pos + pos = n + if not inpython: + # We've just entered a Python block. Add the header. + inpython = True + addsalt(prepos, False) # Make sure we report the exit code. + script.append('%s -m heredoctest <<EOF\n' % PYTHON) + addsalt(n, True) + script.append(l[2:]) + elif l.startswith(' ... '): # python inlines + after.setdefault(prepos, []).append(l) + script.append(l[2:]) + elif l.startswith(' $ '): # commands + if inpython: + script.append('EOF\n') + inpython = False + after.setdefault(pos, []).append(l) + prepos = pos + pos = n + addsalt(n, False) + cmd = l[4:].split() + if len(cmd) == 2 and cmd[0] == 'cd': + l = ' $ cd %s || exit 1\n' % cmd[1] + script.append(l[4:]) + elif l.startswith(' > '): # continuations + after.setdefault(prepos, []).append(l) + script.append(l[4:]) + elif l.startswith(' '): # results + # Queue up a list of expected results. + expected.setdefault(pos, []).append(l[2:]) + else: + if inpython: + script.append('EOF\n') + inpython = False + # Non-command/result. Queue up for merged output. + after.setdefault(pos, []).append(l) + + if inpython: + script.append('EOF\n') + if skipping is not None: + after.setdefault(pos, []).append(' !!! missing #endif\n') + addsalt(n + 1, False) + + return salt, script, after, expected + + def _processoutput(self, exitcode, output, salt, after, expected): + # Merge the script output back into a unified test. + warnonly = 1 # 1: not yet; 2: yes; 3: for sure not + if exitcode != 0: + warnonly = 3 + + pos = -1 + postout = [] + for l in output: + lout, lcmd = l, None + if salt in l: + lout, lcmd = l.split(salt, 1) + + if lout: + if not lout.endswith('\n'): + lout += ' (no-eol)\n' - if inpython: - script.append("EOF\n") - if skipping is not None: - after.setdefault(pos, []).append(' !!! missing #endif\n') - addsalt(n + 1, False) + # Find the expected output at the current position. + el = None + if expected.get(pos, None): + el = expected[pos].pop(0) - # Write out the script and execute it - name = wd + '.sh' - f = open(name, 'w') - for l in script: - f.write(l) - f.close() + r = TTest.linematch(el, lout) + if isinstance(r, str): + if r == '+glob': + lout = el[:-1] + ' (glob)\n' + r = '' # Warn only this line. + elif r == '-glob': + lout = ''.join(el.rsplit(' (glob)', 1)) + r = '' # Warn only this line. + else: + log('\ninfo, unknown linematch result: %r\n' % r) + r = False + if r: + postout.append(' ' + el) + else: + if self.NEEDESCAPE(lout): + lout = TTest._stringescape('%s (esc)\n' % + lout.rstrip('\n')) + postout.append(' ' + lout) # Let diff deal with it. + if r != '': # If line failed. + warnonly = 3 # for sure not + elif warnonly == 1: # Is "not yet" and line is warn only. + warnonly = 2 # Yes do warn. - cmd = '%s "%s"' % (options.shell, name) - vlog("# Running", cmd) - exitcode, output = run(cmd, wd, options, replacements, env) - # do not merge output if skipped, return hghave message instead - # similarly, with --debug, output is None - if exitcode == SKIPPED_STATUS or output is None: - return exitcode, output + if lcmd: + # Add on last return code. + ret = int(lcmd.split()[1]) + if ret != 0: + postout.append(' [%s]\n' % ret) + if pos in after: + # Merge in non-active test bits. + postout += after.pop(pos) + pos = int(lcmd.split()[0]) - # Merge the script output back into a unified test + if pos in after: + postout += after.pop(pos) - warnonly = 1 # 1: not yet, 2: yes, 3: for sure not - if exitcode != 0: # failure has been reported - warnonly = 3 # set to "for sure not" - pos = -1 - postout = [] - for l in output: - lout, lcmd = l, None - if salt in l: - lout, lcmd = l.split(salt, 1) + if warnonly == 2: + exitcode = False # Set exitcode to warned. + + return exitcode, postout - if lout: - if not lout.endswith('\n'): - lout += ' (no-eol)\n' + @staticmethod + def rematch(el, l): + try: + # use \Z to ensure that the regex matches to the end of the string + if os.name == 'nt': + return re.match(el + r'\r?\n\Z', l) + return re.match(el + r'\n\Z', l) + except re.error: + # el is an invalid regex + return False - # find the expected output at the current position - el = None - if pos in expected and expected[pos]: - el = expected[pos].pop(0) - - r = linematch(el, lout) - if isinstance(r, str): - if r == '+glob': - lout = el[:-1] + ' (glob)\n' - r = '' # warn only this line - elif r == '-glob': - lout = ''.join(el.rsplit(' (glob)', 1)) - r = '' # warn only this line - else: - log('\ninfo, unknown linematch result: %r\n' % r) - r = False - if r: - postout.append(" " + el) + @staticmethod + def globmatch(el, l): + # The only supported special characters are * and ? plus / which also + # matches \ on windows. Escaping of these characters is supported. + if el + '\n' == l: + if os.altsep: + # matching on "/" is not needed for this line + return '-glob' + return True + i, n = 0, len(el) + res = '' + while i < n: + c = el[i] + i += 1 + if c == '\\' and el[i] in '*?\\/': + res += el[i - 1:i + 1] + i += 1 + elif c == '*': + res += '.*' + elif c == '?': + res += '.' + elif c == '/' and os.altsep: + res += '[/\\\\]' else: - if needescape(lout): - lout = stringescape(lout.rstrip('\n')) + " (esc)\n" - postout.append(" " + lout) # let diff deal with it - if r != '': # if line failed - warnonly = 3 # set to "for sure not" - elif warnonly == 1: # is "not yet" (and line is warn only) - warnonly = 2 # set to "yes" do warn + res += re.escape(c) + return TTest.rematch(res, l) + + @staticmethod + def linematch(el, l): + if el == l: # perfect match (fast) + return True + if el: + if el.endswith(" (esc)\n"): + el = el[:-7].decode('string-escape') + '\n' + if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l: + return True + if el.endswith(" (re)\n"): + return TTest.rematch(el[:-6], l) + if el.endswith(" (glob)\n"): + return TTest.globmatch(el[:-8], l) + if os.altsep and l.replace('\\', '/') == el: + return '+glob' + return False + + @staticmethod + def parsehghaveoutput(lines): + '''Parse hghave log lines. - if lcmd: - # add on last return code - ret = int(lcmd.split()[1]) - if ret != 0: - postout.append(" [%s]\n" % ret) - if pos in after: - # merge in non-active test bits - postout += after.pop(pos) - pos = int(lcmd.split()[0]) + Return tuple of lists (missing, failed): + * the missing/unknown features + * the features for which existence check failed''' + missing = [] + failed = [] + for line in lines: + if line.startswith(TTest.SKIPPED_PREFIX): + line = line.splitlines()[0] + missing.append(line[len(TTest.SKIPPED_PREFIX):]) + elif line.startswith(TTest.FAILED_PREFIX): + line = line.splitlines()[0] + failed.append(line[len(TTest.FAILED_PREFIX):]) - if pos in after: - postout += after.pop(pos) + return missing, failed - if warnonly == 2: - exitcode = False # set exitcode to warned - return exitcode, postout + @staticmethod + def _escapef(m): + return TTest.ESCAPEMAP[m.group(0)] + + @staticmethod + def _stringescape(s): + return TTest.ESCAPESUB(TTest._escapef, s) + wifexited = getattr(os, "WIFEXITED", lambda x: False) -def run(cmd, wd, options, replacements, env): +def run(cmd, wd, replacements, env, debug=False, timeout=None): """Run command in a sub-process, capturing the output (stdout and stderr). Return a tuple (exitcode, output). output is None in debug mode.""" - # TODO: Use subprocess.Popen if we're running on Python 2.4 - if options.debug: + if debug: proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env) ret = proc.wait() return (ret, None) - proc = Popen4(cmd, wd, options.timeout, env) + proc = Popen4(cmd, wd, timeout, env) def cleanup(): terminate(proc) ret = proc.wait() @@ -880,442 +1033,782 @@ if ret: killdaemons(env['DAEMON_PIDS']) - if abort: - raise KeyboardInterrupt() - for s, r in replacements: output = re.sub(s, r, output) return ret, output.splitlines(True) -def runone(options, test, count): - '''returns a result element: (code, test, msg)''' +iolock = threading.Lock() + +class SkipTest(Exception): + """Raised to indicate that a test is to be skipped.""" + +class IgnoreTest(Exception): + """Raised to indicate that a test is to be ignored.""" + +class WarnTest(Exception): + """Raised to indicate that a test warned.""" + +class TestResult(unittest._TextTestResult): + """Holds results when executing via unittest.""" + # Don't worry too much about accessing the non-public _TextTestResult. + # It is relatively common in Python testing tools. + def __init__(self, options, *args, **kwargs): + super(TestResult, self).__init__(*args, **kwargs) + + self._options = options - def skip(msg): - if options.verbose: - log("\nSkipping %s: %s" % (testpath, msg)) - return 's', test, msg + # unittest.TestResult didn't have skipped until 2.7. We need to + # polyfill it. + self.skipped = [] + + # We have a custom "ignored" result that isn't present in any Python + # unittest implementation. It is very similar to skipped. It may make + # sense to map it into skip some day. + self.ignored = [] + + # We have a custom "warned" result that isn't present in any Python + # unittest implementation. It is very similar to failed. It may make + # sense to map it into fail some day. + self.warned = [] + + self.times = [] + self._started = {} + + def addFailure(self, test, reason): + self.failures.append((test, reason)) - def fail(msg, ret): - warned = ret is False - if not options.nodiff: - log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg)) - if (not ret and options.interactive - and os.path.exists(testpath + ".err")): - iolock.acquire() - print "Accept this change? [n] ", - answer = sys.stdin.readline().strip() - iolock.release() - if answer.lower() in "y yes".split(): - if test.endswith(".t"): - rename(testpath + ".err", testpath) - else: - rename(testpath + ".err", testpath + ".out") - return '.', test, '' - return warned and '~' or '!', test, msg + if self._options.first: + self.stop() + else: + if not self._options.nodiff: + self.stream.write('\nERROR: %s output changed\n' % test) + self.stream.write('!') + + def addError(self, *args, **kwargs): + super(TestResult, self).addError(*args, **kwargs) + + if self._options.first: + self.stop() + + # Polyfill. + def addSkip(self, test, reason): + self.skipped.append((test, reason)) + + if self.showAll: + self.stream.writeln('skipped %s' % reason) + else: + self.stream.write('s') + self.stream.flush() - def success(): - return '.', test, '' + def addIgnore(self, test, reason): + self.ignored.append((test, reason)) - def ignore(msg): - return 'i', test, msg + if self.showAll: + self.stream.writeln('ignored %s' % reason) + else: + if reason != 'not retesting': + self.stream.write('i') + self.stream.flush() - def describe(ret): - if ret < 0: - return 'killed by signal %d' % -ret - return 'returned error code %d' % ret + def addWarn(self, test, reason): + self.warned.append((test, reason)) - testpath = os.path.join(TESTDIR, test) - err = os.path.join(TESTDIR, test + ".err") - lctest = test.lower() + if self._options.first: + self.stop() - if not os.path.exists(testpath): - return skip("doesn't exist") + if self.showAll: + self.stream.writeln('warned %s' % reason) + else: + self.stream.write('~') + self.stream.flush() - if not (options.whitelisted and test in options.whitelisted): - if options.blacklist and test in options.blacklist: - return skip("blacklisted") + def addOutputMismatch(self, test, ret, got, expected): + """Record a mismatch in test output for a particular test.""" + + if self._options.nodiff: + return - if options.retest and not os.path.exists(test + ".err"): - return ignore("not retesting") + if self._options.view: + os.system("%s %s %s" % (self._view, test.refpath, test.errpath)) + else: + failed, lines = getdiff(expected, got, + test.refpath, test.errpath) + if failed: + self.addFailure(test, 'diff generation failed') + else: + self.stream.write('\n') + for line in lines: + self.stream.write(line) + self.stream.flush() + + if ret or not self._options.interactive or \ + not os.path.exists(test.errpath): + return - if options.keywords: - fp = open(test) - t = fp.read().lower() + test.lower() - fp.close() - for k in options.keywords.lower().split(): - if k in t: - break - else: - return ignore("doesn't match keyword") + iolock.acquire() + print 'Accept this change? [n] ', + answer = sys.stdin.readline().strip() + iolock.release() + if answer.lower() in ('y', 'yes'): + if test.name.endswith('.t'): + rename(test.errpath, test.path) + else: + rename(test.errpath, '%s.out' % test.path) + + def startTest(self, test): + super(TestResult, self).startTest(test) + + self._started[test.name] = time.time() - if not os.path.basename(lctest).startswith("test-"): - return skip("not a test file") - for ext, func, out in testtypes: - if lctest.endswith(ext): - runner = func - ref = os.path.join(TESTDIR, test + out) - break - else: - return skip("unknown test type") + def stopTest(self, test, interrupted=False): + super(TestResult, self).stopTest(test) + + self.times.append((test.name, time.time() - self._started[test.name])) + del self._started[test.name] - vlog("# Test", test) + if interrupted: + self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( + test.name, self.times[-1][1])) + +class TestSuite(unittest.TestSuite): + """Custom unitest TestSuite that knows how to execute Mercurial tests.""" + + def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None, + retest=False, keywords=None, loop=False, + *args, **kwargs): + """Create a new instance that can run tests with a configuration. - if os.path.exists(err): - os.remove(err) # Remove any previous output files + testdir specifies the directory where tests are executed from. This + is typically the ``tests`` directory from Mercurial's source + repository. + + jobs specifies the number of jobs to run concurrently. Each test + executes on its own thread. Tests actually spawn new processes, so + state mutation should not be an issue. - # Make a tmp subdirectory to work in - threadtmp = os.path.join(HGTMP, "child%d" % count) - testtmp = os.path.join(threadtmp, os.path.basename(test)) - os.mkdir(threadtmp) - os.mkdir(testtmp) + 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 + populates the TestSuite with tests. They are present to preserve + backwards compatible behavior which reports skipped tests as part + of the results. + + retest denotes whether to retest failed tests. This arguably belongs + outside of TestSuite. + + keywords denotes key words that will be used to filter which tests + to execute. This arguably belongs outside of TestSuite. + + loop denotes whether to loop over tests forever. + """ + super(TestSuite, self).__init__(*args, **kwargs) - port = options.port + count * 3 - replacements = [ - (r':%s\b' % port, ':$HGPORT'), - (r':%s\b' % (port + 1), ':$HGPORT1'), - (r':%s\b' % (port + 2), ':$HGPORT2'), - ] - if os.name == 'nt': - replacements.append( - (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or - c in '/\\' and r'[/\\]' or - c.isdigit() and c or - '\\' + c - for c in testtmp), '$TESTTMP')) - else: - replacements.append((re.escape(testtmp), '$TESTTMP')) + self._jobs = jobs + self._whitelist = whitelist + self._blacklist = blacklist + self._retest = retest + self._keywords = keywords + self._loop = loop - env = createenv(options, testtmp, threadtmp, port) - createhgrc(env['HGRCPATH'], options) + def run(self, result): + # We have a number of filters that need to be applied. We do this + # here instead of inside Test because it makes the running logic for + # Test simpler. + tests = [] + for test in self._tests: + if not os.path.exists(test.path): + result.addSkip(test, "Doesn't exist") + continue + + if not (self._whitelist and test.name in self._whitelist): + if self._blacklist and test.name in self._blacklist: + result.addSkip(test, 'blacklisted') + continue + + if self._retest and not os.path.exists(test.errpath): + result.addIgnore(test, 'not retesting') + continue - starttime = time.time() - try: - ret, out = runner(testpath, testtmp, options, replacements, env) - except KeyboardInterrupt: - endtime = time.time() - log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime)) - raise - endtime = time.time() - times.append((test, endtime - starttime)) - vlog("# Ret was:", ret) + if self._keywords: + f = open(test.path) + t = f.read().lower() + test.name.lower() + f.close() + ignored = False + for k in self._keywords.lower().split(): + if k not in t: + result.addIgnore(test, "doesn't match keyword") + ignored = True + break + + if ignored: + continue + + tests.append(test) + + runtests = list(tests) + done = queue.Queue() + running = 0 + + def job(test, result): + try: + test(result) + done.put(None) + except KeyboardInterrupt: + pass + except: # re-raises + done.put(('!', test, 'run-test raised an error, see traceback')) + raise - killdaemons(env['DAEMON_PIDS']) + try: + while tests or running: + if not done.empty() or running == self._jobs or not tests: + try: + done.get(True, 1) + if result and result.shouldStop: + break + except queue.Empty: + continue + running -= 1 + if tests and not running == self._jobs: + test = tests.pop(0) + if self._loop: + tests.append(test) + t = threading.Thread(target=job, name=test.name, + args=(test, result)) + t.start() + running += 1 + except KeyboardInterrupt: + for test in runtests: + test.abort() - skipped = (ret == SKIPPED_STATUS) + return result + +class TextTestRunner(unittest.TextTestRunner): + """Custom unittest test runner that uses appropriate settings.""" + + def __init__(self, runner, *args, **kwargs): + super(TextTestRunner, self).__init__(*args, **kwargs) + + self._runner = runner + + def run(self, test): + result = TestResult(self._runner.options, self.stream, + self.descriptions, self.verbosity) + + test(result) + + failed = len(result.failures) + warned = len(result.warned) + skipped = len(result.skipped) + ignored = len(result.ignored) + + self.stream.writeln('') - # If we're not in --debug mode and reference output file exists, - # check test output against it. - if options.debug: - refout = None # to match "out is None" - elif os.path.exists(ref): - f = open(ref, "r") - refout = f.read().splitlines(True) - f.close() - else: - refout = [] + if not self._runner.options.noskips: + for test, msg in result.skipped: + self.stream.writeln('Skipped %s: %s' % (test.name, msg)) + for test, msg in result.warned: + self.stream.writeln('Warned %s: %s' % (test.name, msg)) + for test, msg in result.failures: + self.stream.writeln('Failed %s: %s' % (test.name, msg)) + for test, msg in result.errors: + self.stream.writeln('Errored %s: %s' % (test.name, msg)) + + self._runner._checkhglib('Tested') + + # When '--retest' is enabled, only failure tests run. At this point + # "result.testsRun" holds the count of failure test that has run. But + # as while printing output, we have subtracted the skipped and ignored + # count from "result.testsRun". Therefore, to make the count remain + # the same, we need to add skipped and ignored count in here. + if self._runner.options.retest: + result.testsRun = result.testsRun + skipped + ignored + + # This differs from unittest's default output in that we don't count + # skipped and ignored tests as part of the total test count. + self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.' + % (result.testsRun - skipped - ignored, + skipped + ignored, warned, failed)) + if failed: + self.stream.writeln('python hash seed: %s' % + os.environ['PYTHONHASHSEED']) + if self._runner.options.time: + self.printtimes(result.times) + + return result + + def printtimes(self, times): + self.stream.writeln('# Producing time report') + times.sort(key=lambda t: (t[1], t[0]), reverse=True) + cols = '%7.3f %s' + self.stream.writeln('%-7s %s' % ('Time', 'Test')) + for test, timetaken in times: + self.stream.writeln(cols % (timetaken, test)) + +class TestRunner(object): + """Holds context for executing tests. + + Tests rely on a lot of state. This object holds it for them. + """ - if (ret != 0 or out != refout) and not skipped and not options.debug: - # Save errors to a file for diagnosis - f = open(err, "wb") - for line in out: - f.write(line) - f.close() + # Programs required to run tests. + REQUIREDTOOLS = [ + os.path.basename(sys.executable), + 'diff', + 'grep', + 'unzip', + 'gunzip', + 'bunzip2', + 'sed', + ] + + # Maps file extensions to test class. + TESTTYPES = [ + ('.py', PythonTest), + ('.t', TTest), + ] - if skipped: - if out is None: # debug mode: nothing to parse - missing = ['unknown'] - failed = None - else: - missing, failed = parsehghaveoutput(out) - if not missing: - missing = ['irrelevant'] - if failed: - result = fail("hghave failed checking for %s" % failed[-1], ret) - skipped = False + def __init__(self): + self.options = None + self._testdir = None + self._hgtmp = None + self._installdir = None + self._bindir = None + self._tmpbinddir = None + self._pythondir = None + self._coveragefile = None + self._createdfiles = [] + self._hgpath = None + + def run(self, args, parser=None): + """Run the test suite.""" + oldmask = os.umask(022) + try: + parser = parser or getparser() + options, args = parseargs(args, parser) + self.options = options + + self._checktools() + tests = self.findtests(args) + return self._run(tests) + finally: + os.umask(oldmask) + + def _run(self, tests): + if self.options.random: + random.shuffle(tests) else: - result = skip(missing[-1]) - elif ret == 'timeout': - result = fail("timed out", ret) - elif out != refout: - info = {} - if not options.nodiff: - iolock.acquire() - if options.view: - os.system("%s %s %s" % (options.view, ref, err)) - else: - info = showdiff(refout, out, ref, err) - iolock.release() - msg = "" - if info.get('servefail'): msg += "serve failed and " - if ret: - msg += "output changed and " + describe(ret) - else: - msg += "output changed" - result = fail(msg, ret) - elif ret: - result = fail(describe(ret), ret) - else: - result = success() - - if not options.verbose: - iolock.acquire() - sys.stdout.write(result[0]) - sys.stdout.flush() - iolock.release() + # keywords for slow tests + slow = 'svn gendoc check-code-hg'.split() + def sortkey(f): + # run largest tests first, as they tend to take the longest + try: + val = -os.stat(f).st_size + except OSError, e: + if e.errno != errno.ENOENT: + raise + return -1e9 # file does not exist, tell early + for kw in slow: + if kw in f: + val *= 10 + return val + tests.sort(key=sortkey) - if not options.keep_tmpdir: - shutil.rmtree(threadtmp, True) - return result - -_hgpath = None - -def _gethgpath(): - """Return the path to the mercurial package that is actually found by - the current Python interpreter.""" - global _hgpath - if _hgpath is not None: - return _hgpath + self._testdir = os.environ['TESTDIR'] = os.getcwd() - cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"' - pipe = os.popen(cmd % PYTHON) - try: - _hgpath = pipe.read().strip() - finally: - pipe.close() - return _hgpath - -def _checkhglib(verb): - """Ensure that the 'mercurial' package imported by python is - the one we expect it to be. If not, print a warning to stderr.""" - expecthg = os.path.join(PYTHONDIR, 'mercurial') - actualhg = _gethgpath() - if os.path.abspath(actualhg) != os.path.abspath(expecthg): - sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' - ' (expected %s)\n' - % (verb, actualhg, expecthg)) - -results = {'.':[], '!':[], '~': [], 's':[], 'i':[]} -times = [] -iolock = threading.Lock() -abort = False + if 'PYTHONHASHSEED' not in os.environ: + # use a random python hash seed all the time + # we do the randomness ourself to know what seed is used + os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) -def scheduletests(options, tests): - jobs = options.jobs - done = queue.Queue() - running = 0 - count = 0 - global abort - - def job(test, count): - try: - done.put(runone(options, test, count)) - except KeyboardInterrupt: - pass - except: # re-raises - done.put(('!', test, 'run-test raised an error, see traceback')) - raise - - try: - while tests or running: - if not done.empty() or running == jobs or not tests: - try: - code, test, msg = done.get(True, 1) - results[code].append((test, msg)) - if options.first and code not in '.si': - break - except queue.Empty: - continue - running -= 1 - if tests and not running == jobs: - test = tests.pop(0) - if options.loop: - tests.append(test) - t = threading.Thread(target=job, name=test, args=(test, count)) - t.start() - running += 1 - count += 1 - except KeyboardInterrupt: - abort = True - -def runtests(options, tests): - try: - if INST: - installhg(options) - _checkhglib("Testing") - else: - usecorrectpython() + if self.options.tmpdir: + self.options.keep_tmpdir = True + tmpdir = self.options.tmpdir + if os.path.exists(tmpdir): + # Meaning of tmpdir has changed since 1.3: we used to create + # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if + # tmpdir already exists. + print "error: temp dir %r already exists" % tmpdir + return 1 - if options.restart: - orig = list(tests) - while tests: - if os.path.exists(tests[0] + ".err"): - break - tests.pop(0) - if not tests: - print "running all tests" - tests = orig - - scheduletests(options, tests) - - failed = len(results['!']) - warned = len(results['~']) - tested = len(results['.']) + failed + warned - skipped = len(results['s']) - ignored = len(results['i']) + # Automatically removing tmpdir sounds convenient, but could + # really annoy anyone in the habit of using "--tmpdir=/tmp" + # or "--tmpdir=$HOME". + #vlog("# Removing temp dir", tmpdir) + #shutil.rmtree(tmpdir) + os.makedirs(tmpdir) + else: + d = None + if os.name == 'nt': + # without this, we get the default temp dir location, but + # in all lowercase, which causes troubles with paths (issue3490) + d = os.getenv('TMP') + tmpdir = tempfile.mkdtemp('', 'hgtests.', d) + self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir) - print - if not options.noskips: - for s in results['s']: - print "Skipped %s: %s" % s - for s in results['~']: - print "Warned %s: %s" % s - for s in results['!']: - print "Failed %s: %s" % s - _checkhglib("Tested") - print "# Ran %d tests, %d skipped, %d warned, %d failed." % ( - tested, skipped + ignored, warned, failed) - if results['!']: - print 'python hash seed:', os.environ['PYTHONHASHSEED'] - if options.time: - outputtimes(options) - - if options.anycoverage: - outputcoverage(options) - except KeyboardInterrupt: - failed = True - print "\ninterrupted!" + if self.options.with_hg: + self._installdir = None + self._bindir = os.path.dirname(os.path.realpath( + self.options.with_hg)) + self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin') + os.makedirs(self._tmpbindir) - if failed: - return 1 - if warned: - return 80 - -testtypes = [('.py', pytest, '.out'), - ('.t', tsttest, '')] - -def main(args, parser=None): - parser = parser or getparser() - (options, args) = parseargs(args, parser) - os.umask(022) - - checktools() - - if not args: - if options.changed: - proc = Popen4('hg st --rev "%s" -man0 .' % options.changed, - None, 0) - stdout, stderr = proc.communicate() - args = stdout.strip('\0').split('\0') + # This looks redundant with how Python initializes sys.path from + # the location of the script being executed. Needed because the + # "hg" specified by --with-hg is not the only Python script + # executed in the test suite that needs to import 'mercurial' + # ... which means it's not really redundant at all. + self._pythondir = self._bindir else: - args = os.listdir(".") + self._installdir = os.path.join(self._hgtmp, "install") + self._bindir = os.environ["BINDIR"] = \ + os.path.join(self._installdir, "bin") + self._tmpbindir = self._bindir + self._pythondir = os.path.join(self._installdir, "lib", "python") + + os.environ["BINDIR"] = self._bindir + os.environ["PYTHON"] = PYTHON + + path = [self._bindir] + os.environ["PATH"].split(os.pathsep) + if self._tmpbindir != self._bindir: + path = [self._tmpbindir] + path + os.environ["PATH"] = os.pathsep.join(path) - tests = [t for t in args - if os.path.basename(t).startswith("test-") - and (t.endswith(".py") or t.endswith(".t"))] + # Include TESTDIR in PYTHONPATH so that out-of-tree extensions + # can run .../tests/run-tests.py test-foo where test-foo + # adds an extension to HGRC. Also include run-test.py directory to + # import modules like heredoctest. + pypath = [self._pythondir, self._testdir, + os.path.abspath(os.path.dirname(__file__))] + # We have to augment PYTHONPATH, rather than simply replacing + # it, in case external libraries are only available via current + # PYTHONPATH. (In particular, the Subversion bindings on OS X + # are in /opt/subversion.) + oldpypath = os.environ.get(IMPL_PATH) + if oldpypath: + pypath.append(oldpypath) + os.environ[IMPL_PATH] = os.pathsep.join(pypath) + + self._coveragefile = os.path.join(self._testdir, '.coverage') + + vlog("# Using TESTDIR", self._testdir) + vlog("# Using HGTMP", self._hgtmp) + vlog("# Using PATH", os.environ["PATH"]) + vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) + + try: + return self._runtests(tests) or 0 + finally: + time.sleep(.1) + self._cleanup() + + def findtests(self, args): + """Finds possible test files from arguments. - if options.random: - random.shuffle(tests) - else: - # keywords for slow tests - slow = 'svn gendoc check-code-hg'.split() - def sortkey(f): - # run largest tests first, as they tend to take the longest - try: - val = -os.stat(f).st_size - except OSError, e: - if e.errno != errno.ENOENT: - raise - return -1e9 # file does not exist, tell early - for kw in slow: - if kw in f: - val *= 10 - return val - tests.sort(key=sortkey) + If you wish to inject custom tests into the test harness, this would + be a good function to monkeypatch or override in a derived class. + """ + if not args: + if self.options.changed: + proc = Popen4('hg st --rev "%s" -man0 .' % + self.options.changed, None, 0) + stdout, stderr = proc.communicate() + args = stdout.strip('\0').split('\0') + else: + args = os.listdir('.') + + return [t for t in args + if os.path.basename(t).startswith('test-') + and (t.endswith('.py') or t.endswith('.t'))] + + def _runtests(self, tests): + try: + if self._installdir: + self._installhg() + self._checkhglib("Testing") + else: + self._usecorrectpython() + + if self.options.restart: + orig = list(tests) + while tests: + if os.path.exists(tests[0] + ".err"): + break + tests.pop(0) + if not tests: + print "running all tests" + tests = orig + + tests = [self._gettest(t, i) for i, t in enumerate(tests)] + + failed = False + warned = False + + suite = TestSuite(self._testdir, + jobs=self.options.jobs, + whitelist=self.options.whitelisted, + blacklist=self.options.blacklist, + retest=self.options.retest, + keywords=self.options.keywords, + loop=self.options.loop, + tests=tests) + verbosity = 1 + if self.options.verbose: + verbosity = 2 + runner = TextTestRunner(self, verbosity=verbosity) + result = runner.run(suite) + + if result.failures: + failed = True + if result.warned: + warned = True - if 'PYTHONHASHSEED' not in os.environ: - # use a random python hash seed all the time - # we do the randomness ourself to know what seed is used - os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) + if self.options.anycoverage: + self._outputcoverage() + except KeyboardInterrupt: + failed = True + print "\ninterrupted!" + + if failed: + return 1 + if warned: + return 80 + + def _gettest(self, test, count): + """Obtain a Test by looking at its filename. + + Returns a Test instance. The Test may not be runnable if it doesn't + map to a known type. + """ + lctest = test.lower() + testcls = Test + + for ext, cls in self.TESTTYPES: + if lctest.endswith(ext): + testcls = cls + break + + refpath = os.path.join(self._testdir, test) + tmpdir = os.path.join(self._hgtmp, 'child%d' % count) + + return testcls(refpath, tmpdir, + keeptmpdir=self.options.keep_tmpdir, + debug=self.options.debug, + timeout=self.options.timeout, + startport=self.options.port + count * 3, + extraconfigopts=self.options.extra_config_opt, + py3kwarnings=self.options.py3k_warnings, + shell=self.options.shell) + + def _cleanup(self): + """Clean up state from this test invocation.""" + + if self.options.keep_tmpdir: + return + + vlog("# Cleaning up HGTMP", self._hgtmp) + shutil.rmtree(self._hgtmp, True) + for f in self._createdfiles: + try: + os.remove(f) + except OSError: + pass - global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE - TESTDIR = os.environ["TESTDIR"] = os.getcwd() - if options.tmpdir: - options.keep_tmpdir = True - tmpdir = options.tmpdir - if os.path.exists(tmpdir): - # Meaning of tmpdir has changed since 1.3: we used to create - # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if - # tmpdir already exists. - print "error: temp dir %r already exists" % tmpdir - return 1 + def _usecorrectpython(self): + """Configure the environment to use the appropriate Python in tests.""" + # Tests must use the same interpreter as us or bad things will happen. + pyexename = sys.platform == 'win32' and 'python.exe' or 'python' + if getattr(os, 'symlink', None): + vlog("# Making python executable in test path a symlink to '%s'" % + sys.executable) + mypython = os.path.join(self._tmpbindir, pyexename) + try: + if os.readlink(mypython) == sys.executable: + return + os.unlink(mypython) + except OSError, err: + if err.errno != errno.ENOENT: + raise + if self._findprogram(pyexename) != sys.executable: + try: + os.symlink(sys.executable, mypython) + self._createdfiles.append(mypython) + except OSError, err: + # child processes may race, which is harmless + if err.errno != errno.EEXIST: + raise + else: + exedir, exename = os.path.split(sys.executable) + vlog("# Modifying search path to find %s as %s in '%s'" % + (exename, pyexename, exedir)) + path = os.environ['PATH'].split(os.pathsep) + while exedir in path: + path.remove(exedir) + os.environ['PATH'] = os.pathsep.join([exedir] + path) + if not self._findprogram(pyexename): + print "WARNING: Cannot find %s in search path" % pyexename - # Automatically removing tmpdir sounds convenient, but could - # really annoy anyone in the habit of using "--tmpdir=/tmp" - # or "--tmpdir=$HOME". - #vlog("# Removing temp dir", tmpdir) - #shutil.rmtree(tmpdir) - os.makedirs(tmpdir) - else: - d = None + def _installhg(self): + """Install hg into the test environment. + + This will also configure hg with the appropriate testing settings. + """ + vlog("# Performing temporary installation of HG") + installerrs = os.path.join("tests", "install.err") + compiler = '' + if self.options.compiler: + compiler = '--compiler ' + self.options.compiler + pure = self.options.pure and "--pure" or "" + py3 = '' + if sys.version_info[0] == 3: + py3 = '--c2to3' + + # Run installer in hg root + script = os.path.realpath(sys.argv[0]) + hgroot = os.path.dirname(os.path.dirname(script)) + os.chdir(hgroot) + nohome = '--home=""' if os.name == 'nt': - # without this, we get the default temp dir location, but - # in all lowercase, which causes troubles with paths (issue3490) - d = os.getenv('TMP') - tmpdir = tempfile.mkdtemp('', 'hgtests.', d) - HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir) + # The --home="" trick works only on OS where os.sep == '/' + # because of a distutils convert_path() fast-path. Avoid it at + # least on Windows for now, deal with .pydistutils.cfg bugs + # when they happen. + nohome = '' + cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all' + ' build %(compiler)s --build-base="%(base)s"' + ' install --force --prefix="%(prefix)s"' + ' --install-lib="%(libdir)s"' + ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' + % {'exe': sys.executable, 'py3': py3, 'pure': pure, + 'compiler': compiler, + 'base': os.path.join(self._hgtmp, "build"), + 'prefix': self._installdir, 'libdir': self._pythondir, + 'bindir': self._bindir, + 'nohome': nohome, 'logfile': installerrs}) + vlog("# Running", cmd) + if os.system(cmd) == 0: + if not self.options.verbose: + os.remove(installerrs) + else: + f = open(installerrs) + for line in f: + print line, + f.close() + sys.exit(1) + os.chdir(self._testdir) - if options.with_hg: - INST = None - BINDIR = os.path.dirname(os.path.realpath(options.with_hg)) - TMPBINDIR = os.path.join(HGTMP, 'install', 'bin') - os.makedirs(TMPBINDIR) + self._usecorrectpython() - # This looks redundant with how Python initializes sys.path from - # the location of the script being executed. Needed because the - # "hg" specified by --with-hg is not the only Python script - # executed in the test suite that needs to import 'mercurial' - # ... which means it's not really redundant at all. - PYTHONDIR = BINDIR - else: - INST = os.path.join(HGTMP, "install") - BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin") - TMPBINDIR = BINDIR - PYTHONDIR = os.path.join(INST, "lib", "python") + if self.options.py3k_warnings and not self.options.anycoverage: + vlog("# Updating hg command to enable Py3k Warnings switch") + f = open(os.path.join(self._bindir, 'hg'), 'r') + lines = [line.rstrip() for line in f] + lines[0] += ' -3' + f.close() + f = open(os.path.join(self._bindir, 'hg'), 'w') + for line in lines: + f.write(line + '\n') + f.close() - os.environ["BINDIR"] = BINDIR - os.environ["PYTHON"] = PYTHON + hgbat = os.path.join(self._bindir, 'hg.bat') + if os.path.isfile(hgbat): + # hg.bat expects to be put in bin/scripts while run-tests.py + # installation layout put it in bin/ directly. Fix it + f = open(hgbat, 'rb') + data = f.read() + f.close() + if '"%~dp0..\python" "%~dp0hg" %*' in data: + data = data.replace('"%~dp0..\python" "%~dp0hg" %*', + '"%~dp0python" "%~dp0hg" %*') + f = open(hgbat, 'wb') + f.write(data) + f.close() + else: + print 'WARNING: cannot fix hg.bat reference to python.exe' + + if self.options.anycoverage: + custom = os.path.join(self._testdir, 'sitecustomize.py') + target = os.path.join(self._pythondir, 'sitecustomize.py') + vlog('# Installing coverage trigger to %s' % target) + shutil.copyfile(custom, target) + rc = os.path.join(self._testdir, '.coveragerc') + vlog('# Installing coverage rc to %s' % rc) + os.environ['COVERAGE_PROCESS_START'] = rc + fn = os.path.join(self._installdir, '..', '.coverage') + os.environ['COVERAGE_FILE'] = fn - path = [BINDIR] + os.environ["PATH"].split(os.pathsep) - if TMPBINDIR != BINDIR: - path = [TMPBINDIR] + path - os.environ["PATH"] = os.pathsep.join(path) + def _checkhglib(self, verb): + """Ensure that the 'mercurial' package imported by python is + the one we expect it to be. If not, print a warning to stderr.""" + if ((self._bindir == self._pythondir) and + (self._bindir != self._tmpbindir)): + # The pythondir has been infered from --with-hg flag. + # We cannot expect anything sensible here + return + expecthg = os.path.join(self._pythondir, 'mercurial') + actualhg = self._gethgpath() + if os.path.abspath(actualhg) != os.path.abspath(expecthg): + sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' + ' (expected %s)\n' + % (verb, actualhg, expecthg)) + def _gethgpath(self): + """Return the path to the mercurial package that is actually found by + the current Python interpreter.""" + if self._hgpath is not None: + return self._hgpath + + cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"' + pipe = os.popen(cmd % PYTHON) + try: + self._hgpath = pipe.read().strip() + finally: + pipe.close() + + return self._hgpath + + def _outputcoverage(self): + """Produce code coverage output.""" + vlog('# Producing coverage report') + os.chdir(self._pythondir) - # Include TESTDIR in PYTHONPATH so that out-of-tree extensions - # can run .../tests/run-tests.py test-foo where test-foo - # adds an extension to HGRC. Also include run-test.py directory to import - # modules like heredoctest. - pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))] - # We have to augment PYTHONPATH, rather than simply replacing - # it, in case external libraries are only available via current - # PYTHONPATH. (In particular, the Subversion bindings on OS X - # are in /opt/subversion.) - oldpypath = os.environ.get(IMPL_PATH) - if oldpypath: - pypath.append(oldpypath) - os.environ[IMPL_PATH] = os.pathsep.join(pypath) + def covrun(*args): + cmd = 'coverage %s' % ' '.join(args) + vlog('# Running: %s' % cmd) + os.system(cmd) + + covrun('-c') + omit = ','.join(os.path.join(x, '*') for x in + [self._bindir, self._testdir]) + covrun('-i', '-r', '"--omit=%s"' % omit) # report + if self.options.htmlcov: + htmldir = os.path.join(self._testdir, 'htmlcov') + covrun('-i', '-b', '"--directory=%s"' % htmldir, + '"--omit=%s"' % omit) + if self.options.annotate: + adir = os.path.join(self._testdir, 'annotated') + if not os.path.isdir(adir): + os.mkdir(adir) + covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) - COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") + def _findprogram(self, program): + """Search PATH for a executable program""" + for p in os.environ.get('PATH', os.defpath).split(os.pathsep): + name = os.path.join(p, program) + if os.name == 'nt' or os.access(name, os.X_OK): + return name + return None - vlog("# Using TESTDIR", TESTDIR) - vlog("# Using HGTMP", HGTMP) - vlog("# Using PATH", os.environ["PATH"]) - vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) - - try: - return runtests(options, tests) or 0 - finally: - time.sleep(.1) - cleanup(options) + def _checktools(self): + """Ensure tools required to run tests are present.""" + for p in self.REQUIREDTOOLS: + if os.name == 'nt' and not p.endswith('.exe'): + p += '.exe' + found = self._findprogram(p) + if found: + vlog("# Found prerequisite", p, "at", found) + else: + print "WARNING: Did not find prerequisite tool: %s " % p if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + runner = TestRunner() + sys.exit(runner.run(sys.argv[1:]))
--- a/tests/test-add.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-add.t Fri Jun 13 17:42:04 2014 -0500 @@ -107,6 +107,7 @@ M a ? a.orig $ hg resolve -m a + no more unresolved files $ hg ci -m merge Issue683: peculiarity with hg revert of an removed then added file
--- a/tests/test-backout.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-backout.t Fri Jun 13 17:42:04 2014 -0500 @@ -11,6 +11,8 @@ [255] basic operation +(this also tests that editor is invoked if the commit message is not +specified explicitly) $ echo a > a $ hg commit -d '0 0' -A -m a @@ -18,8 +20,19 @@ $ echo b >> a $ hg commit -d '1 0' -m b - $ hg backout -d '2 0' tip --tool=true + $ hg status --rev tip --rev "tip^1" + M a + $ HGEDITOR=cat hg backout -d '2 0' tip --tool=true reverting a + Backed out changeset a820f4f40a57 + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'default' + HG: changed a changeset 2:2929462c3dff backs out changeset 1:a820f4f40a57 $ cat a a @@ -31,6 +44,8 @@ update: (current) file that was removed is recreated +(this also tests that editor is not invoked if the commit message is +specified explicitly) $ cd .. $ hg init remove @@ -43,7 +58,7 @@ $ hg rm a $ hg commit -d '1 0' -m b - $ hg backout -d '2 0' tip --tool=true + $ HGEDITOR=cat hg backout -d '2 0' tip --tool=true -m "Backed out changeset 76862dcce372" adding a changeset 2:de31bdc76c0d backs out changeset 1:76862dcce372 $ cat a @@ -340,9 +355,21 @@ update: (current) with --merge +(this also tests that editor is invoked if '--edit' is specified +explicitly regardless of '--message') + $ hg update -qC - $ hg backout --merge -d '3 0' -r 1 -m 'backout on branch1' --tool=true + $ HGEDITOR=cat hg backout --merge -d '3 0' -r 1 -m 'backout on branch1' --tool=true --edit removing file1 + backout on branch1 + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'branch2' + HG: removed file1 created new head changeset 3:d4e8f6db59fb backs out changeset 1:bf1602f437f3 merging with changeset 3:d4e8f6db59fb @@ -490,6 +517,7 @@ merging foo my foo@b71750c4b0fd+ other foo@a30dd8addae3 ancestor foo@913609522437 premerge successful + no more unresolved files $ hg status M foo ? foo.orig
--- a/tests/test-bookmarks-current.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-bookmarks-current.t Fri Jun 13 17:42:04 2014 -0500 @@ -24,6 +24,7 @@ $ hg update X 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark X) list bookmarks @@ -71,6 +72,7 @@ Verify that switching to Z updates the current bookmark: $ hg update Z 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + (activating bookmark Z) $ hg bookmark Y 0:719295282060 * Z -1:000000000000 @@ -78,6 +80,7 @@ Switch back to Y for the remaining tests in this file: $ hg update Y 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark Y) delete bookmarks @@ -152,6 +155,7 @@ $ hg bookmark X@2 -r 2 $ hg update X 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + (activating bookmark X) $ hg bookmarks * X 0:719295282060 X@1 1:cc586d725fbe
--- a/tests/test-bookmarks-merge.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-bookmarks-merge.t Fri Jun 13 17:42:04 2014 -0500 @@ -32,6 +32,7 @@ $ hg up -C 3 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (leaving bookmark c) $ echo d > d $ hg add d $ hg commit -m'd' @@ -54,6 +55,7 @@ $ hg up -C 4 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark e) $ hg merge abort: heads are bookmarked - please merge with an explicit rev (run 'hg heads' to see all heads) @@ -63,6 +65,7 @@ $ hg up -C e 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (activating bookmark e) $ hg merge abort: no matching bookmark to merge - please merge with an explicit rev or bookmark (run 'hg heads' to see all heads) @@ -72,6 +75,7 @@ $ hg up -C 4 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark e) $ echo f > f $ hg commit -Am "f" adding f @@ -96,6 +100,7 @@ $ hg up -C e 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (activating bookmark e) $ hg bookmarks b 1:d2ae7f538514 c 3:b8f96cf4688b @@ -114,6 +119,7 @@ $ hg up -C 6 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark e) $ echo g > g $ hg commit -Am 'g' adding g
--- a/tests/test-bookmarks-pushpull.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-bookmarks-pushpull.t Fri Jun 13 17:42:04 2014 -0500 @@ -274,7 +274,7 @@ $ hg push http://localhost:$HGPORT2/ pushing to http://localhost:$HGPORT2/ searching for changes - abort: push creates new remote head c922c0139ca0! + abort: push creates new remote head c922c0139ca0 with bookmark 'Y'! (merge or see "hg help push" for details about pushing new heads) [255] $ hg -R ../a book @@ -290,7 +290,7 @@ $ hg push http://localhost:$HGPORT2/ pushing to http://localhost:$HGPORT2/ searching for changes - abort: push creates new remote head c922c0139ca0! + abort: push creates new remote head c922c0139ca0 with bookmark 'Y'! (merge or see "hg help push" for details about pushing new heads) [255] $ hg -R ../a book @@ -411,6 +411,7 @@ $ hg commit -m 'add bar' $ hg co "tip^" 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark @) $ hg book add-foo $ hg book -r tip add-bar Note: this push *must* push only a single changeset, as that's the point
--- a/tests/test-bookmarks-strip.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-bookmarks-strip.t Fri Jun 13 17:42:04 2014 -0500 @@ -38,6 +38,7 @@ $ hg update -r -2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (leaving bookmark test2) $ echo eee>>qqq.txt
--- a/tests/test-bookmarks.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-bookmarks.t Fri Jun 13 17:42:04 2014 -0500 @@ -118,6 +118,7 @@ $ hg update X 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + (activating bookmark X) $ echo c > c $ hg add c $ hg commit -m 2 @@ -501,6 +502,7 @@ $ hg update updating to active bookmark Z 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark Z) $ hg bookmarks X2 1:925d80f479bb Y 2:db815d6d32e6 @@ -513,6 +515,7 @@ moving bookmark 'Y' forward from db815d6d32e6 $ hg -R cloned-bookmarks-update update Y 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark Y) $ hg -R cloned-bookmarks-update pull --update . pulling from . searching for changes @@ -582,6 +585,7 @@ $ hg book should-end-on-two $ hg co --clean 4 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark should-end-on-two) $ hg book four $ hg --config extensions.mq= strip 3 saved backup bundle to * (glob)
--- a/tests/test-bundle2.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-bundle2.t Fri Jun 13 17:42:04 2014 -0500 @@ -47,9 +47,7 @@ > op.ui.write('received ping request (id %i)\n' % part.id) > if op.reply is not None and 'ping-pong' in op.reply.capabilities: > op.ui.write_err('replying to ping request (id %i)\n' % part.id) - > rpart = bundle2.bundlepart('test:pong', - > [('in-reply-to', str(part.id))]) - > op.reply.addpart(rpart) + > op.reply.newpart('test:pong', [('in-reply-to', str(part.id))]) > > @bundle2.parthandler('test:debugreply') > def debugreply(op, part): @@ -66,6 +64,7 @@ > @command('bundle2', > [('', 'param', [], 'stream level parameter'), > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'), + > ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'), > ('', 'parts', False, 'include some arbitrary parts to the bundle'), > ('', 'reply', False, 'produce a reply bundle'), > ('', 'pushrace', False, 'includes a check:head part with unknown nodes'), @@ -83,11 +82,12 @@ > > if opts['reply']: > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville' - > bundler.addpart(bundle2.bundlepart('b2x:replycaps', data=capsstring)) + > bundler.newpart('b2x:replycaps', data=capsstring) > > if opts['pushrace']: - > dummynode = '01234567890123456789' - > bundler.addpart(bundle2.bundlepart('b2x:check:heads', data=dummynode)) + > # also serve to test the assignement of data outside of init + > part = bundler.newpart('b2x:check:heads') + > part.data = '01234567890123456789' > > revs = opts['rev'] > if 'rev' in opts: @@ -99,31 +99,27 @@ > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)] > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing) > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None) - > part = bundle2.bundlepart('b2x:changegroup', data=cg.getchunks()) - > bundler.addpart(part) + > bundler.newpart('b2x:changegroup', data=cg.getchunks()) > > if opts['parts']: - > part = bundle2.bundlepart('test:empty') - > bundler.addpart(part) + > bundler.newpart('test:empty') > # add a second one to make sure we handle multiple parts - > part = bundle2.bundlepart('test:empty') - > bundler.addpart(part) - > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG) - > bundler.addpart(part) - > part = bundle2.bundlepart('test:debugreply') - > bundler.addpart(part) - > part = bundle2.bundlepart('test:math', - > [('pi', '3.14'), ('e', '2.72')], - > [('cooking', 'raw')], - > '42') - > bundler.addpart(part) + > bundler.newpart('test:empty') + > bundler.newpart('test:song', data=ELEPHANTSSONG) + > bundler.newpart('test:debugreply') + > mathpart = bundler.newpart('test:math') + > mathpart.addparam('pi', '3.14') + > mathpart.addparam('e', '2.72') + > mathpart.addparam('cooking', 'raw', mandatory=False) + > mathpart.data = '42' + > # advisory known part with unknown mandatory param + > bundler.newpart('test:song', [('randomparam','')]) > if opts['unknown']: - > part = bundle2.bundlepart('test:UNKNOWN', - > data='some random content') - > bundler.addpart(part) + > bundler.newpart('test:UNKNOWN', data='some random content') + > if opts['unknownparams']: + > bundler.newpart('test:SONG', [('randomparams', '')]) > if opts['parts']: - > part = bundle2.bundlepart('test:ping') - > bundler.addpart(part) + > bundler.newpart('test:ping') > > if path is None: > file = sys.stdout @@ -144,7 +140,7 @@ > unbundler = bundle2.unbundle20(ui, sys.stdin) > op = bundle2.processbundle(repo, unbundler, lambda: tr) > tr.close() - > except KeyError, exc: + > except error.BundleValueError, exc: > raise util.Abort('missing support for %s' % exc) > except error.PushRaced, exc: > raise util.Abort('push race: %s' % exc) @@ -170,7 +166,7 @@ > unbundler = bundle2.unbundle20(ui, sys.stdin) > try: > params = unbundler.params - > except KeyError, exc: + > except error.BundleValueError, exc: > raise util.Abort('unknown parameters: %s' % exc) > ui.write('options count: %i\n' % len(params)) > for key in sorted(params): @@ -194,9 +190,12 @@ > bundle2-exp=True > [ui] > ssh=python "$TESTDIR/dummyssh" + > logtemplate={rev}:{node|short} {phase} {author} {desc|firstline} > [web] > push_ssl = false > allow_push = * + > [phases] + > publish=False > EOF The extension requires a repo (currently unused) @@ -308,7 +307,7 @@ --------------------------------------------------- $ hg bundle2 --param 'Gravity' | hg statbundle2 - abort: unknown parameters: 'Gravity' + abort: unknown parameters: Stream Parameter - Gravity [255] Test debug output @@ -372,6 +371,7 @@ bundle part: "test:song" bundle part: "test:debugreply" bundle part: "test:math" + bundle part: "test:song" bundle part: "test:ping" end of bundle @@ -380,7 +380,7 @@ test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc) test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc) Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko - Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc) + Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x1d test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc) $ hg statbundle2 < ../parts.hg2 @@ -405,11 +405,15 @@ mandatory: 2 advisory: 1 payload: 2 bytes + :test:song: + mandatory: 1 + advisory: 0 + payload: 0 bytes :test:ping: mandatory: 0 advisory: 0 payload: 0 bytes - parts count: 6 + parts count: 7 $ hg statbundle2 --debug < ../parts.hg2 start processing of HG2X stream @@ -463,9 +467,18 @@ payload chunk size: 2 payload chunk size: 0 payload: 2 bytes + part header size: 29 + part type: "test:song" + part id: "5" + part parameters: 1 + :test:song: + mandatory: 1 + advisory: 0 + payload chunk size: 0 + payload: 0 bytes part header size: 16 part type: "test:ping" - part id: "5" + part id: "6" part parameters: 0 :test:ping: mandatory: 0 @@ -474,7 +487,7 @@ payload: 0 bytes part header size: 0 end of bundle2 stream - parts count: 6 + parts count: 7 Test actual unbundling of test part ======================================= @@ -489,13 +502,13 @@ part type: "test:empty" part id: "0" part parameters: 0 - ignoring unknown advisory part 'test:empty' + ignoring unsupported advisory part test:empty payload chunk size: 0 part header size: 17 part type: "test:empty" part id: "1" part parameters: 0 - ignoring unknown advisory part 'test:empty' + ignoring unsupported advisory part test:empty payload chunk size: 0 part header size: 16 part type: "test:song" @@ -519,15 +532,22 @@ part type: "test:math" part id: "4" part parameters: 3 - ignoring unknown advisory part 'test:math' + ignoring unsupported advisory part test:math payload chunk size: 2 payload chunk size: 0 + part header size: 29 + part type: "test:song" + part id: "5" + part parameters: 1 + found a handler for part 'test:song' + ignoring unsupported advisory part test:song - randomparam + payload chunk size: 0 part header size: 16 part type: "test:ping" - part id: "5" + part id: "6" part parameters: 0 found a handler for part 'test:ping' - received ping request (id 5) + received ping request (id 6) payload chunk size: 0 part header size: 0 end of bundle2 stream @@ -546,7 +566,17 @@ Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko. debugreply: no reply 0 unread bytes - abort: missing support for 'test:unknown' + abort: missing support for test:unknown + [255] + +Unbundle with an unknown mandatory part parameters +(should abort) + + $ hg bundle2 --unknownparams ../unknown.hg2 + + $ hg unbundle2 < ../unknown.hg2 + 0 unread bytes + abort: missing support for test:song - randomparams [255] unbundle with a reply @@ -572,9 +602,9 @@ debugreply: 'babar' debugreply: 'celeste' debugreply: 'ping-pong' - \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1f (esc) - b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc) - replying to ping request (id 6) + \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x1f (esc) + b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc) + replying to ping request (id 7) \x00\x00\x00\x00\x00\x00 (no-eol) (esc) The reply is valid @@ -613,8 +643,8 @@ remote: debugreply: 'babar' remote: debugreply: 'celeste' remote: debugreply: 'ping-pong' - remote: received ping request (id 6) - remote: replying to ping request (id 6) + remote: received ping request (id 7) + remote: replying to ping request (id 7) 0 unread bytes Test push race detection @@ -637,57 +667,23 @@ (run 'hg heads' to see heads, 'hg merge' to merge) $ hg log -G - o changeset: 8:02de42196ebe - | tag: tip - | parent: 6:24b6387c8c8c - | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: H - | - | o changeset: 7:eea13746799a - |/| parent: 6:24b6387c8c8c - | | parent: 5:9520eea781bc - | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: G - | | - o | changeset: 6:24b6387c8c8c - | | parent: 1:cd010b8cd998 - | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: F - | | - | o changeset: 5:9520eea781bc - |/ parent: 1:cd010b8cd998 - | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: E + o 8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H | - | o changeset: 4:32af7686d403 - | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: D + | o 7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G + |/| + o | 6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F | | - | o changeset: 3:5fddd98957c8 - | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: C + | o 5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + | o 4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D | | - | o changeset: 2:42ccdea3bb16 - |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: B - | - o changeset: 1:cd010b8cd998 - parent: -1:000000000000 - user: Nicolas Dumazet <nicdumz.commits@gmail.com> - date: Sat Apr 30 15:24:48 2011 +0200 - summary: A + | o 3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C + | | + | o 2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B + |/ + o 1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A - @ changeset: 0:3903775176ed - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: a + @ 0:3903775176ed draft test a $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2 @@ -768,6 +764,7 @@ clone --pull $ cd .. + $ hg -R main phase --public cd010b8cd998 $ hg clone main other --pull --rev 9520eea781bc adding changesets adding manifests @@ -776,20 +773,14 @@ updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R other log -G - @ changeset: 1:9520eea781bc - | tag: tip - | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: E + @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E | - o changeset: 0:cd010b8cd998 - user: Nicolas Dumazet <nicdumz.commits@gmail.com> - date: Sat Apr 30 15:24:48 2011 +0200 - summary: A + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A pull + $ hg -R main phase --public 9520eea781bc $ hg -R other pull -r 24b6387c8c8c pulling from $TESTTMP/main (glob) searching for changes @@ -798,15 +789,43 @@ adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg -R other log -G + o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F + | + | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A + +pull empty (with phase movement) + + $ hg -R main phase --public 24b6387c8c8c + $ hg -R other pull -r 24b6387c8c8c + pulling from $TESTTMP/main (glob) + no changes found + $ hg -R other log -G + o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F + | + | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A + pull empty $ hg -R other pull -r 24b6387c8c8c pulling from $TESTTMP/main (glob) no changes found + $ hg -R other log -G + o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F + | + | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A + push + $ hg -R main phase --public eea13746799a $ hg -R main push other --rev eea13746799a pushing to other searching for changes @@ -814,6 +833,15 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 0 changes to 0 files (-1 heads) + $ hg -R other log -G + o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G + |\ + | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F + | | + @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A + pull over ssh @@ -850,12 +878,28 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files + $ hg -R other log -G + o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C + | + o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B + | + | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H + | | + | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G + | |/| + | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F + |/ / + | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A + push over http $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log $ cat other.pid >> $DAEMON_PIDS + $ hg -R main phase --public 32af7686d403 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 pushing to http://localhost:$HGPORT2/ searching for changes @@ -868,51 +912,21 @@ Check final content. $ hg -R other log -G - o changeset: 7:32af7686d403 - | tag: tip - | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: D + o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> D | - o changeset: 6:5fddd98957c8 - | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: C + o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> C | - o changeset: 5:42ccdea3bb16 - | parent: 0:cd010b8cd998 - | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: B + o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> B | - | o changeset: 4:02de42196ebe - | | parent: 2:24b6387c8c8c - | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: H + | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H | | - | | o changeset: 3:eea13746799a - | |/| parent: 2:24b6387c8c8c - | | | parent: 1:9520eea781bc - | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | | date: Sat Apr 30 15:24:48 2011 +0200 - | | | summary: G - | | | - | o | changeset: 2:24b6387c8c8c - |/ / parent: 0:cd010b8cd998 - | | user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: F - | | - | @ changeset: 1:9520eea781bc - |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com> - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: E - | - o changeset: 0:cd010b8cd998 - user: Nicolas Dumazet <nicdumz.commits@gmail.com> - date: Sat Apr 30 15:24:48 2011 +0200 - summary: A + | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G + | |/| + | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F + |/ / + | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A Error Handling @@ -938,14 +952,12 @@ > reason = pushop.ui.config('failpush', 'reason', None) > part = None > if reason == 'abort': - > part = bundle2.bundlepart('test:abort') + > bundler.newpart('test:abort') > if reason == 'unknown': - > part = bundle2.bundlepart('TEST:UNKNOWN') + > bundler.newpart('TEST:UNKNOWN') > if reason == 'race': > # 20 Bytes of crap - > part = bundle2.bundlepart('b2x:check:heads', data='01234567890123456789') - > if part is not None: - > bundler.addpart(part) + > bundler.newpart('b2x:check:heads', data='01234567890123456789') > return extradata > > @bundle2.parthandler("test:abort") @@ -1015,19 +1027,19 @@ $ hg -R main push other -r e7ec4e813ba6 pushing to other searching for changes - abort: missing support for 'test:unknown' + abort: missing support for test:unknown [255] $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6 pushing to ssh://user@dummy/other searching for changes - abort: missing support for "'test:unknown'" + abort: missing support for test:unknown [255] $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6 pushing to http://localhost:$HGPORT2/ searching for changes - abort: missing support for "'test:unknown'" + abort: missing support for test:unknown [255] Doing the actual push: race
--- a/tests/test-check-code-hg.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-check-code-hg.t Fri Jun 13 17:42:04 2014 -0500 @@ -1,36 +1,17 @@ +#if test-repo + $ check_code="$TESTDIR"/../contrib/check-code.py $ cd "$TESTDIR"/.. - $ if hg identify -q > /dev/null 2>&1; then : - > else - > echo "skipped: not a Mercurial working dir" >&2 - > exit 80 - > fi - -Prepare check for Python files without py extension - - $ cp \ - > hg \ - > hgweb.cgi \ - > contrib/convert-repo \ - > contrib/dumprevlog \ - > contrib/hgweb.fcgi \ - > contrib/hgweb.wsgi \ - > contrib/simplemerge \ - > contrib/undumprevlog \ - > i18n/hggettext \ - > i18n/posplit \ - > tests/hghave \ - > tests/dummyssh \ - > "$TESTTMP"/ - $ for f in "$TESTTMP"/*; do mv "$f" "$f.py"; done New errors are not allowed. Warnings are strongly discouraged. (The writing "no-che?k-code" is for not skipping this file when checking.) - $ { hg manifest 2>/dev/null; ls "$TESTTMP"/*.py | sed 's-\\-/-g'; } | + $ 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) + +#endif
--- a/tests/test-check-code.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-check-code.t Fri Jun 13 17:42:04 2014 -0500 @@ -284,3 +284,19 @@ > 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]
--- a/tests/test-check-pyflakes.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-check-pyflakes.t Fri Jun 13 17:42:04 2014 -0500 @@ -5,7 +5,7 @@ run pyflakes on all tracked files ending in .py or without a file ending (skipping binary file random-seed) - $ hg manifest 2>/dev/null | egrep "\.py$|^[^.]*$" | grep -v /random_seed$ \ + $ hg locate 'set:**.py or grep("^!#.*python")' 2>/dev/null \ > | xargs pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py" contrib/win32/hgwebdir_wsgi.py:*: 'win32traceutil' imported but unused (glob) setup.py:*: 'sha' imported but unused (glob) @@ -17,5 +17,6 @@ tests/hghave.py:*: 'pygments' imported but unused (glob) tests/hghave.py:*: 'ssl' imported but unused (glob) contrib/win32/hgwebdir_wsgi.py:93: 'from isapi.install import *' used; unable to detect undefined names (glob) + tests/filterpyflakes.py:58: undefined name 'undefinedname' #endif
--- a/tests/test-commandserver.py.out Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-commandserver.py.out Fri Jun 13 17:42:04 2014 -0500 @@ -177,6 +177,7 @@ runcommand update -C 0 1 files updated, 0 files merged, 2 files removed, 0 files unresolved +(leaving bookmark bm3) runcommand commit -Am. a created new head runcommand log -Gq
--- a/tests/test-commit-amend.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-commit-amend.t Fri Jun 13 17:42:04 2014 -0500 @@ -586,9 +586,10 @@ merging cc incomplete! (edit conflicts, then use 'hg resolve --mark') [1] $ hg resolve -m cc + no more unresolved files $ hg ci -m 'merge bar' $ hg log --config diff.git=1 -pr . - changeset: 23:d51446492733 + changeset: 23:93cd4445f720 tag: tip parent: 22:30d96aeaf27b parent: 21:1aa437659d19 @@ -603,11 +604,11 @@ --- a/cc +++ b/cc @@ -1,1 +1,5 @@ - +<<<<<<< local + +<<<<<<< local: 30d96aeaf27b - test: aa dd +======= +cc - +>>>>>>> other + +>>>>>>> other: 1aa437659d19 bar - test: aazzcc diff --git a/z b/zz rename from z rename to zz @@ -620,7 +621,7 @@ cc not renamed $ hg ci --amend -m 'merge bar (amend message)' $ hg log --config diff.git=1 -pr . - changeset: 24:59de3dce7a79 + changeset: 24:832b50f2c271 tag: tip parent: 22:30d96aeaf27b parent: 21:1aa437659d19 @@ -635,11 +636,11 @@ --- a/cc +++ b/cc @@ -1,1 +1,5 @@ - +<<<<<<< local + +<<<<<<< local: 30d96aeaf27b - test: aa dd +======= +cc - +>>>>>>> other + +>>>>>>> other: 1aa437659d19 bar - test: aazzcc diff --git a/z b/zz rename from z rename to zz @@ -653,7 +654,7 @@ $ hg mv zz z $ hg ci --amend -m 'merge bar (undo rename)' $ hg log --config diff.git=1 -pr . - changeset: 26:7fb89c461f81 + changeset: 26:bdafc5c72f74 tag: tip parent: 22:30d96aeaf27b parent: 21:1aa437659d19 @@ -668,11 +669,11 @@ --- a/cc +++ b/cc @@ -1,1 +1,5 @@ - +<<<<<<< local + +<<<<<<< local: 30d96aeaf27b - test: aa dd +======= +cc - +>>>>>>> other + +>>>>>>> other: 1aa437659d19 bar - test: aazzcc $ hg debugrename z z not renamed @@ -689,9 +690,9 @@ $ echo aa >> aaa $ hg ci -m 'merge bar again' $ hg log --config diff.git=1 -pr . - changeset: 28:982d7a34ffee + changeset: 28:32f19415b634 tag: tip - parent: 26:7fb89c461f81 + parent: 26:bdafc5c72f74 parent: 27:4c94d5bc65f5 user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -724,9 +725,9 @@ $ hg mv aaa aa $ hg ci --amend -m 'merge bar again (undo rename)' $ hg log --config diff.git=1 -pr . - changeset: 30:522688c0e71b + changeset: 30:1e2a06b3d312 tag: tip - parent: 26:7fb89c461f81 + parent: 26:bdafc5c72f74 parent: 27:4c94d5bc65f5 user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -764,9 +765,9 @@ use (c)hanged version or (d)elete? c $ hg ci -m 'merge bar (with conflicts)' $ hg log --config diff.git=1 -pr . - changeset: 33:5f9904c491b8 + changeset: 33:97a298b0c59f tag: tip - parent: 32:01780b896f58 + parent: 32:3d78ce4226b8 parent: 31:67db8847a540 user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -776,9 +777,9 @@ $ hg rm aa $ hg ci --amend -m 'merge bar (with conflicts, amended)' $ hg log --config diff.git=1 -pr . - changeset: 35:6ce0c89781a3 + changeset: 35:6de0c1bde1c8 tag: tip - parent: 32:01780b896f58 + parent: 32:3d78ce4226b8 parent: 31:67db8847a540 user: test date: Thu Jan 01 00:00:00 1970 +0000
--- a/tests/test-commit-unresolved.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-commit-unresolved.t Fri Jun 13 17:42:04 2014 -0500 @@ -41,6 +41,7 @@ Mark the conflict as resolved and commit $ hg resolve -m A + no more unresolved files $ hg commit -m "Merged" $ cd ..
--- a/tests/test-completion.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-completion.t Fri Jun 13 17:42:04 2014 -0500 @@ -212,10 +212,10 @@ serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos summary: remote - update: clean, check, date, rev + update: clean, check, date, rev, tool addremove: similarity, include, exclude, dry-run archive: no-decode, prefix, rev, type, subrepos, include, exclude - backout: merge, parent, rev, tool, include, exclude, message, logfile, date, user + backout: merge, parent, rev, edit, tool, include, exclude, message, logfile, date, user bisect: reset, good, bad, skip, extend, command, noupdate bookmarks: force, rev, delete, rename, inactive branch: force, clean @@ -262,7 +262,7 @@ heads: rev, topo, active, closed, style, template help: extension, command, keyword identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure - import: strip, base, edit, force, no-commit, bypass, exact, import-branch, message, logfile, date, user, similarity + import: strip, base, edit, force, no-commit, bypass, partial, exact, 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 locate: rev, print0, fullpath, include, exclude manifest: rev, all
--- a/tests/test-conflict.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-conflict.t Fri Jun 13 17:42:04 2014 -0500 @@ -22,12 +22,52 @@ 32e80765d7fe+75234512624c+ tip $ cat a + <<<<<<< local: 32e80765d7fe - test: branch2 + something else + ======= + something + >>>>>>> other: 75234512624c - test: branch1 + + $ hg status + M a + ? a.orig + +Verify custom conflict markers + + $ hg up -q --clean . + $ printf "\n[ui]\nmergemarkertemplate={author} {rev}\n" >> .hg/hgrc + + $ hg merge 1 + merging a + warning: conflicts during merge. + merging a incomplete! (edit conflicts, then use 'hg resolve --mark') + 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] + + $ cat a + <<<<<<< local: test 2 + something else + ======= + something + >>>>>>> other: test 1 + +Verify basic conflict markers + + $ hg up -q --clean . + $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc + + $ hg merge 1 + merging a + warning: conflicts during merge. + merging a incomplete! (edit conflicts, then use 'hg resolve --mark') + 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] + + $ cat a <<<<<<< local something else ======= something >>>>>>> other - - $ hg status - M a - ? a.orig
--- a/tests/test-context.py Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-context.py Fri Jun 13 17:42:04 2014 -0500 @@ -21,7 +21,7 @@ # test memctx with non-ASCII commit message def filectxfn(repo, memctx, path): - return context.memfilectx("foo", "") + return context.memfilectx(repo, "foo", "") ctx = context.memctx(repo, ['tip', None], encoding.tolocal("Gr\xc3\xbcezi!"),
--- a/tests/test-convert-hg-sink.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-convert-hg-sink.t Fri Jun 13 17:42:04 2014 -0500 @@ -16,8 +16,10 @@ $ echo file > foo/file $ hg ci -qAm 'add foo/file' $ hg tag some-tag + $ hg tag -l local-tag $ hg log changeset: 3:593cbf6fb2b4 + tag: local-tag tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -390,3 +392,148 @@ o 0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a $ cd .. + +Two way tests + + $ hg init 0 + $ echo f > 0/f + $ echo a > 0/a-only + $ echo b > 0/b-only + $ hg -R 0 ci -Aqm0 + + $ cat << EOF > filemap-a + > exclude b-only + > EOF + $ cat << EOF > filemap-b + > exclude a-only + > EOF + $ hg convert --filemap filemap-a 0 a + initializing destination a repository + scanning source... + sorting... + converting... + 0 0 + $ hg -R a up -q + $ echo a > a/f + $ hg -R a ci -ma + + $ hg convert --filemap filemap-b 0 b + initializing destination b repository + scanning source... + sorting... + converting... + 0 0 + $ hg -R b up -q + $ echo b > b/f + $ hg -R b ci -mb + + $ tail */.hg/shamap + ==> 0/.hg/shamap <== + 86f3f774ffb682bffb5dc3c1d3b3da637cb9a0d6 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a + dd9f218eb91fb857f2a62fe023e1d64a4e7812fe 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a + + ==> a/.hg/shamap <== + 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a 86f3f774ffb682bffb5dc3c1d3b3da637cb9a0d6 + + ==> b/.hg/shamap <== + 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a dd9f218eb91fb857f2a62fe023e1d64a4e7812fe + + $ hg convert a 0 + scanning source... + sorting... + converting... + 0 a + + $ hg convert b 0 + scanning source... + sorting... + converting... + 0 b + + $ hg -R 0 log -G + o changeset: 2:637fbbbe96b6 + | tag: tip + | parent: 0:8a028c7c77f6 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: b + | + | o changeset: 1:ec7b9c96e692 + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: a + | + @ changeset: 0:8a028c7c77f6 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 0 + + $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1:: + scanning source... + sorting... + converting... + + $ hg -R 0 up -r1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo f >> 0/f + $ hg -R 0 ci -mx + + $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1:: + scanning source... + sorting... + converting... + 0 x + + $ hg -R a log -G -T '{rev} {desc|firstline} ({files})\n' + o 2 x (f) + | + @ 1 a (f) + | + o 0 0 (a-only f) + + $ hg -R a mani -r tip + a-only + f + +An additional round, demonstrating that unchanged files don't get converted + + $ echo f >> 0/f + $ echo f >> 0/a-only + $ hg -R 0 ci -m "extra f+a-only change" + + $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1:: + scanning source... + sorting... + converting... + 0 extra f+a-only change + + $ hg -R a log -G -T '{rev} {desc|firstline} ({files})\n' + o 3 extra f+a-only change (f) + | + o 2 x (f) + | + @ 1 a (f) + | + o 0 0 (a-only f) + + +Conversion after rollback + + $ hg -R a rollback -f + repository tip rolled back to revision 2 (undo commit) + + $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1:: + scanning source... + sorting... + converting... + 0 extra f+a-only change + + $ hg -R a log -G -T '{rev} {desc|firstline} ({files})\n' + o 3 extra f+a-only change (f) + | + o 2 x (f) + | + @ 1 a (f) + | + o 0 0 (a-only f) +
--- a/tests/test-convert-hg-source.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-convert-hg-source.t Fri Jun 13 17:42:04 2014 -0500 @@ -24,6 +24,7 @@ $ hg ci -m 'merge local copy' -d '3 0' $ hg up -C 1 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark premerge1) $ hg bookmark premerge2 $ hg merge 2 merging foo and baz to baz
--- a/tests/test-convert-svn-sink.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-convert-svn-sink.t Fri Jun 13 17:42:04 2014 -0500 @@ -352,6 +352,7 @@ [1] $ hg --cwd b revert -r 2 b $ hg --cwd b resolve -m b + no more unresolved files $ hg --cwd b ci -d '5 0' -m 'merge' Expect 4 changes
--- a/tests/test-copy-move-merge.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-copy-move-merge.t Fri Jun 13 17:42:04 2014 -0500 @@ -31,16 +31,16 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6 + preserving a for resolve of b + preserving a for resolve of c + removing a b: remote moved from a -> m - preserving a for resolve of b - c: remote moved from a -> m - preserving a for resolve of c - removing a updating: b 1/2 files (50.00%) picked tool 'internal:merge' for b (binary False symlink False) merging a and b to b my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc premerge successful + c: remote moved from a -> m updating: c 2/2 files (100.00%) picked tool 'internal:merge' for c (binary False symlink False) merging a and c to c
--- a/tests/test-double-merge.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-double-merge.t Fri Jun 13 17:42:04 2014 -0500 @@ -35,15 +35,15 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: e6dc8efe11cc, local: 6a0df1dad128+, remote: 484bf6903104 + preserving foo for resolve of bar + preserving foo for resolve of foo bar: remote copied from foo -> m - preserving foo for resolve of bar - foo: versions differ -> m - preserving foo for resolve of foo updating: bar 1/2 files (50.00%) picked tool 'internal:merge' for bar (binary False symlink False) merging foo and bar to bar my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc premerge successful + foo: versions differ -> m updating: foo 2/2 files (100.00%) picked tool 'internal:merge' for foo (binary False symlink False) merging foo
--- a/tests/test-encoding-align.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-encoding-align.t Fri Jun 13 17:42:04 2014 -0500 @@ -16,19 +16,18 @@ > f = file('l', 'w'); f.write(l); f.close() > # instant extension to show list of options > f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8 + > from mercurial import cmdutil + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > + > @command('showoptlist', + > [('s', 'opt1', '', 'short width' + ' %(s)s' * 8, '%(s)s'), + > ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'), + > ('l', 'opt3', '', 'long width' + ' %(l)s' * 8, '%(l)s')], + > '') > def showoptlist(ui, repo, *pats, **opts): > '''dummy command to show option descriptions''' > return 0 - > cmdtable = { - > 'showoptlist': - > (showoptlist, - > [('s', 'opt1', '', 'short width' + ' %(s)s' * 8, '%(s)s'), - > ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'), - > ('l', 'opt3', '', 'long width' + ' %(l)s' * 8, '%(l)s') - > ], - > "" - > ) - > } > """ % globals()) > f.close() > EOF
--- a/tests/test-encoding-textwrap.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-encoding-textwrap.t Fri Jun 13 17:42:04 2014 -0500 @@ -6,7 +6,13 @@ define commands to display help text $ cat << EOF > show.py + > from mercurial import cmdutil + > + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > > # Japanese full-width characters: + > @command('show_full_ja', [], '') > def show_full_ja(ui, **opts): > u'''\u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 > @@ -16,6 +22,7 @@ > ''' > > # Japanese half-width characters: + > @command('show_half_ja', [], '') > def show_half_ja(ui, *opts): > u'''\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 > @@ -25,6 +32,7 @@ > ''' > > # Japanese ambiguous-width characters: + > @command('show_ambig_ja', [], '') > def show_ambig_ja(ui, **opts): > u'''\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb > @@ -34,6 +42,7 @@ > ''' > > # Russian ambiguous-width characters: + > @command('show_ambig_ru', [], '') > def show_ambig_ru(ui, **opts): > u'''\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > @@ -41,13 +50,6 @@ > > \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > ''' - > - > cmdtable = { - > 'show_full_ja': (show_full_ja, [], ""), - > 'show_half_ja': (show_half_ja, [], ""), - > 'show_ambig_ja': (show_ambig_ja, [], ""), - > 'show_ambig_ru': (show_ambig_ru, [], ""), - > } > EOF "COLUMNS=60" means that there is no lines which has grater than 58 width
--- a/tests/test-extension.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-extension.t Fri Jun 13 17:42:04 2014 -0500 @@ -2,7 +2,10 @@ $ cat > foobar.py <<EOF > import os - > from mercurial import commands + > from mercurial import cmdutil, commands + > + > cmdtable = {} + > command = cmdutil.command(cmdtable) > > def uisetup(ui): > ui.write("uisetup called\\n") @@ -11,17 +14,14 @@ > ui.write("reposetup called for %s\\n" % os.path.basename(repo.root)) > ui.write("ui %s= repo.ui\\n" % (ui == repo.ui and "=" or "!")) > + > @command('foo', [], 'hg foo') > def foo(ui, *args, **kwargs): > ui.write("Foo\\n") > + > @command('bar', [], 'hg bar') > def bar(ui, *args, **kwargs): > ui.write("Bar\\n") > - > cmdtable = { - > "foo": (foo, [], "hg foo"), - > "bar": (bar, [], "hg bar"), - > } - > > commands.norepo += ' bar' > EOF $ abspath=`pwd`/foobar.py @@ -288,21 +288,22 @@ $ cat > debugextension.py <<EOF > '''only debugcommands > ''' + > from mercurial import cmdutil + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > + > @command('debugfoobar', [], 'hg debugfoobar') > def debugfoobar(ui, repo, *args, **opts): > "yet another debug command" > pass > + > @command('foo', [], 'hg foo') > def foo(ui, repo, *args, **opts): > """yet another foo command > > This command has been DEPRECATED since forever. > """ > pass - > - > cmdtable = { - > "debugfoobar": (debugfoobar, (), "hg debugfoobar"), - > "foo": (foo, (), "hg foo") - > } > EOF $ debugpath=`pwd`/debugextension.py $ echo "debugextension = $debugpath" >> $HGRCPATH @@ -475,15 +476,15 @@ Test help topic with same name as extension $ cat > multirevs.py <<EOF - > from mercurial import commands + > from mercurial import cmdutil, commands + > cmdtable = {} + > command = cmdutil.command(cmdtable) > """multirevs extension > Big multi-line module docstring.""" + > @command('multirevs', [], 'ARG') > def multirevs(ui, repo, arg, *args, **opts): > """multirevs command""" > pass - > cmdtable = { - > "multirevs": (multirevs, [], 'ARG') - > } > commands.norepo += ' multirevs' > EOF $ echo "multirevs = multirevs.py" >> $HGRCPATH @@ -532,13 +533,15 @@ $ cat > debugissue811.py <<EOF > '''show all loaded extensions > ''' - > from mercurial import extensions, commands + > from mercurial import cmdutil, commands, extensions + > cmdtable = {} + > command = cmdutil.command(cmdtable) > + > @command('debugextensions', [], 'hg debugextensions') > def debugextensions(ui): > "yet another debug command" > ui.write("%s\n" % '\n'.join([x for x, y in extensions.extensions()])) > - > cmdtable = {"debugextensions": (debugextensions, (), "hg debugextensions")} > commands.norepo += " debugextensions" > EOF $ echo "debugissue811 = $debugpath" >> $HGRCPATH @@ -618,8 +621,8 @@ > EOF $ hg --config extensions.path=./path.py help foo > /dev/null warning: error finding commands in $TESTTMP/hgext/forest.py (glob) - hg: unknown command 'foo' - warning: error finding commands in $TESTTMP/hgext/forest.py (glob) + abort: no such help topic: foo + (try "hg help --keyword foo") [255] $ cat > throw.py <<EOF
--- a/tests/test-fetch.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-fetch.t Fri Jun 13 17:42:04 2014 -0500 @@ -68,8 +68,9 @@ $ cat a/hg.pid >> "$DAEMON_PIDS" fetch over http, no auth +(this also tests that editor is invoked if '--edit' is specified) - $ hg --cwd d fetch http://localhost:$HGPORT/ + $ HGEDITOR=cat hg --cwd d fetch --edit http://localhost:$HGPORT/ pulling from http://localhost:$HGPORT/ searching for changes adding changesets @@ -80,13 +81,29 @@ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved merging with 1:d36c0562f908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + Automated merge with http://localhost:$HGPORT/ + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch merge + HG: branch 'default' + HG: changed c new changeset 3:* merges remote changes with local (glob) $ hg --cwd d tip --template '{desc}\n' Automated merge with http://localhost:$HGPORT/ + $ hg --cwd d status --rev 'tip^1' --rev tip + A c + $ hg --cwd d status --rev 'tip^2' --rev tip + A b fetch over http with auth (should be hidden in desc) +(this also tests that editor is not invoked if '--edit' is not +specified, even though commit message is not specified explicitly) - $ hg --cwd e fetch http://user:password@localhost:$HGPORT/ + $ HGEDITOR=cat hg --cwd e fetch http://user:password@localhost:$HGPORT/ pulling from http://user:***@localhost:$HGPORT/ searching for changes adding changesets
--- a/tests/test-fileset.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-fileset.t Fri Jun 13 17:42:04 2014 -0500 @@ -154,6 +154,7 @@ b2 $ echo e > b2 $ hg resolve -m b2 + no more unresolved files $ fileset 'resolved()' b2 $ fileset 'unresolved()'
--- a/tests/test-gpg.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-gpg.t Fri Jun 13 17:42:04 2014 -0500 @@ -16,8 +16,17 @@ $ hg sigs - $ hg sign 0 + $ HGEDITOR=cat hg sign -e 0 signing 0:e63c23eaa88a + Added signature for changeset e63c23eaa88a + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'default' + HG: added .hgsigs $ hg sigs hgtest 0:e63c23eaa88ae77967edcf4ea194d31167c478b0
--- a/tests/test-graft.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-graft.t Fri Jun 13 17:42:04 2014 -0500 @@ -76,10 +76,24 @@ $ hg revert a Graft a rename: +(this also tests that editor is invoked if '--edit' is specified) - $ hg graft 2 -u foo + $ hg status --rev "2^1" --rev 2 + A b + R a + $ HGEDITOR=cat hg graft 2 -u foo --edit grafting revision 2 merging a and b to b + 2 + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: foo + HG: branch 'default' + HG: changed b + HG: removed a $ hg export tip --git # HG changeset patch # User foo @@ -114,6 +128,7 @@ Graft out of order, skipping a merge and a duplicate +(this also tests that editor is not invoked if '--edit' is not specified) $ hg graft 1 5 4 3 'merge()' 2 -n skipping ungraftable merge revision 6 @@ -123,7 +138,7 @@ grafting revision 4 grafting revision 3 - $ hg graft 1 5 4 3 'merge()' 2 --debug + $ HGEDITOR=cat hg graft 1 5 4 3 'merge()' 2 --debug skipping ungraftable merge revision 6 scanning for duplicate grafts skipping revision 2 (already grafted to 7) @@ -137,8 +152,8 @@ resolving manifests branchmerge: True, force: True, partial: False ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6 + preserving b for resolve of b b: local copied/moved from a -> m - preserving b for resolve of b updating: b 1/1 files (100.00%) picked tool 'internal:merge' for b (binary False symlink False) merging b and a to b @@ -150,22 +165,22 @@ resolving manifests branchmerge: True, force: True, partial: False ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746 - b: keep -> k e: remote is newer -> g getting e updating: e 1/1 files (100.00%) + b: keep -> k e grafting revision 4 searching for copies back to rev 1 resolving manifests branchmerge: True, force: True, partial: False ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d - b: keep -> k + preserving e for resolve of e d: remote is newer -> g - e: versions differ -> m - preserving e for resolve of e getting d updating: d 1/2 files (50.00%) + b: keep -> k + e: versions differ -> m updating: e 2/2 files (100.00%) picked tool 'internal:merge' for e (binary False symlink False) merging e @@ -220,6 +235,7 @@ $ echo b > e $ hg resolve -m e + no more unresolved files Continue with a revision should fail: @@ -354,6 +370,7 @@ [255] $ hg resolve --all merging a + no more unresolved files $ hg graft -c grafting revision 1 $ hg export tip --git @@ -382,6 +399,7 @@ [255] $ hg resolve --all merging a and b to b + no more unresolved files $ hg graft -c grafting revision 2 $ hg export tip --git
--- a/tests/test-help.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-help.t Fri Jun 13 17:42:04 2014 -0500 @@ -596,30 +596,8 @@ show changed files in the working directory $ hg help foo - hg: unknown command 'foo' - Mercurial Distributed SCM - - basic commands: - - add add the specified files on the next commit - annotate show changeset information by line for each file - clone make a copy of an existing repository - commit commit the specified files or all outstanding changes - diff diff repository (or selected files) - export dump the header and diffs for one or more changesets - forget forget the specified files on the next commit - init create a new repository in the given directory - log show revision history of entire repository or files - merge merge working directory with another revision - pull pull changes from the specified source - push push changes to the specified destination - remove remove the specified files on the next commit - serve start stand-alone webserver - status show changed files in the working directory - summary summarize working directory state - update update working directory (or switch revisions) - - use "hg help" for the full list of commands or "hg -v" for details + abort: no such help topic: foo + (try "hg help --keyword foo") [255] $ hg skjdfks @@ -652,19 +630,20 @@ $ cat > helpext.py <<EOF > import os - > from mercurial import commands + > from mercurial import cmdutil, commands + > + > cmdtable = {} + > command = cmdutil.command(cmdtable) > + > @command('nohelp', + > [('', 'longdesc', 3, 'x'*90), + > ('n', '', None, 'normal desc'), + > ('', 'newline', '', 'line1\nline2')], + > 'hg nohelp') + > @command('debugoptDEP', [('', 'dopt', None, 'option is DEPRECATED')]) > def nohelp(ui, *args, **kwargs): > pass > - > cmdtable = { - > "debugoptDEP": (nohelp, [('', 'dopt', None, 'option is DEPRECATED')],), - > "nohelp": (nohelp, [('', 'longdesc', 3, 'x'*90), - > ('n', '', None, 'normal desc'), - > ('', 'newline', '', 'line1\nline2'), - > ], "hg nohelp"), - > } - > > commands.norepo += ' nohelp' > EOF $ echo '[extensions]' >> $HGRCPATH @@ -987,6 +966,20 @@ qclone clone main and patch repository at same time +Test unfound topic + + $ hg help nonexistingtopicthatwillneverexisteverever + abort: no such help topic: nonexistingtopicthatwillneverexisteverever + (try "hg help --keyword nonexistingtopicthatwillneverexisteverever") + [255] + +Test unfound keyword + + $ hg help --keyword nonexistingwordthatwillneverexisteverever + abort: no matches + (try "hg help" for a list of topics) + [255] + Test omit indicating for help $ cat > addverboseitems.py <<EOF
--- a/tests/test-hgweb-commands.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-hgweb-commands.t Fri Jun 13 17:42:04 2014 -0500 @@ -31,10 +31,15 @@ $ hg ci -l msg $ rm msg - $ echo [graph] >> .hg/hgrc - $ echo default.width = 3 >> .hg/hgrc - $ echo stable.width = 3 >> .hg/hgrc - $ echo stable.color = FF0000 >> .hg/hgrc + $ cat > .hg/hgrc <<EOF + > [graph] + > default.width = 3 + > stable.width = 3 + > stable.color = FF0000 + > [websub] + > append = s|(.*)|\1(websub)| + > EOF + $ hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log $ cat hg.pid >> $DAEMON_PIDS $ hg log -G --template '{rev}:{node|short} {desc}\n' @@ -95,7 +100,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>branch commit with null character: </td> + <td>branch commit with null character: (websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -138,7 +143,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>branch</td> + <td>branch(websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -181,7 +186,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>Added tag 1.0 for changeset 2ef0ac749a14</td> + <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -224,7 +229,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>base</td> + <td>base(websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -235,6 +240,180 @@ </entry> </feed> + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=rss' + 200 Script output follows + + <?xml version="1.0" encoding="ascii"?> + <rss version="2.0"> + <channel> + <link>http://*:$HGPORT/</link> (glob) + <language>en-us</language> + + <title>test Changelog</title> + <description>test Changelog</description> + <item> + <title>[unstable] branch commit with null character: </title> + <guid isPermaLink="true">http://*:$HGPORT/rev/cad8025a2e87</guid> (glob) + <link>http://*:$HGPORT/rev/cad8025a2e87</link> (glob) + <description> + <![CDATA[ + <table> + <tr> + <th style="text-align:left;">changeset</th> + <td>cad8025a2e87</td> + </tr> + <tr> + <th style="text-align:left;">branch</th> + <td>unstable</td> + </tr> + <tr> + <th style="text-align:left;">bookmark</th> + <td>something</td> + </tr> + <tr> + <th style="text-align:left;">tag</th> + <td>tip</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">user</th> + <td>test</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">description</th> + <td>branch commit with null character: (websub)</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">files</th> + <td></td> + </tr> + </table> + ]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + <item> + <title>[stable] branch</title> + <guid isPermaLink="true">http://*:$HGPORT/rev/1d22e65f027e</guid> (glob) + <link>http://*:$HGPORT/rev/1d22e65f027e</link> (glob) + <description> + <![CDATA[ + <table> + <tr> + <th style="text-align:left;">changeset</th> + <td>1d22e65f027e</td> + </tr> + <tr> + <th style="text-align:left;">branch</th> + <td>stable</td> + </tr> + <tr> + <th style="text-align:left;">bookmark</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;">tag</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">user</th> + <td>test</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">description</th> + <td>branch(websub)</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">files</th> + <td>foo<br /></td> + </tr> + </table> + ]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + <item> + <title>[default] Added tag 1.0 for changeset 2ef0ac749a14</title> + <guid isPermaLink="true">http://*:$HGPORT/rev/a4f92ed23982</guid> (glob) + <link>http://*:$HGPORT/rev/a4f92ed23982</link> (glob) + <description> + <![CDATA[ + <table> + <tr> + <th style="text-align:left;">changeset</th> + <td>a4f92ed23982</td> + </tr> + <tr> + <th style="text-align:left;">branch</th> + <td>default</td> + </tr> + <tr> + <th style="text-align:left;">bookmark</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;">tag</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">user</th> + <td>test</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">description</th> + <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">files</th> + <td>.hgtags<br /></td> + </tr> + </table> + ]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + <item> + <title>base</title> + <guid isPermaLink="true">http://*:$HGPORT/rev/2ef0ac749a14</guid> (glob) + <link>http://*:$HGPORT/rev/2ef0ac749a14</link> (glob) + <description> + <![CDATA[ + <table> + <tr> + <th style="text-align:left;">changeset</th> + <td>2ef0ac749a14</td> + </tr> + <tr> + <th style="text-align:left;">branch</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;">bookmark</th> + <td>anotherthing</td> + </tr> + <tr> + <th style="text-align:left;">tag</th> + <td>1.0</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">user</th> + <td>test</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">description</th> + <td>base(websub)</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">files</th> + <td>da/foo<br />foo<br /></td> + </tr> + </table> + ]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + + </channel> + </rss> (no-eol) $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=atom' 200 Script output follows @@ -281,7 +460,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>Added tag 1.0 for changeset 2ef0ac749a14</td> + <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -324,7 +503,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>base</td> + <td>base(websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -335,6 +514,100 @@ </entry> </feed> + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=rss' + 200 Script output follows + + <?xml version="1.0" encoding="ascii"?> + <rss version="2.0"> + <channel> + <link>http://*:$HGPORT/</link> (glob) + <language>en-us</language> + + <title>test Changelog</title> + <description>test Changelog</description> + <item> + <title>[default] Added tag 1.0 for changeset 2ef0ac749a14</title> + <guid isPermaLink="true">http://*:$HGPORT/rev/a4f92ed23982</guid> (glob) + <link>http://*:$HGPORT/rev/a4f92ed23982</link> (glob) + <description> + <![CDATA[ + <table> + <tr> + <th style="text-align:left;">changeset</th> + <td>a4f92ed23982</td> + </tr> + <tr> + <th style="text-align:left;">branch</th> + <td>default</td> + </tr> + <tr> + <th style="text-align:left;">bookmark</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;">tag</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">user</th> + <td>test</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">description</th> + <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">files</th> + <td>.hgtags<br /></td> + </tr> + </table> + ]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + <item> + <title>base</title> + <guid isPermaLink="true">http://*:$HGPORT/rev/2ef0ac749a14</guid> (glob) + <link>http://*:$HGPORT/rev/2ef0ac749a14</link> (glob) + <description> + <![CDATA[ + <table> + <tr> + <th style="text-align:left;">changeset</th> + <td>2ef0ac749a14</td> + </tr> + <tr> + <th style="text-align:left;">branch</th> + <td></td> + </tr> + <tr> + <th style="text-align:left;">bookmark</th> + <td>anotherthing</td> + </tr> + <tr> + <th style="text-align:left;">tag</th> + <td>1.0</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">user</th> + <td>test</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">description</th> + <td>base(websub)</td> + </tr> + <tr> + <th style="text-align:left;vertical-align:top;">files</th> + <td>da/foo<br />foo<br /></td> + </tr> + </table> + ]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + + </channel> + </rss> (no-eol) $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=atom' 200 Script output follows @@ -379,7 +652,7 @@ </tr> <tr> <th style="text-align:left;vertical-align:top;">description</th> - <td>base</td> + <td>base(websub)</td> </tr> <tr> <th style="text-align:left;vertical-align:top;">files</th> @@ -390,6 +663,27 @@ </entry> </feed> + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=rss' + 200 Script output follows + + <?xml version="1.0" encoding="ascii"?> + <rss version="2.0"> + <channel> + <link>http://*:$HGPORT/</link> (glob) + <language>en-us</language> + + <title>test: foo history</title> + <description>foo revision history</description> + <item> + <title>base</title> + <link>http://*:$HGPORT/log2ef0ac749a14/foo</link> (glob) + <description><![CDATA[base(websub)]]></description> + <author>test</author> + <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> + </item> + + </channel> + </rss> $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/' 200 Script output follows @@ -570,7 +864,7 @@ number or hash, or <a href="/help/revsets">revset expression</a>.</div> </form> - <div class="description">base</div> + <div class="description">base(websub)</div> <table id="changesetEntry"> <tr> @@ -992,7 +1286,7 @@ number or hash, or <a href="/help/revsets">revset expression</a>.</div> </form> - <div class="description">Added tag 1.0 for changeset 2ef0ac749a14</div> + <div class="description">Added tag 1.0 for changeset 2ef0ac749a14(websub)</div> <table id="changesetEntry"> <tr> @@ -1116,7 +1410,7 @@ number or hash, or <a href="/help/revsets">revset expression</a>.</div> </form> - <div class="description">branch</div> + <div class="description">branch(websub)</div> <table id="changesetEntry"> <tr>
--- a/tests/test-histedit-edit.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-histedit-edit.t Fri Jun 13 17:42:04 2014 -0500 @@ -156,7 +156,19 @@ update: 1 new changesets (update) hist: 1 remaining (histedit --continue) - $ HGEDITOR='true' hg histedit --continue +(test also that editor is invoked if histedit is continued for +"edit" action) + + $ HGEDITOR='cat' hg histedit --continue + f + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + 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-backup.hg (glob) @@ -200,8 +212,9 @@ > raise util.Abort('emulating unexpected abort') > repo.__class__ = commitfailure > EOF - $ cat > .hg/hgrc <<EOF + $ cat >> .hg/hgrc <<EOF > [extensions] + > # this failure occurs before editor invocation > commitfailure = $TESTTMP/commitfailure.py > EOF @@ -211,22 +224,87 @@ > echo "====" > echo "check saving last-message.txt" >> \$1 > EOF + +(test that editor is not invoked before transaction starting) + $ rm -f .hg/last-message.txt $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle > mess 1fd3b2fe7754 f > EOF 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + abort: emulating unexpected abort + $ cat .hg/last-message.txt + cat: .hg/last-message.txt: No such file or directory + [1] + + $ cat >> .hg/hgrc <<EOF + > [extensions] + > commitfailure = ! + > EOF + $ hg histedit --abort -q + +(test that editor is invoked and commit message is saved into +"last-message.txt") + + $ cat >> .hg/hgrc <<EOF + > [hooks] + > # this failure occurs after editor invocation + > pretxncommit.unexpectedabort = false + > EOF + + $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754 + A f + + $ rm -f .hg/last-message.txt + $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF + > mess 1fd3b2fe7754 f + > EOF + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + adding f ==== before editing f + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'default' + HG: added f ==== - abort: emulating unexpected abort + 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 f + + check saving last-message.txt - $ cat > .hg/hgrc <<EOF - > [extensions] - > commitfailure = ! +(test also that editor is invoked if histedit is continued for "message" +action) + + $ HGEDITOR=cat hg histedit --continue + f + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'default' + HG: added f + transaction abort! + rollback completed + note: commit message saved in .hg/last-message.txt + abort: pretxncommit.unexpectedabort hook exited with status 1 + [255] + + $ cat >> .hg/hgrc <<EOF + > [hooks] + > pretxncommit.unexpectedabort = > EOF $ hg histedit --abort -q
--- a/tests/test-histedit-fold-non-commute.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-histedit-fold-non-commute.t Fri Jun 13 17:42:04 2014 -0500 @@ -95,6 +95,7 @@ fix up $ echo 'I can haz no commute' > e $ hg resolve --mark e + no more unresolved files $ cat > cat.py <<EOF > import sys > print open(sys.argv[1]).read() @@ -129,6 +130,7 @@ just continue this time $ hg revert -r 'p1()' e $ hg resolve --mark e + no more unresolved files $ 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
--- a/tests/test-histedit-fold.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-histedit-fold.t Fri Jun 13 17:42:04 2014 -0500 @@ -217,6 +217,7 @@ U file $ hg revert -r 'p1()' file $ hg resolve --mark file + no more unresolved files $ hg histedit --continue 0 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/*-backup.hg (glob) @@ -276,6 +277,7 @@ > 5 > EOF $ hg resolve --mark file + no more unresolved files $ hg commit -m '+5.2' created new head $ echo 6 >> file
--- a/tests/test-histedit-non-commute.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-histedit-non-commute.t Fri Jun 13 17:42:04 2014 -0500 @@ -154,6 +154,7 @@ fix up $ echo 'I can haz no commute' > e $ hg resolve --mark e + no more unresolved files $ hg histedit --continue 2>&1 | fixbundle 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e @@ -167,6 +168,7 @@ just continue this time $ hg revert -r 'p1()' e $ hg resolve --mark e + no more unresolved files $ 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 @@ -239,6 +241,7 @@ $ echo 'I can haz no commute' > e $ hg resolve --mark e + no more unresolved files $ hg histedit --continue 2>&1 | fixbundle 0 files updated, 0 files merged, 0 files removed, 0 files unresolved merging e @@ -248,6 +251,7 @@ second edit also fails, but just continue $ hg revert -r 'p1()' e $ hg resolve --mark e + no more unresolved files $ 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
--- a/tests/test-import-bypass.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-import-bypass.t Fri Jun 13 17:42:04 2014 -0500 @@ -22,8 +22,10 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved Test importing an existing revision +(this also tests that editor is not invoked for '--bypass', if the +patch contains the commit message, regardless of '--edit') - $ hg import --bypass --exact ../test.diff + $ HGEDITOR=cat hg import --bypass --exact --edit ../test.diff applying ../test.diff $ shortlog o 1:4e322f7ce8e3 test 0 0 - foo - changea @@ -107,6 +109,8 @@ [255] Test commit editor +(this also tests that editor is invoked, if the patch doesn't contain +the commit message, regardless of '--edit') $ cat > ../test.diff <<EOF > diff -r 07f494440405 -r 4e322f7ce8e3 a @@ -131,10 +135,12 @@ [255] Test patch.eol is handled +(this also tests that editor is not invoked for '--bypass', if the +commit message is explicitly specified, regardless of '--edit') $ python -c 'file("a", "wb").write("a\r\n")' $ hg ci -m makeacrlf - $ hg import -m 'should fail because of eol' --bypass ../test.diff + $ HGEDITOR=cat hg import -m 'should fail because of eol' --edit --bypass ../test.diff applying ../test.diff patching file a Hunk #1 FAILED at 0
--- a/tests/test-import.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-import.t Fri Jun 13 17:42:04 2014 -0500 @@ -23,6 +23,8 @@ import exported patch +(this also tests that editor is not invoked, if the patch contains the +commit message and '--edit' is not specified) $ hg clone -r0 a b adding changesets @@ -31,7 +33,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd b import ../exported-tip.patch + $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch applying ../exported-tip.patch message and committer and date should be same @@ -47,6 +49,8 @@ import exported patch with external patcher +(this also tests that editor is invoked, if the '--edit' is specified, +regardless of the commit message in the patch) $ cat > dummypatch.py <<EOF > print 'patching file a' @@ -59,14 +63,25 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch + $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch applying ../exported-tip.patch + second change + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: someone + HG: branch 'default' + HG: changed a $ cat b/a line2 $ rm -r b import of plain diff should fail without message +(this also tests that editor is invoked, if the patch doesn't contain +the commit message, regardless of '--edit') $ hg clone -r0 a b adding changesets @@ -75,8 +90,16 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd b import ../diffed-tip.patch + $ HGEDITOR=cat hg --cwd b import ../diffed-tip.patch applying ../diffed-tip.patch + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'default' + HG: changed a abort: empty commit message [255] $ rm -r b @@ -97,6 +120,8 @@ import of plain diff with specific date and user +(this also tests that editor is not invoked, if +'--message'/'--logfile' is specified and '--edit' is not) $ hg clone -r0 a b adding changesets @@ -128,6 +153,8 @@ import of plain diff should be ok with --no-commit +(this also tests that editor is not invoked, if '--no-commit' is +specified, regardless of '--edit') $ hg clone -r0 a b adding changesets @@ -136,7 +163,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd b import --no-commit ../diffed-tip.patch + $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch applying ../diffed-tip.patch $ hg --cwd b diff --nodates diff -r 80971e65b431 a @@ -1154,5 +1181,272 @@ 3 4 line + $ cd .. - $ cd .. +Test partial application +------------------------ + +prepare a stack of patches depending on each other + + $ hg init partial + $ cd partial + $ cat << EOF > a + > one + > two + > three + > four + > five + > six + > seven + > EOF + $ hg add a + $ echo 'b' > b + $ hg add b + $ hg commit -m 'initial' -u Babar + $ cat << EOF > a + > one + > two + > 3 + > four + > five + > six + > seven + > EOF + $ hg commit -m 'three' -u Celeste + $ cat << EOF > a + > one + > two + > 3 + > 4 + > five + > six + > seven + > EOF + $ hg commit -m 'four' -u Rataxes + $ cat << EOF > a + > one + > two + > 3 + > 4 + > 5 + > six + > seven + > EOF + $ echo bb >> b + $ hg commit -m 'five' -u Arthur + $ echo 'Babar' > jungle + $ hg add jungle + $ hg ci -m 'jungle' -u Zephir + $ echo 'Celeste' >> jungle + $ hg ci -m 'extended jungle' -u Cornelius + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ extended jungle [Cornelius] 1: +1/-0 + | + o jungle [Zephir] 1: +1/-0 + | + o five [Arthur] 2: +2/-1 + | + o four [Rataxes] 1: +1/-1 + | + o three [Celeste] 1: +1/-1 + | + o initial [Babar] 2: +8/-0 + + +Importing with some success and some errors: + + $ hg update --rev 'desc(initial)' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg export --rev 'desc(five)' | hg import --partial - + applying patch from stdin + patching file a + Hunk #1 FAILED at 1 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ five [Arthur] 1: +1/-0 + | + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Arthur + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509 + # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e + five + + diff -r 8e4f0351909e -r 26e6446bb252 b + --- a/b Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,2 @@ + b + +bb + $ hg status -c . + C a + C b + $ ls + a + a.rej + b + +Importing with zero success: + + $ hg update --rev 'desc(initial)' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg export --rev 'desc(four)' | hg import --partial - + applying patch from stdin + patching file a + Hunk #1 FAILED at 0 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ four [Rataxes] 0: +0/-0 + | + | o five [Arthur] 1: +1/-0 + |/ + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Rataxes + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e + # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e + four + + $ hg status -c . + C a + C b + $ ls + a + a.rej + b + +Importing with unknown file: + + $ hg update --rev 'desc(initial)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg export --rev 'desc("extended jungle")' | hg import --partial - + applying patch from stdin + unable to find 'jungle' for patching + 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ extended jungle [Cornelius] 0: +0/-0 + | + | o four [Rataxes] 0: +0/-0 + |/ + | o five [Arthur] 1: +1/-0 + |/ + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Cornelius + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d + # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e + extended jungle + + $ hg status -c . + C a + C b + $ ls + a + a.rej + b + jungle.rej + +Importing multiple failing patches: + + $ hg update --rev 'desc(initial)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 'B' > b # just to make another commit + $ hg commit -m "a new base" + created new head + $ hg export --rev 'desc("extended jungle") + desc("four")' | hg import --partial - + applying patch from stdin + patching file a + Hunk #1 FAILED at 0 + 1 out of 1 hunks FAILED -- saving rejects to file a.rej + patch applied partially + (fix the .rej files and run `hg commit --amend`) + [1] + $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n' + @ four [Rataxes] 0: +0/-0 + | + o a new base [test] 1: +1/-1 + | + | o extended jungle [Cornelius] 0: +0/-0 + |/ + | o four [Rataxes] 0: +0/-0 + |/ + | o five [Arthur] 1: +1/-0 + |/ + | o extended jungle [Cornelius] 1: +1/-0 + | | + | o jungle [Zephir] 1: +1/-0 + | | + | o five [Arthur] 2: +2/-1 + | | + | o four [Rataxes] 1: +1/-1 + | | + | o three [Celeste] 1: +1/-1 + |/ + o initial [Babar] 2: +8/-0 + + $ hg export + # HG changeset patch + # User Rataxes + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d + # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd + four + + $ hg status -c . + C a + C b
--- a/tests/test-issue1877.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-issue1877.t Fri Jun 13 17:42:04 2014 -0500 @@ -34,6 +34,7 @@ $ hg up 1e6c11564562 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark main) $ hg merge main 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)
--- a/tests/test-issue672.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-issue672.t Fri Jun 13 17:42:04 2014 -0500 @@ -35,12 +35,12 @@ branchmerge: True, force: False, partial: False ancestor: 81f4b099af3d, local: c64f439569a9+, remote: c12dcd37c90a 1: other deleted -> r - 1a: remote created -> g - 2: keep -> k removing 1 updating: 1 1/2 files (50.00%) + 1a: remote created -> g getting 1a updating: 1a 2/2 files (100.00%) + 2: keep -> k 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -66,8 +66,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: c64f439569a9, local: e327dca35ac8+, remote: 746e9549ea96 + preserving 1a for resolve of 1a 1a: local copied/moved from 1 -> m - preserving 1a for resolve of 1a updating: 1a 1/1 files (100.00%) picked tool 'internal:merge' for 1a (binary False symlink False) merging 1a and 1 to 1a @@ -89,9 +89,9 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: c64f439569a9, local: 746e9549ea96+, remote: e327dca35ac8 + preserving 1 for resolve of 1a + removing 1 1a: remote moved from 1 -> m - preserving 1 for resolve of 1a - removing 1 updating: 1a 1/1 files (100.00%) picked tool 'internal:merge' for 1a (binary False symlink False) merging 1 and 1a to 1a
--- a/tests/test-journal-exists.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-journal-exists.t Fri Jun 13 17:42:04 2014 -0500 @@ -9,7 +9,8 @@ $ echo foo > a $ hg ci -Am0 - abort: abandoned transaction found - run hg recover! + abort: abandoned transaction found! + (run 'hg recover' to clean up transaction) [255] $ hg recover
--- a/tests/test-keyword.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-keyword.t Fri Jun 13 17:42:04 2014 -0500 @@ -1049,15 +1049,16 @@ [1] $ cat m $Id$ - <<<<<<< local + <<<<<<< local: 88a80c8d172e - test: 8bar bar ======= foo - >>>>>>> other + >>>>>>> other: 85d2d2d732a5 - test: simplemerge resolve to local $ HGMERGE=internal:local hg resolve -a + no more unresolved files $ hg commit -m localresolve $ cat m $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-largefiles-misc.t Fri Jun 13 17:42:04 2014 -0500 @@ -0,0 +1,696 @@ +This file contains testcases that tend to be related to special cases or less +common commands affecting largefile. + +Each sections should be independent of each others. + + $ USERCACHE="$TESTTMP/cache"; export USERCACHE + $ mkdir "${USERCACHE}" + $ cat >> $HGRCPATH <<EOF + > [extensions] + > largefiles= + > purge= + > rebase= + > transplant= + > [phases] + > publish=False + > [largefiles] + > minsize=2 + > patterns=glob:**.dat + > usercache=${USERCACHE} + > [hooks] + > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status" + > EOF + + + +Test copies and moves from a directory other than root (issue3516) +========================================================================= + + $ hg init lf_cpmv + $ cd lf_cpmv + $ mkdir dira + $ mkdir dira/dirb + $ touch dira/dirb/largefile + $ hg add --large dira/dirb/largefile + $ hg commit -m "added" + Invoking status precommit hook + A dira/dirb/largefile + $ cd dira + $ hg cp dirb/largefile foo/largefile + $ hg ci -m "deep copy" + Invoking status precommit hook + A dira/foo/largefile + $ find . | sort + . + ./dirb + ./dirb/largefile + ./foo + ./foo/largefile + $ hg mv foo/largefile baz/largefile + $ hg ci -m "moved" + Invoking status precommit hook + A dira/baz/largefile + R dira/foo/largefile + $ find . | sort + . + ./baz + ./baz/largefile + ./dirb + ./dirb/largefile + $ cd .. + $ hg mv dira dirc + moving .hglf/dira/baz/largefile to .hglf/dirc/baz/largefile (glob) + moving .hglf/dira/dirb/largefile to .hglf/dirc/dirb/largefile (glob) + $ find * | sort + dirc + dirc/baz + dirc/baz/largefile + dirc/dirb + dirc/dirb/largefile + $ hg up -qC + $ cd .. + +Clone a local repository owned by another user +=================================================== + +#if unix-permissions + +We have to simulate that here by setting $HOME and removing write permissions + $ ORIGHOME="$HOME" + $ mkdir alice + $ HOME="`pwd`/alice" + $ cd alice + $ hg init pubrepo + $ cd pubrepo + $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null + $ hg add --large a-large-file + $ hg commit -m "Add a large file" + Invoking status precommit hook + A a-large-file + $ cd .. + $ chmod -R a-w pubrepo + $ cd .. + $ mkdir bob + $ HOME="`pwd`/bob" + $ cd bob + $ hg clone --pull ../alice/pubrepo pubrepo + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + updating to branch default + getting changed largefiles + 1 largefiles updated, 0 removed + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd .. + $ chmod -R u+w alice/pubrepo + $ HOME="$ORIGHOME" + +#endif + + +Symlink to a large largefile should behave the same as a symlink to a normal file +===================================================================================== + +#if symlink + + $ hg init largesymlink + $ cd largesymlink + $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null + $ hg add --large largefile + $ hg commit -m "commit a large file" + Invoking status precommit hook + A largefile + $ ln -s largefile largelink + $ hg add largelink + $ hg commit -m "commit a large symlink" + Invoking status precommit hook + A largelink + $ rm -f largelink + $ hg up >/dev/null + $ test -f largelink + [1] + $ test -L largelink + [1] + $ rm -f largelink # make next part of the test independent of the previous + $ hg up -C >/dev/null + $ test -f largelink + $ test -L largelink + $ cd .. + +#endif + + +test for pattern matching on 'hg status': +============================================== + + +to boost performance, largefiles checks whether specified patterns are +related to largefiles in working directory (NOT to STANDIN) or not. + + $ hg init statusmatch + $ cd statusmatch + + $ mkdir -p a/b/c/d + $ echo normal > a/b/c/d/e.normal.txt + $ hg add a/b/c/d/e.normal.txt + $ echo large > a/b/c/d/e.large.txt + $ hg add --large a/b/c/d/e.large.txt + $ mkdir -p a/b/c/x + $ echo normal > a/b/c/x/y.normal.txt + $ hg add a/b/c/x/y.normal.txt + $ hg commit -m 'add files' + Invoking status precommit hook + A a/b/c/d/e.large.txt + A a/b/c/d/e.normal.txt + A a/b/c/x/y.normal.txt + +(1) no pattern: no performance boost + $ hg status -A + C a/b/c/d/e.large.txt + C a/b/c/d/e.normal.txt + C a/b/c/x/y.normal.txt + +(2) pattern not related to largefiles: performance boost + $ hg status -A a/b/c/x + C a/b/c/x/y.normal.txt + +(3) pattern related to largefiles: no performance boost + $ hg status -A a/b/c/d + C a/b/c/d/e.large.txt + C a/b/c/d/e.normal.txt + +(4) pattern related to STANDIN (not to largefiles): performance boost + $ hg status -A .hglf/a + C .hglf/a/b/c/d/e.large.txt + +(5) mixed case: no performance boost + $ hg status -A a/b/c/x a/b/c/d + C a/b/c/d/e.large.txt + C a/b/c/d/e.normal.txt + C a/b/c/x/y.normal.txt + +verify that largefiles doesn't break filesets + + $ hg log --rev . --exclude "set:binary()" + changeset: 0:41bd42f10efa + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add files + +verify that large files in subrepos handled properly + $ hg init subrepo + $ echo "subrepo = subrepo" > .hgsub + $ hg add .hgsub + $ hg ci -m "add subrepo" + Invoking status precommit hook + A .hgsub + ? .hgsubstate + $ echo "rev 1" > subrepo/large.txt + $ hg -R subrepo add --large subrepo/large.txt + $ hg sum + parent: 1:8ee150ea2e9c tip + add subrepo + branch: default + commit: 1 subrepos + update: (current) + $ hg st + $ hg st -S + A subrepo/large.txt + $ hg ci -S -m "commit top repo" + committing subrepository subrepo + Invoking status precommit hook + A large.txt + Invoking status precommit hook + M .hgsubstate +# No differences + $ hg st -S + $ hg sum + parent: 2:ce4cd0c527a6 tip + commit top repo + branch: default + commit: (clean) + update: (current) + $ echo "rev 2" > subrepo/large.txt + $ hg st -S + M subrepo/large.txt + $ hg sum + parent: 2:ce4cd0c527a6 tip + commit top repo + branch: default + commit: 1 subrepos + update: (current) + $ hg ci -m "this commit should fail without -S" + abort: uncommitted changes in subrepo subrepo + (use --subrepos for recursive commit) + [255] + +Add a normal file to the subrepo, then test archiving + + $ echo 'normal file' > subrepo/normal.txt + $ hg -R subrepo add subrepo/normal.txt + +Lock in subrepo, otherwise the change isn't archived + + $ hg ci -S -m "add normal file to top level" + committing subrepository subrepo + Invoking status precommit hook + M large.txt + A normal.txt + Invoking status precommit hook + M .hgsubstate + $ hg archive -S ../lf_subrepo_archive + $ find ../lf_subrepo_archive | sort + ../lf_subrepo_archive + ../lf_subrepo_archive/.hg_archival.txt + ../lf_subrepo_archive/.hgsub + ../lf_subrepo_archive/.hgsubstate + ../lf_subrepo_archive/a + ../lf_subrepo_archive/a/b + ../lf_subrepo_archive/a/b/c + ../lf_subrepo_archive/a/b/c/d + ../lf_subrepo_archive/a/b/c/d/e.large.txt + ../lf_subrepo_archive/a/b/c/d/e.normal.txt + ../lf_subrepo_archive/a/b/c/x + ../lf_subrepo_archive/a/b/c/x/y.normal.txt + ../lf_subrepo_archive/subrepo + ../lf_subrepo_archive/subrepo/large.txt + ../lf_subrepo_archive/subrepo/normal.txt + +Test update with subrepos. + + $ hg update 0 + getting changed largefiles + 0 largefiles updated, 1 removed + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg status -S + $ hg update tip + getting changed largefiles + 1 largefiles updated, 0 removed + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg status -S +# modify a large file + $ echo "modified" > subrepo/large.txt + $ hg st -S + M subrepo/large.txt +# update -C should revert the change. + $ hg update -C + getting changed largefiles + 1 largefiles updated, 0 removed + getting changed largefiles + 0 largefiles updated, 0 removed + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg status -S + +Test archiving a revision that references a subrepo that is not yet +cloned (see test-subrepo-recursion.t): + + $ hg clone -U . ../empty + $ cd ../empty + $ hg archive --subrepos -r tip ../archive.tar.gz + cloning subrepo subrepo from $TESTTMP/statusmatch/subrepo + $ cd .. + + + + + + +Test addremove, forget and others +============================================== + +Test that addremove picks up largefiles prior to the initial commit (issue3541) + + $ hg init addrm2 + $ cd addrm2 + $ touch large.dat + $ touch large2.dat + $ touch normal + $ hg add --large large.dat + $ hg addremove -v + adding large2.dat as a largefile + adding normal + +Test that forgetting all largefiles reverts to islfilesrepo() == False +(addremove will add *.dat as normal files now) + $ hg forget large.dat + $ hg forget large2.dat + $ hg addremove -v + adding large.dat + adding large2.dat + +Test commit's addremove option prior to the first commit + $ hg forget large.dat + $ hg forget large2.dat + $ hg add --large large.dat + $ hg ci -Am "commit" + adding large2.dat as a largefile + Invoking status precommit hook + A large.dat + A large2.dat + A normal + $ find .hglf | sort + .hglf + .hglf/large.dat + .hglf/large2.dat + +Test actions on largefiles using relative paths from subdir + + $ mkdir sub + $ cd sub + $ echo anotherlarge > anotherlarge + $ hg add --large anotherlarge + $ hg st + A sub/anotherlarge + $ hg st anotherlarge + A anotherlarge + $ hg commit -m anotherlarge anotherlarge + Invoking status precommit hook + A sub/anotherlarge + $ hg log anotherlarge + changeset: 1:9627a577c5e9 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: anotherlarge + + $ hg log -G anotherlarge + @ changeset: 1:9627a577c5e9 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: anotherlarge + | + $ echo more >> anotherlarge + $ hg st . + M anotherlarge + $ hg cat anotherlarge + anotherlarge + $ hg revert anotherlarge + $ hg st + ? sub/anotherlarge.orig + $ cd .. + + $ cd .. + +Check error message while exchange +========================================================= + +issue3651: summary/outgoing with largefiles shows "no remote repo" +unexpectedly + + $ mkdir issue3651 + $ cd issue3651 + + $ hg init src + $ echo a > src/a + $ hg -R src add --large src/a + $ hg -R src commit -m '#0' + Invoking status precommit hook + A a + +check messages when no remote repository is specified: +"no remote repo" route for "hg outgoing --large" is not tested here, +because it can't be reproduced easily. + + $ hg init clone1 + $ hg -R clone1 -q pull src + $ hg -R clone1 -q update + $ hg -R clone1 paths | grep default + [1] + + $ hg -R clone1 summary --large + parent: 0:fc0bd45326d3 tip + #0 + branch: default + commit: (clean) + update: (current) + largefiles: (no remote repo) + +check messages when there is no files to upload: + + $ hg -q clone src clone2 + $ hg -R clone2 paths | grep default + default = $TESTTMP/issue3651/src (glob) + + $ hg -R clone2 summary --large + parent: 0:fc0bd45326d3 tip + #0 + branch: default + commit: (clean) + update: (current) + largefiles: (no files to upload) + $ hg -R clone2 outgoing --large + comparing with $TESTTMP/issue3651/src (glob) + searching for changes + no changes found + largefiles: no files to upload + [1] + + $ hg -R clone2 outgoing --large --graph --template "{rev}" + comparing with $TESTTMP/issue3651/src (glob) + searching for changes + no changes found + largefiles: no files to upload + +check messages when there are files to upload: + + $ echo b > clone2/b + $ hg -R clone2 add --large clone2/b + $ hg -R clone2 commit -m '#1' + Invoking status precommit hook + A b + $ hg -R clone2 summary --large + parent: 1:1acbe71ce432 tip + #1 + branch: default + commit: (clean) + update: (current) + largefiles: 1 to upload + $ hg -R clone2 outgoing --large + comparing with $TESTTMP/issue3651/src (glob) + searching for changes + changeset: 1:1acbe71ce432 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: #1 + + largefiles to upload: + b + + $ hg -R clone2 outgoing --large --graph --template "{rev}" + comparing with $TESTTMP/issue3651/src + searching for changes + @ 1 + + largefiles to upload: + b + + + $ cd .. + +merge action 'd' for 'local renamed directory to d2/g' which has no filename +================================================================================== + + $ hg init merge-action + $ cd merge-action + $ touch l + $ hg add --large l + $ mkdir d1 + $ touch d1/f + $ hg ci -Aqm0 + Invoking status precommit hook + A d1/f + A l + $ echo > d1/f + $ touch d1/g + $ hg ci -Aqm1 + Invoking status precommit hook + M d1/f + A d1/g + $ hg up -qr0 + $ hg mv d1 d2 + moving d1/f to d2/f (glob) + $ hg ci -qm2 + Invoking status precommit hook + A d2/f + R d1/f + $ hg merge + merging d2/f and d1/f to d2/f + 1 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + getting changed largefiles + 0 largefiles updated, 0 removed + $ cd .. + + +Merge conflicts: +===================== + + $ hg init merge + $ cd merge + $ echo 0 > f-different + $ echo 0 > f-same + $ echo 0 > f-unchanged-1 + $ echo 0 > f-unchanged-2 + $ hg add --large * + $ hg ci -m0 + Invoking status precommit hook + A f-different + A f-same + A f-unchanged-1 + A f-unchanged-2 + $ echo tmp1 > f-unchanged-1 + $ echo tmp1 > f-unchanged-2 + $ echo tmp1 > f-same + $ hg ci -m1 + Invoking status precommit hook + M f-same + M f-unchanged-1 + M f-unchanged-2 + $ echo 2 > f-different + $ echo 0 > f-unchanged-1 + $ echo 1 > f-unchanged-2 + $ echo 1 > f-same + $ hg ci -m2 + Invoking status precommit hook + M f-different + M f-same + M f-unchanged-1 + M f-unchanged-2 + $ hg up -qr0 + $ echo tmp2 > f-unchanged-1 + $ echo tmp2 > f-unchanged-2 + $ echo tmp2 > f-same + $ hg ci -m3 + Invoking status precommit hook + M f-same + M f-unchanged-1 + M f-unchanged-2 + created new head + $ echo 1 > f-different + $ echo 1 > f-unchanged-1 + $ echo 0 > f-unchanged-2 + $ echo 1 > f-same + $ hg ci -m4 + Invoking status precommit hook + M f-different + M f-same + M f-unchanged-1 + M f-unchanged-2 + $ hg merge + largefile f-different has a merge conflict + ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7 + keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or + take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l + 0 files updated, 4 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + getting changed largefiles + 1 largefiles updated, 0 removed + $ cat f-different + 1 + $ cat f-same + 1 + $ cat f-unchanged-1 + 1 + $ cat f-unchanged-2 + 1 + $ cd .. + +Test largefile insulation (do not enabled a side effect +======================================================== + +Check whether "largefiles" feature is supported only in repositories +enabling largefiles extension. + + $ mkdir individualenabling + $ cd individualenabling + + $ hg init enabledlocally + $ echo large > enabledlocally/large + $ hg -R enabledlocally add --large enabledlocally/large + $ hg -R enabledlocally commit -m '#0' + Invoking status precommit hook + A large + + $ hg init notenabledlocally + $ echo large > notenabledlocally/large + $ hg -R notenabledlocally add --large notenabledlocally/large + $ hg -R notenabledlocally commit -m '#0' + Invoking status precommit hook + A large + + $ cat >> $HGRCPATH <<EOF + > [extensions] + > # disable globally + > largefiles=! + > EOF + $ cat >> enabledlocally/.hg/hgrc <<EOF + > [extensions] + > # enable locally + > largefiles= + > EOF + $ hg -R enabledlocally root + $TESTTMP/individualenabling/enabledlocally (glob) + $ hg -R notenabledlocally root + abort: repository requires features unknown to this Mercurial: largefiles! + (see http://mercurial.selenic.com/wiki/MissingRequirement for more information) + [255] + + $ hg init push-dst + $ hg -R enabledlocally push push-dst + pushing to push-dst + abort: required features are not supported in the destination: largefiles + [255] + + $ hg init pull-src + $ hg -R pull-src pull enabledlocally + pulling from enabledlocally + abort: required features are not supported in the destination: largefiles + [255] + + $ hg clone enabledlocally clone-dst + abort: repository requires features unknown to this Mercurial: largefiles! + (see http://mercurial.selenic.com/wiki/MissingRequirement for more information) + [255] + $ test -d clone-dst + [1] + $ hg clone --pull enabledlocally clone-pull-dst + abort: required features are not supported in the destination: largefiles + [255] + $ test -d clone-pull-dst + [1] + +#if serve + +Test largefiles specific peer setup, when largefiles is enabled +locally (issue4109) + + $ hg showconfig extensions | grep largefiles + extensions.largefiles=! + $ mkdir -p $TESTTMP/individualenabling/usercache + + $ hg serve -R enabledlocally -d -p $HGPORT --pid-file hg.pid + $ cat hg.pid >> $DAEMON_PIDS + + $ hg init pull-dst + $ cat > pull-dst/.hg/hgrc <<EOF + > [extensions] + > # enable locally + > largefiles= + > [largefiles] + > # ignore system cache to force largefiles specific wire proto access + > usercache=$TESTTMP/individualenabling/usercache + > EOF + $ hg -R pull-dst -q pull -u http://localhost:$HGPORT + + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS +#endif + + $ cd .. + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-largefiles-wireproto.t Fri Jun 13 17:42:04 2014 -0500 @@ -0,0 +1,293 @@ +This file contains testcases that tend to be related to the wireprotocol part of +largefile. + + $ USERCACHE="$TESTTMP/cache"; export USERCACHE + $ mkdir "${USERCACHE}" + $ cat >> $HGRCPATH <<EOF + > [extensions] + > largefiles= + > purge= + > rebase= + > transplant= + > [phases] + > publish=False + > [largefiles] + > minsize=2 + > patterns=glob:**.dat + > usercache=${USERCACHE} + > [hooks] + > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status" + > EOF + + +#if serve +vanilla clients not locked out from largefiles servers on vanilla repos + $ mkdir r1 + $ cd r1 + $ hg init + $ echo c1 > f1 + $ hg add f1 + $ hg commit -m "m1" + Invoking status precommit hook + A f1 + $ cd .. + $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2 + 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 + +largefiles clients still work with vanilla servers + $ hg --config extensions.largefiles=! serve -R r1 -d -p $HGPORT1 --pid-file hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ hg clone http://localhost:$HGPORT1 r3 + 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 +#endif + +vanilla clients locked out from largefiles http repos + $ mkdir r4 + $ cd r4 + $ hg init + $ echo c1 > f1 + $ hg add --large f1 + $ hg commit -m "m1" + Invoking status precommit hook + A f1 + $ cd .. + +largefiles can be pushed locally (issue3583) + $ hg init dest + $ cd r4 + $ hg outgoing ../dest + comparing with ../dest + searching for changes + changeset: 0:639881c12b4c + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: m1 + + $ hg push ../dest + pushing to ../dest + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + +exit code with nothing outgoing (issue3611) + $ hg outgoing ../dest + comparing with ../dest + searching for changes + no changes found + [1] + $ cd .. + +#if serve + $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5 + abort: remote error: + + This repository uses the largefiles extension. + + Please enable it in your Mercurial config file. + [255] + +used all HGPORTs, kill all daemons + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS +#endif + +vanilla clients locked out from largefiles ssh repos + $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5 + abort: remote error: + + This repository uses the largefiles extension. + + Please enable it in your Mercurial config file. + [255] + +#if serve + +largefiles clients refuse to push largefiles repos to vanilla servers + $ mkdir r6 + $ cd r6 + $ hg init + $ echo c1 > f1 + $ hg add f1 + $ hg commit -m "m1" + Invoking status precommit hook + A f1 + $ cat >> .hg/hgrc <<! + > [web] + > push_ssl = false + > allow_push = * + > ! + $ cd .. + $ hg clone r6 r7 + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd r7 + $ echo c2 > f2 + $ hg add --large f2 + $ hg commit -m "m2" + Invoking status precommit hook + A f2 + $ hg --config extensions.largefiles=! -R ../r6 serve -d -p $HGPORT --pid-file ../hg.pid + $ cat ../hg.pid >> $DAEMON_PIDS + $ hg push http://localhost:$HGPORT + pushing to http://localhost:$HGPORT/ + searching for changes + abort: http://localhost:$HGPORT/ does not appear to be a largefile store + [255] + $ cd .. + +putlfile errors are shown (issue3123) +Corrupt the cached largefile in r7 and move it out of the servers usercache + $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 . + $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 + $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8" + $ hg init empty + $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \ + > --config 'web.allow_push=*' --config web.push_ssl=False + $ cat hg.pid >> $DAEMON_PIDS + $ hg push -R r7 http://localhost:$HGPORT1 + pushing to http://localhost:$HGPORT1/ + searching for changes + remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash + abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/ (glob) + [255] + $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 +Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic + $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 + $ hg push -R r7 http://localhost:$HGPORT1 + pushing to http://localhost:$HGPORT1/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 2 changesets with 2 changes to 2 files + $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 + server side corruption + $ rm -rf empty + +Push a largefiles repository to a served empty repository + $ hg init r8 + $ echo c3 > r8/f1 + $ hg add --large r8/f1 -R r8 + $ hg commit -m "m1" -R r8 + Invoking status precommit hook + A f1 + $ hg init empty + $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \ + > --config 'web.allow_push=*' --config web.push_ssl=False + $ cat hg.pid >> $DAEMON_PIDS + $ rm "${USERCACHE}"/* + $ hg push -R r8 http://localhost:$HGPORT2/#default + pushing to http://localhost:$HGPORT2/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ] + $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ] + +Clone over http, no largefiles pulled on clone. + + $ hg clone http://localhost:$HGPORT2/#default http-clone -U + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + +test 'verify' with remotestore: + + $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 + $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 . + $ hg -R http-clone verify --large --lfa + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 1 files, 1 changesets, 1 total revisions + searching 1 changesets for largefiles + changeset 0:cf03e5bb9936: f1 missing + verified existence of 1 revisions of 1 largefiles + [1] + $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/ + $ hg -R http-clone -q verify --large --lfa + +largefiles pulled on update - a largefile missing on the server: + $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 . + $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache + getting changed largefiles + f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/ + 0 largefiles updated, 0 removed + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R http-clone st + ! f1 + $ hg -R http-clone up -Cqr null + +largefiles pulled on update - a largefile corrupted on the server: + $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 + $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache + getting changed largefiles + f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27) + 0 largefiles updated, 0 removed + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R http-clone st + ! f1 + $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ] + $ [ ! -f http-clone/f1 ] + $ [ ! -f http-clone-usercache ] + $ hg -R http-clone verify --large --lfc + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 1 files, 1 changesets, 1 total revisions + searching 1 changesets for largefiles + verified contents of 1 revisions of 1 largefiles + $ hg -R http-clone up -Cqr null + +largefiles pulled on update - no server side problems: + $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/ + $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache + resolving manifests + branchmerge: False, force: False, partial: False + ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936 + .hglf/f1: remote created -> g + getting .hglf/f1 + updating: .hglf/f1 1/1 files (100.00%) + getting changed largefiles + using http://localhost:$HGPORT2/ + sending capabilities command + sending batch command + getting largefiles: 0/1 lfile (0.00%) + getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90 + sending getlfile command + found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store + 1 largefiles updated, 0 removed + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ ls http-clone-usercache/* + http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90 + + $ rm -rf empty http-clone* + +used all HGPORTs, kill all daemons + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS + +#endif
--- a/tests/test-largefiles.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-largefiles.t Fri Jun 13 17:42:04 2014 -0500 @@ -1,3 +1,8 @@ +This file used to contains all largefile tests. +Do not add any new tests in this file as it his already far too long to run. + +It contains all the testing of the basic concepts of large file in a single block. + $ USERCACHE="$TESTTMP/cache"; export USERCACHE $ mkdir "${USERCACHE}" $ cat >> $HGRCPATH <<EOF @@ -180,52 +185,6 @@ $ cat sub/large4 large22 -Test copies and moves from a directory other than root (issue3516) - - $ cd .. - $ hg init lf_cpmv - $ cd lf_cpmv - $ mkdir dira - $ mkdir dira/dirb - $ touch dira/dirb/largefile - $ hg add --large dira/dirb/largefile - $ hg commit -m "added" - Invoking status precommit hook - A dira/dirb/largefile - $ cd dira - $ hg cp dirb/largefile foo/largefile - $ hg ci -m "deep copy" - Invoking status precommit hook - A dira/foo/largefile - $ find . | sort - . - ./dirb - ./dirb/largefile - ./foo - ./foo/largefile - $ hg mv foo/largefile baz/largefile - $ hg ci -m "moved" - Invoking status precommit hook - A dira/baz/largefile - R dira/foo/largefile - $ find . | sort - . - ./baz - ./baz/largefile - ./dirb - ./dirb/largefile - $ cd .. - $ hg mv dira dirc - moving .hglf/dira/baz/largefile to .hglf/dirc/baz/largefile (glob) - moving .hglf/dira/dirb/largefile to .hglf/dirc/dirb/largefile (glob) - $ find * | sort - dirc - dirc/baz - dirc/baz/largefile - dirc/dirb - dirc/dirb/largefile - $ hg up -qC - $ cd ../a #if serve Test display of largefiles in hgweb @@ -390,6 +349,14 @@ A sub2/large7 A z/y/x/large2 A z/y/x/m/large1 + +(and a bit of log testing) + + $ hg log -T '{rev}\n' z/y/x/m/large1 + 7 + $ hg log -T '{rev}\n' z/y/x/m # with only a largefile + 7 + $ hg rollback --quiet $ touch z/y/x/m/normal $ hg add z/y/x/m/normal @@ -1665,873 +1632,5 @@ (use 'hg revert new-largefile' to cancel the pending addition) $ cd .. -#if serve -vanilla clients not locked out from largefiles servers on vanilla repos - $ mkdir r1 - $ cd r1 - $ hg init - $ echo c1 > f1 - $ hg add f1 - $ hg commit -m "m1" - Invoking status precommit hook - A f1 - $ cd .. - $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid - $ cat hg.pid >> $DAEMON_PIDS - $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2 - 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 - -largefiles clients still work with vanilla servers - $ hg --config extensions.largefiles=! serve -R r1 -d -p $HGPORT1 --pid-file hg.pid - $ cat hg.pid >> $DAEMON_PIDS - $ hg clone http://localhost:$HGPORT1 r3 - 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 -#endif - - -vanilla clients locked out from largefiles http repos - $ mkdir r4 - $ cd r4 - $ hg init - $ echo c1 > f1 - $ hg add --large f1 - $ hg commit -m "m1" - Invoking status precommit hook - A f1 - $ cd .. - -largefiles can be pushed locally (issue3583) - $ hg init dest - $ cd r4 - $ hg outgoing ../dest - comparing with ../dest - searching for changes - changeset: 0:639881c12b4c - tag: tip - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: m1 - - $ hg push ../dest - pushing to ../dest - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - -exit code with nothing outgoing (issue3611) - $ hg outgoing ../dest - comparing with ../dest - searching for changes - no changes found - [1] - $ cd .. - -#if serve - $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid - $ cat hg.pid >> $DAEMON_PIDS - $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5 - abort: remote error: - - This repository uses the largefiles extension. - - Please enable it in your Mercurial config file. - [255] - -used all HGPORTs, kill all daemons - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS -#endif - -vanilla clients locked out from largefiles ssh repos - $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5 - abort: remote error: - - This repository uses the largefiles extension. - - Please enable it in your Mercurial config file. - [255] - -#if serve - -largefiles clients refuse to push largefiles repos to vanilla servers - $ mkdir r6 - $ cd r6 - $ hg init - $ echo c1 > f1 - $ hg add f1 - $ hg commit -m "m1" - Invoking status precommit hook - A f1 - $ cat >> .hg/hgrc <<! - > [web] - > push_ssl = false - > allow_push = * - > ! - $ cd .. - $ hg clone r6 r7 - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd r7 - $ echo c2 > f2 - $ hg add --large f2 - $ hg commit -m "m2" - Invoking status precommit hook - A f2 - $ hg --config extensions.largefiles=! -R ../r6 serve -d -p $HGPORT --pid-file ../hg.pid - $ cat ../hg.pid >> $DAEMON_PIDS - $ hg push http://localhost:$HGPORT - pushing to http://localhost:$HGPORT/ - searching for changes - abort: http://localhost:$HGPORT/ does not appear to be a largefile store - [255] - $ cd .. - -putlfile errors are shown (issue3123) -Corrupt the cached largefile in r7 and move it out of the servers usercache - $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 . - $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 - $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8" - $ hg init empty - $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \ - > --config 'web.allow_push=*' --config web.push_ssl=False - $ cat hg.pid >> $DAEMON_PIDS - $ hg push -R r7 http://localhost:$HGPORT1 - pushing to http://localhost:$HGPORT1/ - searching for changes - remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash - abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/ (glob) - [255] - $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 -Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic - $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 - $ hg push -R r7 http://localhost:$HGPORT1 - pushing to http://localhost:$HGPORT1/ - searching for changes - remote: adding changesets - remote: adding manifests - remote: adding file changes - remote: added 2 changesets with 2 changes to 2 files - $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 - server side corruption - $ rm -rf empty - -Push a largefiles repository to a served empty repository - $ hg init r8 - $ echo c3 > r8/f1 - $ hg add --large r8/f1 -R r8 - $ hg commit -m "m1" -R r8 - Invoking status precommit hook - A f1 - $ hg init empty - $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \ - > --config 'web.allow_push=*' --config web.push_ssl=False - $ cat hg.pid >> $DAEMON_PIDS - $ rm "${USERCACHE}"/* - $ hg push -R r8 http://localhost:$HGPORT2/#default - pushing to http://localhost:$HGPORT2/ - searching for changes - remote: adding changesets - remote: adding manifests - remote: adding file changes - remote: added 1 changesets with 1 changes to 1 files - $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ] - $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ] - -Clone over http, no largefiles pulled on clone. - - $ hg clone http://localhost:$HGPORT2/#default http-clone -U - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - -test 'verify' with remotestore: - - $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 - $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 . - $ hg -R http-clone verify --large --lfa - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 1 files, 1 changesets, 1 total revisions - searching 1 changesets for largefiles - changeset 0:cf03e5bb9936: f1 missing - verified existence of 1 revisions of 1 largefiles - [1] - $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/ - $ hg -R http-clone -q verify --large --lfa - -largefiles pulled on update - a largefile missing on the server: - $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 . - $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache - getting changed largefiles - f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/ - 0 largefiles updated, 0 removed - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R http-clone st - ! f1 - $ hg -R http-clone up -Cqr null - -largefiles pulled on update - a largefile corrupted on the server: - $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 - $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache - getting changed largefiles - f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27) - 0 largefiles updated, 0 removed - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R http-clone st - ! f1 - $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ] - $ [ ! -f http-clone/f1 ] - $ [ ! -f http-clone-usercache ] - $ hg -R http-clone verify --large --lfc - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 1 files, 1 changesets, 1 total revisions - searching 1 changesets for largefiles - verified contents of 1 revisions of 1 largefiles - $ hg -R http-clone up -Cqr null - -largefiles pulled on update - no server side problems: - $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/ - $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache - resolving manifests - branchmerge: False, force: False, partial: False - ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936 - .hglf/f1: remote created -> g - getting .hglf/f1 - updating: .hglf/f1 1/1 files (100.00%) - getting changed largefiles - using http://localhost:$HGPORT2/ - sending capabilities command - sending batch command - getting largefiles: 0/1 lfile (0.00%) - getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90 - sending getlfile command - found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store - 1 largefiles updated, 0 removed - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ ls http-clone-usercache/* - http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90 - - $ rm -rf empty http-clone* - -used all HGPORTs, kill all daemons - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS - -#endif -#if unix-permissions - -Clone a local repository owned by another user -We have to simulate that here by setting $HOME and removing write permissions - $ ORIGHOME="$HOME" - $ mkdir alice - $ HOME="`pwd`/alice" - $ cd alice - $ hg init pubrepo - $ cd pubrepo - $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null - $ hg add --large a-large-file - $ hg commit -m "Add a large file" - Invoking status precommit hook - A a-large-file - $ cd .. - $ chmod -R a-w pubrepo - $ cd .. - $ mkdir bob - $ HOME="`pwd`/bob" - $ cd bob - $ hg clone --pull ../alice/pubrepo pubrepo - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - updating to branch default - getting changed largefiles - 1 largefiles updated, 0 removed - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd .. - $ chmod -R u+w alice/pubrepo - $ HOME="$ORIGHOME" - -#endif - -#if symlink - -Symlink to a large largefile should behave the same as a symlink to a normal file - $ hg init largesymlink - $ cd largesymlink - $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null - $ hg add --large largefile - $ hg commit -m "commit a large file" - Invoking status precommit hook - A largefile - $ ln -s largefile largelink - $ hg add largelink - $ hg commit -m "commit a large symlink" - Invoking status precommit hook - A largelink - $ rm -f largelink - $ hg up >/dev/null - $ test -f largelink - [1] - $ test -L largelink - [1] - $ rm -f largelink # make next part of the test independent of the previous - $ hg up -C >/dev/null - $ test -f largelink - $ test -L largelink - $ cd .. - -#endif - -test for pattern matching on 'hg status': -to boost performance, largefiles checks whether specified patterns are -related to largefiles in working directory (NOT to STANDIN) or not. - - $ hg init statusmatch - $ cd statusmatch - - $ mkdir -p a/b/c/d - $ echo normal > a/b/c/d/e.normal.txt - $ hg add a/b/c/d/e.normal.txt - $ echo large > a/b/c/d/e.large.txt - $ hg add --large a/b/c/d/e.large.txt - $ mkdir -p a/b/c/x - $ echo normal > a/b/c/x/y.normal.txt - $ hg add a/b/c/x/y.normal.txt - $ hg commit -m 'add files' - Invoking status precommit hook - A a/b/c/d/e.large.txt - A a/b/c/d/e.normal.txt - A a/b/c/x/y.normal.txt - -(1) no pattern: no performance boost - $ hg status -A - C a/b/c/d/e.large.txt - C a/b/c/d/e.normal.txt - C a/b/c/x/y.normal.txt - -(2) pattern not related to largefiles: performance boost - $ hg status -A a/b/c/x - C a/b/c/x/y.normal.txt - -(3) pattern related to largefiles: no performance boost - $ hg status -A a/b/c/d - C a/b/c/d/e.large.txt - C a/b/c/d/e.normal.txt - -(4) pattern related to STANDIN (not to largefiles): performance boost - $ hg status -A .hglf/a - C .hglf/a/b/c/d/e.large.txt - -(5) mixed case: no performance boost - $ hg status -A a/b/c/x a/b/c/d - C a/b/c/d/e.large.txt - C a/b/c/d/e.normal.txt - C a/b/c/x/y.normal.txt - -verify that largefiles doesn't break filesets - - $ hg log --rev . --exclude "set:binary()" - changeset: 0:41bd42f10efa - tag: tip - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: add files - -verify that large files in subrepos handled properly - $ hg init subrepo - $ echo "subrepo = subrepo" > .hgsub - $ hg add .hgsub - $ hg ci -m "add subrepo" - Invoking status precommit hook - A .hgsub - ? .hgsubstate - $ echo "rev 1" > subrepo/large.txt - $ hg -R subrepo add --large subrepo/large.txt - $ hg sum - parent: 1:8ee150ea2e9c tip - add subrepo - branch: default - commit: 1 subrepos - update: (current) - $ hg st - $ hg st -S - A subrepo/large.txt - $ hg ci -S -m "commit top repo" - committing subrepository subrepo - Invoking status precommit hook - A large.txt - Invoking status precommit hook - M .hgsubstate -# No differences - $ hg st -S - $ hg sum - parent: 2:ce4cd0c527a6 tip - commit top repo - branch: default - commit: (clean) - update: (current) - $ echo "rev 2" > subrepo/large.txt - $ hg st -S - M subrepo/large.txt - $ hg sum - parent: 2:ce4cd0c527a6 tip - commit top repo - branch: default - commit: 1 subrepos - update: (current) - $ hg ci -m "this commit should fail without -S" - abort: uncommitted changes in subrepo subrepo - (use --subrepos for recursive commit) - [255] - -Add a normal file to the subrepo, then test archiving - - $ echo 'normal file' > subrepo/normal.txt - $ hg -R subrepo add subrepo/normal.txt - -Lock in subrepo, otherwise the change isn't archived - - $ hg ci -S -m "add normal file to top level" - committing subrepository subrepo - Invoking status precommit hook - M large.txt - A normal.txt - Invoking status precommit hook - M .hgsubstate - $ hg archive -S ../lf_subrepo_archive - $ find ../lf_subrepo_archive | sort - ../lf_subrepo_archive - ../lf_subrepo_archive/.hg_archival.txt - ../lf_subrepo_archive/.hgsub - ../lf_subrepo_archive/.hgsubstate - ../lf_subrepo_archive/a - ../lf_subrepo_archive/a/b - ../lf_subrepo_archive/a/b/c - ../lf_subrepo_archive/a/b/c/d - ../lf_subrepo_archive/a/b/c/d/e.large.txt - ../lf_subrepo_archive/a/b/c/d/e.normal.txt - ../lf_subrepo_archive/a/b/c/x - ../lf_subrepo_archive/a/b/c/x/y.normal.txt - ../lf_subrepo_archive/subrepo - ../lf_subrepo_archive/subrepo/large.txt - ../lf_subrepo_archive/subrepo/normal.txt - -Test update with subrepos. - - $ hg update 0 - getting changed largefiles - 0 largefiles updated, 1 removed - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg status -S - $ hg update tip - getting changed largefiles - 1 largefiles updated, 0 removed - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg status -S -# modify a large file - $ echo "modified" > subrepo/large.txt - $ hg st -S - M subrepo/large.txt -# update -C should revert the change. - $ hg update -C - getting changed largefiles - 1 largefiles updated, 0 removed - getting changed largefiles - 0 largefiles updated, 0 removed - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg status -S - -Test archiving a revision that references a subrepo that is not yet -cloned (see test-subrepo-recursion.t): - - $ hg clone -U . ../empty - $ cd ../empty - $ hg archive --subrepos -r tip ../archive.tar.gz - cloning subrepo subrepo from $TESTTMP/statusmatch/subrepo - $ cd .. - -Test that addremove picks up largefiles prior to the initial commit (issue3541) - - $ hg init addrm2 - $ cd addrm2 - $ touch large.dat - $ touch large2.dat - $ touch normal - $ hg add --large large.dat - $ hg addremove -v - adding large2.dat as a largefile - adding normal - -Test that forgetting all largefiles reverts to islfilesrepo() == False -(addremove will add *.dat as normal files now) - $ hg forget large.dat - $ hg forget large2.dat - $ hg addremove -v - adding large.dat - adding large2.dat - -Test commit's addremove option prior to the first commit - $ hg forget large.dat - $ hg forget large2.dat - $ hg add --large large.dat - $ hg ci -Am "commit" - adding large2.dat as a largefile - Invoking status precommit hook - A large.dat - A large2.dat - A normal - $ find .hglf | sort - .hglf - .hglf/large.dat - .hglf/large2.dat - -Test actions on largefiles using relative paths from subdir - - $ mkdir sub - $ cd sub - $ echo anotherlarge > anotherlarge - $ hg add --large anotherlarge - $ hg st - A sub/anotherlarge - $ hg st anotherlarge - A anotherlarge - $ hg commit -m anotherlarge anotherlarge - Invoking status precommit hook - A sub/anotherlarge - $ hg log anotherlarge - changeset: 1:9627a577c5e9 - tag: tip - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: anotherlarge - - $ hg log -G anotherlarge - @ changeset: 1:9627a577c5e9 - | tag: tip - | user: test - | date: Thu Jan 01 00:00:00 1970 +0000 - | summary: anotherlarge - | - $ echo more >> anotherlarge - $ hg st . - M anotherlarge - $ hg cat anotherlarge - anotherlarge - $ hg revert anotherlarge - $ hg st - ? sub/anotherlarge.orig - $ cd .. - - $ cd .. - -issue3651: summary/outgoing with largefiles shows "no remote repo" -unexpectedly - - $ mkdir issue3651 - $ cd issue3651 - - $ hg init src - $ echo a > src/a - $ hg -R src add --large src/a - $ hg -R src commit -m '#0' - Invoking status precommit hook - A a - -check messages when no remote repository is specified: -"no remote repo" route for "hg outgoing --large" is not tested here, -because it can't be reproduced easily. - - $ hg init clone1 - $ hg -R clone1 -q pull src - $ hg -R clone1 -q update - $ hg -R clone1 paths | grep default - [1] - - $ hg -R clone1 summary --large - parent: 0:fc0bd45326d3 tip - #0 - branch: default - commit: (clean) - update: (current) - largefiles: (no remote repo) - -check messages when there is no files to upload: - - $ hg -q clone src clone2 - $ hg -R clone2 paths | grep default - default = $TESTTMP/issue3651/src (glob) - - $ hg -R clone2 summary --large - parent: 0:fc0bd45326d3 tip - #0 - branch: default - commit: (clean) - update: (current) - largefiles: (no files to upload) - $ hg -R clone2 outgoing --large - comparing with $TESTTMP/issue3651/src (glob) - searching for changes - no changes found - largefiles: no files to upload - [1] - - $ hg -R clone2 outgoing --large --graph --template "{rev}" - comparing with $TESTTMP/issue3651/src (glob) - searching for changes - no changes found - largefiles: no files to upload - -check messages when there are files to upload: - - $ echo b > clone2/b - $ hg -R clone2 add --large clone2/b - $ hg -R clone2 commit -m '#1' - Invoking status precommit hook - A b - $ hg -R clone2 summary --large - parent: 1:1acbe71ce432 tip - #1 - branch: default - commit: (clean) - update: (current) - largefiles: 1 to upload - $ hg -R clone2 outgoing --large - comparing with $TESTTMP/issue3651/src (glob) - searching for changes - changeset: 1:1acbe71ce432 - tag: tip - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: #1 - - largefiles to upload: - b - - $ hg -R clone2 outgoing --large --graph --template "{rev}" - comparing with $TESTTMP/issue3651/src - searching for changes - @ 1 - - largefiles to upload: - b - - - $ cd .. - -merge action 'd' for 'local renamed directory to d2/g' which has no filename - - $ hg init merge-action - $ cd merge-action - $ touch l - $ hg add --large l - $ mkdir d1 - $ touch d1/f - $ hg ci -Aqm0 - Invoking status precommit hook - A d1/f - A l - $ echo > d1/f - $ touch d1/g - $ hg ci -Aqm1 - Invoking status precommit hook - M d1/f - A d1/g - $ hg up -qr0 - $ hg mv d1 d2 - moving d1/f to d2/f (glob) - $ hg ci -qm2 - Invoking status precommit hook - A d2/f - R d1/f - $ hg merge - merging d2/f and d1/f to d2/f - 1 files updated, 1 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - getting changed largefiles - 0 largefiles updated, 0 removed - $ cd .. - - -Merge conflicts: - - $ hg init merge - $ cd merge - $ echo 0 > f-different - $ echo 0 > f-same - $ echo 0 > f-unchanged-1 - $ echo 0 > f-unchanged-2 - $ hg add --large * - $ hg ci -m0 - Invoking status precommit hook - A f-different - A f-same - A f-unchanged-1 - A f-unchanged-2 - $ echo tmp1 > f-unchanged-1 - $ echo tmp1 > f-unchanged-2 - $ echo tmp1 > f-same - $ hg ci -m1 - Invoking status precommit hook - M f-same - M f-unchanged-1 - M f-unchanged-2 - $ echo 2 > f-different - $ echo 0 > f-unchanged-1 - $ echo 1 > f-unchanged-2 - $ echo 1 > f-same - $ hg ci -m2 - Invoking status precommit hook - M f-different - M f-same - M f-unchanged-1 - M f-unchanged-2 - $ hg up -qr0 - $ echo tmp2 > f-unchanged-1 - $ echo tmp2 > f-unchanged-2 - $ echo tmp2 > f-same - $ hg ci -m3 - Invoking status precommit hook - M f-same - M f-unchanged-1 - M f-unchanged-2 - created new head - $ echo 1 > f-different - $ echo 1 > f-unchanged-1 - $ echo 0 > f-unchanged-2 - $ echo 1 > f-same - $ hg ci -m4 - Invoking status precommit hook - M f-different - M f-same - M f-unchanged-1 - M f-unchanged-2 - $ hg merge - largefile f-different has a merge conflict - ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7 - keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or - take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l - 0 files updated, 4 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - getting changed largefiles - 1 largefiles updated, 0 removed - $ cat f-different - 1 - $ cat f-same - 1 - $ cat f-unchanged-1 - 1 - $ cat f-unchanged-2 - 1 - $ cd .. - -Check whether "largefiles" feature is supported only in repositories -enabling largefiles extension. - - $ mkdir individualenabling - $ cd individualenabling - - $ hg init enabledlocally - $ echo large > enabledlocally/large - $ hg -R enabledlocally add --large enabledlocally/large - $ hg -R enabledlocally commit -m '#0' - Invoking status precommit hook - A large - - $ hg init notenabledlocally - $ echo large > notenabledlocally/large - $ hg -R notenabledlocally add --large notenabledlocally/large - $ hg -R notenabledlocally commit -m '#0' - Invoking status precommit hook - A large - - $ cat >> $HGRCPATH <<EOF - > [extensions] - > # disable globally - > largefiles=! - > EOF - $ cat >> enabledlocally/.hg/hgrc <<EOF - > [extensions] - > # enable locally - > largefiles= - > EOF - $ hg -R enabledlocally root - $TESTTMP/individualenabling/enabledlocally (glob) - $ hg -R notenabledlocally root - abort: repository requires features unknown to this Mercurial: largefiles! - (see http://mercurial.selenic.com/wiki/MissingRequirement for more information) - [255] - - $ hg init push-dst - $ hg -R enabledlocally push push-dst - pushing to push-dst - abort: required features are not supported in the destination: largefiles - [255] - - $ hg init pull-src - $ hg -R pull-src pull enabledlocally - pulling from enabledlocally - abort: required features are not supported in the destination: largefiles - [255] - - $ hg clone enabledlocally clone-dst - abort: repository requires features unknown to this Mercurial: largefiles! - (see http://mercurial.selenic.com/wiki/MissingRequirement for more information) - [255] - $ test -d clone-dst - [1] - $ hg clone --pull enabledlocally clone-pull-dst - abort: required features are not supported in the destination: largefiles - [255] - $ test -d clone-pull-dst - [1] - -#if serve - -Test largefiles specific peer setup, when largefiles is enabled -locally (issue4109) - - $ hg showconfig extensions | grep largefiles - extensions.largefiles=! - $ mkdir -p $TESTTMP/individualenabling/usercache - - $ hg serve -R enabledlocally -d -p $HGPORT --pid-file hg.pid - $ cat hg.pid >> $DAEMON_PIDS - - $ hg init pull-dst - $ cat > pull-dst/.hg/hgrc <<EOF - > [extensions] - > # enable locally - > largefiles= - > [largefiles] - > # ignore system cache to force largefiles specific wire proto access - > usercache=$TESTTMP/individualenabling/usercache - > EOF - $ hg -R pull-dst -q pull -u http://localhost:$HGPORT - - $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS -#endif - - $ cd ..
--- a/tests/test-lfconvert.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-lfconvert.t Fri Jun 13 17:42:04 2014 -0500 @@ -132,6 +132,7 @@ [1] $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat $ hg resolve -m stuff/maybelarge.dat + no more unresolved files $ hg commit -m"merge" $ hg log -G --template "{rev}:{node|short} {desc|firstline}\n" @ 5:4884f215abda merge
--- a/tests/test-log.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-log.t Fri Jun 13 17:42:04 2014 -0500 @@ -990,6 +990,7 @@ [1] $ echo 'merge 1' > foo $ hg resolve -m foo + no more unresolved files $ hg ci -m "First merge, related" $ hg merge 4 @@ -1001,6 +1002,7 @@ [1] $ echo 'merge 2' > foo $ hg resolve -m foo + no more unresolved files $ hg ci -m "Last merge, related" $ hg log --graph
--- a/tests/test-merge-commit.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-merge-commit.t Fri Jun 13 17:42:04 2014 -0500 @@ -71,8 +71,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28 + preserving bar for resolve of bar bar: versions differ -> m - preserving bar for resolve of bar updating: bar 1/1 files (100.00%) picked tool 'internal:merge' for bar (binary False symlink False) merging bar @@ -158,8 +158,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0 + preserving bar for resolve of bar bar: versions differ -> m - preserving bar for resolve of bar updating: bar 1/1 files (100.00%) picked tool 'internal:merge' for bar (binary False symlink False) merging bar
--- a/tests/test-merge-criss-cross.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-merge-criss-cross.t Fri Jun 13 17:42:04 2014 -0500 @@ -81,11 +81,11 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922 + preserving f2 for resolve of f2 f1: remote is newer -> g - f2: versions differ -> m - preserving f2 for resolve of f2 getting f1 updating: f1 1/2 files (50.00%) + f2: versions differ -> m updating: f2 2/2 files (100.00%) picked tool 'internal:dump' for f2 (binary False symlink False) merging f2 @@ -135,16 +135,16 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922 - f1: g - f2: m + f1: remote is newer -> g + f2: versions differ -> m calculating bids for ancestor 40663881a6dd searching for copies back to rev 3 resolving manifests branchmerge: True, force: False, partial: False ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922 - f1: m - f2: k + f2: keep -> k + f1: versions differ -> m auction for merging merge bids f1: picking 'get' action @@ -152,9 +152,9 @@ end of auction f1: remote is newer -> g - f2: keep -> k getting f1 updating: f1 1/1 files (100.00%) + f2: keep -> k 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -180,26 +180,26 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5 - f1: k - f2: m + f1: keep -> k + f2: versions differ -> m calculating bids for ancestor 40663881a6dd searching for copies back to rev 3 resolving manifests branchmerge: True, force: False, partial: False ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5 - f1: m - f2: g + f2: remote is newer -> g + f1: versions differ -> m auction for merging merge bids f1: picking 'keep' action f2: picking 'get' action end of auction - f1: keep -> k f2: remote is newer -> g getting f2 updating: f2 1/1 files (100.00%) + f1: keep -> k 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -246,16 +246,16 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922 - f1: g - f2: m + f1: remote is newer -> g + f2: versions differ -> m calculating bids for ancestor 40663881a6dd searching for copies back to rev 3 resolving manifests branchmerge: True, force: False, partial: False ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922 - f1: m - f2: k + f2: keep -> k + f1: versions differ -> m auction for merging merge bids f1: picking 'get' action @@ -263,9 +263,9 @@ end of auction f1: remote is newer -> g - f2: keep -> k getting f1 updating: f1 1/1 files (100.00%) + f2: keep -> k 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)
--- a/tests/test-merge-revert2.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-merge-revert2.t Fri Jun 13 17:42:04 2014 -0500 @@ -57,11 +57,11 @@ @@ -1,3 +1,7 @@ added file1 another line of text - +<<<<<<< local + +<<<<<<< working copy: c3fa057dd86f - test: added file1 and file2 +changed file1 different +======= changed file1 - +>>>>>>> other + +>>>>>>> destination: dfab7f3c2efb - test: changed file1 $ hg status M file1
--- a/tests/test-merge-tools.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-merge-tools.t Fri Jun 13 17:42:04 2014 -0500 @@ -66,11 +66,11 @@ [1] $ aftermerge # cat f - <<<<<<< local + <<<<<<< local: ef83787e2614 - test: revision 1 revision 1 ======= revision 2 - >>>>>>> other + >>>>>>> other: 0185f4e0cf02 - test: revision 2 space # hg stat M f @@ -587,6 +587,54 @@ $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests +update is a merge ... + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg debugsetparent 0 + $ hg update -r 2 + merging f + revision 1 + space + revision 0 + space + revision 2 + space + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + +update should also have --tool + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg debugsetparent 0 + $ hg update -r 2 --tool false + merging f + merging f failed! + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ aftermerge + # cat f + revision 1 + space + # hg stat + M f + ? f.orig + Default is silent simplemerge: $ beforemerge
--- a/tests/test-merge-types.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-merge-types.t Fri Jun 13 17:42:04 2014 -0500 @@ -34,8 +34,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c + preserving a for resolve of a a: versions differ -> m - preserving a for resolve of a updating: a 1/1 files (100.00%) picked tool 'internal:merge' for a (binary False symlink True) merging a @@ -50,6 +50,7 @@ a is a symlink: a -> symlink $ hg resolve a --tool internal:other + no more unresolved files $ tellmeabout a a is an executable file with content: a @@ -67,8 +68,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f + preserving a for resolve of a a: versions differ -> m - preserving a for resolve of a updating: a 1/1 files (100.00%) picked tool 'internal:merge' for a (binary False symlink True) merging a @@ -101,8 +102,8 @@ resolving manifests branchmerge: False, force: False, partial: False ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f + preserving a for resolve of a a: versions differ -> m - preserving a for resolve of a updating: a 1/1 files (100.00%) (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re) picked tool 'internal:prompt' for a (binary False symlink True) @@ -289,18 +290,18 @@ U h $ tellmeabout a a is a plain file with content: - <<<<<<< local + <<<<<<< local: 0139c5610547 - test: 2 2 ======= 1 - >>>>>>> other + >>>>>>> other: 97e29675e796 - test: 1 $ tellmeabout b b is a plain file with content: - <<<<<<< local + <<<<<<< local: 0139c5610547 - test: 2 2 ======= 1 - >>>>>>> other + >>>>>>> other: 97e29675e796 - test: 1 $ tellmeabout c c is a plain file with content: x @@ -344,18 +345,18 @@ [1] $ tellmeabout a a is a plain file with content: - <<<<<<< local + <<<<<<< local: 97e29675e796 - test: 1 1 ======= 2 - >>>>>>> other + >>>>>>> other: 0139c5610547 - test: 2 $ tellmeabout b b is an executable file with content: - <<<<<<< local + <<<<<<< local: 97e29675e796 - test: 1 1 ======= 2 - >>>>>>> other + >>>>>>> other: 0139c5610547 - test: 2 $ tellmeabout c c is an executable file with content: x
--- a/tests/test-merge7.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-merge7.t Fri Jun 13 17:42:04 2014 -0500 @@ -57,6 +57,7 @@ > EOF $ rm -f *.orig $ hg resolve -m test.txt + no more unresolved files $ hg commit -m "Merge 1" change test-a again @@ -83,8 +84,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 96b70246a118, local: 50c3a7e29886+, remote: 40d11a4173a8 + preserving test.txt for resolve of test.txt test.txt: versions differ -> m - preserving test.txt for resolve of test.txt updating: test.txt 1/1 files (100.00%) picked tool 'internal:merge' for test.txt (binary False symlink False) merging test.txt @@ -97,11 +98,11 @@ $ cat test.txt one - <<<<<<< local + <<<<<<< local: 50c3a7e29886 - test: Merge 1 two-point-five ======= two-point-one - >>>>>>> other + >>>>>>> other: 40d11a4173a8 - test: two -> two-point-one three $ hg debugindex test.txt
--- a/tests/test-mq-qfold.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-mq-qfold.t Fri Jun 13 17:42:04 2014 -0500 @@ -20,6 +20,8 @@ $ hg qnew -f p3 Fold in the middle of the queue: +(this tests also that editor is not invoked if '--edit' is not +specified) $ hg qpop p1 popping p3 @@ -34,7 +36,7 @@ a +a - $ hg qfold p2 + $ HGEDITOR=cat hg qfold p2 $ grep git .hg/patches/p1 && echo 'git patch found!' [1] @@ -153,8 +155,9 @@ > repo.__class__ = commitfailure > EOF - $ cat > .hg/hgrc <<EOF + $ cat >> .hg/hgrc <<EOF > [extensions] + > # this failure occurs before editor invocation > commitfailure = $TESTTMP/commitfailure.py > EOF @@ -165,16 +168,95 @@ > (echo; echo "test saving last-message.txt") >> \$1 > EOF + $ hg qapplied + p1 + git + $ hg tip --template "{files}\n" + aa + +(test that editor is not invoked before transaction starting, +and that combination of '--edit' and '--message' doesn't abort execution) + $ rm -f .hg/last-message.txt - $ HGEDITOR="sh $TESTTMP/editor.sh" hg qfold -e p3 - ==== before editing - original message==== + $ HGEDITOR="sh $TESTTMP/editor.sh" hg qfold -e -m MESSAGE p3 refresh interrupted while patch was popped! (revert --all, qpush to recover) abort: emulating unexpected abort [255] $ cat .hg/last-message.txt + cat: .hg/last-message.txt: No such file or directory + [1] + +(reset applied patches and directory status) + + $ cat >> .hg/hgrc <<EOF + > [extensions] + > # this failure occurs after editor invocation + > commitfailure = ! + > EOF + + $ hg qapplied + p1 + $ hg status -A aa + ? aa + $ rm aa + $ hg status -m + M a + $ hg revert --no-backup -q a + $ hg qpush -q git + now at: git + +(test that editor is invoked and commit message is saved into +"last-message.txt") + + $ cat >> .hg/hgrc <<EOF + > [hooks] + > # this failure occurs after editor invocation + > pretxncommit.unexpectedabort = false + > EOF + + $ rm -f .hg/last-message.txt + $ HGEDITOR="sh $TESTTMP/editor.sh" hg qfold -e p3 + ==== before editing original message + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to use default message. + HG: -- + HG: user: test + HG: branch 'default' + HG: added aa + HG: changed a + ==== + transaction abort! + rollback completed + note: commit message saved in .hg/last-message.txt + refresh interrupted while patch was popped! (revert --all, qpush to recover) + abort: pretxncommit.unexpectedabort hook exited with status 1 + [255] + $ cat .hg/last-message.txt + original message + + + test saving last-message.txt +(confirm whether files listed up in the commit message editing are correct) + + $ cat >> .hg/hgrc <<EOF + > [hooks] + > pretxncommit.unexpectedabort = + > EOF + $ hg status -u | while read f; do rm ${f}; done + $ hg revert --no-backup -q --all + $ hg qpush -q git + now at: git + $ hg qpush -q --move p3 + now at: p3 + + $ hg status --rev "git^1" --rev . -arm + M a + A aa + $ cd ..
--- a/tests/test-mq-qnew.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-mq-qnew.t Fri Jun 13 17:42:04 2014 -0500 @@ -158,6 +158,7 @@ merging a incomplete! (edit conflicts, then use 'hg resolve --mark') 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 + no more unresolved files abort: cannot manage merge changesets $ rm -r sandbox @@ -231,6 +232,7 @@ merging a incomplete! (edit conflicts, then use 'hg resolve --mark') 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 + no more unresolved files abort: cannot manage merge changesets $ rm -r sandbox @@ -247,8 +249,9 @@ > raise util.Abort('emulating unexpected abort') > repo.__class__ = commitfailure > EOF - $ cat > .hg/hgrc <<EOF + $ cat >> .hg/hgrc <<EOF > [extensions] + > # this failure occurs before editor invocation > commitfailure = $TESTTMP/commitfailure.py > EOF @@ -259,13 +262,83 @@ > echo "test saving last-message.txt" >> \$1 > EOF +(test that editor is not invoked before transaction starting) + $ rm -f .hg/last-message.txt $ HGEDITOR="sh $TESTTMP/editor.sh" hg qnew -e patch - ==== before editing - ==== abort: emulating unexpected abort [255] $ cat .hg/last-message.txt + cat: .hg/last-message.txt: No such file or directory + [1] + +(test that editor is invoked and commit message is saved into +"last-message.txt") + + $ cat >> .hg/hgrc <<EOF + > [extensions] + > commitfailure = ! + > [hooks] + > # this failure occurs after editor invocation + > pretxncommit.unexpectedabort = false + > EOF + + $ rm -f .hg/last-message.txt + $ hg status + $ HGEDITOR="sh $TESTTMP/editor.sh" hg qnew -e patch + ==== before editing + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to use default message. + HG: -- + HG: user: test + HG: branch 'default' + HG: no files changed + ==== + 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 + + test saving last-message.txt + $ cat >> .hg/hgrc <<EOF + > [hooks] + > pretxncommit.unexpectedabort = + > EOF + +#if unix-permissions + +Test handling default message with the patch filename with tail whitespaces + + $ cat > $TESTTMP/editor.sh << EOF + > echo "==== before editing" + > cat \$1 + > echo "====" + > echo "[mq]: patch " > \$1 + > EOF + + $ rm -f .hg/last-message.txt + $ hg status + $ HGEDITOR="sh $TESTTMP/editor.sh" hg qnew -e "patch " + ==== before editing + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to use default message. + HG: -- + HG: user: test + HG: branch 'default' + HG: no files changed + ==== + $ cat ".hg/patches/patch " + # HG changeset patch + # Parent 0000000000000000000000000000000000000000 + $ cd .. + +#endif
--- a/tests/test-mq-qrefresh-replace-log-message.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-mq-qrefresh-replace-log-message.t Fri Jun 13 17:42:04 2014 -0500 @@ -6,6 +6,8 @@ $ hg qinit Should fail if no patches applied +(this tests also that editor is not invoked if '--edit' is not +specified) $ hg qrefresh no patches applied @@ -16,7 +18,7 @@ $ hg qnew -m "First commit message" first-patch $ echo aaaa > file $ hg add file - $ hg qrefresh + $ HGEDITOR=cat hg qrefresh Should display 'First commit message' @@ -24,9 +26,19 @@ First commit message Testing changing message with -m +(this tests also that '--edit' can be used with '--message') $ echo bbbb > file - $ hg qrefresh -m "Second commit message" + $ HGEDITOR=cat hg qrefresh -m "Second commit message" -e + Second commit message + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to use default message. + HG: -- + HG: user: test + HG: branch 'default' + HG: added file Should display 'Second commit message' @@ -59,3 +71,98 @@ $ hg log -l1 --template "{desc}\n" Fifth commit message This is the 5th log message + +Test saving last-message.txt: + + $ cat > $TESTTMP/editor.sh << EOF + > echo "==== before editing" + > cat \$1 + > echo "====" + > (echo; echo "test saving last-message.txt") >> \$1 + > EOF + + $ cat > $TESTTMP/commitfailure.py <<EOF + > from mercurial import util + > def reposetup(ui, repo): + > class commitfailure(repo.__class__): + > def commit(self, *args, **kwargs): + > raise util.Abort('emulating unexpected abort') + > repo.__class__ = commitfailure + > EOF + + $ cat >> .hg/hgrc <<EOF + > [extensions] + > # this failure occurs before editor invocation + > commitfailure = $TESTTMP/commitfailure.py + > EOF + + $ hg qapplied + first-patch + second-patch + $ hg tip --template "{files}\n" + file2 + +(test that editor is not invoked before transaction starting) + + $ rm -f .hg/last-message.txt + $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e + refresh interrupted while patch was popped! (revert --all, qpush to recover) + abort: emulating unexpected abort + [255] + $ cat .hg/last-message.txt + cat: .hg/last-message.txt: No such file or directory + [1] + +(reset applied patches and directory status) + + $ cat >> .hg/hgrc <<EOF + > [extensions] + > commitfailure = ! + > EOF + + $ hg qapplied + first-patch + $ hg status -A file2 + ? file2 + $ rm file2 + $ hg qpush -q second-patch + now at: second-patch + +(test that editor is invoked and commit message is saved into +"last-message.txt") + + $ cat >> .hg/hgrc <<EOF + > [hooks] + > # this failure occurs after editor invocation + > pretxncommit.unexpectedabort = false + > EOF + + $ rm -f .hg/last-message.txt + $ hg status --rev "second-patch^1" -arm + A file2 + $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e + ==== before editing + Fifth commit message + This is the 5th log message + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to use default message. + HG: -- + HG: user: test + HG: branch 'default' + HG: added file2 + ==== + transaction abort! + rollback completed + note: commit message saved in .hg/last-message.txt + refresh interrupted while patch was popped! (revert --all, qpush to recover) + abort: pretxncommit.unexpectedabort hook exited with status 1 + [255] + $ cat .hg/last-message.txt + Fifth commit message + This is the 5th log message + + + + test saving last-message.txt
--- a/tests/test-patchbomb.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-patchbomb.t Fri Jun 13 17:42:04 2014 -0500 @@ -1,3 +1,13 @@ +Note for future hackers of patchbomb: this file is a bit heavy on +wildcards in test expectations due to how many things like hostnames +tend to make it into outputs. As a result, you may need to perform the +following regular expression substitutions: +@$HOSTNAME> -> @*> (glob) +Mercurial-patchbomb/.* -> Mercurial-patchbomb/* (glob) +/mixed; boundary="===+[0-9]+==" -> /mixed; boundary="===*== (glob)" +--===+[0-9]+=+--$ -> --===*=-- (glob) +--===+[0-9]+=+$ -> --===*= (glob) + $ echo "[extensions]" >> $HGRCPATH $ echo "patchbomb=" >> $HGRCPATH @@ -17,7 +27,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <8580ff50825a50c8f716.60@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux @@ -87,7 +100,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.121@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.121@*> (glob) In-Reply-To: <patchbomb.120@*> (glob) References: <patchbomb.120@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -116,7 +132,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.122@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.121@*> (glob) In-Reply-To: <patchbomb.120@*> (glob) References: <patchbomb.120@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -200,7 +219,7 @@ 1 changesets found displaying test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: test Message-Id: <patchbomb.180@*> (glob) @@ -210,7 +229,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -219,7 +238,7 @@ description - --===* (glob) + --===*= (glob) Content-Type: application/x-mercurial-bundle MIME-Version: 1.0 Content-Disposition: attachment; filename="bundle.hg" @@ -232,7 +251,7 @@ SlIBpFisgGkyRjX//TMtfcUAEsGu56+YnE1OlTZmzKm8BSu2rvo4rHAYYaadIFFuTy0LYgIkgLVD sgVa2F19D1tx9+hgbAygLgQwaIqcDdgA4BjQgIiz/AEP72++llgDKhKducqodGE4B0ETqF3JFOFC Q70eyNw= - --===*-- (glob) + --===*=-- (glob) utf-8 patch: $ python -c 'fp = open("utf", "wb"); fp.write("h\xC3\xB6mma!\n"); fp.close();' @@ -251,7 +270,10 @@ Content-Transfer-Encoding: 8bit Subject: [PATCH] utf-8 content X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <909a00e13e9d78b575ae.240@*> (glob) + X-Mercurial-Series-Id: <909a00e13e9d78b575ae.240@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:04:00 +0000 From: quux @@ -294,7 +316,10 @@ Content-Transfer-Encoding: base64 Subject: [PATCH] utf-8 content X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <909a00e13e9d78b575ae.240@*> (glob) + X-Mercurial-Series-Id: <909a00e13e9d78b575ae.240@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:04:00 +0000 From: Q <quux> @@ -353,7 +378,10 @@ Content-Transfer-Encoding: quoted-printable Subject: [PATCH] long line X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob) + X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:04:00 +0000 From: quux @@ -404,7 +432,10 @@ Content-Transfer-Encoding: quoted-printable Subject: [PATCH] long line X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob) + X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:04:00 +0000 From: quux @@ -463,7 +494,10 @@ Content-Transfer-Encoding: 8bit Subject: [PATCH] isolatin 8-bit encoding X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720 + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <240fb913fc1b7ff15ddb.300@*> (glob) + X-Mercurial-Series-Id: <240fb913fc1b7ff15ddb.300@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:05:00 +0000 From: quux @@ -508,7 +542,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux @@ -584,7 +621,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -617,7 +657,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -651,18 +694,21 @@ displaying [PATCH] test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -682,7 +728,7 @@ @@ -0,0 +1,1 @@ +c - --===*-- (glob) + --===*=-- (glob) test inline for single patch (quoted-printable): @@ -691,18 +737,21 @@ displaying [PATCH] test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH] test X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob) + X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable @@ -738,7 +787,7 @@ + +bar - --===*-- (glob) + --===*=-- (glob) test inline for multiple patches: $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \ @@ -763,11 +812,14 @@ displaying [PATCH 1 of 3] a ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 1 of 3] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 3 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -776,7 +828,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -796,13 +848,16 @@ @@ -0,0 +1,1 @@ +a - --===*-- (glob) + --===*=-- (glob) displaying [PATCH 2 of 3] b ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 2 of 3] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 3 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -811,7 +866,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -831,13 +886,16 @@ @@ -0,0 +1,1 @@ +b - --===*-- (glob) + --===*=-- (glob) displaying [PATCH 3 of 3] long line ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 3 of 3] long line X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 3 + X-Mercurial-Series-Total: 3 Message-Id: <a2ea8fc83dd8b93cfd86.63@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -846,7 +904,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable @@ -882,7 +940,7 @@ + +bar - --===*-- (glob) + --===*=-- (glob) test attach for single patch: $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 2 @@ -890,18 +948,21 @@ displaying [PATCH] test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -910,7 +971,7 @@ - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -930,7 +991,7 @@ @@ -0,0 +1,1 @@ +c - --===*-- (glob) + --===*=-- (glob) test attach for single patch (quoted-printable): $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 4 @@ -938,18 +999,21 @@ displaying [PATCH] test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH] test X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob) + X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -958,7 +1022,7 @@ - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable @@ -994,7 +1058,7 @@ + +bar - --===*-- (glob) + --===*=-- (glob) test attach and body for single patch: $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a --body -r 2 @@ -1002,18 +1066,21 @@ displaying [PATCH] test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1032,7 +1099,7 @@ @@ -0,0 +1,1 @@ +c - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1052,7 +1119,7 @@ @@ -0,0 +1,1 @@ +c - --===*-- (glob) + --===*=-- (glob) test attach for multiple patches: $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a \ @@ -1077,11 +1144,14 @@ displaying [PATCH 1 of 3] a ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 1 of 3] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 3 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1090,7 +1160,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1099,7 +1169,7 @@ - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1119,13 +1189,16 @@ @@ -0,0 +1,1 @@ +a - --===*-- (glob) + --===*=-- (glob) displaying [PATCH 2 of 3] b ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 2 of 3] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 3 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1134,7 +1207,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1143,7 +1216,7 @@ - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1163,13 +1236,16 @@ @@ -0,0 +1,1 @@ +b - --===*-- (glob) + --===*=-- (glob) displaying [PATCH 3 of 3] long line ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 3 of 3] long line X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 3 + X-Mercurial-Series-Total: 3 Message-Id: <a2ea8fc83dd8b93cfd86.63@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1178,7 +1254,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1187,7 +1263,7 @@ - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable @@ -1223,7 +1299,7 @@ + +bar - --===*-- (glob) + --===*=-- (glob) test intro for single patch: $ hg email --date '1970-1-1 0:1' -n --intro -f quux -t foo -c bar -s test \ @@ -1253,7 +1329,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 1] c X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.61@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1304,7 +1383,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 1] c X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.61@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1356,7 +1438,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1385,7 +1470,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1421,7 +1509,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux @@ -1456,7 +1547,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux @@ -1490,18 +1584,21 @@ displaying [PATCH] test ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1521,7 +1618,7 @@ @@ -0,0 +1,1 @@ +c - --===*-- (glob) + --===*=-- (glob) test inline for multiple named/unnamed patches: $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 0:1 @@ -1545,11 +1642,14 @@ displaying [PATCH 1 of 2] a ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 1 of 2] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1558,7 +1658,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1578,13 +1678,16 @@ @@ -0,0 +1,1 @@ +a - --===*-- (glob) + --===*=-- (glob) displaying [PATCH 2 of 2] b ... - Content-Type: multipart/mixed; boundary="===*" (glob) + Content-Type: multipart/mixed; boundary="===*==" (glob) MIME-Version: 1.0 Subject: [PATCH 2 of 2] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1593,7 +1696,7 @@ To: foo Cc: bar - --===* (glob) + --===*= (glob) Content-Type: text/x-patch; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit @@ -1613,7 +1716,7 @@ @@ -0,0 +1,1 @@ +b - --===*-- (glob) + --===*=-- (glob) test inreplyto: @@ -1628,7 +1731,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b X-Mercurial-Node: 7aead2484924c445ad8ce2613df91f52f9e502ed + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <7aead2484924c445ad8c.60@*> (glob) + X-Mercurial-Series-Id: <7aead2484924c445ad8c.60@*> (glob) In-Reply-To: <baz> References: <baz> User-Agent: Mercurial-patchbomb/* (glob) @@ -1668,7 +1774,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.60@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob) In-Reply-To: <baz> References: <baz> User-Agent: Mercurial-patchbomb/* (glob) @@ -1697,7 +1806,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob) In-Reply-To: <baz> References: <baz> User-Agent: Mercurial-patchbomb/* (glob) @@ -1752,7 +1864,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1781,7 +1896,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1819,7 +1937,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH fooFlag] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux @@ -1870,7 +1991,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2 fooFlag] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1899,7 +2023,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2 fooFlag] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -1937,7 +2064,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH fooFlag barFlag] test X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Thu, 01 Jan 1970 00:01:00 +0000 From: quux @@ -1987,7 +2117,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 2 fooFlag barFlag] a X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 2 Message-Id: <8580ff50825a50c8f716.61@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2016,7 +2149,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2 fooFlag barFlag] b X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 2 Message-Id: <97d72e5f12c7e84f8506.62@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: <patchbomb.60@*> (glob) References: <patchbomb.60@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2055,7 +2191,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] test X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <8580ff50825a50c8f716.315532860@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Tue, 01 Jan 1980 00:01:00 +0000 From: quux @@ -2097,7 +2236,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] test X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <8580ff50825a50c8f716.315532860@*> (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Tue, 01 Jan 1980 00:01:00 +0000 From: quux @@ -2184,7 +2326,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 1 of 6] c X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 6 Message-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) In-Reply-To: <patchbomb.315532860@*> (glob) References: <patchbomb.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2212,7 +2357,10 @@ Content-Transfer-Encoding: 8bit Subject: [PATCH 2 of 6] utf-8 content X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f + X-Mercurial-Series-Index: 2 + X-Mercurial-Series-Total: 6 Message-Id: <909a00e13e9d78b575ae.315532862@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) In-Reply-To: <patchbomb.315532860@*> (glob) References: <patchbomb.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2247,7 +2395,10 @@ Content-Transfer-Encoding: quoted-printable Subject: [PATCH 3 of 6] long line X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1 + X-Mercurial-Series-Index: 3 + X-Mercurial-Series-Total: 6 Message-Id: <a2ea8fc83dd8b93cfd86.315532863@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) In-Reply-To: <patchbomb.315532860@*> (glob) References: <patchbomb.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2291,7 +2442,10 @@ Content-Transfer-Encoding: 8bit Subject: [PATCH 4 of 6] isolatin 8-bit encoding X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720 + X-Mercurial-Series-Index: 4 + X-Mercurial-Series-Total: 6 Message-Id: <240fb913fc1b7ff15ddb.315532864@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) In-Reply-To: <patchbomb.315532860@*> (glob) References: <patchbomb.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2319,7 +2473,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a X-Mercurial-Node: 5d5ef15dfe5e7bd3a4ee154b5fff76c7945ec433 + X-Mercurial-Series-Index: 5 + X-Mercurial-Series-Total: 6 Message-Id: <5d5ef15dfe5e7bd3a4ee.315532865@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) In-Reply-To: <patchbomb.315532860@*> (glob) References: <patchbomb.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2348,7 +2505,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH 6 of 6] d X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268 + X-Mercurial-Series-Index: 6 + X-Mercurial-Series-Total: 6 Message-Id: <2f9fa9b998c5fe3ac2bd.315532866@*> (glob) + X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob) In-Reply-To: <patchbomb.315532860@*> (glob) References: <patchbomb.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) @@ -2386,7 +2546,10 @@ Content-Transfer-Encoding: 7bit Subject: [PATCH] test X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268 + X-Mercurial-Series-Index: 1 + X-Mercurial-Series-Total: 1 Message-Id: <2f9fa9b998c5fe3ac2bd.315532860@*> (glob) + X-Mercurial-Series-Id: <2f9fa9b998c5fe3ac2bd.315532860@*> (glob) User-Agent: Mercurial-patchbomb/* (glob) Date: Tue, 01 Jan 1980 00:01:00 +0000 From: test
--- a/tests/test-progress.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-progress.t Fri Jun 13 17:42:04 2014 -0500 @@ -1,7 +1,11 @@ $ cat > loop.py <<EOF - > from mercurial import commands + > from mercurial import cmdutil, commands > import time + > + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > > class incrementingtime(object): > def __init__(self): > self._time = 0.0 @@ -10,6 +14,11 @@ > return self._time > time.time = incrementingtime() > + > @command('loop', + > [('', 'total', '', 'override for total'), + > ('', 'nested', False, 'show nested results'), + > ('', 'parallel', False, 'show parallel sets of results')], + > 'hg loop LOOPS') > def loop(ui, loops, **opts): > loops = int(loops) > total = None @@ -38,14 +47,6 @@ > ui.progress('loop', None, 'loop.done', 'loopnum', total) > > commands.norepo += " loop" - > - > cmdtable = { - > "loop": (loop, [('', 'total', '', 'override for total'), - > ('', 'nested', False, 'show nested results'), - > ('', 'parallel', False, 'show parallel sets of results'), - > ], - > 'hg loop LOOPS'), - > } > EOF $ cp $HGRCPATH $HGRCPATH.orig
--- a/tests/test-rebase-bookmarks.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-bookmarks.t Fri Jun 13 17:42:04 2014 -0500 @@ -154,6 +154,7 @@ $ hg up 2 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (leaving bookmark X) $ echo 'C' > c $ hg add c $ hg ci -m 'other C' @@ -168,6 +169,7 @@ [1] $ echo 'c' > c $ hg resolve --mark c + no more unresolved files $ hg rebase --continue saved backup bundle to $TESTTMP/a3/.hg/strip-backup/3d5fa227f4b5-backup.hg (glob) $ hg tglog
--- a/tests/test-rebase-check-restore.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-check-restore.t Fri Jun 13 17:42:04 2014 -0500 @@ -76,6 +76,7 @@ $ echo 'conflict solved' > A $ rm A.orig $ hg resolve -m A + no more unresolved files $ hg rebase --continue $ hg tglog @@ -129,6 +130,7 @@ $ echo 'conflict solved' > A $ rm A.orig $ hg resolve -m A + no more unresolved files $ hg rebase --continue saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-conflicts.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-conflicts.t Fri Jun 13 17:42:04 2014 -0500 @@ -77,6 +77,7 @@ $ echo 'resolved merge' >common $ hg resolve -m common + no more unresolved files $ hg rebase --continue saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob) @@ -219,9 +220,9 @@ branchmerge: False, force: True, partial: False ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b f2.txt: other deleted -> r - f1.txt: remote created -> g removing f2.txt updating: f2.txt 1/2 files (50.00%) + f1.txt: remote created -> g getting f1.txt updating: f1.txt 2/2 files (100.00%) merge against 9:e31216eec445 @@ -254,9 +255,9 @@ branchmerge: False, force: False, partial: False ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0 f1.txt: other deleted -> r - f2.txt: remote created -> g removing f1.txt updating: f1.txt 1/2 files (50.00%) + f2.txt: remote created -> g getting f2.txt updating: f2.txt 2/2 files (100.00%) 3 changesets found
--- a/tests/test-rebase-detach.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-detach.t Fri Jun 13 17:42:04 2014 -0500 @@ -374,6 +374,7 @@ unresolved conflicts (see hg resolve, then hg rebase --continue) [1] $ hg resolve --all -t internal:local + no more unresolved files $ hg rebase -c saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6215fafa5447-backup.hg (glob) $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
--- a/tests/test-rebase-interruptions.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-interruptions.t Fri Jun 13 17:42:04 2014 -0500 @@ -104,6 +104,7 @@ $ echo 'conflict solved' > A $ rm A.orig $ hg resolve -m A + no more unresolved files $ hg rebase --continue warning: new changesets detected on source branch, not stripping
--- a/tests/test-rebase-mq-skip.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-mq-skip.t Fri Jun 13 17:42:04 2014 -0500 @@ -111,6 +111,7 @@ [1] $ HGMERGE=internal:local hg resolve --all + no more unresolved files $ hg rebase --continue saved backup bundle to $TESTTMP/b/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-mq.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-mq.t Fri Jun 13 17:42:04 2014 -0500 @@ -69,6 +69,7 @@ $ echo mq1r1 > f $ hg resolve -m f + no more unresolved files $ hg rebase -c merging f warning: conflicts during merge. @@ -80,6 +81,7 @@ $ echo mq1r1mq2 > f $ hg resolve -m f + no more unresolved files $ hg rebase -c saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-parameters.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-parameters.t Fri Jun 13 17:42:04 2014 -0500 @@ -454,6 +454,7 @@ U c2 $ hg resolve -m c2 + no more unresolved files $ hg rebase -c --tool internal:fail tool option will be ignored saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-scenario-global.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rebase-scenario-global.t Fri Jun 13 17:42:04 2014 -0500 @@ -25,6 +25,7 @@ Rebasing D onto H - simple rebase: +(this also tests that editor is invoked if '--edit' is specified) $ hg clone -q -u . a a1 $ cd a1 @@ -47,7 +48,18 @@ o 0: 'A' - $ hg rebase -s 3 -d 7 + $ hg status --rev "3^1" --rev 3 + A D + $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit + D + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com> + HG: branch 'default' + HG: changed D saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -71,11 +83,12 @@ D onto F - intermediate point: +(this also tests that editor is not invoked if '--edit' is not specified) $ hg clone -q -u . a a2 $ cd a2 - $ hg rebase -s 3 -d 5 + $ HGEDITOR=cat hg rebase -s 3 -d 5 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog
--- a/tests/test-rename-dir-merge.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rename-dir-merge.t Fri Jun 13 17:42:04 2014 -0500 @@ -40,16 +40,16 @@ branchmerge: True, force: False, partial: False ancestor: f9b20c0d4c51, local: ce36d17b18fb+, remote: 397f8b00a740 a/a: other deleted -> r + removing a/a a/b: other deleted -> r - b/a: remote created -> g - b/b: remote created -> g - b/c: remote directory rename - move from a/c -> dm - removing a/a removing a/b updating: a/b 2/5 files (40.00%) + b/a: remote created -> g getting b/a + b/b: remote created -> g getting b/b updating: b/b 4/5 files (80.00%) + b/c: remote directory rename - move from a/c -> dm updating: b/c 5/5 files (100.00%) moving a/c to b/c (glob) 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
--- a/tests/test-rename-merge1.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rename-merge1.t Fri Jun 13 17:42:04 2014 -0500 @@ -36,22 +36,22 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: af1939970a1c, local: 044f8520aeeb+, remote: 85c198ef2f6c - a2: divergent renames -> dr - b: remote moved from a -> m - preserving a for resolve of b + preserving a for resolve of b + removing a b2: remote created -> g - removing a getting b2 updating: b2 1/3 files (33.33%) - updating: a2 2/3 files (66.67%) - note: possible conflict - a2 was renamed multiple times to: - c2 - b2 - updating: b 3/3 files (100.00%) + b: remote moved from a -> m + updating: b 2/3 files (66.67%) picked tool 'internal:merge' for b (binary False symlink False) merging a and b to b my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c premerge successful + a2: divergent renames -> dr + updating: a2 3/3 files (100.00%) + 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) @@ -181,10 +181,10 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 19d7f95df299, local: 0084274f6b67+, remote: 5d32493049f0 - file: rename and delete -> rd newfile: remote created -> g getting newfile updating: newfile 1/2 files (50.00%) + file: rename and delete -> rd updating: file 2/2 files (100.00%) note: possible conflict - file was deleted and renamed to: newfile
--- a/tests/test-rename-merge2.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rename-merge2.t Fri Jun 13 17:42:04 2014 -0500 @@ -86,16 +86,16 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: e300d1c794ec+, remote: 4ce40f5aca24 + preserving a for resolve of b + preserving rev for resolve of rev a: keep -> k b: remote copied from a -> m - preserving a for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging a and b to b my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337 premerge successful + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -122,18 +122,18 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 86a2aa42fc76+, remote: f4db7e329e71 + preserving b for resolve of b + preserving rev for resolve of rev a: remote is newer -> g - b: local copied/moved from a -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev getting a updating: a 1/3 files (33.33%) + b: local copied/moved from a -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -160,16 +160,16 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: e300d1c794ec+, remote: bdb19105162a + preserving a for resolve of b + preserving rev for resolve of rev + removing a b: remote moved from a -> m - preserving a for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev - removing a updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging a and b to b my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337 premerge successful + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -195,15 +195,15 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 02963e448370+, remote: f4db7e329e71 + preserving b for resolve of b + preserving rev for resolve of rev b: local copied/moved from a -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -229,11 +229,11 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 94b33a1b7f2d+, remote: 4ce40f5aca24 + preserving rev for resolve of rev b: remote created -> g - rev: versions differ -> m - preserving rev for resolve of rev getting b updating: b 1/2 files (50.00%) + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -259,8 +259,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 97c705ade336 + preserving rev for resolve of rev rev: versions differ -> m - preserving rev for resolve of rev updating: rev 1/1 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -286,14 +286,14 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 94b33a1b7f2d+, remote: bdb19105162a + preserving rev for resolve of rev a: other deleted -> r - b: remote created -> g - rev: versions differ -> m - preserving rev for resolve of rev removing a updating: a 1/3 files (33.33%) + b: remote created -> g getting b updating: b 2/3 files (66.67%) + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -318,8 +318,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 02963e448370+, remote: 97c705ade336 + preserving rev for resolve of rev rev: versions differ -> m - preserving rev for resolve of rev updating: rev 1/1 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -341,14 +341,14 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 62e7bf090eba+, remote: 49b6d8032493 + preserving b for resolve of b + preserving rev for resolve of rev b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337 + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -379,20 +379,20 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 02963e448370+, remote: fe905ef2c33e - a: divergent renames -> dr + preserving rev for resolve of rev c: remote created -> g - rev: versions differ -> m - preserving rev for resolve of rev getting c updating: c 1/3 files (33.33%) - updating: a 2/3 files (66.67%) + rev: versions differ -> m + updating: rev 2/3 files (66.67%) + picked tool 'python ../merge' for rev (binary False symlink False) + merging rev + my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 + a: divergent renames -> dr + updating: a 3/3 files (100.00%) note: possible conflict - a was renamed multiple times to: b c - updating: rev 3/3 files (100.00%) - picked tool 'python ../merge' for rev (binary False symlink False) - merging rev - my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) -------------- @@ -411,14 +411,14 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 86a2aa42fc76+, remote: af30c7647fc7 + preserving b for resolve of b + preserving rev for resolve of rev b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000 + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -441,17 +441,17 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 59318016310c+, remote: bdb19105162a + preserving b for resolve of b + preserving rev for resolve of rev a: other deleted -> r - b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev removing a updating: a 1/3 files (33.33%) + b: versions differ -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -473,17 +473,17 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 8dbce441892a + preserving b for resolve of b + preserving rev for resolve of rev a: remote is newer -> g - b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev getting a updating: a 1/3 files (33.33%) + b: versions differ -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000 + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -506,17 +506,17 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 59318016310c+, remote: bdb19105162a + preserving b for resolve of b + preserving rev for resolve of rev a: other deleted -> r - b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev removing a updating: a 1/3 files (33.33%) + b: versions differ -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -538,17 +538,17 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 8dbce441892a + preserving b for resolve of b + preserving rev for resolve of rev a: remote is newer -> g - b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev getting a updating: a 1/3 files (33.33%) + b: versions differ -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000 + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -571,15 +571,15 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 0b76e65c8289+, remote: 4ce40f5aca24 + preserving b for resolve of b + preserving rev for resolve of rev a: keep -> k b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor b@000000000000 + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -604,17 +604,17 @@ 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 - b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev getting a updating: a 1/3 files (33.33%) + b: versions differ -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@02963e448370+ other b@8dbce441892a ancestor b@000000000000 + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -639,16 +639,16 @@ ancestor: 924404dff337, local: 0b76e65c8289+, remote: bdb19105162a local changed a which remote deleted use (c)hanged version or (d)elete? c + preserving b for resolve of b + preserving rev for resolve of rev a: prompt keep -> a + updating: a 1/3 files (33.33%) b: versions differ -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev - updating: a 1/3 files (33.33%) updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b my b@0b76e65c8289+ other b@bdb19105162a ancestor b@000000000000 + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -674,15 +674,15 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: e300d1c794ec+, remote: 49b6d8032493 + preserving a for resolve of b + preserving rev for resolve of rev + removing a b: remote moved from a -> m - preserving a for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev - removing a updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging a and b to b my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337 + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -708,14 +708,14 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 62e7bf090eba+, remote: f4db7e329e71 + preserving b for resolve of b + preserving rev for resolve of rev b: local copied/moved from a -> m - preserving b for resolve of b - rev: versions differ -> m - preserving rev for resolve of rev updating: b 1/2 files (50.00%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337 + rev: versions differ -> m updating: rev 2/2 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -746,18 +746,18 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 924404dff337, local: 02963e448370+, remote: 2b958612230f - b: local copied/moved from a -> m - preserving b for resolve of b + preserving b for resolve of b + preserving rev for resolve of rev c: remote created -> g - rev: versions differ -> m - preserving rev for resolve of rev getting c updating: c 1/3 files (33.33%) + b: local copied/moved from a -> m updating: b 2/3 files (66.67%) picked tool 'python ../merge' for b (binary False symlink False) merging b and a to b my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337 premerge successful + rev: versions differ -> m updating: rev 3/3 files (100.00%) picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -836,28 +836,18 @@ ancestor: e6cb3cf11019, local: ec44bf929ab5+, remote: c62e34d0b898 remote changed 8/f which local deleted use (c)hanged version or leave (d)eleted? c - 0/f: versions differ -> m - preserving 0/f for resolve of 0/f - 1/g: versions differ -> m - preserving 1/g for resolve of 1/g - 2/f: versions differ -> m - preserving 2/f for resolve of 2/f - 3/f: versions differ -> m - preserving 3/f for resolve of 3/f - 3/g: remote copied from 3/f -> m - preserving 3/f for resolve of 3/g - 4/g: remote moved from 4/f -> m - preserving 4/f for resolve of 4/g - 5/f: versions differ -> m - preserving 5/f for resolve of 5/f - 5/g: local copied/moved from 5/f -> m - preserving 5/g for resolve of 5/g - 6/g: local copied/moved from 6/f -> m - preserving 6/g for resolve of 6/g - 7/f: remote differs from untracked local -> m - preserving 7/f for resolve of 7/f + 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 - removing 4/f getting 8/f $ hg mani 0/f
--- a/tests/test-resolve.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-resolve.t Fri Jun 13 17:42:04 2014 -0500 @@ -26,14 +26,31 @@ use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon [1] +resolve -l should contain an unresolved entry + + $ hg resolve -l + U file + +resolving an unknown path emits a warning + $ hg resolve -m does-not-exist + arguments do not match paths that need resolving + +resolve the failure + $ echo resolved > file $ hg resolve -m file + no more unresolved files $ hg commit -m 'resolved' -resolve -l, should be empty +resolve -l should be empty $ hg resolve -l +resolve -m should abort since no merge in progress + $ hg resolve -m + abort: resolve command not applicable when not merging + [255] + test crashed merge with empty mergestate $ mkdir .hg/merge
--- a/tests/test-revert.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-revert.t Fri Jun 13 17:42:04 2014 -0500 @@ -299,4 +299,86 @@ removed R ignored - $ cd .. +Test revert of a file added by one side of the merge + +(remove any pending change) + + $ hg revert --all + forgetting allyour + forgetting base + undeleting ignored + $ hg purge --all --config extensions.purge= + +(Adds a new commit) + + $ echo foo > newadd + $ hg add newadd + $ hg commit -m 'other adds' + created new head + + +(merge it with the other head) + + $ hg merge # merge 1 into 2 + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg summary + parent: 2:b8ec310b2d4e tip + other adds + parent: 1:f6180deb8fbe + rename + branch: default + commit: 2 modified, 1 removed (merge) + update: (current) + +(clarifies who added what) + + $ hg status + M allyour + M base + R ignored + $ hg status --change 'p1()' + A newadd + $ hg status --change 'p2()' + A allyour + A base + R ignored + +(revert file added by p1() to p1() state) + + $ hg revert -r 'p1()' 'glob:newad?' + $ hg status + M allyour + M base + R ignored + +(revert file added by p1() to p2() state) + + $ hg revert -r 'p2()' 'glob:newad?' + removing newadd + $ hg status + M allyour + M base + R ignored + R newadd + +(revert file added by p2() to p2() state) + + $ hg revert -r 'p2()' 'glob:allyou?' + $ hg status + M allyour + M base + R ignored + R newadd + +(revert file added by p2() to p1() state) + + $ hg revert -r 'p1()' 'glob:allyou?' + removing allyour + $ hg status + M base + R allyour + R ignored + R newadd + +
--- a/tests/test-rollback.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-rollback.t Fri Jun 13 17:42:04 2014 -0500 @@ -82,6 +82,7 @@ 0 default add a again $ hg update default 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (leaving bookmark foo) $ hg bookmark bar $ cat .hg/undo.branch ; echo test
--- a/tests/test-run-tests.py Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-run-tests.py Fri Jun 13 17:42:04 2014 -0500 @@ -29,7 +29,7 @@ assert expected.endswith('\n') and output.endswith('\n'), 'missing newline' assert not re.search(r'[^ \w\\/\r\n()*?]', expected + output), \ 'single backslash or unknown char' - match = run_tests.linematch(expected, output) + match = run_tests.TTest.linematch(expected, output) if isinstance(match, str): return 'special: ' + match else:
--- a/tests/test-run-tests.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-run-tests.t Fri Jun 13 17:42:04 2014 -0500 @@ -1,121 +1,145 @@ -Simple commands: +This file tests the behavior of run-tests.py itself. + +Smoke test +============ + + $ $TESTDIR/run-tests.py + + # Ran 0 tests, 0 skipped, 0 warned, 0 failed. + +a succesful test +======================= - $ echo foo - foo - $ printf 'oh no' - oh no (no-eol) - $ printf 'bar\nbaz\n' | cat - bar - baz + $ cat > test-success.t << EOF + > $ echo babar + > babar + > EOF + + $ $TESTDIR/run-tests.py --with-hg=`which hg` + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + +failing test +================== + + $ cat > test-failure.t << EOF + > $ echo babar + > rataxes + > EOF -Multi-line command: + $ $TESTDIR/run-tests.py --with-hg=`which hg` + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + + ERROR: test-failure.t output changed + !. + Failed test-failure.t: output changed + # Ran 2 tests, 0 skipped, 0 warned, 1 failed. + python hash seed: * (glob) + [1] - $ foo() { - > echo bar - > } - $ foo - bar +test for --retest +==================== -Return codes before inline python: - - $ sh -c 'exit 1' + $ $TESTDIR/run-tests.py --with-hg=`which hg` --retest + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + + ERROR: test-failure.t output changed + ! + Failed test-failure.t: output changed + # Ran 1 tests, 1 skipped, 0 warned, 1 failed. + python hash seed: * (glob) [1] -Doctest commands: +Selecting Tests To Run +====================== + +successful - >>> print 'foo' - foo - $ echo interleaved - interleaved - >>> for c in 'xyz': - ... print c - x - y - z - >>> print - + $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. -Regular expressions: - - $ echo foobarbaz - foobar.* (re) - $ echo barbazquux - .*quux.* (re) +failed -Globs: - - $ printf '* \\foobarbaz {10}\n' - \* \\fo?bar* {10} (glob) - -Literal match ending in " (re)": - - $ echo 'foo (re)' - foo (re) + $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + + ERROR: test-failure.t output changed + ! + Failed test-failure.t: output changed + # Ran 1 tests, 0 skipped, 0 warned, 1 failed. + python hash seed: * (glob) + [1] -Windows: \r\n is handled like \n and can be escaped: - -#if windows - $ printf 'crlf\r\ncr\r\tcrlf\r\ncrlf\r\n' - crlf - cr\r (no-eol) (esc) - \tcrlf (esc) - crlf\r (esc) -#endif - -Combining esc with other markups - and handling lines ending with \r instead of \n: +Running In Debug Mode +====================== - $ printf 'foo/bar\r' - fo?/bar\r (no-eol) (glob) (esc) -#if windows - $ printf 'foo\\bar\r' - foo/bar\r (no-eol) (glob) (esc) -#endif - $ printf 'foo/bar\rfoo/bar\r' - foo.bar\r \(no-eol\) (re) (esc) - foo.bar\r \(no-eol\) (re) + $ $TESTDIR/run-tests.py --with-hg=`which hg` --debug + + echo SALT* 0 0 (glob) + SALT* 0 0 (glob) + + echo babar + babar + + echo SALT* 2 0 (glob) + SALT* 2 0 (glob) + .+ echo SALT* 0 0 (glob) + SALT* 0 0 (glob) + + echo babar + babar + + echo SALT* 2 0 (glob) + SALT* 2 0 (glob) + . + # Ran 2 tests, 0 skipped, 0 warned, 0 failed. -testing hghave +Parallel runs +============== - $ "$TESTDIR/hghave" true - $ "$TESTDIR/hghave" false - skipped: missing feature: nail clipper - [1] - $ "$TESTDIR/hghave" no-true - skipped: system supports yak shaving - [1] - $ "$TESTDIR/hghave" no-false - -Conditional sections based on hghave: +(duplicate the failing test to get predictable output) + $ cp test-failure.t test-failure-copy.t -#if true - $ echo tested - tested -#else - $ echo skipped -#endif - -#if false - $ echo skipped -#else - $ echo tested - tested -#endif + $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t + + --- $TESTTMP/test-failure*.t (glob) + +++ $TESTTMP/test-failure*.t.err (glob) + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + + ERROR: test-failure*.t output changed (glob) + ! + --- $TESTTMP/test-failure*.t (glob) + +++ $TESTTMP/test-failure*.t.err (glob) + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + + ERROR: test-failure*.t output changed (glob) + ! + Failed test-failure*.t: output changed (glob) + Failed test-failure*.t: output changed (glob) + # Ran 2 tests, 0 skipped, 0 warned, 2 failed. + python hash seed: * (glob) + [1] -#if no-false - $ echo tested - tested -#else - $ echo skipped -#endif +(delete the duplicated test file) + $ rm test-failure-copy.t -#if no-true - $ echo skipped -#else - $ echo tested - tested -#endif - -Exit code: - - $ (exit 1) - [1]
--- a/tests/test-shelve.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-shelve.t Fri Jun 13 17:42:04 2014 -0500 @@ -90,6 +90,10 @@ a +a + $ hg shelve --list --addremove + abort: options '--list' and '--addremove' may not be used together + [255] + delete our older shelved change $ hg shelve -d default @@ -210,11 +214,11 @@ +++ b/a/a @@ -1,2 +1,6 @@ a - +<<<<<<< local + +<<<<<<< dest: * - shelve: pending changes temporary commit (glob) c +======= +a - +>>>>>>> other + +>>>>>>> source: 4702e8911fe0 - shelve: changes to '[mq]: second.patch' diff --git a/b.rename/b b/b.rename/b new file mode 100644 --- /dev/null @@ -292,6 +296,7 @@ $ hg revert -r . a/a $ hg resolve -m a/a + no more unresolved files $ hg commit -m 'commit while unshelve in progress' abort: unshelve already in progress @@ -394,6 +399,16 @@ $ hg shelve --cleanup $ hg shelve --list + $ hg shelve --cleanup --delete + abort: options '--cleanup' and '--delete' may not be used together + [255] + $ hg shelve --cleanup --patch + abort: options '--cleanup' and '--patch' may not be used together + [255] + $ hg shelve --cleanup --message MESSAGE + abort: options '--cleanup' and '--message' may not be used together + [255] + test bookmarks $ hg bookmark test @@ -601,11 +616,11 @@ M f ? f.orig $ cat f - <<<<<<< local + <<<<<<< dest: 5f6b880e719b - shelve: pending changes temporary commit g ======= f - >>>>>>> other + >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff' $ cat f.orig g $ hg unshelve --abort @@ -644,11 +659,11 @@ M f ? f.orig $ cat f - <<<<<<< local + <<<<<<< dest: * - test: intermediate other change (glob) g ======= f - >>>>>>> other + >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff' $ cat f.orig g $ hg unshelve --abort @@ -663,4 +678,11 @@ g $ hg shelve --delete default + $ hg shelve --delete --stat + abort: options '--delete' and '--stat' may not be used together + [255] + $ hg shelve --delete --name NAME + abort: options '--delete' and '--name' may not be used together + [255] + $ cd ..
--- a/tests/test-strip.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-strip.t Fri Jun 13 17:42:04 2014 -0500 @@ -490,6 +490,7 @@ $ hg bookmark -r 'c' 'delete' $ hg up -C todelete 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark todelete) $ hg strip -B nostrip bookmark 'nostrip' deleted abort: empty revision set
--- a/tests/test-subrepo-git.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-subrepo-git.t Fri Jun 13 17:42:04 2014 -0500 @@ -155,7 +155,7 @@ added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg merge 2>/dev/null - subrepository s diverged (local revision: 796959400868, remote revision: aa84837ccfbd) + subrepository s diverged (local revision: 7969594, remote revision: aa84837) (M)erge, keep (l)ocal or keep (r)emote? m pulling subrepo s from $TESTTMP/gitroot 0 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -464,7 +464,7 @@ da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7 $ cd .. $ hg update 4 - subrepository s diverged (local revision: da5f5b1d8ffc, remote revision: aa84837ccfbd) + subrepository s diverged (local revision: da5f5b1, remote revision: aa84837) (M)erge, keep (l)ocal or keep (r)emote? m subrepository sources for s differ use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? @@ -491,7 +491,7 @@ HEAD is now at aa84837... f $ cd .. $ hg update 1 - subrepository s diverged (local revision: 32a343883b74, remote revision: da5f5b1d8ffc) + subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1) (M)erge, keep (l)ocal or keep (r)emote? m subrepository sources for s differ (in checked out version) use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? @@ -514,7 +514,7 @@ $ hg id -n 1+ $ hg update 7 - subrepository s diverged (local revision: 32a343883b74, remote revision: 32a343883b74) + subrepository s diverged (local revision: 32a3438, remote revision: 32a3438) (M)erge, keep (l)ocal or keep (r)emote? m subrepository sources for s differ use (l)ocal source (32a3438) or (r)emote source (32a3438)?
--- a/tests/test-subrepo-svn.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-subrepo-svn.t Fri Jun 13 17:42:04 2014 -0500 @@ -470,6 +470,7 @@ $ hg book other $ hg co -r 'p1(tip)' 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (leaving bookmark other) $ echo "obstruct = [svn] $SVNREPOURL/src" >> .hgsub $ svn co -r5 --quiet "$SVNREPOURL"/src obstruct $ hg commit -m 'Other branch which will be obstructed' @@ -481,6 +482,7 @@ A *obstruct/other (glob) Checked out revision 1. 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark other) This is surprising, but is also correct based on the current code: $ echo "updating should (maybe) fail" > obstruct/other @@ -543,6 +545,7 @@ A *recreated/somethingold (glob) Checked out revision 10. 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (leaving bookmark other) $ test -f recreated/somethingold Test archive
--- a/tests/test-subrepo.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-subrepo.t Fri Jun 13 17:42:04 2014 -0500 @@ -281,8 +281,8 @@ resolving manifests branchmerge: True, force: False, partial: False ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198 + preserving t for resolve of t t: versions differ -> m - preserving t for resolve of t updating: t 1/1 files (100.00%) picked tool 'internal:merge' for t (binary False symlink False) merging t @@ -298,11 +298,11 @@ should conflict $ cat t/t - <<<<<<< local + <<<<<<< local: 20a0db6fbf6c - test: 10 conflict ======= t3 - >>>>>>> other + >>>>>>> other: 7af322bc1198 - test: 7 clone
--- a/tests/test-tag.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-tag.t Fri Jun 13 17:42:04 2014 -0500 @@ -16,7 +16,10 @@ abort: tag names cannot consist entirely of whitespace [255] - $ hg tag "bleah" +(this tests also that editor is not invoked, if '--edit' is not +specified) + + $ HGEDITOR=cat hg tag "bleah" $ hg history changeset: 1:d4f0d2909abc tag: tip @@ -219,14 +222,20 @@ test custom commit messages $ cat > editor.sh << '__EOF__' + > echo "==== before editing" + > cat "$1" + > echo "====" > echo "custom tag message" > "$1" > echo "second line" >> "$1" > __EOF__ at first, test saving last-message.txt +(test that editor is not invoked before transaction starting) + $ cat > .hg/hgrc << '__EOF__' > [hooks] + > # this failure occurs before editor invocation > pretag.test-saving-lastmessage = false > __EOF__ $ rm -f .hg/last-message.txt @@ -234,16 +243,66 @@ abort: pretag.test-saving-lastmessage hook exited with status 1 [255] $ cat .hg/last-message.txt + cat: .hg/last-message.txt: No such file or directory + [1] + +(test that editor is invoked and commit message is saved into +"last-message.txt") + + $ cat >> .hg/hgrc << '__EOF__' + > [hooks] + > pretag.test-saving-lastmessage = + > # this failure occurs after editor invocation + > pretxncommit.unexpectedabort = false + > __EOF__ + +(this tests also that editor is invoked, if '--edit' is specified, +regardless of '--message') + + $ rm -f .hg/last-message.txt + $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e -m "foo bar" + ==== before editing + foo bar + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'tag-and-branch-same-name' + HG: changed .hgtags + ==== + 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 custom tag message second line - $ cat > .hg/hgrc << '__EOF__' + + $ cat >> .hg/hgrc << '__EOF__' > [hooks] - > pretag.test-saving-lastmessage = + > pretxncommit.unexpectedabort = > __EOF__ + $ hg status .hgtags + M .hgtags + $ hg revert --no-backup -q .hgtags then, test custom commit message itself $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e + ==== before editing + Added tag custom-tag for changeset 75a534207be6 + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'tag-and-branch-same-name' + HG: changed .hgtags + ==== $ hg log -l1 --template "{desc}\n" custom tag message second line
--- a/tests/test-transplant.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-transplant.t Fri Jun 13 17:42:04 2014 -0500 @@ -43,8 +43,9 @@ 1 files updated, 0 files merged, 3 files removed, 0 files unresolved rebase b onto r1 +(this also tests that editor is not invoked if '--edit' is not specified) - $ hg transplant -a -b tip + $ HGEDITOR=cat hg transplant -a -b tip applying 37a1297eb21b 37a1297eb21b transplanted to e234d668f844 applying 722f4667af76 @@ -85,13 +86,26 @@ test destination() revset predicate with a transplant of a transplant; new clone so subsequent rollback isn't affected +(this also tests that editor is invoked if '--edit' is specified) + $ hg clone -q . ../destination $ cd ../destination $ hg up -Cq 0 $ hg branch -q b4 $ hg ci -qm "b4" - $ hg transplant 7 + $ hg status --rev "7^1" --rev 7 + A b3 + $ HGEDITOR=cat hg transplant --edit 7 applying ffd6818a3975 + b3 + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'b4' + HG: added b3 ffd6818a3975 transplanted to 502236fa76bb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-unified-test.t Fri Jun 13 17:42:04 2014 -0500 @@ -0,0 +1,124 @@ +Test that the syntax of "unified tests" is properly processed +============================================================== + +Simple commands: + + $ echo foo + foo + $ printf 'oh no' + oh no (no-eol) + $ printf 'bar\nbaz\n' | cat + bar + baz + +Multi-line command: + + $ foo() { + > echo bar + > } + $ foo + bar + +Return codes before inline python: + + $ sh -c 'exit 1' + [1] + +Doctest commands: + + >>> print 'foo' + foo + $ echo interleaved + interleaved + >>> for c in 'xyz': + ... print c + x + y + z + >>> print + + +Regular expressions: + + $ echo foobarbaz + foobar.* (re) + $ echo barbazquux + .*quux.* (re) + +Globs: + + $ printf '* \\foobarbaz {10}\n' + \* \\fo?bar* {10} (glob) + +Literal match ending in " (re)": + + $ echo 'foo (re)' + foo (re) + +Windows: \r\n is handled like \n and can be escaped: + +#if windows + $ printf 'crlf\r\ncr\r\tcrlf\r\ncrlf\r\n' + crlf + cr\r (no-eol) (esc) + \tcrlf (esc) + crlf\r (esc) +#endif + +Combining esc with other markups - and handling lines ending with \r instead of \n: + + $ printf 'foo/bar\r' + fo?/bar\r (no-eol) (glob) (esc) +#if windows + $ printf 'foo\\bar\r' + foo/bar\r (no-eol) (glob) (esc) +#endif + $ printf 'foo/bar\rfoo/bar\r' + foo.bar\r \(no-eol\) (re) (esc) + foo.bar\r \(no-eol\) (re) + +testing hghave + + $ "$TESTDIR/hghave" true + $ "$TESTDIR/hghave" false + skipped: missing feature: nail clipper + [1] + $ "$TESTDIR/hghave" no-true + skipped: system supports yak shaving + [1] + $ "$TESTDIR/hghave" no-false + +Conditional sections based on hghave: + +#if true + $ echo tested + tested +#else + $ echo skipped +#endif + +#if false + $ echo skipped +#else + $ echo tested + tested +#endif + +#if no-false + $ echo tested + tested +#else + $ echo skipped +#endif + +#if no-true + $ echo skipped +#else + $ echo tested + tested +#endif + +Exit code: + + $ (exit 1) + [1]
--- a/tests/test-up-local-change.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-up-local-change.t Fri Jun 13 17:42:04 2014 -0500 @@ -46,11 +46,11 @@ resolving manifests branchmerge: False, force: False, partial: False ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb - a: versions differ -> m - preserving a for resolve of a + preserving a for resolve of a b: remote created -> g getting b updating: b 1/2 files (50.00%) + a: versions differ -> m updating: a 2/2 files (100.00%) picked tool 'true' for a (binary False symlink False) merging a @@ -67,11 +67,11 @@ resolving manifests branchmerge: False, force: False, partial: False ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a + preserving a for resolve of a b: other deleted -> r - a: versions differ -> m - preserving a for resolve of a removing b updating: b 1/2 files (50.00%) + a: versions differ -> m updating: a 2/2 files (100.00%) picked tool 'true' for a (binary False symlink False) merging a @@ -100,11 +100,11 @@ resolving manifests branchmerge: False, force: False, partial: False ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb - a: versions differ -> m - preserving a for resolve of a + preserving a for resolve of a b: remote created -> g getting b updating: b 1/2 files (50.00%) + a: versions differ -> m updating: a 2/2 files (100.00%) picked tool 'true' for a (binary False symlink False) merging a @@ -181,14 +181,14 @@ resolving manifests branchmerge: True, force: True, partial: False ancestor: c19d34741b0a, local: 1e71731e6fbb+, remote: 83c51d0caff4 + preserving a for resolve of a + preserving b for resolve of b a: versions differ -> m - preserving a for resolve of a - b: versions differ -> m - preserving b for resolve of b updating: a 1/2 files (50.00%) picked tool 'true' for a (binary False symlink False) merging a my a@1e71731e6fbb+ other a@83c51d0caff4 ancestor a@c19d34741b0a + b: versions differ -> m updating: b 2/2 files (100.00%) picked tool 'true' for b (binary False symlink False) merging b
--- a/tests/test-update-reverse.t Fri Jun 13 14:17:14 2014 -0500 +++ b/tests/test-update-reverse.t Fri Jun 13 17:42:04 2014 -0500 @@ -69,11 +69,11 @@ branchmerge: False, force: True, partial: False ancestor: 91ebc10ed028+, local: 91ebc10ed028+, remote: 71a760306caf side1: other deleted -> r + removing side1 side2: other deleted -> r - main: remote created -> g - removing side1 removing side2 updating: side2 2/3 files (66.67%) + main: remote created -> g getting main updating: main 3/3 files (100.00%) 1 files updated, 0 files merged, 2 files removed, 0 files unresolved