# HG changeset patch # User Matt Mackall # Date 1405746622 18000 # Node ID 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 # Parent 584bbfd1b50dad59871dcc7eba258491a2cb395f# Parent 7142e04b438eab39685e63085503b8af0e42b31e merge default into stable for 3.1 code freeze diff -r 584bbfd1b50d -r 6c36dc6cd61a .hgignore --- a/.hgignore Sat Jul 12 02:23:17 2014 -0700 +++ b/.hgignore Sat Jul 19 00:10:22 2014 -0500 @@ -25,7 +25,9 @@ tests/htmlcov build contrib/hgsh/hgsh +contrib/vagrant/.vagrant dist +packages doc/common.txt doc/*.[0-9] doc/*.[0-9].txt diff -r 584bbfd1b50d -r 6c36dc6cd61a Makefile --- a/Makefile Sat Jul 12 02:23:17 2014 -0700 +++ b/Makefile Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/bash_completion --- a/contrib/bash_completion Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/bash_completion Sat Jul 19 00:10:22 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/buildrpm --- a/contrib/buildrpm Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/buildrpm Sat Jul 19 00:10:22 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," \ diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/check-code.py --- a/contrib/check-code.py Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/check-code.py Sat Jul 19 00:10:22 2014 -0500 @@ -247,8 +247,6 @@ (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"), (r'(?> /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/" diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/hgfixes/fix_bytes.py --- a/contrib/hgfixes/fix_bytes.py Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/hgfixes/fix_bytes.py Sat Jul 19 00:10:22 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): diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/mercurial.spec --- a/contrib/mercurial.spec Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/mercurial.spec Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/mergetools.hgrc --- a/contrib/mergetools.hgrc Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/mergetools.hgrc Sat Jul 19 00:10:22 2014 -0500 @@ -14,6 +14,7 @@ gvimdiff.regkeyalt=Software\Wow6432Node\Vim\GVim gvimdiff.regname=path gvimdiff.priority=-9 +gvimdiff.diffargs=--nofork -d -g -O $parent $child vimdiff.args=$local $other $base -c 'redraw | echomsg "hg merge conflict, type \":cq\" to abort vimdiff"' vimdiff.check=changed @@ -71,6 +72,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 diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/revsetbenchmarks.py --- a/contrib/revsetbenchmarks.py Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/revsetbenchmarks.py Sat Jul 19 00:10:22 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] ") +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() diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/revsetbenchmarks.txt --- a/contrib/revsetbenchmarks.txt Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/revsetbenchmarks.txt Sat Jul 19 00:10:22 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()) diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/synthrepo.py --- a/contrib/synthrepo.py Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/synthrepo.py Sat Jul 19 00:10:22 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/vagrant/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vagrant/README.md Sat Jul 19 00:10:22 2014 -0500 @@ -0,0 +1,4 @@ +Run Mercurial tests with Vagrant: + +$ vagrant up +$ vagrant ssh -c ./run-tests.sh diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/vagrant/Vagrantfile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vagrant/Vagrantfile Sat Jul 19 00:10:22 2014 -0500 @@ -0,0 +1,14 @@ +# -*- mode: ruby -*- + +Vagrant.configure('2') do |config| + # Debian 7.4 32-bit i386 without configuration management software + config.vm.box = "puppetlabs/debian-7.4-32-nocm" + #config.vm.box = "pnd/debian-wheezy32-basebox" + config.vm.hostname = "tests" + + config.vm.define "tests" do |conf| + conf.vm.provision :file, source: "run-tests.sh", destination:"run-tests.sh" + conf.vm.provision :shell, path: "provision.sh" + conf.vm.synced_folder "../..", "/hgshared" + end +end diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/vagrant/provision.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vagrant/provision.sh Sat Jul 19 00:10:22 2014 -0500 @@ -0,0 +1,10 @@ +#!/bin/sh +# This scripts is used to install dependencies for +# testing Mercurial. Mainly used by Vagrant (see +# Vagrantfile for details). + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y -q python-dev unzip +# run-tests.sh is added by Vagrantfile +chmod +x run-tests.sh diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/vagrant/run-tests.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vagrant/run-tests.sh Sat Jul 19 00:10:22 2014 -0500 @@ -0,0 +1,13 @@ +#!/bin/sh +# This scripts is used to setup temp directory in memory +# for running Mercurial tests in vritual machine managed +# by Vagrant (see Vagrantfile for details). + +cd /hgshared +make local +cd tests +mkdir /tmp/ram +sudo mount -t tmpfs -o size=100M tmpfs /tmp/ram +export TMPDIR=/tmp/ram +./run-tests.py -l --time + diff -r 584bbfd1b50d -r 6c36dc6cd61a contrib/vim/hgcommand.vim --- a/contrib/vim/hgcommand.vim Sat Jul 12 02:23:17 2014 -0700 +++ b/contrib/vim/hgcommand.vim Sat Jul 19 00:10:22 2014 -0500 @@ -105,7 +105,7 @@ let fileName=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 diff -r 584bbfd1b50d -r 6c36dc6cd61a doc/check-seclevel.py --- a/doc/check-seclevel.py Sat Jul 12 02:23:17 2014 -0700 +++ b/doc/check-seclevel.py Sat Jul 19 00:10:22 2014 -0500 @@ -14,7 +14,6 @@ from mercurial.help import helptable from mercurial import extensions from mercurial import minirst -from mercurial import util _verbose = False @@ -87,7 +86,7 @@ def checkhghelps(): errorcnt = 0 for names, sec, doc in helptable: - if util.safehasattr(doc, '__call__'): + if callable(doc): doc = doc() errorcnt += checkseclevel(doc, '%s help topic' % names[0], diff -r 584bbfd1b50d -r 6c36dc6cd61a doc/gendoc.py --- a/doc/gendoc.py Sat Jul 12 02:23:17 2014 -0700 +++ b/doc/gendoc.py Sat Jul 19 00:10:22 2014 -0500 @@ -14,7 +14,6 @@ from mercurial.i18n import gettext, _ from mercurial.help import helptable, loaddoc from mercurial import extensions -from mercurial import util def get_desc(docstr): if not docstr: @@ -137,7 +136,7 @@ ui.write("\n") if sectionfunc: ui.write(sectionfunc(sec)) - if util.safehasattr(doc, '__call__'): + if callable(doc): doc = doc() ui.write(doc) ui.write("\n") diff -r 584bbfd1b50d -r 6c36dc6cd61a hg --- a/hg Sat Jul 12 02:23:17 2014 -0700 +++ b/hg Sat Jul 19 00:10:22 2014 -0500 @@ -10,6 +10,11 @@ import os import sys +if os.environ.get('HGUNICODEPEDANTRY', False): + reload(sys) + sys.setdefaultencoding("undefined") + + libdir = '@LIBDIR@' if libdir != '@' 'LIBDIR' '@': diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/children.py --- a/hgext/children.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/children.py Sat Jul 19 00:10:22 2014 -0500 @@ -14,12 +14,20 @@ "children(REV)"` instead. ''' -from mercurial import cmdutil, commands +from mercurial import cmdutil 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]'), + inferrepo=True) def children(ui, repo, file_=None, **opts): """show the children of the given or working directory revision @@ -39,14 +47,3 @@ for cctx in ctx.children(): 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/churn.py --- a/hgext/churn.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/churn.py Sat Jul 19 00:10:22 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,22 @@ 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]"), + inferrepo=True) def churn(ui, repo, *pats, **opts): '''histogram of changes to the repository @@ -180,26 +198,3 @@ 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/color.py --- a/hgext/color.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/color.py Sat Jul 19 00:10:22 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')) -} diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/convert/__init__.py --- a/hgext/convert/__init__.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/convert/__init__.py Sat Jul 19 00:10:22 2014 -0500 @@ -10,13 +10,35 @@ import convcmd import cvsps import subversion -from mercurial import commands, templatekw +from mercurial import cmdutil, 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]]'), + norepo=True) def convert(ui, src, dest=None, revmapfile=None, **opts): """convert a foreign SCM repository to a Mercurial one. @@ -282,9 +304,29 @@ """ return convcmd.convert(ui, src, dest, revmapfile, **opts) +@command('debugsvnlog', [], 'hg debugsvnlog', norepo=True) 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]...'), + norepo=True) def debugcvsps(ui, *args, **opts): '''create changeset information from CVS @@ -298,59 +340,6 @@ dates.''' return cvsps.debugcvsps(ui, *args, **opts) -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:'): diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/convert/common.py --- a/hgext/convert/common.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/convert/common.py Sat Jul 19 00:10:22 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): diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/convert/convcmd.py Sat Jul 19 00:10:22 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/convert/git.py --- a/hgext/convert/git.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/convert/git.py Sat Jul 19 00:10:22 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): diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/convert/hg.py --- a/hgext/convert/hg.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/convert/hg.py Sat Jul 19 00:10:22 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: @@ -165,6 +165,24 @@ text = text.replace(sha1, newrev[:len(sha1)]) extra = commit.extra.copy() + + for label in ('source', 'transplant_source', 'rebase_source'): + node = extra.get(label) + + if node is None: + continue + + # Only transplant stores its reference in binary + if label == 'transplant_source': + node = hex(node) + + newrev = revmap.get(node) + if newrev is not None: + if label == 'transplant_source': + newrev = bin(newrev) + + extra[label] = newrev + if self.branchnames and commit.branch: extra['branch'] = commit.branch if commit.rev: @@ -229,7 +247,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 +271,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 +416,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)]) diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/convert/subversion.py --- a/hgext/convert/subversion.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/convert/subversion.py Sat Jul 19 00:10:22 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/extdiff.py --- a/hgext/extdiff.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/extdiff.py Sat Jul 19 00:10:22 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,16 @@ 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]...'), + inferrepo=True) def extdiff(ui, repo, *pats, **opts): '''use external program to diff repository (or selected files) @@ -262,21 +274,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.'): @@ -329,5 +326,3 @@ cmdtable[cmd] = (save(cmd, path, diffopts), cmdtable['extdiff'][1][1:], _('hg %s [OPTION]... [FILE]...') % cmd) - -commands.inferrepo += " extdiff" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/factotum.py --- a/hgext/factotum.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/factotum.py Sat Jul 19 00:10:22 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')) diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/fetch.py --- a/hgext/fetch.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/fetch.py Sat Jul 19 00:10:22 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]')), -} diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/gpg.py --- a/hgext/gpg.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/gpg.py Sat Jul 19 00:10:22 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)) diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/graphlog.py --- a/hgext/graphlog.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/graphlog.py Sat Jul 19 00:10:22 2014 -0500 @@ -43,7 +43,8 @@ ('P', 'prune', [], _('do not display revision or any of its ancestors'), _('REV')), ] + commands.logopts + commands.walkopts, - _('[OPTION]... [FILE]')) + _('[OPTION]... [FILE]'), + inferrepo=True) def graphlog(ui, repo, *pats, **opts): """show revision history alongside an ASCII revision graph @@ -54,5 +55,3 @@ directory. """ return cmdutil.graphlog(ui, repo, *pats, **opts) - -commands.inferrepo += " glog" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/hgk.py --- a/hgext/hgk.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/hgk.py Sat Jul 19 00:10:22 2014 -0500 @@ -35,12 +35,23 @@ ''' 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]...'), + inferrepo=True) def difftree(ui, repo, node1=None, node2=None, *files, **opts): """diff trees from two commits""" def __difftree(repo, node1, node2, files=[]): @@ -125,6 +136,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 +144,10 @@ 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'), + inferrepo=True) 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 +292,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 +311,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 +326,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 +335,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) @@ -316,41 +346,3 @@ cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc)) 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/histedit.py --- a/hgext/histedit.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/histedit.py Sat Jul 19 00:10:22 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(), @@ -763,7 +760,8 @@ if c.description(): summary = c.description().splitlines()[0] line = 'pick %s %d %s' % (c, c.rev(), summary) - return line[:80] # trim to 80 chars so it's not stupidly wide in my editor + # trim to 80 columns so it's not stupidly wide in my editor + return util.ellipsis(line, 80) def verifyrules(rules, repo, ctxs): """Verify that there exists exactly one edit rule per given changeset. diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/keyword.py --- a/hgext/keyword.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/keyword.py Sat Jul 19 00:10:22 2014 -0500 @@ -89,9 +89,6 @@ from mercurial.i18n import _ import os, re, shutil, tempfile -commands.optionalrepo += ' kwdemo' -commands.inferrepo += ' kwexpand kwfiles kwshrink' - cmdtable = {} command = cmdutil.command(cmdtable) testedwith = 'internal' @@ -363,7 +360,8 @@ [('d', 'default', None, _('show default keyword template maps')), ('f', 'rcfile', '', _('read maps from rcfile'), _('FILE'))], - _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')) + _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'), + optionalrepo=True) def demo(ui, repo, *args, **opts): '''print [keywordmaps] configuration and an expansion example @@ -454,7 +452,10 @@ ui.write(repo.wread(fn)) shutil.rmtree(tmpdir, ignore_errors=True) -@command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...')) +@command('kwexpand', + commands.walkopts, + _('hg kwexpand [OPTION]... [FILE]...'), + inferrepo=True) def expand(ui, repo, *pats, **opts): '''expand keywords in the working directory @@ -470,7 +471,8 @@ ('i', 'ignore', None, _('show files excluded from expansion')), ('u', 'unknown', None, _('only show unknown (not tracked) files')), ] + commands.walkopts, - _('hg kwfiles [OPTION]... [FILE]...')) + _('hg kwfiles [OPTION]... [FILE]...'), + inferrepo=True) def files(ui, repo, *pats, **opts): '''show files configured for keyword expansion @@ -524,7 +526,10 @@ repo.pathto(f, cwd), label=label) fm.end() -@command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...')) +@command('kwshrink', + commands.walkopts, + _('hg kwshrink [OPTION]... [FILE]...'), + inferrepo=True) def shrink(ui, repo, *pats, **opts): '''revert expanded keywords in the working directory diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/largefiles/__init__.py --- a/hgext/largefiles/__init__.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/largefiles/__init__.py Sat Jul 19 00:10:22 2014 -0500 @@ -105,7 +105,7 @@ command. ''' -from mercurial import commands, hg, localrepo +from mercurial import hg, localrepo import lfcommands import proto @@ -125,6 +125,4 @@ hg.wirepeersetupfuncs.append(proto.wirereposetup) uisetupmod.uisetup(ui) -commands.norepo += " lfconvert" - cmdtable = lfcommands.cmdtable diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/largefiles/lfcommands.py --- a/hgext/largefiles/lfcommands.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/largefiles/lfcommands.py Sat Jul 19 00:10:22 2014 -0500 @@ -21,6 +21,18 @@ # -- Commands ---------------------------------------------------------- +cmdtable = {} +command = cmdutil.command(cmdtable) + +@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 ...]'), + norepo=True, + inferrepo=True) 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/largefiles/lfutil.py Sat Jul 19 00:10:22 2014 -0500 @@ -123,9 +123,13 @@ # it. This ensures that we create it on the first meaningful # largefiles operation in a new clone. if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')): - util.makedirs(lfstoredir) matcher = getstandinmatcher(repo) - for standin in repo.dirstate.walk(matcher, [], False, False): + standins = repo.dirstate.walk(matcher, [], False, False) + + if len(standins) > 0: + util.makedirs(lfstoredir) + + for standin in standins: lfile = splitstandin(standin) lfdirstate.normallookup(lfile) return lfdirstate diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/largefiles/overrides.py Sat Jul 19 00:10:22 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() @@ -989,17 +992,59 @@ return result +def _getoutgoings(repo, other, missing, addfunc): + """get pairs of filename and largefile hash in outgoing revisions + in 'missing'. + + largefiles already existing on 'other' repository are ignored. + + 'addfunc' is invoked with each unique pairs of filename and + largefile hash value. + """ + knowns = set() + lfhashes = set() + def dedup(fn, lfhash): + k = (fn, lfhash) + if k not in knowns: + knowns.add(k) + lfhashes.add(lfhash) + lfutil.getlfilestoupload(repo, missing, dedup) + if lfhashes: + lfexists = basestore._openstore(repo, other).exists(lfhashes) + for fn, lfhash in knowns: + if not lfexists[lfhash]: # lfhash doesn't exist on "other" + addfunc(fn, lfhash) + def outgoinghook(ui, repo, other, opts, missing): if opts.pop('large', None): - toupload = set() - lfutil.getlfilestoupload(repo, missing, - lambda fn, lfhash: toupload.add(fn)) + lfhashes = set() + if ui.debugflag: + toupload = {} + def addfunc(fn, lfhash): + if fn not in toupload: + toupload[fn] = [] + toupload[fn].append(lfhash) + lfhashes.add(lfhash) + def showhashes(fn): + for lfhash in sorted(toupload[fn]): + ui.debug(' %s\n' % (lfhash)) + else: + toupload = set() + def addfunc(fn, lfhash): + toupload.add(fn) + lfhashes.add(lfhash) + def showhashes(fn): + pass + _getoutgoings(repo, other, missing, addfunc) + if not toupload: ui.status(_('largefiles: no files to upload\n')) else: - ui.status(_('largefiles to upload:\n')) + ui.status(_('largefiles to upload (%d entities):\n') + % (len(lfhashes))) for file in sorted(toupload): ui.status(lfutil.splitstandin(file) + '\n') + showhashes(file) ui.status('\n') def summaryremotehook(ui, repo, opts, changes): @@ -1017,14 +1062,19 @@ return toupload = set() - lfutil.getlfilestoupload(repo, outgoing.missing, - lambda fn, lfhash: toupload.add(fn)) + lfhashes = set() + def addfunc(fn, lfhash): + toupload.add(fn) + lfhashes.add(lfhash) + _getoutgoings(repo, peer, outgoing.missing, addfunc) + if not toupload: # i18n: column positioning for "hg summary" ui.status(_('largefiles: (no files to upload)\n')) else: # i18n: column positioning for "hg summary" - ui.status(_('largefiles: %d to upload\n') % len(toupload)) + ui.status(_('largefiles: %d entities for %d files to upload\n') + % (len(lfhashes), len(toupload))) def overridesummary(orig, ui, repo, *pats, **opts): try: diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/mq.py --- a/hgext/mq.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/mq.py Sat Jul 19 00:10:22 2014 -0500 @@ -72,8 +72,6 @@ from mercurial import subrepo import os, re, errno, shutil -commands.norepo += " qclone" - seriesopts = [('s', 'summary', None, _('print first line of patch header'))] cmdtable = {} @@ -1026,6 +1024,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 +1077,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 +1104,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 +1484,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 +1509,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 +1518,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 +1637,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 +1653,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() @@ -2228,7 +2253,8 @@ ('p', 'patches', '', _('location of source patch repository'), _('REPO')), ] + commands.remoteopts, - _('hg qclone [OPTION]... SOURCE [DEST]')) + _('hg qclone [OPTION]... SOURCE [DEST]'), + norepo=True) def clone(ui, source, dest=None, **opts): '''clone main and patch repository at same time @@ -2307,7 +2333,8 @@ @command("qcommit|qci", commands.table["^commit|ci"][1], - _('hg qcommit [OPTION]... [FILE]...')) + _('hg qcommit [OPTION]... [FILE]...'), + inferrepo=True) def commit(ui, repo, *pats, **opts): """commit changes in the queue repository (DEPRECATED) @@ -2390,7 +2417,8 @@ ('d', 'date', '', _('add "Date: " to patch'), _('DATE')) ] + commands.walkopts + commands.commitopts, - _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')) + _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'), + inferrepo=True) def new(ui, repo, patch, *args, **opts): """create a new patch @@ -2417,14 +2445,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() @@ -2444,7 +2466,8 @@ ('d', 'date', '', _('add/update date field in patch with given date'), _('DATE')) ] + commands.walkopts + commands.commitopts, - _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')) + _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'), + inferrepo=True) def refresh(ui, repo, *pats, **opts): """update the current patch @@ -2468,17 +2491,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: @@ -2490,7 +2502,8 @@ @command("^qdiff", commands.diffopts + commands.diffopts2 + commands.walkopts, - _('hg qdiff [OPTION]... [FILE]...')) + _('hg qdiff [OPTION]... [FILE]...'), + inferrepo=True) def diff(ui, repo, *pats, **opts): """diff of the current patch and subsequent modifications @@ -2536,9 +2549,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 +2574,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 +2582,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: @@ -3454,5 +3460,3 @@ 'qseries.guarded': 'black bold', 'qseries.missing': 'red bold', 'qseries.unapplied': 'black bold'} - -commands.inferrepo += " qnew qrefresh qdiff qcommit" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/pager.py --- a/hgext/pager.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/pager.py Sat Jul 19 00:10:22 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- 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/patchbomb.py --- a/hgext/patchbomb.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/patchbomb.py Sat Jul 19 00:10:22 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/progress.py --- a/hgext/progress.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/progress.py Sat Jul 19 00:10:22 2014 -0500 @@ -41,6 +41,8 @@ from mercurial.i18n import _ testedwith = 'internal' +from mercurial import encoding + def spacejoin(*args): return ' '.join(s for s in args if s) @@ -137,10 +139,10 @@ else: wid = 20 if slice == 'end': - add = item[-wid:] + add = encoding.trim(item, wid, leftside=True) else: - add = item[:wid] - add += (wid - len(add)) * ' ' + add = encoding.trim(item, wid) + add += (wid - encoding.colwidth(add)) * ' ' elif indicator == 'bar': add = '' needprogress = True @@ -157,9 +159,9 @@ if needprogress: used = 0 if head: - used += len(head) + 1 + used += encoding.colwidth(head) + 1 if tail: - used += len(tail) + 1 + used += encoding.colwidth(tail) + 1 progwidth = termwidth - used - 3 if total and pos <= total: amt = pos * progwidth // total @@ -180,7 +182,7 @@ out = spacejoin(head, prog, tail) else: out = spacejoin(head, tail) - sys.stderr.write('\r' + out[:termwidth]) + sys.stderr.write('\r' + encoding.trim(out, termwidth)) self.lasttopic = topic sys.stderr.flush() diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/purge.py --- a/hgext/purge.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/purge.py Sat Jul 19 00:10:22 2014 -0500 @@ -35,6 +35,8 @@ @command('purge|clean', [('a', 'abort-on-err', None, _('abort if an error occurs')), ('', 'all', None, _('purge ignored files too')), + ('', 'dirs', None, _('purge empty directories')), + ('', 'files', None, _('purge files')), ('p', 'print', None, _('print filenames instead of deleting them')), ('0', 'print0', None, _('end filenames with NUL, for use with xargs' ' (implies -p/--print)')), @@ -46,7 +48,7 @@ Delete files not known to Mercurial. This is useful to test local and uncommitted changes in an otherwise-clean source tree. - This means that purge will delete: + This means that purge will delete the following by default: - Unknown files: files marked with "?" by :hg:`status` - Empty directories: in fact Mercurial ignores directories unless @@ -58,6 +60,10 @@ - Ignored files (unless --all is specified) - New files added to the repository (with :hg:`add`) + The --files and --dirs options can be used to direct purge to delete + only files, only directories, or both. If neither option is given, + both will be deleted. + If directories are given on the command line, only files in these directories are considered. @@ -71,6 +77,11 @@ if opts['print0']: eol = '\0' act = False # --print0 implies --print + removefiles = opts['files'] + removedirs = opts['dirs'] + if not removefiles and not removedirs: + removefiles = True + removedirs = True def remove(remove_func, name): if act: @@ -100,13 +111,15 @@ match.explicitdir = match.traversedir = directories.append status = repo.status(match=match, ignored=opts['all'], unknown=True) - for f in sorted(status[4] + status[5]): - if act: - ui.note(_('removing file %s\n') % f) - remove(removefile, f) + if removefiles: + for f in sorted(status[4] + status[5]): + if act: + ui.note(_('removing file %s\n') % f) + remove(removefile, f) - for f in sorted(directories, reverse=True): - if match(f) and not os.listdir(repo.wjoin(f)): - if act: - ui.note(_('removing directory %s\n') % f) - remove(os.rmdir, f) + if removedirs: + for f in sorted(directories, reverse=True): + if match(f) and not os.listdir(repo.wjoin(f)): + if act: + ui.note(_('removing directory %s\n') % f) + remove(os.rmdir, f) diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/rebase.py --- a/hgext/rebase.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/rebase.py Sat Jul 19 00:10:22 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: @@ -385,7 +383,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(): @@ -542,7 +540,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""" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/record.py --- a/hgext/record.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/record.py Sat Jul 19 00:10:22 2014 -0500 @@ -459,6 +459,11 @@ # 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]...'), + inferrepo=True) def qrecord(ui, repo, patch, *pats, **opts): '''interactively record a new patch @@ -598,9 +603,7 @@ # patch. Now is the time to delegate the job to # commit/qrefresh or the like! - # it is important to first chdir to repo root -- we'll call - # a highlevel command with list of pathnames relative to - # repo root + # Make all of the pathnames absolute. newfiles = [repo.wjoin(nf) for nf in newfiles] commitfunc(ui, repo, *newfiles, **opts) @@ -637,10 +640,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') @@ -661,5 +660,3 @@ def _wrapcmd(cmd, table, wrapfn, msg): entry = extensions.wrapcommand(table, cmd, wrapfn) entry[1].append(('i', 'interactive', None, msg)) - -commands.inferrepo += " record qrecord" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/relink.py --- a/hgext/relink.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/relink.py Sat Jul 19 00:10:22 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]') - ) -} diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/share.py --- a/hgext/share.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/share.py Sat Jul 19 00:10:22 2014 -0500 @@ -6,10 +6,16 @@ '''share a common history between several working directories''' from mercurial.i18n import _ -from mercurial import hg, commands, util +from mercurial import cmdutil, hg, util +cmdtable = {} +command = cmdutil.command(cmdtable) testedwith = 'internal' +@command('share', + [('U', 'noupdate', None, _('do not create a working copy'))], + _('[-U] SOURCE [DEST]'), + norepo=True) def share(ui, source, dest=None, noupdate=False): """create a new shared repository @@ -30,6 +36,7 @@ return hg.share(ui, source, dest, not noupdate) +@command('unshare', [], '') def unshare(ui, repo): """convert a shared repository to a normal one @@ -60,16 +67,3 @@ # 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/shelve.py --- a/hgext/shelve.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/shelve.py Sat Jul 19 00:10:22 2014 -0500 @@ -178,7 +178,8 @@ if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False try: - return repo.commit(message, user, opts.get('date'), match) + return repo.commit(message, user, opts.get('date'), match, + editor=cmdutil.getcommiteditor(**opts)) finally: if hasmq: repo.mq.checkapplied = saved @@ -635,6 +636,8 @@ _('shelve with the specified commit date'), _('DATE')), ('d', 'delete', None, _('delete the named shelved change(s)')), + ('e', 'edit', False, + _('invoke editor on commit messages')), ('l', 'list', None, _('list current shelves')), ('m', 'message', '', @@ -675,20 +678,32 @@ ''' cmdutil.checkunfinished(repo) - def checkopt(opt, incompatible): + allowables = [ + ('addremove', 'create'), # 'create' is pseudo action + ('cleanup', 'cleanup'), +# ('date', 'create'), # ignored for passing '--date "0 0"' in tests + ('delete', 'delete'), + ('edit', 'create'), + ('list', 'list'), + ('message', 'create'), + ('name', 'create'), + ('patch', 'list'), + ('stat', 'list'), + ] + def checkopt(opt): if opts[opt]: - for i in incompatible.split(): - if opts[i]: + for i, allowable in allowables: + if opts[i] and opt != allowable: raise util.Abort(_("options '--%s' and '--%s' may not be " "used together") % (opt, i)) return True - if checkopt('cleanup', 'addremove delete list message name patch stat'): + if checkopt('cleanup'): if pats: raise util.Abort(_("cannot specify names when using '--cleanup'")) return cleanupcmd(ui, repo) - elif checkopt('delete', 'addremove cleanup list message name patch stat'): + elif checkopt('delete'): return deletecmd(ui, repo, pats) - elif checkopt('list', 'addremove cleanup delete message name'): + elif checkopt('list'): return listcmd(ui, repo, pats, opts) else: for i in ('patch', 'stat'): diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/strip.py --- a/hgext/strip.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/strip.py Sat Jul 19 00:10:22 2014 -0500 @@ -42,7 +42,7 @@ raise util.Abort(_("local changed subrepos found" + excsuffix)) return m, a, r, d -def strip(ui, repo, revs, update=True, backup="all", force=None): +def strip(ui, repo, revs, update=True, backup="all", force=None, bookmark=None): wlock = lock = None try: wlock = repo.wlock() @@ -59,6 +59,14 @@ repo.dirstate.write() repair.strip(ui, repo, revs, backup) + + marks = repo._bookmarks + if bookmark: + if bookmark == repo._bookmarkcurrent: + bookmarks.unsetcurrent(repo) + del marks[bookmark] + marks.write() + ui.write(_("bookmark '%s' deleted\n") % bookmark) finally: release(lock, wlock) @@ -70,9 +78,6 @@ 'option)'), _('REV')), ('f', 'force', None, _('force removal of changesets, discard ' 'uncommitted changes (no backup)')), - ('b', 'backup', None, _('bundle only changesets with local revision' - ' number greater than REV which are not' - ' descendants of REV (DEPRECATED)')), ('', 'no-backup', None, _('no backups')), ('', 'nobackup', None, _('no backups (DEPRECATED)')), ('n', '', None, _('ignored (DEPRECATED)')), @@ -205,15 +210,9 @@ repo.dirstate.write() update = False - if opts.get('bookmark'): - if mark == repo._bookmarkcurrent: - bookmarks.unsetcurrent(repo) - del marks[mark] - marks.write() - ui.write(_("bookmark '%s' deleted\n") % mark) strip(ui, repo, revs, backup=backup, update=update, - force=opts.get('force')) + force=opts.get('force'), bookmark=opts.get('bookmark')) finally: wlock.release() diff -r 584bbfd1b50d -r 6c36dc6cd61a hgext/transplant.py --- a/hgext/transplant.py Sat Jul 12 02:23:17 2014 -0700 +++ b/hgext/transplant.py Sat Jul 19 00:10:22 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() diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/branchmap.py --- a/mercurial/branchmap.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/branchmap.py Sat Jul 19 00:10:22 2014 -0500 @@ -58,7 +58,7 @@ if repo.filtername is not None: msg += ' (%s)' % repo.filtername msg += ': %s\n' - repo.ui.warn(msg % inst) + repo.ui.debug(msg % inst) partial = None return partial @@ -221,7 +221,8 @@ repo.ui.log('branchcache', 'wrote %s branch cache with %d labels and %d nodes\n', repo.filtername, len(self), nodecount) - except (IOError, OSError, util.Abort): + except (IOError, OSError, util.Abort), inst: + repo.ui.debug("couldn't write branch cache: %s\n" % inst) # Abort may be raise by read only opener pass diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/bundle2.py --- a/mercurial/bundle2.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/bundle2.py Sat Jul 19 00:10:22 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 ``. @@ -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,12 @@ self._parts = [] self.capabilities = dict(capabilities) + @property + def nbparts(self): + """total number of parts added to the bundler""" + return len(self._parts) + + # methods used to defines the bundle2 content def addparam(self, name, value=None): """add a stream level parameter""" if not name: @@ -407,6 +417,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 +529,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 +560,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 +662,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 +695,8 @@ self.type = None self.mandatoryparams = None self.advisoryparams = None + self.params = None + self.mandatorykeys = () self._payloadstream = None self._readheader() @@ -633,6 +714,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 +750,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 +775,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 +802,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 +844,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) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/bundlerepo.py diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/changegroup.py --- a/mercurial/changegroup.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/changegroup.py Sat Jul 19 00:10:22 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): diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/cmdutil.py --- a/mercurial/cmdutil.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/cmdutil.py Sat Jul 19 00:10:22 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) @@ -1468,7 +1499,6 @@ fcache = {} fcacheready = [False] pctx = repo['.'] - wctx = repo[None] def populate(): for fn in files: @@ -1481,7 +1511,7 @@ # Lazy initialization fcacheready[0] = True populate() - return scmutil.match(wctx, fcache.get(rev, []), default='path') + return scmutil.matchfiles(repo, fcache.get(rev, [])) return filematcher @@ -1690,7 +1720,7 @@ if opts.get('rev'): revs = scmutil.revrange(repo, opts['rev']) elif follow: - revs = revset.baseset(repo.revs('reverse(:.)')) + revs = repo.revs('reverse(:.)') else: revs = revset.spanset(repo) revs.reverse() @@ -2039,7 +2069,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)) @@ -2058,12 +2089,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() @@ -2075,10 +2104,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) @@ -2143,7 +2170,46 @@ return ctx.description() return commitforceeditor(repo, ctx, subs) -def commitforceeditor(repo, ctx, subs): +def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None): + if not extramsg: + extramsg = _("Leave message empty to abort commit.") + tmpl = repo.ui.config('committemplate', 'changeset', '').strip() + if tmpl: + committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl) + else: + committext = buildcommittext(repo, ctx, subs, extramsg) + + # run editor in the repository root + olddir = os.getcwd() + os.chdir(repo.root) + text = repo.ui.edit(committext, ctx.user(), ctx.extra()) + 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")) + + return text + +def buildcommittemplate(repo, ctx, subs, extramsg, tmpl): + ui = repo.ui + tmpl, mapfile = gettemplate(ui, tmpl, None) + + try: + t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False) + except SyntaxError, inst: + raise util.Abort(inst.args[0]) + + if not extramsg: + extramsg = '' # ensure that extramsg is string + + ui.pushbuffer() + t.show(ctx, extramsg=extramsg) + return ui.popbuffer() + +def buildcommittext(repo, ctx, subs, extramsg): edittext = [] modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() if ctx.description(): @@ -2152,7 +2218,7 @@ 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.")) + edittext.append("HG: %s" % extramsg) edittext.append("HG: --") edittext.append(_("HG: user: %s") % ctx.user()) if ctx.p2(): @@ -2168,17 +2234,8 @@ if not added and not modified and not removed: edittext.append(_("HG: no files changed")) edittext.append("") - # run editor in the repository root - olddir = os.getcwd() - os.chdir(repo.root) - text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra()) - text = re.sub("(?m)^HG:.*(\n|$)", "", text) - os.chdir(olddir) - if not text.strip(): - raise util.Abort(_("empty commit message")) - - return text + return "\n".join(edittext) def commitstatus(repo, node, branch, bheads=None, opts={}): ctx = repo[node] @@ -2231,6 +2288,8 @@ node = ctx.node() mf = ctx.manifest() + if node == p2: + parent = p2 if node == parent: pmf = mf else: @@ -2240,18 +2299,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: + # -> (, ) 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: @@ -2272,11 +2335,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) @@ -2284,15 +2349,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 + # (, message>) tuple + actions = {'revert': ([], _('reverting %s\n')), + 'add': ([], _('adding %s\n')), + 'remove': ([], removeforget), + 'undelete': ([], _('undeleting %s\n'))} disptable = ( # dispatch table: @@ -2301,14 +2370,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) @@ -2325,27 +2400,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 @@ -2355,11 +2438,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 @@ -2368,8 +2452,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. @@ -2383,7 +2467,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 @@ -2403,38 +2487,79 @@ 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) def command(table): - '''returns a function object bound to table which can be used as - a decorator for populating table as a command table''' + """Returns a function object to be used as a decorator for making commands. + + This function receives a command table as its argument. The table should + be a dict. + + The returned function can be used as a decorator for adding commands + to that command table. This function accepts multiple arguments to define + a command. + + The first argument is the command name. + + The options argument is an iterable of tuples defining command arguments. + See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple. - def cmd(name, options=(), synopsis=None): + The synopsis argument defines a short, one line summary of how to use the + command. This shows up in the help output. + + The norepo argument defines whether the command does not require a + local repository. Most commands operate against a repository, thus the + default is False. + + The optionalrepo argument defines whether the command optionally requires + a local repository. + + The inferrepo argument defines whether to try to find a repository from the + command line arguments. If True, arguments will be examined for potential + repository locations. See ``findrepo()``. If a repository is found, it + will be used. + """ + def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False, + inferrepo=False): def decorator(func): if synopsis: table[name] = func, list(options), synopsis else: table[name] = func, list(options) + + if norepo: + # Avoid import cycle. + import commands + commands.norepo += ' %s' % ' '.join(parsealiases(name)) + + if optionalrepo: + import commands + commands.optionalrepo += ' %s' % ' '.join(parsealiases(name)) + + if inferrepo: + import commands + commands.inferrepo += ' %s' % ' '.join(parsealiases(name)) + return func return decorator diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/commands.py --- a/mercurial/commands.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/commands.py Sat Jul 19 00:10:22 2014 -0500 @@ -14,6 +14,7 @@ import patch, help, encoding, templatekw, discovery import archival, changegroup, cmdutil, hbisect import sshserver, hgweb, commandserver +import extensions from hgweb import server as hgweb_server import merge as mergemod import minirst, revset, fileset @@ -26,6 +27,18 @@ command = cmdutil.command(table) +# Space delimited list of commands that don't require local repositories. +# This should be populated by passing norepo=True into the @command decorator. +norepo = '' +# Space delimited list of commands that optionally require local repositories. +# This should be populated by passing optionalrepo=True into the @command +# decorator. +optionalrepo = '' +# Space delimited list of commands that will examine arguments looking for +# a repository. This should be populated by passing inferrepo=True into the +# @command decorator. +inferrepo = '' + # common command options globalopts = [ @@ -147,7 +160,8 @@ @command('^add', walkopts + subrepoopts + dryrunopts, - _('[OPTION]... [FILE]...')) + _('[OPTION]... [FILE]...'), + inferrepo=True) def add(ui, repo, *pats, **opts): """add the specified files on the next commit @@ -183,7 +197,8 @@ @command('addremove', similarityopts + walkopts + dryrunopts, - _('[OPTION]... [FILE]...')) + _('[OPTION]... [FILE]...'), + inferrepo=True) def addremove(ui, repo, *pats, **opts): """add all new files, delete all missing files @@ -227,7 +242,8 @@ ('c', 'changeset', None, _('list the changeset')), ('l', 'line-number', None, _('show line number at the first appearance')) ] + diffwsopts + walkopts, - _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')) + _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'), + inferrepo=True) def annotate(ui, repo, *pats, **opts): """show changeset information by line for each file @@ -386,6 +402,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 +504,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) @@ -795,31 +811,45 @@ ('i', 'inactive', False, _('mark a bookmark inactive'))], _('hg bookmarks [OPTIONS]... [NAME]...')) def bookmark(ui, repo, *names, **opts): - '''track a line of development with movable markers - - Bookmarks are pointers to certain commits that move when committing. - Bookmarks are local. They can be renamed, copied and deleted. It is - possible to use :hg:`merge NAME` to merge from a given bookmark, and - :hg:`update NAME` to update to a given bookmark. - - You can use :hg:`bookmark NAME` to set a bookmark on the working - directory's parent revision with the given name. If you specify - a revision using -r REV (where REV may be an existing bookmark), - the bookmark is assigned to that revision. - - Bookmarks can be pushed and pulled between repositories (see :hg:`help - push` and :hg:`help pull`). This requires both the local and remote - repositories to support bookmarks. For versions prior to 1.8, this means - the bookmarks extension must be enabled. - - If you set a bookmark called '@', new clones of the repository will - have that revision checked out (and the bookmark made active) by - default. - - With -i/--inactive, the new bookmark will not be made the active - bookmark. If -r/--rev is given, the new bookmark will not be made - active even if -i/--inactive is not given. If no NAME is given, the - current active bookmark will be marked inactive. + '''create a new bookmark or list existing bookmarks + + Bookmarks are labels on changesets to help track lines of development. + Bookmarks are unversioned and can be moved, renamed and deleted. + Deleting or moving a bookmark has no effect on the associated changesets. + + Creating or updating to a bookmark causes it to be marked as 'active'. + Active bookmarks are indicated with a '*'. + When a commit is made, an active bookmark will advance to the new commit. + A plain :hg:`update` will also advance an active bookmark, if possible. + Updating away from a bookmark will cause it to be deactivated. + + Bookmarks can be pushed and pulled between repositories (see + :hg:`help push` and :hg:`help pull`). If a shared bookmark has + diverged, a new 'divergent bookmark' of the form 'name@path' will + be created. Using :hg:'merge' will resolve the divergence. + + A bookmark named '@' has the special property that :hg:`clone` will + check it out by default if it exists. + + .. container:: verbose + + Examples: + + - create an active bookmark for a new line of development:: + + hg book new-feature + + - create an inactive bookmark as a place marker:: + + hg book -i reviewed + + - create an inactive bookmark on another changeset:: + + hg book -r .^ tested + + - move the '@' bookmark from another branch:: + + hg book -f @ ''' force = opts.get('force') rev = opts.get('rev') @@ -1155,7 +1185,8 @@ ('r', 'rev', '', _('print the given revision'), _('REV')), ('', 'decode', None, _('apply any matching decode filter')), ] + walkopts, - _('[OPTION]... FILE...')) + _('[OPTION]... FILE...'), + inferrepo=True) def cat(ui, repo, file1, *pats, **opts): """output the current or given revision of files @@ -1191,7 +1222,8 @@ ('', 'pull', None, _('use pull protocol to copy metadata')), ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')), ] + remoteopts, - _('[OPTION]... SOURCE [DEST]')) + _('[OPTION]... SOURCE [DEST]'), + norepo=True) def clone(ui, source, dest=None, **opts): """make a copy of an existing repository @@ -1310,7 +1342,8 @@ ('e', 'edit', None, _('further edit commit message already specified')), ] + walkopts + commitopts + commitopts2 + subrepoopts, - _('[OPTION]... [FILE]...')) + _('[OPTION]... [FILE]...'), + inferrepo=True) def commit(ui, repo, *pats, **opts): """commit the specified files or all outstanding changes @@ -1347,8 +1380,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 +1441,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 +1450,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, @@ -1448,7 +1477,8 @@ ('e', 'edit', None, _('edit user config')), ('l', 'local', None, _('edit repository config')), ('g', 'global', None, _('edit global config'))], - _('[-u] [NAME]...')) + _('[-u] [NAME]...'), + optionalrepo=True) def config(ui, repo, *values, **opts): """show combined config settings from all hgrc files @@ -1567,7 +1597,7 @@ finally: wlock.release() -@command('debugancestor', [], _('[INDEX] REV1 REV2')) +@command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True) def debugancestor(ui, repo, *args): """find the ancestor revision of two revisions in a given index""" if len(args) == 3: @@ -1686,17 +1716,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]] @@ -1737,7 +1767,10 @@ ui.progress(_('building'), None) release(tr, lock) -@command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE')) +@command('debugbundle', + [('a', 'all', None, _('show all details'))], + _('FILE'), + norepo=True) def debugbundle(ui, bundlepath, all=None, **opts): """lists the contents of a bundle""" f = hg.openpath(ui, bundlepath) @@ -1815,7 +1848,7 @@ error = _(".hg/dirstate inconsistent with current parent's manifest") raise util.Abort(error) -@command('debugcommands', [], _('[COMMAND]')) +@command('debugcommands', [], _('[COMMAND]'), norepo=True) def debugcommands(ui, cmd='', *args): """list all available commands and options""" for cmd, vals in sorted(table.iteritems()): @@ -1825,7 +1858,8 @@ @command('debugcomplete', [('o', 'options', None, _('show the command options'))], - _('[-o] CMD')) + _('[-o] CMD'), + norepo=True) def debugcomplete(ui, cmd='', **opts): """returns the completion list associated with the given command""" @@ -1855,7 +1889,8 @@ ('b', 'branches', None, _('annotate with branch names')), ('', 'dots', None, _('use dots for runs')), ('s', 'spaces', None, _('separate elements by spaces'))], - _('[OPTION]... [FILE [REV]...]')) + _('[OPTION]... [FILE [REV]...]'), + optionalrepo=True) def debugdag(ui, repo, file_=None, *revs, **opts): """format the changelog or an index DAG as a concise textual description @@ -1929,7 +1964,8 @@ @command('debugdate', [('e', 'extended', None, _('try extended date formats'))], - _('[-e] DATE [RANGE]')) + _('[-e] DATE [RANGE]'), + norepo=True, optionalrepo=True) def debugdate(ui, date, range=None, **opts): """parse and display a date""" if opts["extended"]: @@ -2025,7 +2061,7 @@ for f in ctx.getfileset(expr): ui.write("%s\n" % f) -@command('debugfsinfo', [], _('[PATH]')) +@command('debugfsinfo', [], _('[PATH]'), norepo=True) def debugfsinfo(ui, path="."): """show information detected about current filesystem""" util.writefile('.debugfsinfo', '') @@ -2040,7 +2076,8 @@ [('H', 'head', [], _('id of head node'), _('ID')), ('C', 'common', [], _('id of common node'), _('ID')), ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))], - _('REPO FILE [-H|-C ID]...')) + _('REPO FILE [-H|-C ID]...'), + norepo=True) def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts): """retrieves a bundle from a repo @@ -2080,7 +2117,8 @@ [('c', 'changelog', False, _('open changelog')), ('m', 'manifest', False, _('open manifest')), ('f', 'format', 0, _('revlog format'), _('FORMAT'))], - _('[-f FORMAT] -c|-m|FILE')) + _('[-f FORMAT] -c|-m|FILE'), + optionalrepo=True) def debugindex(ui, repo, file_=None, **opts): """dump the contents of an index file""" r = cmdutil.openrevlog(repo, 'debugindex', file_, opts) @@ -2122,7 +2160,7 @@ i, r.flags(i), r.start(i), r.length(i), r.rawsize(i), base, r.linkrev(i), pr[0], pr[1], short(node))) -@command('debugindexdot', [], _('FILE')) +@command('debugindexdot', [], _('FILE'), optionalrepo=True) def debugindexdot(ui, repo, file_): """dump an index DAG as a graphviz dot file""" r = None @@ -2141,7 +2179,7 @@ ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i)) ui.write("}\n") -@command('debuginstall', [], '') +@command('debuginstall', [], '', norepo=True) def debuginstall(ui): '''test Mercurial installation @@ -2239,7 +2277,7 @@ return problems -@command('debugknown', [], _('REPO ID...')) +@command('debugknown', [], _('REPO ID...'), norepo=True) def debugknown(ui, repopath, *ids, **opts): """test whether node ids are known to a repo @@ -2277,6 +2315,7 @@ """create arbitrary obsolete marker With no arguments, displays the list of obsolescence markers.""" + def parsenodeid(s): try: # We do not use revsingle/revrange functions here to accept @@ -2376,7 +2415,7 @@ ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files))) ui.write('\n') -@command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]')) +@command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True) def debugpushkey(ui, repopath, namespace, *keyinfo, **opts): '''access the pushkey key/value protocol @@ -2461,7 +2500,8 @@ [('c', 'changelog', False, _('open changelog')), ('m', 'manifest', False, _('open manifest')), ('d', 'dump', False, _('dump index data'))], - _('-c|-m|FILE')) + _('-c|-m|FILE'), + optionalrepo=True) def debugrevlog(ui, repo, file_=None, **opts): """show data and statistics about a revlog""" r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts) @@ -2768,7 +2808,7 @@ ui.write(node2str(node)) ui.write('\n') -@command('debugwalk', walkopts, _('[OPTION]... [FILE]...')) +@command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True) def debugwalk(ui, repo, *pats, **opts): """show how files match on given patterns""" m = scmutil.match(repo[None], pats, opts) @@ -2790,7 +2830,8 @@ ('', 'four', '', 'four'), ('', 'five', '', 'five'), ] + remoteopts, - _('REPO [OPTIONS]... [ONE [TWO]]')) + _('REPO [OPTIONS]... [ONE [TWO]]'), + norepo=True) def debugwireargs(ui, repopath, *vals, **opts): repo = hg.peer(ui, opts, repopath) for opt in remoteopts: @@ -2810,7 +2851,8 @@ [('r', 'rev', [], _('revision'), _('REV')), ('c', 'change', '', _('change made by revision'), _('REV')) ] + diffopts + diffopts2 + walkopts + subrepoopts, - _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')) + _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'), + inferrepo=True) def diff(ui, repo, *pats, **opts): """diff repository (or selected files) @@ -2972,7 +3014,7 @@ switch_parent=opts.get('switch_parent'), opts=patch.diffopts(ui, opts)) -@command('^forget', walkopts, _('[OPTION]... FILE...')) +@command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True) def forget(ui, repo, *pats, **opts): """forget the specified files on the next commit @@ -3077,9 +3119,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 +3226,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 @@ -3238,7 +3279,8 @@ ('u', 'user', None, _('list the author (long with -v)')), ('d', 'date', None, _('list the date (short with -q)')), ] + walkopts, - _('[OPTION]... PATTERN [FILE]...')) + _('[OPTION]... PATTERN [FILE]...'), + inferrepo=True) def grep(ui, repo, pattern, *pats, **opts): """search for a pattern in specified files and revisions @@ -3261,7 +3303,7 @@ if opts.get('ignore_case'): reflags |= re.I try: - regexp = util.compilere(pattern, reflags) + regexp = util.re.compile(pattern, reflags) except re.error, inst: ui.warn(_("grep: invalid match pattern: %s\n") % inst) return 1 @@ -3517,7 +3559,8 @@ ('c', 'command', None, _('show only help for commands')), ('k', 'keyword', '', _('show topics matching keyword')), ], - _('[-ec] [TOPIC]')) + _('[-ec] [TOPIC]'), + norepo=True) def help_(ui, name=None, **opts): """show help for a given topic or a help overview @@ -3552,7 +3595,8 @@ ('t', 'tags', None, _('show tags')), ('B', 'bookmarks', None, _('show bookmarks')), ] + remoteopts, - _('[-nibtB] [-r REV] [SOURCE]')) + _('[-nibtB] [-r REV] [SOURCE]'), + optionalrepo=True) def identify(ui, repo, source=None, rev=None, num=None, id=None, branch=None, tags=None, bookmarks=None, **opts): """identify the working copy or specified revision @@ -3692,6 +3736,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 +3779,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 .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 +3814,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 +3846,7 @@ base = opts["base"] wlock = lock = tr = None msgs = [] + ret = 0 try: @@ -3811,8 +3868,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 +3878,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 +3892,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 @@ -3913,7 +3978,8 @@ del repo._subtoppath -@command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]')) +@command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'), + norepo=True) def init(ui, dest=".", **opts): """create a new repository in the given directory @@ -3993,7 +4059,8 @@ ('P', 'prune', [], _('do not display revision or any of its ancestors'), _('REV')), ] + logopts + walkopts, - _('[OPTION]... [FILE]')) + _('[OPTION]... [FILE]'), + inferrepo=True) def log(ui, repo, *pats, **opts): """show revision history of entire repository or files @@ -4351,7 +4418,8 @@ @command('parents', [('r', 'rev', '', _('show parents of the specified revision'), _('REV')), ] + templateopts, - _('[-r REV] [FILE]')) + _('[-r REV] [FILE]'), + inferrepo=True) def parents(ui, repo, file_=None, **opts): """show the parents of the working directory or revision @@ -4394,7 +4462,7 @@ displayer.show(repo[n]) displayer.close() -@command('paths', [], _('[NAME]')) +@command('paths', [], _('[NAME]'), optionalrepo=True) def paths(ui, repo, search=None): """show aliases for remote repositories @@ -4748,7 +4816,8 @@ ('f', 'force', None, _('remove (and delete) file even if added or modified')), ] + walkopts, - _('[OPTION]... FILE...')) + _('[OPTION]... FILE...'), + inferrepo=True) def remove(ui, repo, *pats, **opts): """remove the specified files on the next commit @@ -4877,7 +4946,8 @@ ('u', 'unmark', None, _('mark files as unresolved')), ('n', 'no-status', None, _('hide status prefix'))] + mergetoolopts + walkopts, - _('[OPTION]... [FILE]...')) + _('[OPTION]... [FILE]...'), + inferrepo=True) def resolve(ui, repo, *pats, **opts): """redo merges or set/view the merge status of files @@ -4930,46 +5000,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', @@ -5126,7 +5216,8 @@ ('', 'style', '', _('template style to use'), _('STYLE')), ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), ('', 'certificate', '', _('SSL certificate file'), _('FILE'))], - _('[OPTION]...')) + _('[OPTION]...'), + optionalrepo=True) def serve(ui, repo, **opts): """start stand-alone webserver @@ -5155,13 +5246,10 @@ if opts["stdio"] and opts["cmdserver"]: raise util.Abort(_("cannot use --stdio with --cmdserver")) - def checkrepo(): + if opts["stdio"]: if repo is None: raise error.RepoError(_("there is no Mercurial repository here" - " (.hg not found)")) - - if opts["stdio"]: - checkrepo() + " (.hg not found)")) s = sshserver.sshserver(ui, repo) s.serve_forever() @@ -5232,6 +5320,7 @@ write = self.ui.write write(_('listening at http://%s%s/%s (bound to %s:%d)\n') % (fqaddr, port, prefix, bindaddr, self.httpd.port)) + self.ui.flush() # avoid buffering of status message def run(self): self.httpd.serve_forever() @@ -5252,7 +5341,8 @@ ('', 'rev', [], _('show difference from revision'), _('REV')), ('', 'change', '', _('list the changed files of a revision'), _('REV')), ] + walkopts + subrepoopts, - _('[OPTION]... [FILE]...')) + _('[OPTION]... [FILE]...'), + inferrepo=True) def status(ui, repo, *pats, **opts): """show changed files in the working directory @@ -5689,16 +5779,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 +5880,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 +5965,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 +5977,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 @@ -5908,7 +6005,7 @@ """ return hg.verify(repo) -@command('version', []) +@command('version', [], norepo=True) def version_(ui): """output version and copyright information""" ui.write(_("Mercurial Distributed SCM (version %s)\n") @@ -5921,10 +6018,14 @@ "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" )) -norepo = ("clone init version help debugcommands debugcomplete" - " debugdate debuginstall debugfsinfo debugpushkey debugwireargs" - " debugknown debuggetbundle debugbundle") -optionalrepo = ("identify paths serve config showconfig debugancestor debugdag" - " debugdata debugindex debugindexdot debugrevlog") -inferrepo = ("add addremove annotate cat commit diff grep forget log parents" - " remove resolve status debugwalk") + ui.note(_("\nEnabled extensions:\n\n")) + if ui.verbose: + # format names and versions into columns + names = [] + vers = [] + for name, module in extensions.extensions(): + names.append(name) + vers.append(extensions.moduleversion(module)) + maxnamelen = max(len(n) for n in names) + for i, name in enumerate(names): + ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i])) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/config.py --- a/mercurial/config.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/config.py Sat Jul 19 00:10:22 2014 -0500 @@ -9,37 +9,6 @@ import error, util import os, errno -class sortdict(dict): - 'a simple sorted dictionary' - def __init__(self, data=None): - self._list = [] - if data: - self.update(data) - def copy(self): - return sortdict(self) - def __setitem__(self, key, val): - if key in self: - self._list.remove(key) - self._list.append(key) - dict.__setitem__(self, key, val) - def __iter__(self): - return self._list.__iter__() - def update(self, src): - for k in src: - self[k] = src[k] - def clear(self): - dict.clear(self) - self._list = [] - def items(self): - return [(k, self[k]) for k in self._list] - def __delitem__(self, key): - dict.__delitem__(self, key) - self._list.remove(key) - def keys(self): - return self._list - def iterkeys(self): - return self._list.__iter__() - class config(object): def __init__(self, data=None): self._data = {} @@ -65,7 +34,7 @@ del self._source[(s, n)] for s in src: if s not in self: - self._data[s] = sortdict() + self._data[s] = util.sortdict() self._data[s].update(src._data[s]) self._source.update(src._source) def get(self, section, item, default=None): @@ -91,7 +60,7 @@ return self._data.get(section, {}).items() def set(self, section, item, value, source=""): if section not in self: - self._data[section] = sortdict() + self._data[section] = util.sortdict() self._data[section][item] = value if source: self._source[(section, item)] = source @@ -111,13 +80,13 @@ self._source.pop((section, item), None) def parse(self, src, data, sections=None, remap=None, include=None): - sectionre = util.compilere(r'\[([^\[]+)\]') - itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)') - contre = util.compilere(r'\s+(\S|\S.*\S)\s*$') - emptyre = util.compilere(r'(;|#|\s*$)') - commentre = util.compilere(r'(;|#)') - unsetre = util.compilere(r'%unset\s+(\S+)') - includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$') + sectionre = util.re.compile(r'\[([^\[]+)\]') + itemre = util.re.compile(r'([^=\s][^=]*?)\s*=\s*(.*\S|)') + contre = util.re.compile(r'\s+(\S|\S.*\S)\s*$') + emptyre = util.re.compile(r'(;|#|\s*$)') + commentre = util.re.compile(r'(;|#)') + unsetre = util.re.compile(r'%unset\s+(\S+)') + includere = util.re.compile(r'%include\s+(\S|\S.*\S)\s*$') section = "" item = None line = 0 @@ -162,7 +131,7 @@ if remap: section = remap.get(section, section) if section not in self: - self._data[section] = sortdict() + self._data[section] = util.sortdict() continue m = itemre.match(l) if m: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/context.py --- a/mercurial/context.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/context.py Sat Jul 19 00:10:22 2014 -0500 @@ -13,6 +13,7 @@ import obsolete as obsmod import repoview import fileset +import revlog propertycache = util.propertycache @@ -63,10 +64,87 @@ 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. + """ + if match.always(): + return self.manifest().copy() + + files = match.files() + if (match.matchfn == match.exact or + (not match.anypats() and util.all(fn in self for fn in files))): + return self.manifest().intersectfiles(files) + + mf = self.manifest().copy() + 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): @@ -185,8 +263,7 @@ if ctx2 is not None: ctx2 = self._repo[ctx2] diffopts = patch.diffopts(self._repo.ui, opts) - return patch.diff(self._repo, ctx2.node(), self.node(), - match=match, opts=diffopts) + return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts) @propertycache def _dirs(self): @@ -198,19 +275,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 +962,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: @@ -850,9 +982,6 @@ def __nonzero__(self): return True - def __contains__(self, key): - return self._repo.dirstate[key] not in "?r" - def _buildflagfunc(self): # Create a fallback function for getting file flags when the # filesystem doesn't support them @@ -891,7 +1020,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 +1034,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 +1052,7 @@ @propertycache def _status(self): - return self._repo.status()[:4] + return self._repo.status() @propertycache def _user(self): @@ -933,21 +1062,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 +1083,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): @@ -1069,6 +1182,9 @@ if d[f] != 'r': yield f + def __contains__(self, key): + return self._repo.dirstate[key] not in "?r" + @propertycache def _parents(self): p = self._repo.dirstate.parents() @@ -1180,6 +1296,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 +1539,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,73 +1567,25 @@ 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)] files = sorted(set(files)) self._status = [files, [], [], [], []] self._filectxfn = filectxfn + self.substate = None self._extra = extra and extra.copy() or {} 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 +1595,34 @@ """commit context to the repo""" return self._repo.commitctx(self) -class memfilectx(object): + @propertycache + def _manifest(self): + """generate a manifest based on the return values of filectxfn""" + + # keep this simple for now; just worry about p1 + pctx = self._parents[0] + man = pctx.manifest().copy() + + for f, fnode in man.iteritems(): + p1node = nullid + p2node = nullid + p = pctx[f].parents() + if len(p) > 0: + p1node = p[0].node() + if len(p) > 1: + p2node = p[1].node() + man[f] = revlog.hash(self[f].data(), p1node, p2node) + + return man + + +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 +1630,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): diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/demandimport.py --- a/mercurial/demandimport.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/demandimport.py Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/dirs.c --- a/mercurial/dirs.c Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/dirs.c Sat Jul 19 00:10:22 2014 -0500 @@ -138,25 +138,12 @@ return -1; } if (skipchar) { - PyObject *st; - - if (!PyTuple_Check(value) || - PyTuple_GET_SIZE(value) == 0) { + if (!dirstate_tuple_check(value)) { PyErr_SetString(PyExc_TypeError, - "expected non-empty tuple"); + "expected a dirstate tuple"); return -1; } - - st = PyTuple_GET_ITEM(value, 0); - - if (!PyString_Check(st) || PyString_GET_SIZE(st) == 0) { - PyErr_SetString(PyExc_TypeError, - "expected non-empty string " - "at tuple index 0"); - return -1; - } - - if (PyString_AS_STRING(st)[0] == skipchar) + if (((dirstateTupleObject *)value)->state == skipchar) continue; } diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/dirstate.py --- a/mercurial/dirstate.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/dirstate.py Sat Jul 19 00:10:22 2014 -0500 @@ -14,6 +14,8 @@ filecache = scmutil.filecache _rangemask = 0x7fffffff +dirstatetuple = parsers.dirstatetuple + class repocache(filecache): """filecache for files in .hg/""" def join(self, obj, fname): @@ -335,7 +337,7 @@ if oldstate in "?r" and "_dirs" in self.__dict__: self._dirs.addpath(f) self._dirty = True - self._map[f] = (state, mode, size, mtime) + self._map[f] = dirstatetuple(state, mode, size, mtime) def normal(self, f): '''Mark a file normal and clean.''' @@ -400,7 +402,7 @@ size = -1 elif entry[0] == 'n' and entry[2] == -2: # other parent size = -2 - self._map[f] = ('r', 0, size, 0) + self._map[f] = dirstatetuple('r', 0, size, 0) if size == 0 and f in self._copymap: del self._copymap[f] @@ -493,9 +495,9 @@ self._map[f] = oldmap[f] else: if 'x' in allfiles.flags(f): - self._map[f] = ('n', 0777, -1, 0) + self._map[f] = dirstatetuple('n', 0777, -1, 0) else: - self._map[f] = ('n', 0666, -1, 0) + self._map[f] = dirstatetuple('n', 0666, -1, 0) self._pl = (parent, nullid) self._dirty = True @@ -821,7 +823,18 @@ uadd(fn) continue - state, mode, size, time = dmap[fn] + # This is equivalent to 'state, mode, size, time = dmap[fn]' but not + # written like that for performance reasons. dmap[fn] is not a + # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE + # opcode has fast paths when the value to be unpacked is a tuple or + # a list, but falls back to creating a full-fledged iterator in + # general. That is much slower than simply accessing and storing the + # tuple members one by one. + t = dmap[fn] + state = t[0] + mode = t[1] + size = t[2] + time = t[3] if not st and state in "nma": dadd(fn) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/discovery.py --- a/mercurial/discovery.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/discovery.py Sat Jul 19 00:10:22 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]) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/dispatch.py --- a/mercurial/dispatch.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/dispatch.py Sat Jul 19 00:10:22 2014 -0500 @@ -225,7 +225,8 @@ # it might be anything, for example a string reason = inst.reason ui.warn(_("abort: error: %s\n") % reason) - elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE: + elif (util.safehasattr(inst, "args") + and inst.args and inst.args[0] == errno.EPIPE): if ui.debugflag: ui.warn(_("broken pipe\n")) elif getattr(inst, "strerror", None): diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/encoding.py --- a/mercurial/encoding.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/encoding.py Sat Jul 19 00:10:22 2014 -0500 @@ -165,6 +165,99 @@ if colwidth(t) == c: return t +def trim(s, width, ellipsis='', leftside=False): + """Trim string 's' to at most 'width' columns (including 'ellipsis'). + + If 'leftside' is True, left side of string 's' is trimmed. + 'ellipsis' is always placed at trimmed side. + + >>> ellipsis = '+++' + >>> from mercurial import encoding + >>> encoding.encoding = 'utf-8' + >>> t= '1234567890' + >>> print trim(t, 12, ellipsis=ellipsis) + 1234567890 + >>> print trim(t, 10, ellipsis=ellipsis) + 1234567890 + >>> print trim(t, 8, ellipsis=ellipsis) + 12345+++ + >>> print trim(t, 8, ellipsis=ellipsis, leftside=True) + +++67890 + >>> print trim(t, 8) + 12345678 + >>> print trim(t, 8, leftside=True) + 34567890 + >>> print trim(t, 3, ellipsis=ellipsis) + +++ + >>> print trim(t, 1, ellipsis=ellipsis) + + + >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns + >>> t = u.encode(encoding.encoding) + >>> print trim(t, 12, ellipsis=ellipsis) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a + >>> print trim(t, 10, ellipsis=ellipsis) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a + >>> print trim(t, 8, ellipsis=ellipsis) + \xe3\x81\x82\xe3\x81\x84+++ + >>> print trim(t, 8, ellipsis=ellipsis, leftside=True) + +++\xe3\x81\x88\xe3\x81\x8a + >>> print trim(t, 5) + \xe3\x81\x82\xe3\x81\x84 + >>> print trim(t, 5, leftside=True) + \xe3\x81\x88\xe3\x81\x8a + >>> print trim(t, 4, ellipsis=ellipsis) + +++ + >>> print trim(t, 4, ellipsis=ellipsis, leftside=True) + +++ + >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence + >>> print trim(t, 12, ellipsis=ellipsis) + \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa + >>> print trim(t, 10, ellipsis=ellipsis) + \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa + >>> print trim(t, 8, ellipsis=ellipsis) + \x11\x22\x33\x44\x55+++ + >>> print trim(t, 8, ellipsis=ellipsis, leftside=True) + +++\x66\x77\x88\x99\xaa + >>> print trim(t, 8) + \x11\x22\x33\x44\x55\x66\x77\x88 + >>> print trim(t, 8, leftside=True) + \x33\x44\x55\x66\x77\x88\x99\xaa + >>> print trim(t, 3, ellipsis=ellipsis) + +++ + >>> print trim(t, 1, ellipsis=ellipsis) + + + """ + try: + u = s.decode(encoding) + except UnicodeDecodeError: + if len(s) <= width: # trimming is not needed + return s + width -= len(ellipsis) + if width <= 0: # no enough room even for ellipsis + return ellipsis[:width + len(ellipsis)] + if leftside: + return ellipsis + s[-width:] + return s[:width] + ellipsis + + if ucolwidth(u) <= width: # trimming is not needed + return s + + width -= len(ellipsis) + if width <= 0: # no enough room even for ellipsis + return ellipsis[:width + len(ellipsis)] + + if leftside: + uslice = lambda i: u[i:] + concat = lambda s: ellipsis + s + else: + uslice = lambda i: u[:-i] + concat = lambda s: s + ellipsis + for i in xrange(1, len(u)): + usub = uslice(i) + if ucolwidth(usub) <= width: + return concat(usub.encode(encoding)) + return ellipsis # no enough room for multi-column characters + def lower(s): "best-effort encoding-aware case-folding of local string s" try: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/error.py --- a/mercurial/error.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/error.py Sat Jul 19 00:10:22 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 + diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/exchange.py --- a/mercurial/exchange.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/exchange.py Sat Jul 19 00:10:22 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) @@ -61,6 +61,9 @@ self.newbranch = newbranch # did a local lock get acquired? self.locallocked = None + # step already performed + # (used to check what steps have been already performed through bundle2) + self.stepsdone = set() # Integer version of the push result # - None means nothing to push # - 0 means HTTP error @@ -128,16 +131,11 @@ lock = pushop.remote.lock() try: _pushdiscovery(pushop) - if _pushcheckoutgoing(pushop): - pushop.repo.prepushoutgoinghooks(pushop.repo, - pushop.remote, - pushop.outgoing) - if (pushop.repo.ui.configbool('experimental', 'bundle2-exp', - False) - and pushop.remote.capable('bundle2-exp')): - _pushbundle2(pushop) - else: - _pushchangeset(pushop) + if (pushop.repo.ui.configbool('experimental', 'bundle2-exp', + False) + and pushop.remote.capable('bundle2-exp')): + _pushbundle2(pushop) + _pushchangeset(pushop) _pushcomputecommonheads(pushop) _pushsyncphase(pushop) _pushobsolete(pushop) @@ -203,57 +201,73 @@ newbm) return True +def _pushb2ctx(pushop, bundler): + """handle changegroup push through bundle2 + + addchangegroup result is stored in the ``pushop.ret`` attribute. + """ + if 'changesets' in pushop.stepsdone: + return + pushop.stepsdone.add('changesets') + # Send known heads to the server for race detection. + pushop.stepsdone.add('changesets') + if not _pushcheckoutgoing(pushop): + return + pushop.repo.prepushoutgoinghooks(pushop.repo, + pushop.remote, + pushop.outgoing) + if not pushop.force: + bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads)) + cg = changegroup.getlocalbundle(pushop.repo, 'push', pushop.outgoing) + cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg.getchunks()) + def handlereply(op): + """extract addchangroup returns from server reply""" + cgreplies = op.records.getreplies(cgpart.id) + assert len(cgreplies['changegroup']) == 1 + pushop.ret = cgreplies['changegroup'][0]['return'] + return handlereply + +# list of function that may decide to add parts to an outgoing bundle2 +bundle2partsgenerators = [_pushb2ctx] + def _pushbundle2(pushop): """push data to the remote using bundle2 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)) - if not pushop.force: - part = bundle2.bundlepart('B2X:CHECK:HEADS', - data=iter(pushop.remoteheads)) - bundler.addpart(part) - 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) + bundler.newpart('b2x:replycaps', data=capsblob) + replyhandlers = [] + for partgen in bundle2partsgenerators: + ret = partgen(pushop, bundler) + replyhandlers.append(ret) + # do not push if nothing to push + if bundler.nbparts <= 1: + return 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 - pushop.ret = cgreplies['changegroup'][0]['return'] - _pushbundle2extrareply(pushop, op, extrainfo) - -def _pushbundle2extraparts(pushop, bundler): - """hook function to let extensions add parts - - Return a dict to let extensions pass data to the reply processing. - """ - return {} - -def _pushbundle2extrareply(pushop, op, extrainfo): - """hook function to let extensions react to part replies - - The dict from _pushbundle2extrareply is fed to this function. - """ - pass + for rephand in replyhandlers: + rephand(op) def _pushchangeset(pushop): """Make the actual push of changeset bundle to remote repo""" + if 'changesets' in pushop.stepsdone: + return + pushop.stepsdone.add('changesets') + if not _pushcheckoutgoing(pushop): + return + pushop.repo.prepushoutgoinghooks(pushop.repo, + pushop.remote, + pushop.outgoing) outgoing = pushop.outgoing unbundle = pushop.remote.capable('unbundle') # TODO: get bundlecaps from remote @@ -330,37 +344,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 +378,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 to in the local source repo""" @@ -568,14 +589,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 +610,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 +651,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 +660,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 +703,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 +729,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 +742,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()) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/extensions.py --- a/mercurial/extensions.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/extensions.py Sat Jul 19 00:10:22 2014 -0500 @@ -138,7 +138,7 @@ where orig is the original (wrapped) function, and *args, **kwargs are the arguments passed to it. ''' - assert util.safehasattr(wrapper, '__call__') + assert callable(wrapper) aliases, entry = cmdutil.findcmd(command, table) for alias, e in table.iteritems(): if e is entry: @@ -191,12 +191,12 @@ your end users, you should play nicely with others by using the subclass trick. ''' - assert util.safehasattr(wrapper, '__call__') + assert callable(wrapper) def wrap(*args, **kwargs): return wrapper(origfn, *args, **kwargs) origfn = getattr(container, funcname) - assert util.safehasattr(origfn, '__call__') + assert callable(origfn) setattr(container, funcname, wrap) return origfn @@ -367,3 +367,16 @@ exts[ename] = doc.splitlines()[0].strip() return exts + +def moduleversion(module): + '''return version information from given module as a string''' + if (util.safehasattr(module, 'getversion') + and callable(module.getversion)): + version = module.getversion() + elif util.safehasattr(module, '__version__'): + version = module.__version__ + else: + version = '' + if isinstance(version, (list, tuple)): + version = '.'.join(str(o) for o in version) + return version diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/fancyopts.py --- a/mercurial/fancyopts.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/fancyopts.py Sat Jul 19 00:10:22 2014 -0500 @@ -77,7 +77,7 @@ # copy defaults to state if isinstance(default, list): state[name] = default[:] - elif getattr(default, '__call__', False): + elif callable(default): state[name] = None else: state[name] = default diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/filemerge.py --- a/mercurial/filemerge.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/filemerge.py Sat Jul 19 00:10:22 2014 -0500 @@ -7,8 +7,9 @@ from node import short from i18n import _ -import util, simplemerge, match, error +import util, simplemerge, match, error, templater, templatekw import os, tempfile, re, filecmp +import tagmerge def _toolstr(ui, tool, part, default=""): return ui.config("merge-tools", tool + "." + part, default) @@ -169,7 +170,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 +191,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 +202,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 +212,28 @@ 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, no_minimal=True) return True, r return False, 0 +@internaltool('tagmerge', True, + _("automatic tag merging of %s failed! " + "(use 'hg resolve --tool internal:merge' or another merge " + "tool of your choice)\n")) +def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): + """ + Uses the internal tag merge algorithm (experimental). + """ + return tagmerge.merge(repo, fcd, fco, fca) + @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 +241,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 +252,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 +280,57 @@ 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)) + + if mark: + mark = mark.splitlines()[0] # split for safety + + # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') + return util.ellipsis(mark, 80 - 8) + +_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 +387,17 @@ ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) + markerstyle = ui.config('ui', 'mergemarkers', 'basic') + 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/help.py --- a/mercurial/help.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/help.py Sat Jul 19 00:10:22 2014 -0500 @@ -404,7 +404,7 @@ # description if not doc: rst.append(" %s\n" % _("(no help text available)")) - if util.safehasattr(doc, '__call__'): + if callable(doc): rst += [" %s\n" % l for l in doc().splitlines()] if not ui.verbose: @@ -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: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/help/config.txt --- a/mercurial/help/config.txt Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/help/config.txt Sat Jul 19 00:10:22 2014 -0500 @@ -330,6 +330,64 @@ for credentials as usual if required by the remote. +``committemplate`` +------------------ + +``changeset`` configuration in this section is used as the template to +customize the text shown in the editor when committing. + +In addition to pre-defined template keywords, commit log specific one +below can be used for customization: + +``extramsg`` + String: Extra message (typically 'Leave message empty to abort + commit.'). This may be changed by some commands or extensions. + +For example, the template configuration below shows as same text as +one shown by default:: + + [committemplate] + changeset = {desc}\n\n + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: {extramsg} + HG: -- + HG: user: {author}\n{ifeq(p2rev, "-1", "", + "HG: branch merge\n") + }HG: branch '{branch}'\n{if(currentbookmark, + "HG: bookmark '{currentbookmark}'\n") }{subrepos % + "HG: subrepo {subrepo}\n" }{file_adds % + "HG: added {file}\n" }{file_mods % + "HG: changed {file}\n" }{file_dels % + "HG: removed {file}\n" }{if(files, "", + "HG: no files changed\n")} + +.. note:: + + For some problematic encodings (see :hg:`help win32mbcs` for + detail), this customization should be configured carefully, to + avoid showing broken characters. + + For example, if multibyte character ending with backslash (0x5c) is + followed by ASCII character 'n' in the customized template, + sequence of backslash and 'n' is treated as line-feed unexpectedly + (and multibyte character is broken, too). + +Customized template is used for commands below (``--edit`` may be +required): + +- :hg:`backout` +- :hg:`commit` +- :hg:`fetch` (for merge commit only) +- :hg:`graft` +- :hg:`histedit` +- :hg:`import` +- :hg:`qfold`, :hg:`qnew` and :hg:`qrefresh` +- :hg:`rebase` +- :hg:`shelve` +- :hg:`sign` +- :hg:`tag` +- :hg:`transplant` + ``decode/encode`` ----------------- @@ -807,7 +865,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 +879,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 +901,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 +1272,27 @@ 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 ``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 ``basic``. + +``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. + You have to pay attention to encodings of managed files, if you + use non-ASCII characters in tags, branches, bookmarks, author + and/or commit descriptions. At template expansion, non-ASCII + characters use the encoding specified by ``--encoding`` global + option, ``HGENCODING`` or other locale setting environment + variables. The difference of encoding between merged file and + conflict markers causes serious problem. + ``portablefilenames`` Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``. Default is ``warn``. diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/help/templates.txt --- a/mercurial/help/templates.txt Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/help/templates.txt Sat Jul 19 00:10:22 2014 -0500 @@ -66,10 +66,14 @@ - shortest(node) +- startswith(string, text) + - strip(text[, chars]) - sub(pat, repl, expr) +- word(number, text[, separator]) + Also, for any expression that returns a list, there is a list operator: - expr % "{template}" @@ -84,6 +88,10 @@ $ hg log -r 0 --template "files: {join(files, ', ')}\n" +- Modify each line of a commit description:: + + $ hg log --template "{splitlines(desc) % '**** {line}\n'}" + - Format date:: $ hg log -r 0 --template "{date(date, '%Y')}\n" @@ -120,3 +128,11 @@ - Mark the working copy parent with '@':: $ hg log --template "{ifcontains(rev, revset('.'), '@')}\n" + +- Show only commit descriptions that start with "template":: + + $ hg log --template "{startswith(\"template\", firstline(desc))}\n" + +- Print the first word of each line of a commit message:: + + $ hg log --template "{word(\"0\", desc)}\n" diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/hg.py --- a/mercurial/hg.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/hg.py Sat Jul 19 00:10:22 2014 -0500 @@ -172,15 +172,15 @@ sharedpath = srcrepo.sharedpath # if our source is already sharing - root = os.path.realpath(dest) - roothg = os.path.join(root, '.hg') + destwvfs = scmutil.vfs(dest, realpath=True) + destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True) - if os.path.exists(roothg): + if destvfs.lexists(): raise util.Abort(_('destination already exists')) - if not os.path.isdir(root): - os.mkdir(root) - util.makedir(roothg, notindexed=True) + if not destwvfs.isdir(): + destwvfs.mkdir() + destvfs.makedir() requirements = '' try: @@ -190,10 +190,10 @@ raise requirements += 'shared\n' - util.writefile(os.path.join(roothg, 'requires'), requirements) - util.writefile(os.path.join(roothg, 'sharedpath'), sharedpath) + destvfs.write('requires', requirements) + destvfs.write('sharedpath', sharedpath) - r = repository(ui, root) + r = repository(ui, destwvfs.base) default = srcrepo.ui.config('paths', 'default') if default: @@ -311,10 +311,12 @@ if not dest: raise util.Abort(_("empty destination path is not valid")) - if os.path.exists(dest): - if not os.path.isdir(dest): + + destvfs = scmutil.vfs(dest, expandpath=True) + if destvfs.lexists(): + if not destvfs.isdir(): raise util.Abort(_("destination '%s' already exists") % dest) - elif os.listdir(dest): + elif destvfs.listdir(): raise util.Abort(_("destination '%s' is not empty") % dest) srclock = destlock = cleandir = None @@ -483,7 +485,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""" diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/hook.py --- a/mercurial/hook.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/hook.py Sat Jul 19 00:10:22 2014 -0500 @@ -19,7 +19,7 @@ unmodified commands (e.g. mercurial.commands.update) can be run as hooks without wrappers to convert return values.''' - if util.safehasattr(funcname, '__call__'): + if callable(funcname): obj = funcname funcname = obj.__module__ + "." + obj.__name__ else: @@ -70,7 +70,7 @@ raise util.Abort(_('%s hook is invalid ' '("%s" is not defined)') % (hname, funcname)) - if not util.safehasattr(obj, '__call__'): + if not callable(obj): raise util.Abort(_('%s hook is invalid ' '("%s" is not callable)') % (hname, funcname)) @@ -117,7 +117,7 @@ starttime = time.time() env = {} for k, v in args.iteritems(): - if util.safehasattr(v, '__call__'): + if callable(v): v = v() if isinstance(v, dict): # make the dictionary element order stable across Python @@ -184,7 +184,7 @@ # files seem to be bogus, give up on redirecting (WSGI, etc) pass - if util.safehasattr(cmd, '__call__'): + if callable(cmd): r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r elif cmd.startswith('python:'): if cmd.count(':') >= 2: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/i18n.py --- a/mercurial/i18n.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/i18n.py Sat Jul 19 00:10:22 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]) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/localrepo.py --- a/mercurial/localrepo.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/localrepo.py Sat Jul 19 00:10:22 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 @@ -476,10 +478,17 @@ return 'file:' + self.root def hook(self, name, throw=False, **args): + """Call a hook, passing this repo instance. + + This a convenience method to aid invoking hooks. Extensions likely + won't call this unless they have registered a custom hook or are + replacing code that is expected to call a hook. + """ 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 +548,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 +584,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 +865,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 +1512,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) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/manifest.py --- a/mercurial/manifest.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/manifest.py Sat Jul 19 00:10:22 2014 -0500 @@ -25,6 +25,18 @@ self._flags[f] = flags def copy(self): return manifestdict(self, dict.copy(self._flags)) + def intersectfiles(self, files): + '''make a new manifestdict with the intersection of self with files + + The algorithm assumes that files is much smaller than self.''' + ret = manifestdict() + for fn in files: + if fn in self: + ret[fn] = self[fn] + flags = self._flags.get(fn, None) + if flags: + ret._flags[fn] = flags + return ret def flagsdiff(self, d2): return dicthelpers.diff(self._flags, d2._flags, "") diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/match.py --- a/mercurial/match.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/match.py Sat Jul 19 00:10:22 2014 -0500 @@ -12,7 +12,7 @@ def _rematcher(regex): '''compile the regexp with the best available regexp engine and return a matcher function''' - m = util.compilere(regex) + m = util.re.compile(regex) try: # slightly faster, provided by facebook's re2 bindings return m.test_match @@ -247,7 +247,7 @@ i, n = 0, len(pat) res = '' group = 0 - escape = re.escape + escape = util.re.escape def peek(): return i < n and pat[i] while i < n: @@ -310,11 +310,11 @@ if kind == 're': return pat if kind == 'path': - return '^' + re.escape(pat) + '(?:/|$)' + return '^' + util.re.escape(pat) + '(?:/|$)' if kind == 'relglob': return '(?:|.*/)' + _globre(pat) + globsuffix if kind == 'relpath': - return re.escape(pat) + '(?:/|$)' + return util.re.escape(pat) + '(?:/|$)' if kind == 'relre': if pat.startswith('^'): return pat diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/mdiff.py --- a/mercurial/mdiff.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/mdiff.py Sat Jul 19 00:10:22 2014 -0500 @@ -37,6 +37,7 @@ 'showfunc': False, 'git': False, 'nodates': False, + 'nobinary': False, 'ignorews': False, 'ignorewsamount': False, 'ignoreblanklines': False, diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/merge.py --- a/mercurial/merge.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/merge.py Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/minirst.py --- a/mercurial/minirst.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/minirst.py Sat Jul 19 00:10:22 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)+") diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/parsers.c --- a/mercurial/parsers.c Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/parsers.c Sat Jul 19 00:10:22 2014 -0500 @@ -153,6 +153,122 @@ return NULL; } +static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode, + int size, int mtime) +{ + dirstateTupleObject *t = PyObject_New(dirstateTupleObject, + &dirstateTupleType); + if (!t) + return NULL; + t->state = state; + t->mode = mode; + t->size = size; + t->mtime = mtime; + return t; +} + +static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args, + PyObject *kwds) +{ + /* We do all the initialization here and not a tp_init function because + * dirstate_tuple is immutable. */ + dirstateTupleObject *t; + char state; + int size, mode, mtime; + if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) + return NULL; + + t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1); + if (!t) + return NULL; + t->state = state; + t->mode = mode; + t->size = size; + t->mtime = mtime; + + return (PyObject *)t; +} + +static void dirstate_tuple_dealloc(PyObject *o) +{ + PyObject_Del(o); +} + +static Py_ssize_t dirstate_tuple_length(PyObject *o) +{ + return 4; +} + +static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i) +{ + dirstateTupleObject *t = (dirstateTupleObject *)o; + switch (i) { + case 0: + return PyBytes_FromStringAndSize(&t->state, 1); + case 1: + return PyInt_FromLong(t->mode); + case 2: + return PyInt_FromLong(t->size); + case 3: + return PyInt_FromLong(t->mtime); + default: + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } +} + +static PySequenceMethods dirstate_tuple_sq = { + dirstate_tuple_length, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + dirstate_tuple_item, /* sq_item */ + 0, /* sq_ass_item */ + 0, /* sq_contains */ + 0, /* sq_inplace_concat */ + 0 /* sq_inplace_repeat */ +}; + +PyTypeObject dirstateTupleType = { + PyVarObject_HEAD_INIT(NULL, 0) + "dirstate_tuple", /* tp_name */ + sizeof(dirstateTupleObject),/* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)dirstate_tuple_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dirstate_tuple_sq, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "dirstate tuple", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + dirstate_tuple_new, /* tp_new */ +}; + static PyObject *parse_dirstate(PyObject *self, PyObject *args) { PyObject *dmap, *cmap, *parents = NULL, *ret = NULL; @@ -192,11 +308,8 @@ goto quit; } - entry = Py_BuildValue("ciii", state, mode, size, mtime); - if (!entry) - goto quit; - PyObject_GC_UnTrack(entry); /* don't waste time with this */ - + entry = (PyObject *)make_dirstate_tuple(state, mode, size, + mtime); cpos = memchr(cur, 0, flen); if (cpos) { fname = PyBytes_FromStringAndSize(cur, cpos - cur); @@ -229,39 +342,13 @@ return ret; } -static inline int getintat(PyObject *tuple, int off, uint32_t *v) -{ - PyObject *o = PyTuple_GET_ITEM(tuple, off); - long val; - - if (PyInt_Check(o)) - val = PyInt_AS_LONG(o); - else if (PyLong_Check(o)) { - val = PyLong_AsLong(o); - if (val == -1 && PyErr_Occurred()) - return -1; - } else { - PyErr_SetString(PyExc_TypeError, "expected an int or long"); - return -1; - } - if (LONG_MAX > INT_MAX && (val > INT_MAX || val < INT_MIN)) { - PyErr_SetString(PyExc_OverflowError, - "Python value to large to convert to uint32_t"); - return -1; - } - *v = (uint32_t)val; - return 0; -} - -static PyObject *dirstate_unset; - /* * Efficiently pack a dirstate object into its on-disk format. */ static PyObject *pack_dirstate(PyObject *self, PyObject *args) { PyObject *packobj = NULL; - PyObject *map, *copymap, *pl; + PyObject *map, *copymap, *pl, *mtime_unset = NULL; Py_ssize_t nbytes, pos, l; PyObject *k, *v, *pn; char *p, *s; @@ -318,34 +405,38 @@ p += 20; for (pos = 0; PyDict_Next(map, &pos, &k, &v); ) { + dirstateTupleObject *tuple; + char state; uint32_t mode, size, mtime; Py_ssize_t len, l; PyObject *o; - char *s, *t; + char *t; - if (!PyTuple_Check(v) || PyTuple_GET_SIZE(v) != 4) { - PyErr_SetString(PyExc_TypeError, "expected a 4-tuple"); - goto bail; - } - o = PyTuple_GET_ITEM(v, 0); - if (PyString_AsStringAndSize(o, &s, &l) == -1 || l != 1) { - PyErr_SetString(PyExc_TypeError, "expected one byte"); + if (!dirstate_tuple_check(v)) { + PyErr_SetString(PyExc_TypeError, + "expected a dirstate tuple"); goto bail; } - *p++ = *s; - if (getintat(v, 1, &mode) == -1) - goto bail; - if (getintat(v, 2, &size) == -1) - goto bail; - if (getintat(v, 3, &mtime) == -1) - goto bail; - if (*s == 'n' && mtime == (uint32_t)now) { + tuple = (dirstateTupleObject *)v; + + state = tuple->state; + mode = tuple->mode; + size = tuple->size; + mtime = tuple->mtime; + if (state == 'n' && mtime == (uint32_t)now) { /* See pure/parsers.py:pack_dirstate for why we do * this. */ - if (PyDict_SetItem(map, k, dirstate_unset) == -1) + mtime = -1; + mtime_unset = (PyObject *)make_dirstate_tuple( + state, mode, size, mtime); + if (!mtime_unset) goto bail; - mtime = -1; + if (PyDict_SetItem(map, k, mtime_unset) == -1) + goto bail; + Py_DECREF(mtime_unset); + mtime_unset = NULL; } + *p++ = state; putbe32(mode, p); putbe32(size, p + 4); putbe32(mtime, p + 8); @@ -374,6 +465,7 @@ return packobj; bail: + Py_XDECREF(mtime_unset); Py_XDECREF(packobj); return NULL; } @@ -2016,18 +2108,19 @@ dirs_module_init(mod); indexType.tp_new = PyType_GenericNew; - if (PyType_Ready(&indexType) < 0) + if (PyType_Ready(&indexType) < 0 || + PyType_Ready(&dirstateTupleType) < 0) return; Py_INCREF(&indexType); - PyModule_AddObject(mod, "index", (PyObject *)&indexType); + Py_INCREF(&dirstateTupleType); + PyModule_AddObject(mod, "dirstatetuple", + (PyObject *)&dirstateTupleType); nullentry = Py_BuildValue("iiiiiiis#", 0, 0, 0, -1, -1, -1, -1, nullid, 20); if (nullentry) PyObject_GC_UnTrack(nullentry); - - dirstate_unset = Py_BuildValue("ciii", 'n', 0, -1, -1); } static int check_python_version(void) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/patch.py --- a/mercurial/patch.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/patch.py Sat Jul 19 00:10:22 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) @@ -1564,6 +1561,7 @@ text=opts and opts.get('text'), git=get('git'), nodates=get('nodates'), + nobinary=get('nobinary'), showfunc=get('show_function', 'showfunc'), ignorews=get('ignore_all_space', 'ignorews'), ignorewsamount=get('ignore_space_change', 'ignorewsamount'), @@ -1624,7 +1622,7 @@ revs = None hexfunc = repo.ui.debugflag and hex or short - revs = [hexfunc(node) for node in [node1, node2] if node] + revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node] copy = {} if opts.git or opts.upgrade: @@ -1818,7 +1816,7 @@ if dodiff: if opts.git or revs: header.insert(0, diffline(join(a), join(b), revs)) - if dodiff == 'binary': + if dodiff == 'binary' and not opts.nobinary: text = mdiff.b85diff(to, tn) if text: addindexmeta(header, [gitindex(to), gitindex(tn)]) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/pure/parsers.py Sat Jul 19 00:10:22 2014 -0500 @@ -15,6 +15,12 @@ _decompress = zlib.decompress _sha = util.sha1 +# Some code below makes tuples directly because it's more convenient. However, +# code outside this module should always use dirstatetuple. +def dirstatetuple(*x): + # x is a tuple + return x + def parse_manifest(mfdict, fdict, lines): for l in lines.splitlines(): f, n = l.split('\0') @@ -104,7 +110,7 @@ # dirstate, forcing future 'status' calls to compare the # contents of the file if the size is the same. This prevents # mistakenly treating such files as clean. - e = (e[0], e[1], e[2], -1) + e = dirstatetuple(e[0], e[1], e[2], -1) dmap[f] = e if f in copymap: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/pushkey.py --- a/mercurial/pushkey.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/pushkey.py Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/py3kcompat.py --- a/mercurial/py3kcompat.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/py3kcompat.py Sat Jul 19 00:10:22 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): diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/revset.py --- a/mercurial/revset.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/revset.py Sat Jul 19 00:10:22 2014 -0500 @@ -399,6 +399,9 @@ args = getargs(x, 1, 2, _('only takes one or two arguments')) include = getset(repo, spanset(repo), args[0]).set() if len(args) == 1: + if len(include) == 0: + return baseset([]) + descendants = set(_revdescendants(repo, include, False)) exclude = [rev for rev in cl.headrevs() if not rev in descendants and not rev in include] @@ -1102,16 +1105,6 @@ return baseset([m]) return baseset([]) -def _missingancestors(repo, subset, x): - # i18n: "_missingancestors" is a keyword - revs, bases = getargs(x, 2, 2, - _("_missingancestors requires two arguments")) - rs = baseset(repo) - revs = getset(repo, rs, revs) - bases = getset(repo, rs, bases) - missing = set(repo.changelog.findmissingrevs(bases, revs)) - return baseset([r for r in subset if r in missing]) - def modifies(repo, subset, x): """``modifies(pattern)`` Changesets modifying files matched by pattern. @@ -1724,7 +1717,6 @@ "max": maxrev, "merge": merge, "min": minrev, - "_missingancestors": _missingancestors, "modifies": modifies, "obsolete": obsolete, "origin": origin, @@ -1796,7 +1788,6 @@ "max", "merge", "min", - "_missingancestors", "modifies", "obsolete", "origin", @@ -1867,7 +1858,7 @@ wb, tb = optimize(x[2], True) # (::x and not ::y)/(not ::y and ::x) have a fast path - def ismissingancestors(revs, bases): + def isonly(revs, bases): return ( revs[0] == 'func' and getstring(revs[1], _('not a symbol')) == 'ancestors' @@ -1876,12 +1867,10 @@ and getstring(bases[1][1], _('not a symbol')) == 'ancestors') w = min(wa, wb) - if ismissingancestors(ta, tb): - return w, ('func', ('symbol', '_missingancestors'), - ('list', ta[2], tb[1][2])) - if ismissingancestors(tb, ta): - return w, ('func', ('symbol', '_missingancestors'), - ('list', tb[2], ta[1][2])) + if isonly(ta, tb): + return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2])) + if isonly(tb, ta): + return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2])) if wa > wb: return w, (op, tb, ta) @@ -2243,11 +2232,7 @@ """Returns a new object with the substraction of the two collections. This is part of the mandatory API for smartset.""" - if isinstance(other, baseset): - s = other.set() - else: - s = set(other) - return baseset(self.set() - s) + return self.filter(lambda x: x not in other) def __and__(self, other): """Returns a new object with the intersection of the two collections. @@ -2764,10 +2749,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 +2806,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 diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/scmutil.py --- a/mercurial/scmutil.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/scmutil.py Sat Jul 19 00:10:22 2014 -0500 @@ -178,9 +178,15 @@ 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)) + def listdir(self, path=None): + return os.listdir(self.join(path)) + def makedir(self, path=None, notindexed=True): return util.makedir(self.join(path), notindexed) @@ -223,6 +229,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) diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/simplemerge.py --- a/mercurial/simplemerge.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/simplemerge.py Sat Jul 19 00:10:22 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/subrepo.py --- a/mercurial/subrepo.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/subrepo.py Sat Jul 19 00:10:22 2014 -0500 @@ -35,8 +35,10 @@ data = '' if os.path.exists(filename): fd = open(filename, 'rb') - data = fd.read() - fd.close() + try: + data = fd.read() + finally: + fd.close() return util.sha1(data).hexdigest() class SubrepoAbort(error.Abort): @@ -205,12 +207,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 +505,9 @@ % (substate[0], substate[2])) return [] + def shortid(self, revid): + return revid + class hgsubrepo(abstractsubrepo): def __init__(self, ctx, path, state): self._path = path @@ -521,8 +527,14 @@ self._initrepo(r, state[0], create) def storeclean(self, path): + lock = self._repo.lock() + try: + return self._storeclean(path) + finally: + lock.release() + + def _storeclean(self, path): clean = True - lock = self._repo.lock() itercache = self._calcstorehash(path) try: for filehash in self._readstorehashcache(path): @@ -539,7 +551,6 @@ clean = False except StopIteration: pass - lock.release() return clean def _calcstorehash(self, remotepath): @@ -565,8 +576,10 @@ if not os.path.exists(cachefile): return '' fd = open(cachefile, 'r') - pullstate = fd.readlines() - fd.close() + try: + pullstate = fd.readlines() + finally: + fd.close() return pullstate def _cachestorehash(self, remotepath): @@ -577,14 +590,18 @@ ''' cachefile = self._getstorehashcachepath(remotepath) lock = self._repo.lock() - storehash = list(self._calcstorehash(remotepath)) - cachedir = os.path.dirname(cachefile) - if not os.path.exists(cachedir): - util.makedirs(cachedir, notindexed=True) - fd = open(cachefile, 'w') - fd.writelines(storehash) - fd.close() - lock.release() + try: + storehash = list(self._calcstorehash(remotepath)) + cachedir = os.path.dirname(cachefile) + if not os.path.exists(cachedir): + util.makedirs(cachedir, notindexed=True) + fd = open(cachefile, 'w') + try: + fd.writelines(storehash) + finally: + fd.close() + finally: + lock.release() @annotatesubrepoerror def _initrepo(self, parentrepo, source, create): @@ -592,12 +609,11 @@ self._repo._subsource = source if create: - fp = self._repo.opener("hgrc", "w", text=True) - fp.write('[paths]\n') + lines = ['[paths]\n'] def addpathconfig(key, value): if value: - fp.write('%s = %s\n' % (key, value)) + lines.append('%s = %s\n' % (key, value)) self._repo.ui.setconfig('paths', key, value, 'subrepo') defpath = _abssource(self._repo, abort=False) @@ -605,7 +621,12 @@ addpathconfig('default', defpath) if defpath != defpushpath: addpathconfig('default-push', defpushpath) - fp.close() + + fp = self._repo.opener("hgrc", "w", text=True) + try: + fp.write(''.join(lines)) + finally: + fp.close() @annotatesubrepoerror def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly): @@ -867,6 +888,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 +1587,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, diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/tagmerge.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/tagmerge.py Sat Jul 19 00:10:22 2014 -0500 @@ -0,0 +1,265 @@ +# tagmerge.py - merge .hgtags files +# +# Copyright 2014 Angel Ezquerra +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# This module implements an automatic merge algorithm for mercurial's tag files +# +# The tagmerge algorithm implemented in this module is able to resolve most +# merge conflicts that currently would trigger a .hgtags merge conflict. The +# only case that it does not (and cannot) handle is that in which two tags point +# to different revisions on each merge parent _and_ their corresponding tag +# histories have the same rank (i.e. the same length). In all other cases the +# merge algorithm will choose the revision belonging to the parent with the +# highest ranked tag history. The merged tag history is the combination of both +# tag histories (special care is taken to try to combine common tag histories +# where possible). +# +# In addition to actually merging the tags from two parents, taking into +# account the base, the algorithm also tries to minimize the difference +# between the merged tag file and the first parent's tag file (i.e. it tries to +# make the merged tag order as as similar as possible to the first parent's tag +# file order). +# +# The algorithm works as follows: +# 1. read the tags from p1, p2 and the base +# - when reading the p1 tags, also get the line numbers associated to each +# tag node (these will be used to sort the merged tags in a way that +# minimizes the diff to p1). Ignore the file numbers when reading p2 and +# the base +# 2. recover the "lost tags" (i.e. those that are found in the base but not on +# p1 or p2) and add them back to p1 and/or p2 +# - at this point the only tags that are on p1 but not on p2 are those new +# tags that were introduced in p1. Same thing for the tags that are on p2 +# but not on p2 +# 3. take all tags that are only on p1 or only on p2 (but not on the base) +# - Note that these are the tags that were introduced between base and p1 +# and between base and p2, possibly on separate clones +# 4. for each tag found both on p1 and p2 perform the following merge algorithm: +# - the tags conflict if their tag "histories" have the same "rank" (i.e. +# length) _AND_ the last (current) tag is _NOT_ the same +# - for non conflicting tags: +# - choose which are the high and the low ranking nodes +# - the high ranking list of nodes is the one that is longer. +# In case of draw favor p1 +# - the merged node list is made of 3 parts: +# - first the nodes that are common to the beginning of both +# the low and the high ranking nodes +# - second the non common low ranking nodes +# - finally the non common high ranking nodes (with the last +# one being the merged tag node) +# - note that this is equivalent to putting the whole low ranking +# node list first, followed by the non common high ranking nodes +# - note that during the merge we keep the "node line numbers", which will +# be used when writing the merged tags to the tag file +# 5. write the merged tags taking into account to their positions in the first +# parent (i.e. try to keep the relative ordering of the nodes that come +# from p1). This minimizes the diff between the merged and the p1 tag files +# This is donw by using the following algorithm +# - group the nodes for a given tag that must be written next to each other +# - A: nodes that come from consecutive lines on p1 +# - B: nodes that come from p2 (i.e. whose associated line number is +# None) and are next to one of the a nodes in A +# - each group is associated with a line number coming from p1 +# - generate a "tag block" for each of the groups +# - a tag block is a set of consecutive "node tag" lines belonging to +# the same tag and which will be written next to each other on the +# merged tags file +# - sort the "tag blocks" according to their associated number line +# - put blocks whose nodes come all from p2 first +# - write the tag blocks in the sorted order + +import tags +import util +from node import nullid, hex +from i18n import _ +import operator +hexnullid = hex(nullid) + +def readtagsformerge(ui, repo, lines, fn='', keeplinenums=False): + '''read the .hgtags file into a structure that is suitable for merging + + Sepending on the keeplinenumbers flag, clear the line numbers associated + with each tag. Rhis is done because only the line numbers of the first + parent are useful for merging + ''' + filetags = tags._readtaghist(ui, repo, lines, fn=fn, recode=None, + calcnodelines=True)[1] + for tagname, taginfo in filetags.items(): + if not keeplinenums: + for el in taginfo: + el[1] = None + return filetags + +def grouptagnodesbyline(tagnodes): + ''' + Group nearby nodes (i.e. those that must be written next to each other) + + The input is a list of [node, position] pairs, corresponding to a given tag + The position is the line number where the node was found on the first parent + .hgtags file, or None for those nodes that came from the base or the second + parent .hgtags files. + + This function groups those [node, position] pairs, returning a list of + groups of nodes that must be written next to each other because their + positions are consecutive or have no position preference (because their + position is None). + + The result is a list of [position, [consecutive node list]] + ''' + firstlinenum = None + for hexnode, linenum in tagnodes: + firstlinenum = linenum + if firstlinenum is not None: + break + if firstlinenum is None: + return [[None, [el[0] for el in tagnodes]]] + tagnodes[0][1] = firstlinenum + groupednodes = [[firstlinenum, []]] + prevlinenum = firstlinenum + for hexnode, linenum in tagnodes: + if linenum is not None and linenum - prevlinenum > 1: + groupednodes.append([linenum, []]) + groupednodes[-1][1].append(hexnode) + if linenum is not None: + prevlinenum = linenum + return groupednodes + +def writemergedtags(repo, mergedtags): + ''' + write the merged tags while trying to minimize the diff to the first parent + + This function uses the ordering info stored on the merged tags dict to + generate an .hgtags file which is correct (in the sense that its contents + correspond to the result of the tag merge) while also being as close as + possible to the first parent's .hgtags file. + ''' + # group the node-tag pairs that must be written next to each other + for tname, taglist in mergedtags.items(): + mergedtags[tname] = grouptagnodesbyline(taglist) + + # convert the grouped merged tags dict into a format that resembles the + # final .hgtags file (i.e. a list of blocks of 'node tag' pairs) + def taglist2string(tlist, tname): + return '\n'.join(['%s %s' % (hexnode, tname) for hexnode in tlist]) + + finaltags = [] + for tname, tags in mergedtags.items(): + for block in tags: + block[1] = taglist2string(block[1], tname) + finaltags += tags + + # the tag groups are linked to a "position" that can be used to sort them + # before writing them + # the position is calculated to ensure that the diff of the merged .hgtags + # file to the first parent's .hgtags file is as small as possible + finaltags.sort(key=operator.itemgetter(0)) + + # finally we can join the sorted groups to get the final contents of the + # merged .hgtags file, and then write it to disk + mergedtagstring = '\n'.join([tags for rank, tags in finaltags if tags]) + fp = repo.wfile('.hgtags', 'wb') + fp.write(mergedtagstring + '\n') + fp.close() + +def singletagmerge(p1nodes, p2nodes): + ''' + merge the nodes corresponding to a single tag + + Note that the inputs are lists of node-linenum pairs (i.e. not just lists + of nodes) + ''' + if not p2nodes: + return p1nodes + if not p1nodes: + return p2nodes + + # there is no conflict unless both tags point to different revisions + # and have a non identical tag history + p1currentnode = p1nodes[-1][0] + p2currentnode = p2nodes[-1][0] + if p1currentnode != p2currentnode and len(p1nodes) == len(p2nodes): + # cannot merge two tags with same rank pointing to different nodes + return None + + # which are the highest ranking (hr) / lowest ranking (lr) nodes? + if len(p1nodes) >= len(p2nodes): + hrnodes, lrnodes = p1nodes, p2nodes + else: + hrnodes, lrnodes = p2nodes, p1nodes + + # the lowest ranking nodes will be written first, followed by the highest + # ranking nodes + # to avoid unwanted tag rank explosion we try to see if there are some + # common nodes that can be written only once + commonidx = len(lrnodes) + for n in range(len(lrnodes)): + if hrnodes[n][0] != lrnodes[n][0]: + commonidx = n + break + lrnodes[n][1] = p1nodes[n][1] + + # the merged node list has 3 parts: + # - common nodes + # - non common lowest ranking nodes + # - non common highest ranking nodes + # note that the common nodes plus the non common lowest ranking nodes is the + # whole list of lr nodes + return lrnodes + hrnodes[commonidx:] + +def merge(repo, fcd, fco, fca): + ''' + Merge the tags of two revisions, taking into account the base tags + Try to minimize the diff between the merged tags and the first parent tags + ''' + ui = repo.ui + # read the p1, p2 and base tags + # only keep the line numbers for the p1 tags + p1tags = readtagsformerge( + ui, repo, fcd.data().splitlines(), fn="p1 tags", + keeplinenums=True) + p2tags = readtagsformerge( + ui, repo, fco.data().splitlines(), fn="p2 tags", + keeplinenums=False) + basetags = readtagsformerge( + ui, repo, fca.data().splitlines(), fn="base tags", + keeplinenums=False) + + # recover the list of "lost tags" (i.e. those that were found on the base + # revision but not on one of the revisions being merged) + basetagset = set(basetags) + for n, pntags in enumerate((p1tags, p2tags)): + pntagset = set(pntags) + pnlosttagset = basetagset - pntagset + for t in pnlosttagset: + pntags[t] = basetags[t] + if pntags[t][-1][0] != hexnullid: + pntags[t].append([hexnullid, None]) + + conflictedtags = [] # for reporting purposes + mergedtags = util.sortdict(p1tags) + # sortdict does not implement iteritems() + for tname, p2nodes in p2tags.items(): + if tname not in mergedtags: + mergedtags[tname] = p2nodes + continue + p1nodes = mergedtags[tname] + mergednodes = singletagmerge(p1nodes, p2nodes) + if mergednodes is None: + conflictedtags.append(tname) + continue + mergedtags[tname] = mergednodes + + if conflictedtags: + numconflicts = len(conflictedtags) + ui.warn(_('automatic .hgtags merge failed\n' + 'the following %d tags are in conflict: %s\n') + % (numconflicts, ', '.join(sorted(conflictedtags)))) + return True, 1 + + writemergedtags(repo, mergedtags) + ui.note(_('.hgtags merged successfully\n')) + return False, 0 + diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/tags.py --- a/mercurial/tags.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/tags.py Sat Jul 19 00:10:22 2014 -0500 @@ -12,6 +12,7 @@ from node import nullid, bin, hex, short from i18n import _ +import util import encoding import error import errno @@ -83,20 +84,29 @@ _updatetags(filetags, "local", alltags, tagtypes) -def _readtags(ui, repo, lines, fn, recode=None): +def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False): '''Read tag definitions from a file (or any source of lines). - Return a mapping from tag name to (node, hist): node is the node id - from the last line read for that name, and hist is the list of node - ids previously associated with it (in file order). All node ids are - binary, not hex.''' + This function returns two sortdicts with similar information: + - the first dict, bingtaglist, contains the tag information as expected by + the _readtags function, i.e. a mapping from tag name to (node, hist): + - node is the node id from the last line read for that name, + - hist is the list of node ids previously associated with it (in file + order). All node ids are binary, not hex. + - the second dict, hextaglines, is a mapping from tag name to a list of + [hexnode, line number] pairs, ordered from the oldest to the newest node. + When calcnodelines is False the hextaglines dict is not calculated (an + empty dict is returned). This is done to improve this function's + performance in cases where the line numbers are not needed. + ''' - filetags = {} # map tag name to (node, hist) + bintaghist = util.sortdict() + hextaglines = util.sortdict() count = 0 def warn(msg): ui.warn(_("%s, line %s: %s\n") % (fn, count, msg)) - for line in lines: + for nline, line in enumerate(lines): count += 1 if not line: continue @@ -115,11 +125,28 @@ continue # update filetags - hist = [] - if name in filetags: - n, hist = filetags[name] - hist.append(n) - filetags[name] = (nodebin, hist) + if calcnodelines: + # map tag name to a list of line numbers + if name not in hextaglines: + hextaglines[name] = [] + hextaglines[name].append([nodehex, nline]) + continue + # map tag name to (node, hist) + if name not in bintaghist: + bintaghist[name] = [] + bintaghist[name].append(nodebin) + return bintaghist, hextaglines + +def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False): + '''Read tag definitions from a file (or any source of lines). + Return a mapping from tag name to (node, hist): node is the node id + from the last line read for that name, and hist is the list of node + ids previously associated with it (in file order). All node ids are + binary, not hex.''' + filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode, + calcnodelines=calcnodelines) + for tag, taghist in filetags.items(): + filetags[tag] = (taghist[-1], taghist[:-1]) return filetags def _updatetags(filetags, tagtype, alltags, tagtypes): diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/templatefilters.py --- a/mercurial/templatefilters.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/templatefilters.py Sat Jul 19 00:10:22 2014 -0500 @@ -8,6 +8,7 @@ import cgi, re, os, time, urllib import encoding, node, util import hbisect +import templatekw def addbreaks(text): """:addbreaks: Any text. Add an XHTML "
" tag before the end of @@ -302,6 +303,10 @@ """:shortdate: Date. Returns a date like "2006-09-18".""" return util.shortdate(text) +def splitlines(text): + """:splitlines: Any text. Split text into a list of lines.""" + return templatekw.showlist('line', text.splitlines(), 'lines') + def stringescape(text): return text.encode('string_escape') @@ -384,6 +389,7 @@ "short": short, "shortbisect": shortbisect, "shortdate": shortdate, + "splitlines": splitlines, "stringescape": stringescape, "stringify": stringify, "strip": strip, diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/templatekw.py --- a/mercurial/templatekw.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/templatekw.py Sat Jul 19 00:10:22 2014 -0500 @@ -208,6 +208,17 @@ childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()] return showlist('children', childrevs, element='child', **args) +def showcurrentbookmark(**args): + """:currentbookmark: String. The active bookmark, if it is + associated with the changeset""" + import bookmarks as bookmarks # to avoid circular import issues + repo = args['repo'] + if bookmarks.iscurrent(repo): + current = repo._bookmarkcurrent + if current in args['ctx'].bookmarks(): + return current + return '' + def showdate(repo, ctx, templ, **args): """:date: Date information. The date when the changeset was committed.""" return ctx.date() @@ -345,6 +356,22 @@ """:rev: Integer. The repository-local changeset revision number.""" return ctx.rev() +def showsubrepos(**args): + """:subrepos: List of strings. Updated subrepositories in the changeset.""" + ctx = args['ctx'] + substate = ctx.substate + if not substate: + return showlist('subrepo', [], **args) + psubstate = ctx.parents()[0].substate or {} + subrepos = [] + for sub in substate: + if sub not in psubstate or substate[sub] != psubstate[sub]: + subrepos.append(sub) # modified or newly added in ctx + for sub in psubstate: + if sub not in substate: + subrepos.append(sub) # removed in ctx + return showlist('subrepo', sorted(subrepos), **args) + def showtags(**args): """:tags: List of strings. Any tags associated with the changeset.""" return showlist('tag', args['ctx'].tags(), **args) @@ -364,6 +391,7 @@ 'branches': showbranches, 'bookmarks': showbookmarks, 'children': showchildren, + 'currentbookmark': showcurrentbookmark, 'date': showdate, 'desc': showdescription, 'diffstat': showdiffstat, @@ -385,6 +413,7 @@ 'phase': showphase, 'phaseidx': showphaseidx, 'rev': showrev, + 'subrepos': showsubrepos, 'tags': showtags, } diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/templater.py --- a/mercurial/templater.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/templater.py Sat Jul 19 00:10:22 2014 -0500 @@ -111,7 +111,7 @@ def getsymbol(exp): if exp[0] == 'symbol': return exp[1] - raise error.ParseError(_("expected a symbol")) + raise error.ParseError(_("expected a symbol, got '%s'") % exp[0]) def getlist(x): if not x: @@ -148,7 +148,7 @@ v = context.process(key, mapping) except TemplateNotFound: v = '' - if util.safehasattr(v, '__call__'): + if callable(v): return v(**mapping) if isinstance(v, types.GeneratorType): v = list(v) @@ -185,7 +185,7 @@ def runmap(context, mapping, data): func, data, ctmpl = data d = func(context, mapping, data) - if util.safehasattr(d, '__call__'): + if callable(d): d = d() lm = mapping.copy() @@ -335,7 +335,7 @@ raise error.ParseError(_("join expects one or two arguments")) joinset = args[0][0](context, mapping, args[0][1]) - if util.safehasattr(joinset, '__call__'): + if callable(joinset): jf = joinset.joinfmt joinset = [jf(x) for x in joinset()] @@ -466,6 +466,36 @@ src = stringify(_evalifliteral(args[2], context, mapping)) yield re.sub(pat, rpl, src) +def startswith(context, mapping, args): + if len(args) != 2: + raise error.ParseError(_("startswith expects two arguments")) + + patn = stringify(args[0][0](context, mapping, args[0][1])) + text = stringify(args[1][0](context, mapping, args[1][1])) + if text.startswith(patn): + return text + return '' + + +def word(context, mapping, args): + """return nth word from a string""" + if not (2 <= len(args) <= 3): + raise error.ParseError(_("word expects two or three arguments, got %d") + % len(args)) + + num = int(stringify(args[0][0](context, mapping, args[0][1]))) + text = stringify(args[1][0](context, mapping, args[1][1])) + if len(args) == 3: + splitter = stringify(args[2][0](context, mapping, args[2][1])) + else: + splitter = None + + tokens = text.split(splitter) + if num >= len(tokens): + return '' + else: + return tokens[num] + methods = { "string": lambda e, c: (runstring, e[1]), "rawstring": lambda e, c: (runrawstring, e[1]), @@ -490,8 +520,10 @@ "revset": revset, "rstdoc": rstdoc, "shortest": shortest, + "startswith": startswith, "strip": strip, "sub": sub, + "word": word, } # template engine diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/templates/atom/changelogentry.tmpl --- a/mercurial/templates/atom/changelogentry.tmpl Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/templates/atom/changelogentry.tmpl Sat Jul 19 00:10:22 2014 -0500 @@ -32,7 +32,7 @@ description - {desc|strip|escape|addbreaks|nonempty} + {desc|strip|escape|websub|addbreaks|nonempty} files diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/templates/rss/changelogentry.tmpl --- a/mercurial/templates/rss/changelogentry.tmpl Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/templates/rss/changelogentry.tmpl Sat Jul 19 00:10:22 2014 -0500 @@ -27,7 +27,7 @@ description - {desc|strip|escape|addbreaks|nonempty} + {desc|strip|escape|websub|addbreaks|nonempty} files diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/templates/rss/filelogentry.tmpl --- a/mercurial/templates/rss/filelogentry.tmpl Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/templates/rss/filelogentry.tmpl Sat Jul 19 00:10:22 2014 -0500 @@ -1,7 +1,7 @@ {desc|strip|firstline|strip|escape} {urlbase}{url|urlescape}log{node|short}/{file|urlescape} - + {author|obfuscate} {date|rfc822date} diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/util.h --- a/mercurial/util.h Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/util.h Sat Jul 19 00:10:22 2014 -0500 @@ -151,6 +151,17 @@ #define inline __inline #endif +typedef struct { + PyObject_HEAD + char state; + int mode; + int size; + int mtime; +} dirstateTupleObject; + +extern PyTypeObject dirstateTupleType; +#define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType) + static inline uint32_t getbe32(const char *c) { const unsigned char *d = (const unsigned char *)c; diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/util.py --- a/mercurial/util.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/util.py Sat Jul 19 00:10:22 2014 -0500 @@ -15,7 +15,8 @@ from i18n import _ import error, osutil, encoding -import errno, re, shutil, sys, tempfile, traceback +import errno, shutil, sys, tempfile, traceback +import re as remod import os, time, datetime, calendar, textwrap, signal, collections import imp, socket, urllib @@ -223,6 +224,37 @@ del self[i] break +class sortdict(dict): + '''a simple sorted dictionary''' + def __init__(self, data=None): + self._list = [] + if data: + self.update(data) + def copy(self): + return sortdict(self) + def __setitem__(self, key, val): + if key in self: + self._list.remove(key) + self._list.append(key) + dict.__setitem__(self, key, val) + def __iter__(self): + return self._list.__iter__() + def update(self, src): + for k in src: + self[k] = src[k] + def clear(self): + dict.clear(self) + self._list = [] + def items(self): + return [(k, self[k]) for k in self._list] + def __delitem__(self, key): + dict.__delitem__(self, key) + self._list.remove(key) + def keys(self): + return self._list + def iterkeys(self): + return self._list.__iter__() + class lrucachedict(object): '''cache most recent gets from or sets to this dictionary''' def __init__(self, maxsize): @@ -684,29 +716,50 @@ except ImportError: _re2 = False -def compilere(pat, flags=0): - '''Compile a regular expression, using re2 if possible - - For best performance, use only re2-compatible regexp features. The - only flags from the re module that are re2-compatible are - IGNORECASE and MULTILINE.''' - global _re2 - if _re2 is None: +class _re(object): + def _checkre2(self): + global _re2 try: # check if match works, see issue3964 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]')) except ImportError: _re2 = False - if _re2 and (flags & ~(re.IGNORECASE | re.MULTILINE)) == 0: - if flags & re.IGNORECASE: - pat = '(?i)' + pat - if flags & re.MULTILINE: - pat = '(?m)' + pat - try: - return re2.compile(pat) - except re2.error: - pass - return re.compile(pat, flags) + + def compile(self, pat, flags=0): + '''Compile a regular expression, using re2 if possible + + For best performance, use only re2-compatible regexp features. The + only flags from the re module that are re2-compatible are + IGNORECASE and MULTILINE.''' + if _re2 is None: + self._checkre2() + if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0: + if flags & remod.IGNORECASE: + pat = '(?i)' + pat + if flags & remod.MULTILINE: + pat = '(?m)' + pat + try: + return re2.compile(pat) + except re2.error: + pass + return remod.compile(pat, flags) + + @propertycache + def escape(self): + '''Return the version of escape corresponding to self.compile. + + This is imperfect because whether re2 or re is used for a particular + function depends on the flags, etc, but it's the best we can do. + ''' + global _re2 + if _re2 is None: + self._checkre2() + if _re2: + return re2.escape + else: + return remod.escape + +re = _re() _fspathcache = {} def fspath(name, root): @@ -730,7 +783,7 @@ seps = seps + os.altsep # Protect backslashes. This gets silly very quickly. seps.replace('\\','\\\\') - pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps)) + pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps)) dir = os.path.normpath(root) result = [] for part, sep in pattern.findall(name): @@ -1287,23 +1340,9 @@ r = None return author[author.find('<') + 1:r] -def _ellipsis(text, maxlength): - if len(text) <= maxlength: - return text, False - else: - return "%s..." % (text[:maxlength - 3]), True - def ellipsis(text, maxlength=400): - """Trim string to at most maxlength (default: 400) characters.""" - try: - # use unicode not to split at intermediate multi-byte sequence - utext, truncated = _ellipsis(text.decode(encoding.encoding), - maxlength) - if not truncated: - return text - return utext.encode(encoding.encoding) - except (UnicodeDecodeError, UnicodeEncodeError): - return _ellipsis(text, maxlength)[0] + """Trim string to at most maxlength (default: 400) columns in display.""" + return encoding.trim(text, maxlength, ellipsis='...') def unitcountfn(*unittable): '''return a function that renders a readable count of some quantity''' @@ -1548,7 +1587,7 @@ else: prefix_char = prefix mapping[prefix_char] = prefix_char - r = re.compile(r'%s(%s)' % (prefix, patterns)) + r = remod.compile(r'%s(%s)' % (prefix, patterns)) return r.sub(lambda x: fn(mapping[x.group()[1:]]), s) def getport(port): @@ -1663,7 +1702,7 @@ _safechars = "!~*'()+" _safepchars = "/!~*'()+:\\" - _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match + _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match def __init__(self, path, parsequery=True, parsefragment=True): # We slowly chomp away at path until we have only the path left diff -r 584bbfd1b50d -r 6c36dc6cd61a mercurial/wireproto.py --- a/mercurial/wireproto.py Sat Jul 12 02:23:17 2014 -0700 +++ b/mercurial/wireproto.py Sat Jul 19 00:10:22 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)) diff -r 584bbfd1b50d -r 6c36dc6cd61a setup.py --- a/setup.py Sat Jul 12 02:23:17 2014 -0700 +++ b/setup.py Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/autodiff.py --- a/tests/autodiff.py Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/autodiff.py Sat Jul 19 00:10:22 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]...'), -} diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/filterpyflakes.py --- a/tests/filterpyflakes.py Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/filterpyflakes.py Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/run-tests.py --- a/tests/run-tests.py Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/run-tests.py Sat Jul 19 00:10:22 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), @@ -117,7 +112,7 @@ for filename in files: try: path = os.path.expanduser(os.path.expandvars(filename)) - f = open(path, "r") + f = open(path, "rb") except IOError, err: if err.errno != errno.ENOENT: raise @@ -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 " - 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,413 @@ 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, 'rb') + 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. + if result.addFailure(self, str(e)): + success = True + 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() -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) + # The result object handles diff calculation for us. + if self._result.addOutputMismatch(self, ret, out, self._refout): + # change was accepted, skip failing + return + + if ret: + msg = 'output changed and ' + describe(ret) + else: + msg = 'output changed' -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 + self.fail(msg) + elif ret: + self.fail(describe(ret)) + + 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) -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 += '[/\\\\]' + 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() + + 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 " + 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, 'wb') + hgrc.write('[ui]\n') + hgrc.write('slash = True\n') + hgrc.write('interactive = False\n') + hgrc.write('mergemarkers = detailed\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, 'rb') + 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, 'wb') + 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 +744,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 < '): # 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 < '): # 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 +1037,787 @@ 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.""" - def skip(msg): - if options.verbose: - log("\nSkipping %s: %s" % (testpath, msg)) - return 's', test, msg +class IgnoreTest(Exception): + """Raised to indicate that a test is to be ignored.""" + +class WarnTest(Exception): + """Raised to indicate that a test warned.""" - 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 +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 success(): - return '.', test, '' - - def ignore(msg): - return 'i', test, msg + # unittest.TestResult didn't have skipped until 2.7. We need to + # polyfill it. + self.skipped = [] - def describe(ret): - if ret < 0: - return 'killed by signal %d' % -ret - return 'returned error code %d' % ret - - testpath = os.path.join(TESTDIR, test) - err = os.path.join(TESTDIR, test + ".err") - lctest = test.lower() + # 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 = [] - if not os.path.exists(testpath): - return skip("doesn't exist") - - if not (options.whitelisted and test in options.whitelisted): - if options.blacklist and test in options.blacklist: - return skip("blacklisted") - - if options.retest and not os.path.exists(test + ".err"): - return ignore("not retesting") + # 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 = [] - 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") + self.times = [] + self._started = {} + + def addFailure(self, test, reason): + self.failures.append((test, reason)) - 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") + if self._options.first: + self.stop() + else: + if not self._options.nodiff: + self.stream.write('\nERROR: %s output changed\n' % test) + + self.stream.write('!') - vlog("# Test", test) - - if os.path.exists(err): - os.remove(err) # Remove any previous output files + def addError(self, *args, **kwargs): + super(TestResult, self).addError(*args, **kwargs) - # 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) + if self._options.first: + self.stop() + + # Polyfill. + def addSkip(self, test, reason): + self.skipped.append((test, reason)) - 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')) + if self.showAll: + self.stream.writeln('skipped %s' % reason) + else: + self.stream.write('s') + self.stream.flush() - env = createenv(options, testtmp, threadtmp, port) - createhgrc(env['HGRCPATH'], options) + def addIgnore(self, test, reason): + self.ignored.append((test, reason)) - 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.showAll: + self.stream.writeln('ignored %s' % reason) + else: + if reason != 'not retesting': + self.stream.write('i') + self.stream.flush() - killdaemons(env['DAEMON_PIDS']) + def addWarn(self, test, reason): + self.warned.append((test, reason)) - skipped = (ret == SKIPPED_STATUS) + if self._options.first: + self.stop() - # 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 self.showAll: + self.stream.writeln('warned %s' % reason) + else: + self.stream.write('~') + self.stream.flush() + + def addOutputMismatch(self, test, ret, got, expected): + """Record a mismatch in test output for a particular test.""" - 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() + accepted = False - 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 + iolock.acquire() + if self._options.nodiff: + pass + elif self._options.view: + os.system("%s %s %s" % + (self._options.view, test.refpath, test.errpath)) 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)) + failed, lines = getdiff(expected, got, + test.refpath, test.errpath) + if failed: + self.addFailure(test, 'diff generation failed') 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() + self.stream.write('\n') + for line in lines: + self.stream.write(line) + self.stream.flush() - if not options.verbose: - iolock.acquire() - sys.stdout.write(result[0]) - sys.stdout.flush() + # handle interactive prompt without releasing iolock + if self._options.interactive: + self.stream.write('Accept this change? [n] ') + answer = sys.stdin.readline().strip() + if answer.lower() in ('y', 'yes'): + if test.name.endswith('.t'): + rename(test.errpath, test.path) + else: + rename(test.errpath, '%s.out' % test.path) + accepted = True + iolock.release() - if not options.keep_tmpdir: - shutil.rmtree(threadtmp, True) - return result + return accepted -_hgpath = None + def startTest(self, test): + super(TestResult, self).startTest(test) -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._started[test.name] = time.time() + + 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] - cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"' - pipe = os.popen(cmd % PYTHON) - try: - _hgpath = pipe.read().strip() - finally: - pipe.close() - return _hgpath + 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 _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)) + 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. + + 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. -results = {'.':[], '!':[], '~': [], 's':[], 'i':[]} -times = [] -iolock = threading.Lock() -abort = False + 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. -def scheduletests(options, tests): - jobs = options.jobs - done = queue.Queue() - running = 0 - count = 0 - global abort + 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) - 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 + self._jobs = jobs + self._whitelist = whitelist + self._blacklist = blacklist + self._retest = retest + self._keywords = keywords + self._loop = loop - 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: + 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 - 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 + + if self._keywords: + f = open(test.path, 'rb') + 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 + + 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() + + 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) -def runtests(options, tests): - try: - if INST: - installhg(options) - _checkhglib("Testing") - else: - usecorrectpython() + 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 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') - 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 + # 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 - scheduletests(options, tests) + # 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. + """ - failed = len(results['!']) - warned = len(results['~']) - tested = len(results['.']) + failed + warned - skipped = len(results['s']) - ignored = len(results['i']) + # 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), + ] + + 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) - 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) + def _run(self, tests): + if self.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) + + self._testdir = os.environ['TESTDIR'] = os.getcwd() + + 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.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.anycoverage: - outputcoverage(options) - except KeyboardInterrupt: - failed = True - print "\ninterrupted!" + # 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) - if failed: - return 1 - if warned: - return 80 + 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) + + # 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: + 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) -testtypes = [('.py', pytest, '.out'), - ('.t', tsttest, '')] + # 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) -def main(args, parser=None): - parser = parser or getparser() - (options, args) = parseargs(args, parser) - os.umask(022) + 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]) - checktools() + try: + return self._runtests(tests) or 0 + finally: + time.sleep(.1) + self._cleanup() + + def findtests(self, args): + """Finds possible test files from arguments. - 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') - else: - args = os.listdir(".") + 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'))] - tests = [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 - 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 + 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 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: - 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 '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)) - - 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 + os.remove(f) + except OSError: + pass - # 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) - HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir) + 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 + + def _installhg(self): + """Install hg into the test environment. - 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) + 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' - # 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") + # 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(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, 'rb') + for line in f: + print line, + f.close() + sys.exit(1) + os.chdir(self._testdir) + + self._usecorrectpython() + + 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'), 'rb') + lines = [line.rstrip() for line in f] + lines[0] += ' -3' + f.close() + f = open(os.path.join(self._bindir, 'hg'), 'wb') + 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' - path = [BINDIR] + os.environ["PATH"].split(os.pathsep) - if TMPBINDIR != BINDIR: - path = [TMPBINDIR] + path - os.environ["PATH"] = os.pathsep.join(path) + 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 + + 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 - # 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) + 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) + + def covrun(*args): + cmd = 'coverage %s' % ' '.join(args) + vlog('# Running: %s' % cmd) + os.system(cmd) - COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") + 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) - vlog("# Using TESTDIR", TESTDIR) - vlog("# Using HGTMP", HGTMP) - vlog("# Using PATH", os.environ["PATH"]) - vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) + 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 - 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:])) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-add.t --- a/tests/test-add.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-add.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-backout.t --- a/tests/test-backout.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-backout.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-basic.t --- a/tests/test-basic.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-basic.t Sat Jul 19 00:10:22 2014 -0500 @@ -7,6 +7,7 @@ defaults.tag=-d "0 0" ui.slash=True ui.interactive=False + ui.mergemarkers=detailed $ hg init t $ cd t diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-bookmarks-current.t --- a/tests/test-bookmarks-current.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-bookmarks-current.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-bookmarks-merge.t --- a/tests/test-bookmarks-merge.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-bookmarks-merge.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-bookmarks-pushpull.t --- a/tests/test-bookmarks-pushpull.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-bookmarks-pushpull.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-bookmarks-strip.t --- a/tests/test-bookmarks-strip.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-bookmarks-strip.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-bookmarks.t --- a/tests/test-bookmarks.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-bookmarks.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-bundle2.t --- a/tests/test-bundle2.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-bundle2.t Sat Jul 19 00:10:22 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 - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: H - | - | o changeset: 7:eea13746799a - |/| parent: 6:24b6387c8c8c - | | parent: 5:9520eea781bc - | | user: Nicolas Dumazet - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: G - | | - o | changeset: 6:24b6387c8c8c - | | parent: 1:cd010b8cd998 - | | user: Nicolas Dumazet - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: F - | | - | o changeset: 5:9520eea781bc - |/ parent: 1:cd010b8cd998 - | user: Nicolas Dumazet - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: E + o 8:02de42196ebe draft Nicolas Dumazet H | - | o changeset: 4:32af7686d403 - | | user: Nicolas Dumazet - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: D + | o 7:eea13746799a draft Nicolas Dumazet G + |/| + o | 6:24b6387c8c8c draft Nicolas Dumazet F | | - | o changeset: 3:5fddd98957c8 - | | user: Nicolas Dumazet - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: C + | o 5:9520eea781bc draft Nicolas Dumazet E + |/ + | o 4:32af7686d403 draft Nicolas Dumazet D | | - | o changeset: 2:42ccdea3bb16 - |/ user: Nicolas Dumazet - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: B - | - o changeset: 1:cd010b8cd998 - parent: -1:000000000000 - user: Nicolas Dumazet - date: Sat Apr 30 15:24:48 2011 +0200 - summary: A + | o 3:5fddd98957c8 draft Nicolas Dumazet C + | | + | o 2:42ccdea3bb16 draft Nicolas Dumazet B + |/ + o 1:cd010b8cd998 draft Nicolas Dumazet 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 - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: E + @ 1:9520eea781bc draft Nicolas Dumazet E | - o changeset: 0:cd010b8cd998 - user: Nicolas Dumazet - date: Sat Apr 30 15:24:48 2011 +0200 - summary: A + o 0:cd010b8cd998 public Nicolas Dumazet 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 F + | + | @ 1:9520eea781bc draft Nicolas Dumazet E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet 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 F + | + | @ 1:9520eea781bc draft Nicolas Dumazet E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet 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 F + | + | @ 1:9520eea781bc draft Nicolas Dumazet E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet 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 G + |\ + | o 2:24b6387c8c8c public Nicolas Dumazet F + | | + @ | 1:9520eea781bc public Nicolas Dumazet E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet 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 C + | + o 5:42ccdea3bb16 draft Nicolas Dumazet B + | + | o 4:02de42196ebe draft Nicolas Dumazet H + | | + | | o 3:eea13746799a public Nicolas Dumazet G + | |/| + | o | 2:24b6387c8c8c public Nicolas Dumazet F + |/ / + | @ 1:9520eea781bc public Nicolas Dumazet E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet 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 - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: D + o 7:32af7686d403 public Nicolas Dumazet D | - o changeset: 6:5fddd98957c8 - | user: Nicolas Dumazet - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: C + o 6:5fddd98957c8 public Nicolas Dumazet C | - o changeset: 5:42ccdea3bb16 - | parent: 0:cd010b8cd998 - | user: Nicolas Dumazet - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: B + o 5:42ccdea3bb16 public Nicolas Dumazet B | - | o changeset: 4:02de42196ebe - | | parent: 2:24b6387c8c8c - | | user: Nicolas Dumazet - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: H + | o 4:02de42196ebe draft Nicolas Dumazet H | | - | | o changeset: 3:eea13746799a - | |/| parent: 2:24b6387c8c8c - | | | parent: 1:9520eea781bc - | | | user: Nicolas Dumazet - | | | date: Sat Apr 30 15:24:48 2011 +0200 - | | | summary: G - | | | - | o | changeset: 2:24b6387c8c8c - |/ / parent: 0:cd010b8cd998 - | | user: Nicolas Dumazet - | | date: Sat Apr 30 15:24:48 2011 +0200 - | | summary: F - | | - | @ changeset: 1:9520eea781bc - |/ user: Nicolas Dumazet - | date: Sat Apr 30 15:24:48 2011 +0200 - | summary: E - | - o changeset: 0:cd010b8cd998 - user: Nicolas Dumazet - date: Sat Apr 30 15:24:48 2011 +0200 - summary: A + | | o 3:eea13746799a public Nicolas Dumazet G + | |/| + | o | 2:24b6387c8c8c public Nicolas Dumazet F + |/ / + | @ 1:9520eea781bc public Nicolas Dumazet E + |/ + o 0:cd010b8cd998 public Nicolas Dumazet A Error Handling @@ -933,27 +947,24 @@ > from mercurial import exchange > from mercurial import extensions > - > def _pushbundle2failpart(orig, pushop, bundler): - > extradata = orig(pushop, bundler) + > def _pushbundle2failpart(pushop, bundler): > 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) - > return extradata + > bundler.newpart('b2x:check:heads', data='01234567890123456789') + > return lambda op: None > > @bundle2.parthandler("test:abort") > def handleabort(op, part): > raise util.Abort('Abandon ship!', hint="don't panic") > > def uisetup(ui): - > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart) + > exchange.bundle2partsgenerators.insert(0, _pushbundle2failpart) > > EOF @@ -1015,19 +1026,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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-check-code-hg.t --- a/tests/test-check-code-hg.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-check-code-hg.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-check-code.t --- a/tests/test-check-code.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-check-code.t Sat Jul 19 00:10:22 2014 -0500 @@ -284,3 +284,19 @@ > print _( don't use % inside _() [1] + +web templates + + $ mkdir -p mercurial/templates + $ cat > mercurial/templates/example.tmpl < {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] diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-check-pyflakes.t --- a/tests/test-check-pyflakes.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-check-pyflakes.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-command-template.t --- a/tests/test-command-template.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-command-template.t Sat Jul 19 00:10:22 2014 -0500 @@ -1850,6 +1850,15 @@ 2 bar* foo 1 0 + $ hg log --template "{rev} {currentbookmark}\n" + 2 bar + 1 + 0 + $ hg bookmarks --inactive bar + $ hg log --template "{rev} {currentbookmark}\n" + 2 + 1 + 0 Test stringify on sub expressions @@ -1859,3 +1868,119 @@ $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n' abc +Test splitlines + + $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}" + @ foo future + | + o foo third + | + o foo second + + o foo merge + |\ + | o foo new head + | | + o | foo new branch + |/ + o foo no user, no domain + | + o foo no person + | + o foo other 1 + | foo other 2 + | foo + | foo other 3 + o foo line 1 + foo line 2 + +Test startswith + $ hg log -Gv -R a --template "{startswith(desc)}" + hg: parse error: startswith expects two arguments + [255] + + $ hg log -Gv -R a --template "{startswith('line', desc)}" + @ + | + o + | + o + + o + |\ + | o + | | + o | + |/ + o + | + o + | + o + | + o line 1 + line 2 + +Test bad template with better error message + + $ hg log -Gv -R a --template '{desc|user()}' + hg: parse error: expected a symbol, got 'func' + [255] + +Test word function (including index out of bounds graceful failure) + + $ hg log -Gv -R a --template "{word('1', desc)}" + @ + | + o + | + o + + o + |\ + | o head + | | + o | branch + |/ + o user, + | + o person + | + o 1 + | + o 1 + + +Test word third parameter used as splitter + + $ hg log -Gv -R a --template "{word('0', desc, 'o')}" + @ future + | + o third + | + o sec + + o merge + |\ + | o new head + | | + o | new branch + |/ + o n + | + o n + | + o + | + o line 1 + line 2 + +Test word error messages for not enough and too many arguments + + $ hg log -Gv -R a --template "{word('0')}" + hg: parse error: word expects two or three arguments, got 1 + [255] + + $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}" + hg: parse error: word expects two or three arguments, got 7 + [255] diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-commandserver.py --- a/tests/test-commandserver.py Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-commandserver.py Sat Jul 19 00:10:22 2014 -0500 @@ -341,10 +341,9 @@ check(mqoutsidechanges) dbg = open('dbgui.py', 'w') dbg.write('from mercurial import cmdutil, commands\n' - 'commands.norepo += " debuggetpass"\n' 'cmdtable = {}\n' 'command = cmdutil.command(cmdtable)\n' - '@command("debuggetpass")\n' + '@command("debuggetpass", norepo=True)\n' 'def debuggetpass(ui):\n' ' ui.write("%s\\n" % ui.getpass())\n') dbg.close() diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-commandserver.py.out --- a/tests/test-commandserver.py.out Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-commandserver.py.out Sat Jul 19 00:10:22 2014 -0500 @@ -80,6 +80,7 @@ defaults.tag=-d "0 0" ui.slash=True ui.interactive=False +ui.mergemarkers=detailed ui.foo=bar ui.nontty=true runcommand init foo @@ -90,6 +91,7 @@ defaults.tag=-d "0 0" ui.slash=True ui.interactive=False +ui.mergemarkers=detailed ui.nontty=true testing hookoutput: @@ -177,6 +179,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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-commit-amend.t --- a/tests/test-commit-amend.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-commit-amend.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-commit-unresolved.t --- a/tests/test-commit-unresolved.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-commit-unresolved.t Sat Jul 19 00:10:22 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 .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-commit.t --- a/tests/test-commit.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-commit.t Sat Jul 19 00:10:22 2014 -0500 @@ -354,6 +354,48 @@ test saving last-message.txt +test that '[committemplate] changeset' definition and commit log +specific template keywords work well + + $ cat >> .hg/hgrc < [committemplate] + > changeset = HG: this is customized commit template + > HG: {extramsg} + > {if(currentbookmark, + > "HG: bookmark '{currentbookmark}' is activated\n", + > "HG: no bookmark is activated\n")}{subrepos % + > "HG: subrepo '{subrepo}' is changed\n"} + > EOF + + $ hg init sub2 + $ echo a > sub2/a + $ hg -R sub2 add sub2/a + $ echo 'sub2 = sub2' >> .hgsub + + $ HGEDITOR=cat hg commit -S -q + HG: this is customized commit template + HG: Leave message empty to abort commit. + HG: bookmark 'currentbookmark' is activated + HG: subrepo 'sub' is changed + HG: subrepo 'sub2' is changed + abort: empty commit message + [255] + + $ hg bookmark --inactive currentbookmark + $ hg forget .hgsub + $ HGEDITOR=cat hg commit -q + HG: this is customized commit template + HG: Leave message empty to abort commit. + HG: no bookmark is activated + abort: empty commit message + [255] + + $ cat >> .hg/hgrc < # disable customizing for subsequent tests + > [committemplate] + > changeset = + > EOF + $ cd .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-completion.t --- a/tests/test-completion.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-completion.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-conflict.t --- a/tests/test-conflict.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-conflict.t Sat Jul 19 00:10:22 2014 -0500 @@ -1,12 +1,36 @@ $ hg init - $ echo "nothing" > a + $ cat << EOF > a + > Small Mathematical Series. + > One + > Two + > Three + > Four + > Five + > Hop we are done. + > EOF $ hg add a $ hg commit -m ancestor - $ echo "something" > a + $ cat << EOF > a + > Small Mathematical Series. + > 1 + > 2 + > 3 + > 4 + > 5 + > Hop we are done. + > EOF $ hg commit -m branch1 $ hg co 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ echo "something else" > a + $ cat << EOF > a + > Small Mathematical Series. + > 1 + > 2 + > 3 + > 6 + > 8 + > Hop we are done. + > EOF $ hg commit -m branch2 created new head @@ -19,15 +43,158 @@ [1] $ hg id - 32e80765d7fe+75234512624c+ tip + 618808747361+c0c68e4fe667+ tip $ cat a - <<<<<<< local - something else + Small Mathematical Series. + <<<<<<< local: 618808747361 - test: branch2 + 1 + 2 + 3 + 6 + 8 ======= - something - >>>>>>> other + 1 + 2 + 3 + 4 + 5 + >>>>>>> other: c0c68e4fe667 - test: branch1 + Hop we are done. $ 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 + Small Mathematical Series. + <<<<<<< local: test 2 + 1 + 2 + 3 + 6 + 8 + ======= + 1 + 2 + 3 + 4 + 5 + >>>>>>> other: test 1 + Hop we are done. + +Verify line splitting of custom conflict marker which causes multiple lines + + $ hg up -q --clean . + $ cat >> .hg/hgrc < [ui] + > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz + > EOF + + $ hg -q merge 1 + warning: conflicts during merge. + merging a incomplete! (edit conflicts, then use 'hg resolve --mark') + [1] + + $ cat a + Small Mathematical Series. + <<<<<<< local: test 2 + 1 + 2 + 3 + 6 + 8 + ======= + 1 + 2 + 3 + 4 + 5 + >>>>>>> other: test 1 + Hop we are done. + +Verify line trimming of custom conflict marker using multi-byte characters + + $ hg up -q --clean . + $ python < fp = open('logfile', 'w') + > fp.write('12345678901234567890123456789012345678901234567890' + + > '1234567890') # there are 5 more columns for 80 columns + > + > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes + > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8')) + > + > fp.close() + > EOF + $ hg add logfile + $ hg --encoding utf-8 commit --logfile logfile + + $ cat >> .hg/hgrc < [ui] + > mergemarkertemplate={desc|firstline} + > EOF + + $ hg -q --encoding utf-8 merge 1 + warning: conflicts during merge. + merging a incomplete! (edit conflicts, then use 'hg resolve --mark') + [1] + + $ cat a + Small Mathematical Series. + <<<<<<< local: 123456789012345678901234567890123456789012345678901234567890\xe3\x81\x82... (esc) + 1 + 2 + 3 + 6 + 8 + ======= + 1 + 2 + 3 + 4 + 5 + >>>>>>> other: branch1 + Hop we are done. + +Verify basic conflict markers + + $ hg up -q --clean 2 + $ 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 + Small Mathematical Series. + <<<<<<< local + 1 + 2 + 3 + 6 + 8 + ======= + 1 + 2 + 3 + 4 + 5 + >>>>>>> other + Hop we are done. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-context.py --- a/tests/test-context.py Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-context.py Sat Jul 19 00:10:22 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!"), @@ -30,3 +30,23 @@ for enc in "ASCII", "Latin-1", "UTF-8": encoding.encoding = enc print "%-8s: %s" % (enc, repo["tip"].description()) + +# test performing a status + +def getfilectx(repo, memctx, f): + fctx = memctx.parents()[0][f] + data, flags = fctx.data(), fctx.flags() + if f == 'foo': + data += 'bar\n' + return context.memfilectx(repo, f, data, 'l' in flags, 'x' in flags) + +ctxa = repo.changectx(0) +ctxb = context.memctx(repo, [ctxa.node(), None], "test diff", ["foo"], + getfilectx, ctxa.user(), ctxa.date()) + +print ctxb.status(ctxa) + +# test performing a diff on a memctx + +for d in ctxb.diff(ctxa, git=True): + print d diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-context.py.out --- a/tests/test-context.py.out Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-context.py.out Sat Jul 19 00:10:22 2014 -0500 @@ -2,3 +2,12 @@ ASCII : Gr?ezi! Latin-1 : Grüezi! UTF-8 : Grüezi! +(['foo'], [], [], [], [], [], []) +diff --git a/foo b/foo + +--- a/foo ++++ b/foo +@@ -1,1 +1,2 @@ + foo ++bar + diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-convert-hg-sink.t --- a/tests/test-convert-hg-sink.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-convert-hg-sink.t Sat Jul 19 00:10:22 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) + diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-convert-hg-source.t --- a/tests/test-convert-hg-source.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-convert-hg-source.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-convert-svn-sink.t --- a/tests/test-convert-svn-sink.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-convert-svn-sink.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-copy-move-merge.t --- a/tests/test-copy-move-merge.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-copy-move-merge.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-diff-binary-file.t --- a/tests/test-diff-binary-file.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-diff-binary-file.t Sat Jul 19 00:10:22 2014 -0500 @@ -37,4 +37,8 @@ $ hg diff --git -r 0 -r 2 + $ hg diff --config diff.nobinary=True --git -r 0 -r 1 + diff --git a/binfile.bin b/binfile.bin + Binary file binfile.bin has changed + $ cd .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-double-merge.t --- a/tests/test-double-merge.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-double-merge.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-encoding-align.t --- a/tests/test-encoding-align.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-encoding-align.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-encoding-textwrap.t --- a/tests/test-encoding-textwrap.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-encoding-textwrap.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-extension.t --- a/tests/test-extension.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-extension.t Sat Jul 19 00:10:22 2014 -0500 @@ -2,27 +2,20 @@ $ cat > foobar.py < import os - > from mercurial import commands - > + > from mercurial import cmdutil, commands + > cmdtable = {} + > command = cmdutil.command(cmdtable) > def uisetup(ui): > ui.write("uisetup called\\n") - > > def reposetup(ui, repo): > 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', norepo=True) > 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 @@ -107,7 +100,6 @@ > from mercurial import demandimport; demandimport.enable() > from mercurial.hgweb import hgweb > from mercurial.hgweb import wsgicgi - > > application = hgweb('.', 'test repo') > wsgicgi.launch(application) > EOF @@ -202,21 +194,16 @@ > # "not locals" case > import extroot.bar > buf.append('import extroot.bar in func(): %s' % extroot.bar.s) - > > return '\n(extroot) '.join(buf) - > > # "fromlist == ('*',)" case > from extroot.bar import * > buf.append('from extroot.bar import *: %s' % s) - > > # "not fromlist" and "if '.' in name" case > import extroot.sub1.baz > buf.append('import extroot.sub1.baz: %s' % extroot.sub1.baz.s) - > > # "not fromlist" and NOT "if '.' in name" case > import extroot > buf.append('import extroot: %s' % extroot.s) - > > # NOT "not fromlist" and NOT "level != -1" case > from extroot.bar import s > buf.append('from extroot.bar import s: %s' % s) @@ -238,21 +225,16 @@ > # "not locals" case > import bar > buf.append('import bar in func(): %s' % bar.s) - > > return '\n(extroot) '.join(buf) - > > # "fromlist == ('*',)" case > from bar import * > buf.append('from bar import *: %s' % s) - > > # "not fromlist" and "if '.' in name" case > import sub1.baz > buf.append('import sub1.baz: %s' % sub1.baz.s) - > > # "not fromlist" and NOT "if '.' in name" case > import sub1 > buf.append('import sub1: %s' % sub1.s) - > > # NOT "not fromlist" and NOT "level != -1" case > from bar import s > buf.append('from bar import s: %s' % s) @@ -283,26 +265,25 @@ no commands defined + $ echo 'empty = !' >> $HGRCPATH $ cat > debugextension.py < '''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 @@ -312,6 +293,7 @@ no commands defined + $ hg --verbose help debugextension debugextension extension - only debugcommands @@ -342,6 +324,11 @@ [+] marked option can be specified multiple times + + + + + $ hg --debug help debugextension debugextension extension - only debugcommands @@ -372,6 +359,11 @@ --hidden consider hidden changesets [+] marked option can be specified multiple times + + + + + $ echo 'debugextension = !' >> $HGRCPATH Extension module help vs command help: @@ -411,6 +403,15 @@ use "hg -v help extdiff" to show the global options + + + + + + + + + $ hg help --extension extdiff extdiff extension - command to allow external programs to compare revisions @@ -470,21 +471,35 @@ use "hg -v help extdiff" to show builtin aliases and global options + + + + + + + + + + + + + + + $ echo 'extdiff = !' >> $HGRCPATH Test help topic with same name as extension $ cat > multirevs.py < from mercurial import commands + > from mercurial import cmdutil, commands + > cmdtable = {} + > command = cmdutil.command(cmdtable) > """multirevs extension > Big multi-line module docstring.""" + > @command('multirevs', [], 'ARG', norepo=True) > def multirevs(ui, repo, arg, *args, **opts): > """multirevs command""" > pass - > cmdtable = { - > "multirevs": (multirevs, [], 'ARG') - > } - > commands.norepo += ' multirevs' > EOF $ echo "multirevs = multirevs.py" >> $HGRCPATH @@ -508,6 +523,11 @@ use "hg help -c multirevs" to see help for the multirevs command + + + + + $ hg help -c multirevs hg multirevs ARG @@ -515,6 +535,8 @@ use "hg -v help multirevs" to show the global options + + $ hg multirevs hg multirevs: invalid arguments hg multirevs ARG @@ -524,6 +546,8 @@ use "hg help multirevs" to show the full help text [255] + + $ echo "multirevs = !" >> $HGRCPATH Issue811: Problem loading extensions twice (by site and by user) @@ -532,14 +556,13 @@ $ cat > debugissue811.py < '''show all loaded extensions > ''' - > from mercurial import extensions, commands - > + > from mercurial import cmdutil, commands, extensions + > cmdtable = {} + > command = cmdutil.command(cmdtable) + > @command('debugextensions', [], 'hg debugextensions', norepo=True) > 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 $ echo "mq=" >> $HGRCPATH @@ -566,6 +589,8 @@ patchbomb command to send changesets as (a series of) patch emails use "hg help extensions" for information on enabling extensions + + $ hg qdel hg: unknown command 'qdel' 'qdelete' is provided by the following extension: @@ -574,6 +599,8 @@ use "hg help extensions" for information on enabling extensions [255] + + $ hg churn hg: unknown command 'churn' 'churn' is provided by the following extension: @@ -583,17 +610,21 @@ use "hg help extensions" for information on enabling extensions [255] + + Disabled extensions: $ hg help churn churn extension - command to display statistics about repository history use "hg help extensions" for information on enabling extensions + $ hg help patchbomb patchbomb extension - command to send changesets as (a series of) patch emails use "hg help extensions" for information on enabling extensions + Broken disabled extension and command: $ mkdir hgext @@ -613,13 +644,14 @@ use "hg help extensions" for information on enabling extensions + $ cat > hgext/forest.py < cmdtable = None > 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 < cmdtable = {} > command = cmdutil.command(cmdtable) > class Bogon(Exception): pass - > - > @command('throw', [], 'hg throw') + > @command('throw', [], 'hg throw', norepo=True) > def throw(ui, **opts): > """throws an exception""" > raise Bogon() - > commands.norepo += " throw" > EOF No declared supported version, extension complains: $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*' @@ -715,6 +745,34 @@ ** Mercurial Distributed SCM (*) (glob) ** Extensions loaded: throw +Test version number support in 'hg version': + $ echo '__version__ = (1, 2, 3)' >> throw.py + $ rm -f throw.pyc throw.pyo + $ hg version -v --config extensions.throw=throw.py + Mercurial Distributed SCM (version *) (glob) + (see http://mercurial.selenic.com for more information) + + Copyright (C) 2005-* Matt Mackall and others (glob) + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + Enabled extensions: + + throw 1.2.3 + $ echo 'getversion = lambda: "1.twentythree"' >> throw.py + $ rm -f throw.pyc throw.pyo + $ hg version -v --config extensions.throw=throw.py + Mercurial Distributed SCM (version *) (glob) + (see http://mercurial.selenic.com for more information) + + Copyright (C) 2005-* Matt Mackall and others (glob) + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + Enabled extensions: + + throw 1.twentythree + Restore HGRCPATH $ HGRCPATH=$ORGHGRCPATH diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-fetch.t --- a/tests/test-fetch.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-fetch.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-fileset.t --- a/tests/test-fileset.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-fileset.t Sat Jul 19 00:10:22 2014 -0500 @@ -154,6 +154,7 @@ b2 $ echo e > b2 $ hg resolve -m b2 + no more unresolved files $ fileset 'resolved()' b2 $ fileset 'unresolved()' diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-globalopts.t --- a/tests/test-globalopts.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-globalopts.t Sat Jul 19 00:10:22 2014 -0500 @@ -290,7 +290,7 @@ archive create an unversioned archive of a repository revision backout reverse effect of earlier changeset bisect subdivision search of changesets - bookmarks track a line of development with movable markers + bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches bundle create a changegroup file @@ -372,7 +372,7 @@ archive create an unversioned archive of a repository revision backout reverse effect of earlier changeset bisect subdivision search of changesets - bookmarks track a line of development with movable markers + bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches bundle create a changegroup file diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-gpg.t --- a/tests/test-gpg.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-gpg.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-graft.t --- a/tests/test-graft.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-graft.t Sat Jul 19 00:10:22 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 @@ -443,6 +461,37 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: 2 +Test that the graft and transplant markers in extra are converted, allowing +origin() to still work. Note that these recheck the immediately preceeding two +tests. + $ hg --quiet --config extensions.convert= --config convert.hg.saverev=True convert . ../converted + +The graft case + $ hg -R ../converted log -r 7 --template "{rev}: {node}\n{join(extras, '\n')}\n" + 7: 7ae846e9111fc8f57745634250c7b9ac0a60689b + branch=default + convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82 + source=e0213322b2c1a5d5d236c74e79666441bee67a7d + $ hg -R ../converted log -r 'origin(7)' + changeset: 2:e0213322b2c1 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 2 + +The transplant case + $ hg -R ../converted log -r tip --template "{rev}: {node}\n{join(extras, '\n')}\n" + 21: fbb6c5cc81002f2b4b49c9d731404688bcae5ade + branch=dev + convert_revision=7e61b508e709a11d28194a5359bc3532d910af21 + transplant_source=z\xe8F\xe9\x11\x1f\xc8\xf5wEcBP\xc7\xb9\xac (esc) + `h\x9b (esc) + $ hg -R ../converted log -r 'origin(tip)' + changeset: 2:e0213322b2c1 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 2 + + Test simple destination $ hg log -r 'destination()' changeset: 7:ef0ef43d49e7 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-help.t --- a/tests/test-help.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-help.t Sat Jul 19 00:10:22 2014 -0500 @@ -55,7 +55,7 @@ archive create an unversioned archive of a repository revision backout reverse effect of earlier changeset bisect subdivision search of changesets - bookmarks track a line of development with movable markers + bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches bundle create a changegroup file @@ -131,7 +131,7 @@ archive create an unversioned archive of a repository revision backout reverse effect of earlier changeset bisect subdivision search of changesets - bookmarks track a line of development with movable markers + bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches bundle create a changegroup file @@ -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,20 +630,21 @@ $ cat > helpext.py < 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', + > norepo=True) + > @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 $ echo "helpext = `pwd`/helpext.py" >> $HGRCPATH @@ -708,7 +687,7 @@ archive create an unversioned archive of a repository revision backout reverse effect of earlier changeset bisect subdivision search of changesets - bookmarks track a line of development with movable markers + bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches bundle create a changegroup file @@ -973,7 +952,7 @@ Commands: - bookmarks track a line of development with movable markers + bookmarks create a new bookmark or list existing bookmarks clone make a copy of an existing repository paths show aliases for remote repositories update update working directory (or switch revisions) @@ -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 < - track a line of development with movable markers + create a new bookmark or list existing bookmarks diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-hgweb-commands.t Sat Jul 19 00:10:22 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 < [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 @@ description - branch commit with null character: + branch commit with null character: (websub) files @@ -138,7 +143,7 @@ description - branch + branch(websub) files @@ -181,7 +186,7 @@ description - Added tag 1.0 for changeset 2ef0ac749a14 + Added tag 1.0 for changeset 2ef0ac749a14(websub) files @@ -224,7 +229,7 @@ description - base + base(websub) files @@ -235,6 +240,180 @@ + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=rss' + 200 Script output follows + + + + + http://*:$HGPORT/ (glob) + en-us + + test Changelog + test Changelog + + [unstable] branch commit with null character: + http://*:$HGPORT/rev/cad8025a2e87 (glob) + http://*:$HGPORT/rev/cad8025a2e87 (glob) + + + + changeset + cad8025a2e87 + + + branch + unstable + + + bookmark + something + + + tag + tip + + + user + test + + + description + branch commit with null character: (websub) + + + files + + + + ]]> + test + Thu, 01 Jan 1970 00:00:00 +0000 + + + [stable] branch + http://*:$HGPORT/rev/1d22e65f027e (glob) + http://*:$HGPORT/rev/1d22e65f027e (glob) + + + + changeset + 1d22e65f027e + + + branch + stable + + + bookmark + + + + tag + + + + user + test + + + description + branch(websub) + + + files + foo
+ + + ]]>
+ test + Thu, 01 Jan 1970 00:00:00 +0000 +
+ + [default] Added tag 1.0 for changeset 2ef0ac749a14 + http://*:$HGPORT/rev/a4f92ed23982 (glob) + http://*:$HGPORT/rev/a4f92ed23982 (glob) + + + + changeset + a4f92ed23982 + + + branch + default + + + bookmark + + + + tag + + + + user + test + + + description + Added tag 1.0 for changeset 2ef0ac749a14(websub) + + + files + .hgtags
+ + + ]]>
+ test + Thu, 01 Jan 1970 00:00:00 +0000 +
+ + base + http://*:$HGPORT/rev/2ef0ac749a14 (glob) + http://*:$HGPORT/rev/2ef0ac749a14 (glob) + + + + changeset + 2ef0ac749a14 + + + branch + + + + bookmark + anotherthing + + + tag + 1.0 + + + user + test + + + description + base(websub) + + + files + da/foo
foo
+ + + ]]>
+ test + Thu, 01 Jan 1970 00:00:00 +0000 +
+ +
+
(no-eol) $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=atom' 200 Script output follows @@ -281,7 +460,7 @@ description - Added tag 1.0 for changeset 2ef0ac749a14 + Added tag 1.0 for changeset 2ef0ac749a14(websub) files @@ -324,7 +503,7 @@ description - base + base(websub) files @@ -335,6 +514,100 @@ + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=rss' + 200 Script output follows + + + + + http://*:$HGPORT/ (glob) + en-us + + test Changelog + test Changelog + + [default] Added tag 1.0 for changeset 2ef0ac749a14 + http://*:$HGPORT/rev/a4f92ed23982 (glob) + http://*:$HGPORT/rev/a4f92ed23982 (glob) + + + + changeset + a4f92ed23982 + + + branch + default + + + bookmark + + + + tag + + + + user + test + + + description + Added tag 1.0 for changeset 2ef0ac749a14(websub) + + + files + .hgtags
+ + + ]]>
+ test + Thu, 01 Jan 1970 00:00:00 +0000 +
+ + base + http://*:$HGPORT/rev/2ef0ac749a14 (glob) + http://*:$HGPORT/rev/2ef0ac749a14 (glob) + + + + changeset + 2ef0ac749a14 + + + branch + + + + bookmark + anotherthing + + + tag + 1.0 + + + user + test + + + description + base(websub) + + + files + da/foo
foo
+ + + ]]>
+ test + Thu, 01 Jan 1970 00:00:00 +0000 +
+ +
+
(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 @@ description - base + base(websub) files @@ -390,6 +663,27 @@ + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=rss' + 200 Script output follows + + + + + http://*:$HGPORT/ (glob) + en-us + + test: foo history + foo revision history + + base + http://*:$HGPORT/log2ef0ac749a14/foo (glob) + + test + Thu, 01 Jan 1970 00:00:00 +0000 + + + + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/' 200 Script output follows @@ -570,7 +864,7 @@ number or hash, or
revset expression. -
base
+
base(websub)
@@ -992,7 +1286,7 @@ number or hash, or revset expression. -
Added tag 1.0 for changeset 2ef0ac749a14
+
Added tag 1.0 for changeset 2ef0ac749a14(websub)
@@ -1116,7 +1410,7 @@ number or hash, or revset expression. -
branch
+
branch(websub)
diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-histedit-arguments.t --- a/tests/test-histedit-arguments.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-histedit-arguments.t Sat Jul 19 00:10:22 2014 -0500 @@ -227,3 +227,35 @@ $ hg histedit -r 'heads(all())' abort: The specified revisions must have exactly one common root [255] + +Test that trimming description using multi-byte characters +-------------------------------------------------------------------- + + $ python < fp = open('logfile', 'w') + > fp.write('12345678901234567890123456789012345678901234567890' + + > '12345') # there are 5 more columns for 80 columns + > + > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes + > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8')) + > + > fp.close() + > EOF + $ echo xx >> x + $ hg --encoding utf-8 commit --logfile logfile + + $ HGEDITOR=cat hg --encoding utf-8 histedit tip + pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc) + + # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b + # + # Commits are listed from least to most recent + # + # Commands: + # p, pick = use commit + # e, edit = use commit, but stop for amending + # f, fold = use commit, but combine it with the one above + # d, drop = remove commit from history + # m, mess = edit message without changing commit content + # + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-histedit-edit.t --- a/tests/test-histedit-edit.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-histedit-edit.t Sat Jul 19 00:10:22 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 <> .hg/hgrc < [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 < [extensions] + > commitfailure = ! + > EOF + $ hg histedit --abort -q + +(test that editor is invoked and commit message is saved into +"last-message.txt") + + $ cat >> .hg/hgrc < [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 < [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 < [hooks] + > pretxncommit.unexpectedabort = > EOF $ hg histedit --abort -q diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-histedit-fold-non-commute.t --- a/tests/test-histedit-fold-non-commute.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-histedit-fold-non-commute.t Sat Jul 19 00:10:22 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 < 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-histedit-fold.t --- a/tests/test-histedit-fold.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-histedit-fold.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-histedit-non-commute-abort.t diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-histedit-non-commute.t --- a/tests/test-histedit-non-commute.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-histedit-non-commute.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-import-bypass.t --- a/tests/test-import-bypass.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-import-bypass.t Sat Jul 19 00:10:22 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 < 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-import.t --- a/tests/test-import.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-import.t Sat Jul 19 00:10:22 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 < 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-issue1877.t --- a/tests/test-issue1877.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-issue1877.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-issue3084.t --- a/tests/test-issue3084.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-issue3084.t Sat Jul 19 00:10:22 2014 -0500 @@ -154,7 +154,20 @@ $ hg init merges $ cd merges $ touch f1 - $ hg ci -Aqm "0-root" + $ hg ci -Aqm "0-root" --config extensions.largefiles=! + +Ensure that .hg/largefiles isn't created before largefiles are added +#if unix-permissions + $ chmod 555 .hg +#endif + $ hg status +#if unix-permissions + $ chmod 755 .hg +#endif + + $ find .hg/largefiles + find: `.hg/largefiles': No such file or directory + [1] ancestor is "normal": $ echo normal > f diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-issue672.t --- a/tests/test-issue672.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-issue672.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-journal-exists.t --- a/tests/test-journal-exists.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-journal-exists.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-keyword.t --- a/tests/test-keyword.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-keyword.t Sat Jul 19 00:10:22 2014 -0500 @@ -507,7 +507,6 @@ $ hg -q commit -d '14 1' -m 'prepare amend' $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords - invalid branchheads cache (served): tip differs overwriting a expanding keywords $ hg -q id 67d8c481a6be @@ -1049,15 +1048,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 $ diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-largefiles-misc.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-largefiles-misc.t Sat Jul 19 00:10:22 2014 -0500 @@ -0,0 +1,835 @@ +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 < [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 entities for 1 files 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 (1 entities): + b + + $ hg -R clone2 outgoing --large --graph --template "{rev}" + comparing with $TESTTMP/issue3651/src + searching for changes + @ 1 + + largefiles to upload (1 entities): + b + + + $ cp clone2/b clone2/b1 + $ cp clone2/b clone2/b2 + $ hg -R clone2 add --large clone2/b1 clone2/b2 + $ hg -R clone2 commit -m '#2: add largefiles referring same entity' + Invoking status precommit hook + A b1 + A b2 + $ hg -R clone2 summary --large + parent: 2:6095d0695d70 tip + #2: add largefiles referring same entity + branch: default + commit: (clean) + update: (current) + largefiles: 1 entities for 3 files to upload + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" + comparing with $TESTTMP/issue3651/src (glob) + searching for changes + 1:1acbe71ce432 + 2:6095d0695d70 + largefiles to upload (1 entities): + b + b1 + b2 + + $ hg -R clone2 cat -r 1 clone2/.hglf/b + 89e6c98d92887913cadf06b2adb97f26cde4849b + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug + comparing with $TESTTMP/issue3651/src (glob) + query 1; heads + searching for changes + all remote heads known locally + 1:1acbe71ce432 + 2:6095d0695d70 + largefiles to upload (1 entities): + b + 89e6c98d92887913cadf06b2adb97f26cde4849b + b1 + 89e6c98d92887913cadf06b2adb97f26cde4849b + b2 + 89e6c98d92887913cadf06b2adb97f26cde4849b + + + $ echo bbb > clone2/b + $ hg -R clone2 commit -m '#3: add new largefile entity as existing file' + Invoking status precommit hook + M b + $ echo bbbb > clone2/b + $ hg -R clone2 commit -m '#4: add new largefile entity as existing file' + Invoking status precommit hook + M b + $ cp clone2/b1 clone2/b + $ hg -R clone2 commit -m '#5: refer existing largefile entity again' + Invoking status precommit hook + M b + $ hg -R clone2 summary --large + parent: 5:036794ea641c tip + #5: refer existing largefile entity again + branch: default + commit: (clean) + update: (current) + largefiles: 3 entities for 3 files to upload + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" + comparing with $TESTTMP/issue3651/src (glob) + searching for changes + 1:1acbe71ce432 + 2:6095d0695d70 + 3:7983dce246cc + 4:233f12ada4ae + 5:036794ea641c + largefiles to upload (3 entities): + b + b1 + b2 + + $ hg -R clone2 cat -r 3 clone2/.hglf/b + c801c9cfe94400963fcb683246217d5db77f9a9a + $ hg -R clone2 cat -r 4 clone2/.hglf/b + 13f9ed0898e315bf59dc2973fec52037b6f441a2 + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug + comparing with $TESTTMP/issue3651/src (glob) + query 1; heads + searching for changes + all remote heads known locally + 1:1acbe71ce432 + 2:6095d0695d70 + 3:7983dce246cc + 4:233f12ada4ae + 5:036794ea641c + largefiles to upload (3 entities): + b + 13f9ed0898e315bf59dc2973fec52037b6f441a2 + 89e6c98d92887913cadf06b2adb97f26cde4849b + c801c9cfe94400963fcb683246217d5db77f9a9a + b1 + 89e6c98d92887913cadf06b2adb97f26cde4849b + b2 + 89e6c98d92887913cadf06b2adb97f26cde4849b + + +Pusing revision #1 causes uploading entity 89e6c98d9288, which is +shared also by largefiles b1, b2 in revision #2 and b in revision #5. + +Then, entity 89e6c98d9288 is not treated as "outgoing entity" at "hg +summary" and "hg outgoing", even though files in outgoing revision #2 +and #5 refer it. + + $ hg -R clone2 push -r 1 -q + $ hg -R clone2 summary --large + parent: 5:036794ea641c tip + #5: refer existing largefile entity again + branch: default + commit: (clean) + update: (current) + largefiles: 2 entities for 1 files to upload + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" + comparing with $TESTTMP/issue3651/src (glob) + searching for changes + 2:6095d0695d70 + 3:7983dce246cc + 4:233f12ada4ae + 5:036794ea641c + largefiles to upload (2 entities): + b + + $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug + comparing with $TESTTMP/issue3651/src (glob) + query 1; heads + searching for changes + all remote heads known locally + 2:6095d0695d70 + 3:7983dce246cc + 4:233f12ada4ae + 5:036794ea641c + largefiles to upload (2 entities): + b + 13f9ed0898e315bf59dc2973fec52037b6f441a2 + c801c9cfe94400963fcb683246217d5db77f9a9a + + + $ 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 < [extensions] + > # disable globally + > largefiles=! + > EOF + $ cat >> enabledlocally/.hg/hgrc < [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 < [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 .. + + + diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-largefiles-wireproto.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-largefiles-wireproto.t Sat Jul 19 00:10:22 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 < [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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-largefiles.t --- a/tests/test-largefiles.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-largefiles.t Sat Jul 19 00:10:22 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 < 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 < [extensions] - > # disable globally - > largefiles=! - > EOF - $ cat >> enabledlocally/.hg/hgrc < [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 < [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 .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-lfconvert.t --- a/tests/test-lfconvert.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-lfconvert.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-log.t --- a/tests/test-log.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-log.t Sat Jul 19 00:10:22 2014 -0500 @@ -1027,6 +1027,7 @@ [1] $ echo 'merge 1' > foo $ hg resolve -m foo + no more unresolved files $ hg ci -m "First merge, related" $ hg merge 4 @@ -1038,6 +1039,7 @@ [1] $ echo 'merge 2' > foo $ hg resolve -m foo + no more unresolved files $ hg ci -m "Last merge, related" $ hg log --graph diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-merge-commit.t --- a/tests/test-merge-commit.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-merge-commit.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-merge-criss-cross.t --- a/tests/test-merge-criss-cross.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-merge-criss-cross.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-merge-revert2.t --- a/tests/test-merge-revert2.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-merge-revert2.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-merge-tools.t --- a/tests/test-merge-tools.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-merge-tools.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-merge-types.t --- a/tests/test-merge-types.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-merge-types.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-merge7.t --- a/tests/test-merge7.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-merge7.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-mq-qfold.t --- a/tests/test-mq-qfold.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-mq-qfold.t Sat Jul 19 00:10:22 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 <> .hg/hgrc < [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 < [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 < [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 < [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 .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-mq-qnew.t --- a/tests/test-mq-qnew.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-mq-qnew.t Sat Jul 19 00:10:22 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 <> .hg/hgrc < [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 < [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 < [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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-mq-qrefresh-replace-log-message.t --- a/tests/test-mq-qrefresh-replace-log-message.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-mq-qrefresh-replace-log-message.t Sat Jul 19 00:10:22 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,43 @@ First commit message Testing changing message with -m +(this tests also that '--edit' can be used with '--message', and +that '[committemplate] changeset' definition and commit log specific +template keyword 'extramsg' work well) + + $ cat >> .hg/hgrc < [committemplate] + > changeset = HG: this is customized commit template + > {desc}\n\n + > HG: Enter commit message. Lines beginning with 'HG:' are removed. + > HG: {extramsg} + > HG: -- + > HG: user: {author} + > HG: branch '{branch}'\n{file_adds % + > "HG: added {file}\n" }{file_mods % + > "HG: changed {file}\n" }{file_dels % + > "HG: removed {file}\n" }{if(files, "", + > "HG: no files changed\n")} + > EOF $ echo bbbb > file - $ hg qrefresh -m "Second commit message" + $ HGEDITOR=cat hg qrefresh -m "Second commit message" -e + HG: this is customized commit template + 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 + + $ cat >> .hg/hgrc < # disable customizing for subsequent tests + > [committemplate] + > changeset = + > EOF Should display 'Second commit message' @@ -59,3 +95,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 < 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 < [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 < [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 < [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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-obsolete-divergent.t --- a/tests/test-obsolete-divergent.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-obsolete-divergent.t Sat Jul 19 00:10:22 2014 -0500 @@ -27,7 +27,7 @@ > hg ci -m "$1" > } $ getid() { - > hg id --debug --hidden -ir "desc('$1')" + > hg log --hidden -r "desc('$1')" -T '{node}\n' > } setup repo @@ -62,7 +62,6 @@ $ newcase direct $ hg debugobsolete `getid A_0` `getid A_1` $ hg debugobsolete `getid A_0` `getid A_2` - invalid branchheads cache (served): tip differs $ hg log -G --hidden o 3:392fd25390da A_2 | @@ -104,7 +103,6 @@ $ newcase indirect_known $ hg debugobsolete `getid A_0` `getid A_1` $ hg debugobsolete `getid A_0` `getid A_2` - invalid branchheads cache (served): tip differs $ mkcommit A_3 created new head $ hg debugobsolete `getid A_2` `getid A_3` @@ -143,7 +141,6 @@ $ newcase indirect_unknown $ hg debugobsolete `getid A_0` aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid A_1` - invalid branchheads cache (served): tip differs $ hg debugobsolete `getid A_0` `getid A_2` $ hg log -G --hidden o 3:392fd25390da A_2 @@ -175,7 +172,6 @@ $ newcase final-unknown $ hg debugobsolete `getid A_0` `getid A_1` $ hg debugobsolete `getid A_1` `getid A_2` - invalid branchheads cache (served): tip differs $ hg debugobsolete `getid A_0` bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb $ hg debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccc $ hg debugobsolete `getid A_1` dddddddddddddddddddddddddddddddddddddddd @@ -192,7 +188,6 @@ $ newcase converged_divergence $ hg debugobsolete `getid A_0` `getid A_1` $ hg debugobsolete `getid A_0` `getid A_2` - invalid branchheads cache (served): tip differs $ mkcommit A_3 created new head $ hg debugobsolete `getid A_1` `getid A_3` @@ -439,7 +434,6 @@ $ newcase subset $ hg debugobsolete `getid A_0` `getid A_2` $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2` - invalid branchheads cache (served): tip differs $ hg debugsuccessorssets --hidden 'desc('A_0')' 007dc284c1f8 82623d38b9ba 392fd25390da diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-patchbomb.t --- a/tests/test-patchbomb.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-patchbomb.t Sat Jul 19 00:10:22 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: (glob) References: (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: (glob) References: (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: (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 @@ -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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob) In-Reply-To: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (glob) In-Reply-To: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (glob) In-Reply-To: (glob) References: (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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) References: (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: (glob) References: (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: References: 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: References: 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: References: 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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (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: (glob) References: (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: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (glob) In-Reply-To: (glob) References: (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: (glob) In-Reply-To: (glob) References: (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: (glob) + X-Mercurial-Series-Id: (glob) In-Reply-To: (glob) References: (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: (glob) In-Reply-To: (glob) References: (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: (glob) In-Reply-To: (glob) References: (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: (glob) In-Reply-To: (glob) References: (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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-progress.t --- a/tests/test-progress.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-progress.t Sat Jul 19 00:10:22 2014 -0500 @@ -1,7 +1,11 @@ $ cat > loop.py < 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,12 @@ > 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', + > norepo=True) > def loop(ui, loops, **opts): > loops = int(loops) > total = None @@ -23,7 +33,7 @@ > loops = abs(loops) > > for i in range(loops): - > ui.progress('loop', i, 'loop.%d' % i, 'loopnum', total) + > ui.progress(topiclabel, i, getloopitem(i), 'loopnum', total) > if opts.get('parallel'): > ui.progress('other', i, 'other.%d' % i, 'othernum', total) > if nested: @@ -35,17 +45,12 @@ > 'nested', j, 'nested.%d' % j, 'nestnum', nested_steps) > ui.progress( > 'nested', None, 'nested.done', 'nestnum', nested_steps) - > ui.progress('loop', None, 'loop.done', 'loopnum', total) - > - > commands.norepo += " loop" + > ui.progress(topiclabel, None, 'loop.done', 'loopnum', total) > - > cmdtable = { - > "loop": (loop, [('', 'total', '', 'override for total'), - > ('', 'nested', False, 'show nested results'), - > ('', 'parallel', False, 'show parallel sets of results'), - > ], - > 'hg loop LOOPS'), - > } + > topiclabel = 'loop' + > def getloopitem(i): + > return 'loop.%d' % i + > > EOF $ cp $HGRCPATH $HGRCPATH.orig @@ -237,3 +242,98 @@ loop [ <=> ] 2\r (no-eol) (esc) loop [ <=> ] 3\r (no-eol) (esc) \r (no-eol) (esc) + +test line trimming by '[progress] width', when progress topic contains +multi-byte characters, of which length of byte sequence and columns in +display are different from each other. + + $ cp $HGRCPATH.orig $HGRCPATH + $ cat >> $HGRCPATH < [extensions] + > progress= + > loop=`pwd`/loop.py + > [progress] + > assume-tty = 1 + > delay = 0 + > refresh = 0 + > EOF + + $ rm -f loop.pyc + $ cat >> loop.py < # use non-ascii characters as topic label of progress + > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes + > topiclabel = u'\u3042\u3044\u3046\u3048'.encode('utf-8') + > EOF + + $ cat >> $HGRCPATH < [progress] + > format = topic number + > width= 12 + > EOF + + $ hg --encoding utf-8 -y loop --total 3 3 + \r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88 0/3\r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88 1/3\r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88 2/3\r (no-eol) (esc) + \r (no-eol) (esc) + +test calculation of bar width, when progress topic contains multi-byte +characters, of which length of byte sequence and columns in display +are different from each other. + + $ cat >> $HGRCPATH < [progress] + > format = topic bar + > width= 21 + > # progwidth should be 9 (= 21 - (8+1) - 3) + > EOF + + $ hg --encoding utf-8 -y loop --total 3 3 + \r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88 [ ]\r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88 [==> ]\r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88 [=====> ]\r (no-eol) (esc) + \r (no-eol) (esc) + +test triming progress items, when they contain multi-byte characters, +of which length of byte sequence and columns in display are different +from each other. + + $ rm -f loop.pyc + $ cat >> loop.py < # use non-ascii characters as loop items of progress + > loopitems = [ + > u'\u3042\u3044'.encode('utf-8'), # 2 x 2 = 4 columns + > u'\u3042\u3044\u3046'.encode('utf-8'), # 2 x 3 = 6 columns + > u'\u3042\u3044\u3046\u3048'.encode('utf-8'), # 2 x 4 = 8 columns + > ] + > def getloopitem(i): + > return loopitems[i % len(loopitems)] + > EOF + + $ cat >> $HGRCPATH < [progress] + > # trim at tail side + > format = item+6 + > EOF + + $ hg --encoding utf-8 -y loop --total 3 3 + \r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84 \r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\r (no-eol) (esc) + \r (no-eol) (esc) + + $ cat >> $HGRCPATH < [progress] + > # trim at left side + > format = item-6 + > EOF + + $ hg --encoding utf-8 -y loop --total 3 3 + \r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84 \r (no-eol) (esc) + \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\r (no-eol) (esc) + \xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\r (no-eol) (esc) + \r (no-eol) (esc) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-purge.t --- a/tests/test-purge.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-purge.t Sat Jul 19 00:10:22 2014 -0500 @@ -215,4 +215,50 @@ $ hg purge -p -X .svn -X '*/.svn' $ hg purge -p -X re:.*.svn + $ rm -R .svn directory r1 + +only remove files + + $ mkdir -p empty_dir dir + $ touch untracked_file dir/untracked_file + $ hg purge -p --files + dir/untracked_file + untracked_file + $ hg purge -v --files + removing file dir/untracked_file + removing file untracked_file + $ ls + dir + empty_dir + $ ls dir + +only remove dirs + + $ mkdir -p empty_dir dir + $ touch untracked_file dir/untracked_file + $ hg purge -p --dirs + empty_dir + $ hg purge -v --dirs + removing directory empty_dir + $ ls + dir + untracked_file + $ ls dir + untracked_file + +remove both files and dirs + + $ mkdir -p empty_dir dir + $ touch untracked_file dir/untracked_file + $ hg purge -p --files --dirs + dir/untracked_file + untracked_file + empty_dir + $ hg purge -v --files --dirs + removing file dir/untracked_file + removing file untracked_file + removing directory empty_dir + removing directory dir + $ ls + $ cd .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-bookmarks.t --- a/tests/test-rebase-bookmarks.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-bookmarks.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-check-restore.t --- a/tests/test-rebase-check-restore.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-check-restore.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-conflicts.t --- a/tests/test-rebase-conflicts.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-conflicts.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-detach.t --- a/tests/test-rebase-detach.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-detach.t Sat Jul 19 00:10:22 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" diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-interruptions.t --- a/tests/test-rebase-interruptions.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-interruptions.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-mq-skip.t --- a/tests/test-rebase-mq-skip.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-mq-skip.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-mq.t --- a/tests/test-rebase-mq.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-mq.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-parameters.t --- a/tests/test-rebase-parameters.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-parameters.t Sat Jul 19 00:10:22 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) diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rebase-scenario-global.t --- a/tests/test-rebase-scenario-global.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rebase-scenario-global.t Sat Jul 19 00:10:22 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 + 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rename-dir-merge.t --- a/tests/test-rename-dir-merge.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rename-dir-merge.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rename-merge1.t --- a/tests/test-rename-merge1.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rename-merge1.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rename-merge2.t --- a/tests/test-rename-merge2.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rename-merge2.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-resolve.t --- a/tests/test-resolve.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-resolve.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-revert.t --- a/tests/test-revert.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-revert.t Sat Jul 19 00:10:22 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 + + diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-revset.t --- a/tests/test-revset.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-revset.t Sat Jul 19 00:10:22 2014 -0500 @@ -427,6 +427,16 @@ 7 8 9 + +Test empty set input + $ log 'only(p2())' + $ log 'only(p1(), p2())' + 0 + 1 + 2 + 4 + 8 + 9 $ log 'outgoing()' 8 9 @@ -562,7 +572,7 @@ 5 8 -check that conversion to _missingancestors works +check that conversion to only works $ try --optimize '::3 - ::1' (minus (dagrangepre @@ -571,7 +581,7 @@ ('symbol', '1'))) * optimized: (func - ('symbol', '_missingancestors') + ('symbol', 'only') (list ('symbol', '3') ('symbol', '1'))) @@ -586,7 +596,7 @@ ('symbol', '3'))) * optimized: (func - ('symbol', '_missingancestors') + ('symbol', 'only') (list ('symbol', '1') ('symbol', '3'))) @@ -599,7 +609,7 @@ ('symbol', '6'))) * optimized: (func - ('symbol', '_missingancestors') + ('symbol', 'only') (list ('symbol', '6') ('symbol', '2'))) @@ -618,7 +628,7 @@ ('symbol', '4')))) * optimized: (func - ('symbol', '_missingancestors') + ('symbol', 'only') (list ('symbol', '6') ('symbol', '4'))) @@ -1008,6 +1018,11 @@ $ log 'min(1 or 2) and not 1' $ log 'last(1 or 2, 1) and not 2' +issue4289 - ordering of built-ins + $ hg log -M -q -r 3:2 + 3:8528aa5637f2 + 2:5ed5505e9f1c + test revsets started with 40-chars hash (issue3669) $ ISSUE3669_TIP=`hg tip --template '{node}'` diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-rollback.t --- a/tests/test-rollback.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-rollback.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-run-tests.py --- a/tests/test-run-tests.py Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-run-tests.py Sat Jul 19 00:10:22 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: diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-run-tests.t --- a/tests/test-run-tests.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-run-tests.t Sat Jul 19 00:10:22 2014 -0500 @@ -1,121 +1,203 @@ -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 +======================= + + $ 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. - $ echo foo - foo - $ printf 'oh no' - oh no (no-eol) - $ printf 'bar\nbaz\n' | cat - bar - baz +failing test +================== + + $ cat > test-failure.t << EOF + > $ echo babar + > rataxes + > EOF + + $ $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] -Multi-line command: +test for --retest +==================== + + $ $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] + +Selecting Tests To Run +====================== - $ foo() { - > echo bar - > } - $ foo - bar +successful + + $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t + . + # Ran 1 tests, 0 skipped, 0 warned, 0 failed. + +failed -Return codes before inline python: - - $ sh -c 'exit 1' + $ $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] -Doctest commands: +Running In Debug Mode +====================== - >>> print 'foo' - foo - $ echo interleaved - interleaved - >>> for c in 'xyz': - ... print c - x - y - z - >>> print - + $ $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. -Regular expressions: +Parallel runs +============== - $ echo foobarbaz - foobar.* (re) - $ echo barbazquux - .*quux.* (re) +(duplicate the failing test to get predictable output) + $ cp test-failure.t test-failure-copy.t -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` --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] -Windows: \r\n is handled like \n and can be escaped: +(delete the duplicated test file) + $ rm test-failure-copy.t -#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) +Interactive run +=============== + +(backup the failing test) + $ cp test-failure.t backup + +Refuse the fix -testing hghave - - $ "$TESTDIR/hghave" true - $ "$TESTDIR/hghave" false - skipped: missing feature: nail clipper + $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + Accept this change? [n] + 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] - $ "$TESTDIR/hghave" no-true - skipped: system supports yak shaving - [1] - $ "$TESTDIR/hghave" no-false -Conditional sections based on hghave: + $ cat test-failure.t + $ echo babar + rataxes -#if true - $ echo tested - tested -#else - $ echo skipped -#endif +Accept the fix -#if false - $ echo skipped -#else - $ echo tested - tested -#endif + $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` -i + + --- $TESTTMP/test-failure.t + +++ $TESTTMP/test-failure.t.err + @@ -1,2 +1,2 @@ + $ echo babar + - rataxes + + babar + Accept this change? [n] .. + # Ran 2 tests, 0 skipped, 0 warned, 0 failed. -#if no-false - $ echo tested - tested -#else - $ echo skipped -#endif + $ cat test-failure.t + $ echo babar + babar + +(reinstall) + $ mv backup test-failure.t -#if no-true - $ echo skipped -#else - $ echo tested - tested -#endif +No Diff +=============== -Exit code: - - $ (exit 1) + $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff + !. + Failed test-failure.t: output changed + # Ran 2 tests, 0 skipped, 0 warned, 1 failed. + python hash seed: * (glob) [1] diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-shelve.t --- a/tests/test-shelve.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-shelve.t Sat Jul 19 00:10:22 2014 -0500 @@ -16,8 +16,10 @@ $ hg addremove -q shelving in an empty repo should be possible +(this tests also that editor is not invoked, if '--edit' is not +specified) - $ hg shelve + $ HGEDITOR=cat hg shelve shelved as default 0 files updated, 0 files merged, 5 files removed, 0 files unresolved @@ -90,6 +92,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 @@ -128,6 +134,7 @@ [255] named shelves, specific filenames, and "commit messages" should all work +(this tests also that editor is invoked, if '--edit' is specified) $ hg status -C M a/a @@ -136,7 +143,16 @@ A c.copy c R b/b - $ hg shelve -q -n wibble -m wat a + $ HGEDITOR=cat hg shelve -q -n wibble -m wat -e a + wat + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: shelve@localhost + HG: branch 'default' + HG: changed a/a expect "a" to no longer be present, but status otherwise unchanged @@ -210,11 +226,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 +308,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 +411,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 +628,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 +671,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 +690,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 .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-strip.t --- a/tests/test-strip.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-strip.t Sat Jul 19 00:10:22 2014 -0500 @@ -490,14 +490,15 @@ $ 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 [255] $ hg strip -B todelete - bookmark 'todelete' deleted 0 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob) + bookmark 'todelete' deleted $ hg id -ir dcbb326fdec2 abort: unknown revision 'dcbb326fdec2'! [255] @@ -507,10 +508,44 @@ B 9:ff43616e5d0f delete 6:2702dd0c91e7 $ hg strip -B delete + saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob) bookmark 'delete' deleted - saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob) $ hg id -ir 6:2702dd0c91e7 abort: unknown revision '2702dd0c91e7'! [255] + $ hg update B + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (activating bookmark B) + $ echo a > a + $ hg add a + $ hg strip -B B + abort: local changes found + [255] + $ hg bookmarks + * B 6:ff43616e5d0f - $ cd .. +Make sure no one adds back a -b option: + + $ hg strip -b tip + hg strip: option -b not recognized + hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV... + + strip changesets and all their descendants from the repository + + use "hg help -e strip" to show help for the strip extension + + options: + + -r --rev REV [+] strip specified revision (optional, can specify revisions + without this option) + -f --force force removal of changesets, discard uncommitted changes + (no backup) + --no-backup no backups + -k --keep do not modify working copy during strip + -B --bookmark VALUE remove revs only reachable from given bookmark + --mq operate on patch repository + + [+] marked option can be specified multiple times + + use "hg help strip" to show the full help text + [255] diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-subrepo-git.t --- a/tests/test-subrepo-git.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-subrepo-git.t Sat Jul 19 00:10:22 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)? diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-subrepo-svn.t --- a/tests/test-subrepo-svn.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-subrepo-svn.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-subrepo.t --- a/tests/test-subrepo.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-subrepo.t Sat Jul 19 00:10:22 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 @@ -1359,3 +1359,118 @@ $ hg phase -r . 6: secret $ cd ../../ + +Test "subrepos" template keyword + + $ cd t + $ hg update -q 15 + $ cat > .hgsub < s = s + > EOF + $ hg commit -m "16" + warning: changes are committed in secret phase from subrepository s + +(addition of ".hgsub" itself) + + $ hg diff --nodates -c 1 .hgsubstate + diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate + --- /dev/null + +++ b/.hgsubstate + @@ -0,0 +1,1 @@ + +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s + $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}" + f7b1eb17ad24 000000000000 + s + +(modification of existing entry) + + $ hg diff --nodates -c 2 .hgsubstate + diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate + --- a/.hgsubstate + +++ b/.hgsubstate + @@ -1,1 +1,1 @@ + -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s + +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s + $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}" + 7cf8cfea66e4 000000000000 + s + +(addition of entry) + + $ hg diff --nodates -c 5 .hgsubstate + diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate + --- a/.hgsubstate + +++ b/.hgsubstate + @@ -1,1 +1,2 @@ + e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s + +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t + $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}" + 7cf8cfea66e4 000000000000 + t + +(removal of existing entry) + + $ hg diff --nodates -c 16 .hgsubstate + diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate + --- a/.hgsubstate + +++ b/.hgsubstate + @@ -1,2 +1,1 @@ + 0731af8ca9423976d3743119d0865097c07bdc1b s + -e202dc79b04c88a636ea8913d9182a1346d9b3dc t + $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}" + 8bec38d2bd0b 000000000000 + t + +(merging) + + $ hg diff --nodates -c 9 .hgsubstate + diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate + --- a/.hgsubstate + +++ b/.hgsubstate + @@ -1,1 +1,2 @@ + fc627a69481fcbe5f1135069e8a3881c023e4cf5 s + +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t + $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}" + f6affe3fbfaa 1f14a2e2d3ec + t + +(removal of ".hgsub" itself) + + $ hg diff --nodates -c 8 .hgsubstate + diff -r f94576341bcf -r 96615c1dad2d .hgsubstate + --- a/.hgsubstate + +++ /dev/null + @@ -1,2 +0,0 @@ + -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s + -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t + $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}" + f94576341bcf 000000000000 + +Test that '[paths]' is configured correctly at subrepo creation + + $ cd $TESTTMP/tc + $ cat > .hgsub < # to clear bogus subrepo path 'bogus=[boguspath' + > s = s + > t = t + > EOF + $ hg update -q --clean null + $ rm -rf s t + $ cat >> .hg/hgrc < [paths] + > default-push = /foo/bar + > EOF + $ hg update -q + $ cat s/.hg/hgrc + [paths] + default = $TESTTMP/t/s + default-push = /foo/bar/s + $ cat s/ss/.hg/hgrc + [paths] + default = $TESTTMP/t/s/ss + default-push = /foo/bar/s/ss + $ cat t/.hg/hgrc + [paths] + default = $TESTTMP/t/t + default-push = /foo/bar/t + $ cd .. diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-tag.t --- a/tests/test-tag.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-tag.t Sat Jul 19 00:10:22 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 @@ -344,3 +403,204 @@ adding file changes added 2 changesets with 2 changes to 2 files +automatically merge resolvable tag conflicts (i.e. tags that differ in rank) +create two clones with some different tags as well as some common tags +check that we can merge tags that differ in rank + + $ hg init repo-automatic-tag-merge + $ cd repo-automatic-tag-merge + $ echo c0 > f0 + $ hg ci -A -m0 + adding f0 + $ hg tag tbase + $ cd .. + $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd repo-automatic-tag-merge-clone + $ echo c1 > f1 + $ hg ci -A -m1 + adding f1 + $ hg tag t1 t2 t3 + $ hg tag --remove t2 + $ hg tag t5 + $ echo c2 > f2 + $ hg ci -A -m2 + adding f2 + $ hg tag -f t3 + + $ cd ../repo-automatic-tag-merge + $ echo c3 > f3 + $ hg ci -A -m3 + adding f3 + $ hg tag -f t4 t5 t6 + $ hg tag --remove t5 + $ echo c4 > f4 + $ hg ci -A -m4 + adding f4 + $ hg tag t2 + $ hg tag -f t6 + + $ cd ../repo-automatic-tag-merge-clone + $ hg pull + pulling from $TESTTMP/repo-automatic-tag-merge (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 6 changes to 3 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg merge --tool internal:tagmerge + merging .hgtags + 2 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg status + M .hgtags + M f3 + M f4 + $ hg resolve -l + R .hgtags + $ cat .hgtags + 9aa4e1292a27a248f8d07339bed9931d54907be7 t4 + 9aa4e1292a27a248f8d07339bed9931d54907be7 t6 + 9aa4e1292a27a248f8d07339bed9931d54907be7 t6 + 09af2ce14077a94effef208b49a718f4836d4338 t6 + 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1 + 929bca7b18d067cbf3844c3896319a940059d748 t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 0000000000000000000000000000000000000000 t2 + 875517b4806a848f942811a315a5bce30804ae85 t5 + 9aa4e1292a27a248f8d07339bed9931d54907be7 t5 + 9aa4e1292a27a248f8d07339bed9931d54907be7 t5 + 0000000000000000000000000000000000000000 t5 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 79505d5360b07e3e79d1052e347e73c02b8afa5b t3 + +check that the merge tried to minimize the diff witht he first merge parent + + $ hg diff --git -r 'p1()' .hgtags + diff --git a/.hgtags b/.hgtags + --- a/.hgtags + +++ b/.hgtags + @@ -1,9 +1,17 @@ + +9aa4e1292a27a248f8d07339bed9931d54907be7 t4 + +9aa4e1292a27a248f8d07339bed9931d54907be7 t6 + +9aa4e1292a27a248f8d07339bed9931d54907be7 t6 + +09af2ce14077a94effef208b49a718f4836d4338 t6 + 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1 + +929bca7b18d067cbf3844c3896319a940059d748 t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 0000000000000000000000000000000000000000 t2 + 875517b4806a848f942811a315a5bce30804ae85 t5 + +9aa4e1292a27a248f8d07339bed9931d54907be7 t5 + +9aa4e1292a27a248f8d07339bed9931d54907be7 t5 + +0000000000000000000000000000000000000000 t5 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 79505d5360b07e3e79d1052e347e73c02b8afa5b t3 + +detect merge tag conflicts + + $ hg update -C -r tip + 3 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg tag t7 + $ hg update -C -r 'first(sort(head()))' + 3 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags + $ hg commit -m "manually add conflicting t7 tag" + $ hg merge --tool internal:tagmerge + merging .hgtags + automatic .hgtags merge failed + the following 1 tags are in conflict: t7 + automatic tag merging of .hgtags failed! (use 'hg resolve --tool internal:merge' or another merge tool of your choice) + 2 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] + $ hg resolve -l + U .hgtags + $ cat .hgtags + 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 0000000000000000000000000000000000000000 t2 + 875517b4806a848f942811a315a5bce30804ae85 t5 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 79505d5360b07e3e79d1052e347e73c02b8afa5b t3 + ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7 + + $ cd .. + +handle the loss of tags + + $ hg clone repo-automatic-tag-merge-clone repo-merge-lost-tags + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd repo-merge-lost-tags + $ echo c5 > f5 + $ hg ci -A -m5 + adding f5 + $ hg tag -f t7 + $ hg update -r 'p1(t7)' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ printf '' > .hgtags + $ hg commit -m 'delete all tags' + created new head + $ hg update -r 'max(t7::)' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r tip --tool internal:tagmerge + merging .hgtags + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg resolve -l + R .hgtags + $ cat .hgtags + 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase + 0000000000000000000000000000000000000000 tbase + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1 + 0000000000000000000000000000000000000000 t1 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 0000000000000000000000000000000000000000 t2 + 875517b4806a848f942811a315a5bce30804ae85 t5 + 0000000000000000000000000000000000000000 t5 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 79505d5360b07e3e79d1052e347e73c02b8afa5b t3 + 0000000000000000000000000000000000000000 t3 + ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7 + 0000000000000000000000000000000000000000 t7 + ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7 + fd3a9e394ce3afb354a496323bf68ac1755a30de t7 + +also check that we minimize the diff with the 1st merge parent + + $ hg diff --git -r 'p1()' .hgtags + diff --git a/.hgtags b/.hgtags + --- a/.hgtags + +++ b/.hgtags + @@ -1,12 +1,17 @@ + 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase + +0000000000000000000000000000000000000000 tbase + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1 + +0000000000000000000000000000000000000000 t1 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2 + 0000000000000000000000000000000000000000 t2 + 875517b4806a848f942811a315a5bce30804ae85 t5 + +0000000000000000000000000000000000000000 t5 + 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3 + 79505d5360b07e3e79d1052e347e73c02b8afa5b t3 + +0000000000000000000000000000000000000000 t3 + ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7 + +0000000000000000000000000000000000000000 t7 + ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7 + fd3a9e394ce3afb354a496323bf68ac1755a30de t7 + diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-transplant.t --- a/tests/test-transplant.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-transplant.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-unified-test.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-unified-test.t Sat Jul 19 00:10:22 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] diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-up-local-change.t --- a/tests/test-up-local-change.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-up-local-change.t Sat Jul 19 00:10:22 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 diff -r 584bbfd1b50d -r 6c36dc6cd61a tests/test-update-reverse.t --- a/tests/test-update-reverse.t Sat Jul 12 02:23:17 2014 -0700 +++ b/tests/test-update-reverse.t Sat Jul 19 00:10:22 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