--- a/.hgignore Mon May 26 19:02:11 2014 +0200
+++ b/.hgignore Mon Jun 09 13:53:23 2014 -0500
@@ -26,6 +26,7 @@
build
contrib/hgsh/hgsh
dist
+packages
doc/common.txt
doc/*.[0-9]
doc/*.[0-9].txt
--- a/Makefile Mon May 26 19:02:11 2014 +0200
+++ b/Makefile Mon Jun 09 13:53:23 2014 -0500
@@ -132,6 +132,37 @@
msgmerge --no-location --update $@.tmp $^
mv -f $@.tmp $@
+# Packaging targets
+
+osx:
+ @which -s bdist_mpkg || \
+ (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false)
+ bdist_mpkg setup.py
+ mkdir -p packages/osx
+ rm -rf dist/mercurial-*.mpkg
+ mv dist/mercurial*macosx*.zip packages/osx
+
+fedora:
+ mkdir -p packages/fedora
+ contrib/buildrpm
+ cp rpmbuild/RPMS/*/* packages/fedora
+ cp rpmbuild/SRPMS/* packages/fedora
+ rm -rf rpmbuild
+
+docker-fedora:
+ mkdir -p packages/fedora
+ contrib/dockerrpm fedora
+
+centos6:
+ mkdir -p packages/centos6
+ contrib/buildrpm
+ cp rpmbuild/RPMS/*/* packages/centos6
+ cp rpmbuild/SRPMS/* packages/centos6
+
+docker-centos6:
+ mkdir -p packages/centos6
+ contrib/dockerrpm centos6
+
.PHONY: help all local build doc clean install install-bin install-doc \
install-home install-home-bin install-home-doc dist dist-notests tests \
- update-pot
+ update-pot fedora docker-fedora
--- a/contrib/bash_completion Mon May 26 19:02:11 2014 +0200
+++ b/contrib/bash_completion Mon Jun 09 13:53:23 2014 -0500
@@ -629,7 +629,7 @@
_hg_cmd_shelve()
{
- if [[ "$prev" = @(-d|--delete) ]]; then
+ if [[ "$prev" = @(-d|--delete|-l|--list) ]]; then
_hg_shelves
else
_hg_status "mard"
--- a/contrib/buildrpm Mon May 26 19:02:11 2014 +0200
+++ b/contrib/buildrpm Mon Jun 09 13:53:23 2014 -0500
@@ -1,16 +1,13 @@
-#!/bin/sh
+#!/bin/sh -e
#
-# Build a Mercurial RPM in place.
+# Build a Mercurial RPM from the current repo
#
# Tested on
-# - Fedora 8 (with docutils 0.5)
-# - Fedora 11
-# - OpenSuse 11.2
+# - Fedora 20
+# - CentOS 5
+# - centOS 6
cd "`dirname $0`/.."
-HG="$PWD/hg"
-PYTHONPATH="$PWD/mercurial/pure"
-export PYTHONPATH
specfile=contrib/mercurial.spec
if [ ! -f $specfile ]; then
@@ -23,21 +20,17 @@
exit 1
fi
-if $HG id -i | grep '+$' > /dev/null 2>&1; then
- echo -n "Your local changes will NOT be in the RPM. Continue [y/n] ? "
- read answer
- if echo $answer | grep -iv '^y'; then
- exit
- fi
-fi
+# build local hg and use it
+python setup.py build_py -c -d .
+HG="$PWD/hg"
+PYTHONPATH="$PWD/mercurial/pure"
+export PYTHONPATH
rpmdir="$PWD/rpmbuild"
rm -rf $rpmdir
mkdir -p $rpmdir/SOURCES $rpmdir/SPECS $rpmdir/RPMS $rpmdir/SRPMS $rpmdir/BUILD
-# make setup.py build the version string
-python setup.py build_py -c -d .
hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
if echo $hgversion | grep -- '-' > /dev/null 2>&1; then
@@ -50,8 +43,8 @@
release='0'
fi
-$HG archive -t tgz $rpmdir/SOURCES/mercurial-$version.tar.gz
-rpmspec=$rpmdir/SPECS/mercurial-$version.spec
+$HG archive -t tgz $rpmdir/SOURCES/mercurial-$version-$release.tar.gz
+rpmspec=$rpmdir/SPECS/mercurial.spec
sed -e "s,^Version:.*,Version: $version," \
-e "s,^Release:.*,Release: $release," \
--- a/contrib/check-code.py Mon May 26 19:02:11 2014 +0200
+++ b/contrib/check-code.py Mon Jun 09 13:53:23 2014 -0500
@@ -367,16 +367,28 @@
[]
]
+webtemplatefilters = []
+
+webtemplatepats = [
+ [],
+ [
+ (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
+ 'follow desc keyword with either firstline or websub'),
+ ]
+]
+
checks = [
- ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
- ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
- ('c', r'.*\.[ch]$', cfilters, cpats),
- ('unified test', r'.*\.t$', utestfilters, utestpats),
- ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
- inrevlogpats),
- ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
+ ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
+ ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
+ ('c', r'.*\.[ch]$', '', cfilters, cpats),
+ ('unified test', r'.*\.t$', '', utestfilters, utestpats),
+ ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
+ pyfilters, inrevlogpats),
+ ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
inutilpats),
- ('txt', r'.*\.txt$', txtfilters, txtpats),
+ ('txt', r'.*\.txt$', '', txtfilters, txtpats),
+ ('web template', r'mercurial/templates/.*\.tmpl', '',
+ webtemplatefilters, webtemplatepats),
]
def _preparepats():
@@ -392,7 +404,7 @@
p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
- filters = c[2]
+ filters = c[3]
for i, flt in enumerate(filters):
filters[i] = re.compile(flt[0]), flt[1]
_preparepats()
@@ -446,22 +458,24 @@
"""
blamecache = None
result = True
- for name, match, filters, pats in checks:
+
+ try:
+ fp = open(f)
+ except IOError, e:
+ print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
+ return result
+ pre = post = fp.read()
+ fp.close()
+
+ for name, match, magic, filters, pats in checks:
if debug:
print name, f
fc = 0
- if not re.match(match, f):
+ if not (re.match(match, f) or (magic and re.search(magic, f))):
if debug:
print "Skipping %s for %s it doesn't match %s" % (
name, match, f)
continue
- try:
- fp = open(f)
- except IOError, e:
- print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
- continue
- pre = post = fp.read()
- fp.close()
if "no-" "check-code" in pre:
print "Skipping %s it has no-" "check-code" % f
return "Skip" # skip checking this file
--- a/contrib/debugshell.py Mon May 26 19:02:11 2014 +0200
+++ b/contrib/debugshell.py Mon Jun 09 13:53:23 2014 -0500
@@ -4,6 +4,10 @@
import sys
import mercurial
import code
+from mercurial import cmdutil
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
def pdb(ui, repo, msg, **opts):
objects = {
@@ -24,6 +28,7 @@
IPython.embed()
+@command('debugshell|dbsh', [])
def debugshell(ui, repo, **opts):
bannermsg = "loaded repo : %s\n" \
"using source: %s" % (repo.root,
@@ -47,7 +52,3 @@
debugger = 'pdb'
getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts)
-
-cmdtable = {
- "debugshell|dbsh": (debugshell, [])
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/docker/centos6 Mon Jun 09 13:53:23 2014 -0500
@@ -0,0 +1,7 @@
+FROM centos
+RUN yum install -y gcc
+RUN yum install -y python-devel python-docutils
+RUN yum install -y make
+RUN yum install -y rpm-build
+RUN yum install -y gettext
+RUN yum install -y tar
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/docker/fedora Mon Jun 09 13:53:23 2014 -0500
@@ -0,0 +1,6 @@
+FROM fedora
+RUN yum install -y gcc
+RUN yum install -y python-devel python-docutils
+RUN yum install -y make
+RUN yum install -y rpm-build
+RUN yum install -y gettext
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/dockerrpm Mon Jun 09 13:53:23 2014 -0500
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+BUILDDIR=$(dirname $0)
+ROOTDIR=$(cd $BUILDDIR/..; pwd)
+
+if which docker.io >> /dev/null ; then
+ DOCKER=docker.io
+elif which docker >> /dev/null ; then
+ DOCKER=docker
+fi
+
+$DOCKER build --tag "hg-dockerrpm-$1" - < $BUILDDIR/docker/$1
+$DOCKER run --rm -v $ROOTDIR:/hg "hg-dockerrpm-$1" bash -c \
+ "cp -a hg hg-build; cd hg-build; make clean local $1; cp packages/$1/* /hg/packages/$1/"
--- a/contrib/hgfixes/fix_bytes.py Mon May 26 19:02:11 2014 +0200
+++ b/contrib/hgfixes/fix_bytes.py Mon Jun 09 13:53:23 2014 -0500
@@ -12,10 +12,10 @@
# XXX: Implementing a blacklist in 2to3 turned out to be more troublesome than
# blacklisting some modules inside the fixers. So, this is what I came with.
-blacklist = ['mercurial/demandimport.py',
+blacklist = ('mercurial/demandimport.py',
'mercurial/py3kcompat.py', # valid python 3 already
'mercurial/i18n.py',
- ]
+ )
def isdocstring(node):
def isclassorfunction(ancestor):
@@ -83,7 +83,8 @@
PATTERN = 'STRING'
def transform(self, node, results):
- if self.filename in blacklist:
+ # The filename may be prefixed with a build directory.
+ if self.filename.endswith(blacklist):
return
if node.type == token.STRING:
if _re.match(node.value):
--- a/contrib/mercurial.spec Mon May 26 19:02:11 2014 +0200
+++ b/contrib/mercurial.spec Mon Jun 09 13:53:23 2014 -0500
@@ -1,3 +1,6 @@
+%global emacs_lispdir %{_datadir}/emacs/site-lisp
+%global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
+
Summary: A fast, lightweight Source Control Management system
Name: mercurial
Version: snapshot
@@ -5,33 +8,21 @@
License: GPLv2+
Group: Development/Tools
URL: http://mercurial.selenic.com/
-Source0: http://mercurial.selenic.com/release/%{name}-%{version}.tar.gz
+Source0: %{name}-%{version}-%{release}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
-# From the README:
-#
-# Note: some distributions fails to include bits of distutils by
-# default, you'll need python-dev to install. You'll also need a C
-# compiler and a 3-way merge tool like merge, tkdiff, or kdiff3.
-#
-# python-devel provides an adequate python-dev. The merge tool is a
-# run-time dependency.
-#
BuildRequires: python >= 2.4, python-devel, make, gcc, python-docutils >= 0.5, gettext
Provides: hg = %{version}-%{release}
Requires: python >= 2.4
# The hgk extension uses the wish tcl interpreter, but we don't enforce it
#Requires: tk
-%define pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
-%define emacs_lispdir %{_datadir}/emacs/site-lisp
-
%description
Mercurial is a fast, lightweight source control management system designed
for efficient handling of very large distributed projects.
%prep
-%setup -q
+%setup -q -n mercurial-%{version}-%{release}
%build
make all
@@ -40,8 +31,8 @@
rm -rf $RPM_BUILD_ROOT
make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir}
-install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}
-install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}
+install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
+install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
mkdir -p $bash_completion_dir
@@ -52,8 +43,8 @@
install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial
mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir}
-install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}
-install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}
+install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/
+install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/
mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/mercurial/hgrc.d
install -m 644 contrib/mergetools.hgrc $RPM_BUILD_ROOT%{_sysconfdir}/mercurial/hgrc.d/mergetools.rc
--- a/contrib/mergetools.hgrc Mon May 26 19:02:11 2014 +0200
+++ b/contrib/mergetools.hgrc Mon Jun 09 13:53:23 2014 -0500
@@ -71,6 +71,12 @@
ecmerge.gui=True
ecmerge.diffargs=$parent $child --mode=diff2 --title1='$plabel1' --title2='$clabel'
+# editmerge is a small script shipped in contrib.
+# It needs this config otherwise it behaves the same as internal:local
+editmerge.args=$output
+editmerge.check=changed
+editmerge.premerge=keep
+
filemerge.executable=/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge
filemerge.args=-left $other -right $local -ancestor $base -merge $output
filemerge.gui=True
--- a/contrib/revsetbenchmarks.py Mon May 26 19:02:11 2014 +0200
+++ b/contrib/revsetbenchmarks.py Mon Jun 09 13:53:23 2014 -0500
@@ -14,7 +14,12 @@
# to compare performance.
import sys
+import os
from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE
+# cannot use argparse, python 2.7 only
+from optparse import OptionParser
+
+
def check_output(*args, **kwargs):
kwargs.setdefault('stderr', PIPE)
@@ -33,15 +38,19 @@
print >> sys.stderr, 'update to revision %s failed, aborting' % rev
sys.exit(exc.returncode)
-def perf(revset):
+def perf(revset, target=None):
"""run benchmark for this very revset"""
try:
- output = check_output(['./hg',
- '--config',
- 'extensions.perf=contrib/perf.py',
- 'perfrevset',
- revset],
- stderr=STDOUT)
+ cmd = ['./hg',
+ '--config',
+ 'extensions.perf='
+ + os.path.join(contribdir, 'perf.py'),
+ 'perfrevset',
+ revset]
+ if target is not None:
+ cmd.append('-R')
+ cmd.append(target)
+ output = check_output(cmd, stderr=STDOUT)
output = output.lstrip('!') # remove useless ! in this context
return output.strip()
except CalledProcessError, exc:
@@ -65,12 +74,26 @@
return [r for r in out.split() if r]
+parser = OptionParser(usage="usage: %prog [options] <revs>")
+parser.add_option("-f", "--file",
+ help="read revset from FILE", metavar="FILE")
+parser.add_option("-R", "--repo",
+ help="run benchmark on REPO", metavar="REPO")
-target_rev = sys.argv[1]
+(options, args) = parser.parse_args()
+
+if len(sys.argv) < 2:
+ parser.print_help()
+ sys.exit(255)
+
+# the directory where both this script and the perf.py extension live.
+contribdir = os.path.dirname(__file__)
+
+target_rev = args[0]
revsetsfile = sys.stdin
-if len(sys.argv) > 2:
- revsetsfile = open(sys.argv[2])
+if options.file:
+ revsetsfile = open(options.file)
revsets = [l.strip() for l in revsetsfile]
@@ -95,7 +118,7 @@
res = []
results.append(res)
for idx, rset in enumerate(revsets):
- data = perf(rset)
+ data = perf(rset, target=options.repo)
res.append(data)
print "%i)" % idx, data
sys.stdout.flush()
--- a/contrib/revsetbenchmarks.txt Mon May 26 19:02:11 2014 +0200
+++ b/contrib/revsetbenchmarks.txt Mon Jun 09 13:53:23 2014 -0500
@@ -2,11 +2,13 @@
draft()
::tip
draft() and ::tip
+::tip and draft()
0::tip
roots(0::tip)
author(lmoscovicz)
author(mpm)
author(lmoscovicz) or author(mpm)
+author(mpm) or author(lmoscovicz)
tip:0
max(tip:0)
min(0:tip)
@@ -18,3 +20,4 @@
:10000 and public()
draft()
:10000 and draft()
+max(::(tip~20) - obsolete())
--- a/contrib/synthrepo.py Mon May 26 19:02:11 2014 +0200
+++ b/contrib/synthrepo.py Mon Jun 09 13:53:23 2014 -0500
@@ -334,7 +334,7 @@
for __ in xrange(add):
lines.insert(random.randint(0, len(lines)), makeline())
path = fctx.path()
- changes[path] = context.memfilectx(path,
+ changes[path] = context.memfilectx(repo, path,
'\n'.join(lines) + '\n')
for __ in xrange(pick(filesremoved)):
path = random.choice(mfk)
@@ -354,7 +354,7 @@
path = '/'.join(filter(None, path))
data = '\n'.join(makeline()
for __ in xrange(pick(linesinfilesadded))) + '\n'
- changes[path] = context.memfilectx(path, data)
+ changes[path] = context.memfilectx(repo, path, data)
def filectxfn(repo, memctx, path):
data = changes[path]
if data is None:
--- a/contrib/vim/hgcommand.vim Mon May 26 19:02:11 2014 +0200
+++ b/contrib/vim/hgcommand.vim Mon Jun 09 13:53:23 2014 -0500
@@ -105,7 +105,7 @@
let fileName=<SID>HGResolveLink(a:fileName)
let newCwd=fnamemodify(fileName, ':h')
if strlen(newCwd) > 0
- execute 'cd' escape(newCwd, ' ')
+ try | execute 'cd' escape(newCwd, ' ') | catch | | endtry
endif
return oldCwd
endfunction
@@ -396,7 +396,7 @@
return returnExpression
finally
- execute 'cd' escape(oldCwd, ' ')
+ try | execute 'cd' escape(oldCwd, ' ') | catch | | endtry
endtry
endfunction
--- a/hgext/children.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/children.py Mon Jun 09 13:53:23 2014 -0500
@@ -18,8 +18,15 @@
from mercurial.commands import templateopts
from mercurial.i18n import _
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
+@command('children',
+ [('r', 'rev', '',
+ _('show children of the specified revision'), _('REV')),
+ ] + templateopts,
+ _('hg children [-r REV] [FILE]'))
def children(ui, repo, file_=None, **opts):
"""show the children of the given or working directory revision
@@ -40,13 +47,4 @@
displayer.show(cctx)
displayer.close()
-cmdtable = {
- "children":
- (children,
- [('r', 'rev', '',
- _('show children of the specified revision'), _('REV')),
- ] + templateopts,
- _('hg children [-r REV] [FILE]')),
-}
-
commands.inferrepo += " children"
--- a/hgext/churn.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/churn.py Mon Jun 09 13:53:23 2014 -0500
@@ -14,6 +14,8 @@
import os
import time, datetime
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
def maketemplater(ui, repo, tmpl):
@@ -88,6 +90,21 @@
return rate
+@command('churn',
+ [('r', 'rev', [],
+ _('count rate for the specified revision or range'), _('REV')),
+ ('d', 'date', '',
+ _('count rate for revisions matching date spec'), _('DATE')),
+ ('t', 'template', '{author|email}',
+ _('template to group changesets'), _('TEMPLATE')),
+ ('f', 'dateformat', '',
+ _('strftime-compatible format for grouping by date'), _('FORMAT')),
+ ('c', 'changesets', False, _('count rate by number of changesets')),
+ ('s', 'sort', False, _('sort by key (default: sort by count)')),
+ ('', 'diffstat', False, _('display added/removed lines separately')),
+ ('', 'aliases', '', _('file with email aliases'), _('FILE')),
+ ] + commands.walkopts,
+ _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"))
def churn(ui, repo, *pats, **opts):
'''histogram of changes to the repository
@@ -181,25 +198,4 @@
for name, count in rate:
ui.write(format(name, count))
-
-cmdtable = {
- "churn":
- (churn,
- [('r', 'rev', [],
- _('count rate for the specified revision or range'), _('REV')),
- ('d', 'date', '',
- _('count rate for revisions matching date spec'), _('DATE')),
- ('t', 'template', '{author|email}',
- _('template to group changesets'), _('TEMPLATE')),
- ('f', 'dateformat', '',
- _('strftime-compatible format for grouping by date'), _('FORMAT')),
- ('c', 'changesets', False, _('count rate by number of changesets')),
- ('s', 'sort', False, _('sort by key (default: sort by count)')),
- ('', 'diffstat', False, _('display added/removed lines separately')),
- ('', 'aliases', '',
- _('file with email aliases'), _('FILE')),
- ] + commands.walkopts,
- _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
-}
-
commands.inferrepo += " churn"
--- a/hgext/color.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/color.py Mon Jun 09 13:53:23 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
@@ -440,6 +442,7 @@
_("when to colorize (boolean, always, auto, or never)"),
_('TYPE')))
+@command('debugcolor', [], 'hg debugcolor')
def debugcolor(ui, repo, **opts):
global _styles
_styles = {}
@@ -579,8 +582,3 @@
finally:
# Explicitly reset original attributes
_kernel32.SetConsoleTextAttribute(stdout, origattr)
-
-cmdtable = {
- 'debugcolor':
- (debugcolor, [], ('hg debugcolor'))
-}
--- a/hgext/convert/__init__.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/convert/__init__.py Mon Jun 09 13:53:23 2014 -0500
@@ -10,13 +10,34 @@
import convcmd
import cvsps
import subversion
-from mercurial import commands, templatekw
+from mercurial import cmdutil, commands, templatekw
from mercurial.i18n import _
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
# Commands definition was moved elsewhere to ease demandload job.
+@command('convert',
+ [('', 'authors', '',
+ _('username mapping filename (DEPRECATED, use --authormap instead)'),
+ _('FILE')),
+ ('s', 'source-type', '', _('source repository type'), _('TYPE')),
+ ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
+ ('r', 'rev', '', _('import up to source revision REV'), _('REV')),
+ ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
+ ('', 'filemap', '', _('remap file names using contents of file'),
+ _('FILE')),
+ ('', 'splicemap', '', _('splice synthesized history into place'),
+ _('FILE')),
+ ('', 'branchmap', '', _('change branch names while converting'),
+ _('FILE')),
+ ('', 'branchsort', None, _('try to sort changesets by branches')),
+ ('', 'datesort', None, _('try to sort changesets by date')),
+ ('', 'sourcesort', None, _('preserve source changesets order')),
+ ('', 'closesort', None, _('try to reorder closed revisions'))],
+ _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'))
def convert(ui, src, dest=None, revmapfile=None, **opts):
"""convert a foreign SCM repository to a Mercurial one.
@@ -282,9 +303,28 @@
"""
return convcmd.convert(ui, src, dest, revmapfile, **opts)
+@command('debugsvnlog', [], 'hg debugsvnlog')
def debugsvnlog(ui, **opts):
return subversion.debugsvnlog(ui, **opts)
+@command('debugcvsps',
+ [
+ # Main options shared with cvsps-2.1
+ ('b', 'branches', [], _('only return changes on specified branches')),
+ ('p', 'prefix', '', _('prefix to remove from file names')),
+ ('r', 'revisions', [],
+ _('only return changes after or between specified tags')),
+ ('u', 'update-cache', None, _("update cvs log cache")),
+ ('x', 'new-cache', None, _("create new cvs log cache")),
+ ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
+ ('', 'root', '', _('specify cvsroot')),
+ # Options specific to builtin cvsps
+ ('', 'parents', '', _('show parent changesets')),
+ ('', 'ancestors', '', _('show current changeset in ancestor branches')),
+ # Options that are ignored for compatibility with cvsps-2.1
+ ('A', 'cvs-direct', None, _('ignored for compatibility')),
+ ],
+ _('hg debugcvsps [OPTION]... [PATH]...'))
def debugcvsps(ui, *args, **opts):
'''create changeset information from CVS
@@ -300,57 +340,6 @@
commands.norepo += " convert debugsvnlog debugcvsps"
-cmdtable = {
- "convert":
- (convert,
- [('', 'authors', '',
- _('username mapping filename (DEPRECATED, use --authormap instead)'),
- _('FILE')),
- ('s', 'source-type', '',
- _('source repository type'), _('TYPE')),
- ('d', 'dest-type', '',
- _('destination repository type'), _('TYPE')),
- ('r', 'rev', '',
- _('import up to source revision REV'), _('REV')),
- ('A', 'authormap', '',
- _('remap usernames using this file'), _('FILE')),
- ('', 'filemap', '',
- _('remap file names using contents of file'), _('FILE')),
- ('', 'splicemap', '',
- _('splice synthesized history into place'), _('FILE')),
- ('', 'branchmap', '',
- _('change branch names while converting'), _('FILE')),
- ('', 'branchsort', None, _('try to sort changesets by branches')),
- ('', 'datesort', None, _('try to sort changesets by date')),
- ('', 'sourcesort', None, _('preserve source changesets order')),
- ('', 'closesort', None, _('try to reorder closed revisions'))],
- _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
- "debugsvnlog":
- (debugsvnlog,
- [],
- 'hg debugsvnlog'),
- "debugcvsps":
- (debugcvsps,
- [
- # Main options shared with cvsps-2.1
- ('b', 'branches', [], _('only return changes on specified branches')),
- ('p', 'prefix', '', _('prefix to remove from file names')),
- ('r', 'revisions', [],
- _('only return changes after or between specified tags')),
- ('u', 'update-cache', None, _("update cvs log cache")),
- ('x', 'new-cache', None, _("create new cvs log cache")),
- ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
- ('', 'root', '', _('specify cvsroot')),
- # Options specific to builtin cvsps
- ('', 'parents', '', _('show parent changesets')),
- ('', 'ancestors', '',
- _('show current changeset in ancestor branches')),
- # Options that are ignored for compatibility with cvsps-2.1
- ('A', 'cvs-direct', None, _('ignored for compatibility')),
- ],
- _('hg debugcvsps [OPTION]... [PATH]...')),
-}
-
def kwconverted(ctx, name):
rev = ctx.extra().get('convert_revision', '')
if rev.startswith('svn:'):
--- a/hgext/convert/common.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/convert/common.py Mon Jun 09 13:53:23 2014 -0500
@@ -260,8 +260,15 @@
"""
pass
- def hascommit(self, rev):
- """Return True if the sink contains rev"""
+ def hascommitfrommap(self, rev):
+ """Return False if a rev mentioned in a filemap is known to not be
+ present."""
+ raise NotImplementedError
+
+ def hascommitforsplicemap(self, rev):
+ """This method is for the special needs for splicemap handling and not
+ for general use. Returns True if the sink contains rev, aborts on some
+ special cases."""
raise NotImplementedError
class commandline(object):
--- a/hgext/convert/convcmd.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/convert/convcmd.py Mon Jun 09 13:53:23 2014 -0500
@@ -173,8 +173,12 @@
parents = {}
while visit:
n = visit.pop(0)
- if n in known or n in self.map:
+ if n in known:
continue
+ if n in self.map:
+ m = self.map[n]
+ if m == SKIPREV or self.dest.hascommitfrommap(m):
+ continue
known.add(n)
self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
commit = self.cachecommit(n)
@@ -193,7 +197,7 @@
"""
for c in sorted(splicemap):
if c not in parents:
- if not self.dest.hascommit(self.map.get(c, c)):
+ if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
# Could be in source but not converted during this run
self.ui.warn(_('splice map revision %s is not being '
'converted, ignoring\n') % c)
@@ -201,7 +205,7 @@
pc = []
for p in splicemap[c]:
# We do not have to wait for nodes already in dest.
- if self.dest.hascommit(self.map.get(p, p)):
+ if self.dest.hascommitforsplicemap(self.map.get(p, p)):
continue
# Parent is not in dest and not being converted, not good
if p not in parents:
--- a/hgext/convert/git.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/convert/git.py Mon Jun 09 13:53:23 2014 -0500
@@ -46,6 +46,18 @@
del os.environ['GIT_DIR']
else:
os.environ['GIT_DIR'] = prevgitdir
+
+ def gitpipe(self, s):
+ prevgitdir = os.environ.get('GIT_DIR')
+ os.environ['GIT_DIR'] = self.path
+ try:
+ return util.popen3(s)
+ finally:
+ if prevgitdir is None:
+ del os.environ['GIT_DIR']
+ else:
+ os.environ['GIT_DIR'] = prevgitdir
+
else:
def gitopen(self, s, err=None):
if err == subprocess.PIPE:
@@ -56,6 +68,9 @@
else:
return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
+ def gitpipe(self, s):
+ return util.popen3('GIT_DIR=%s %s' % (self.path, s))
+
def popen_with_stderr(self, s):
p = subprocess.Popen(s, shell=True, bufsize=-1,
close_fds=util.closefds,
@@ -84,6 +99,12 @@
self.path = path
self.submodules = []
+ self.catfilepipe = self.gitpipe('git cat-file --batch')
+
+ def after(self):
+ for f in self.catfilepipe:
+ f.close()
+
def getheads(self):
if not self.rev:
heads, ret = self.gitread('git rev-parse --branches --remotes')
@@ -98,9 +119,17 @@
def catfile(self, rev, type):
if rev == hex(nullid):
raise IOError
- data, ret = self.gitread("git cat-file %s %s" % (type, rev))
- if ret:
+ self.catfilepipe[0].write(rev+'\n')
+ self.catfilepipe[0].flush()
+ info = self.catfilepipe[1].readline().split()
+ if info[1] != type:
raise util.Abort(_('cannot read %r object at %s') % (type, rev))
+ size = int(info[2])
+ data = self.catfilepipe[1].read(size)
+ if len(data) < size:
+ raise util.Abort(_('cannot read %r object at %s: %s') % (type, rev))
+ # read the trailing newline
+ self.catfilepipe[1].read(1)
return data
def getfile(self, name, rev):
--- a/hgext/convert/hg.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/convert/hg.py Mon Jun 09 13:53:23 2014 -0500
@@ -136,8 +136,8 @@
data, mode = source.getfile(f, v)
if f == '.hgtags':
data = self._rewritetags(source, revmap, data)
- return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
- copies.get(f))
+ return context.memfilectx(self.repo, f, data, 'l' in mode,
+ 'x' in mode, copies.get(f))
pl = []
for p in parents:
@@ -229,7 +229,7 @@
data = "".join(newlines)
def getfilectx(repo, memctx, f):
- return context.memfilectx(f, data, False, False, None)
+ return context.memfilectx(repo, f, data, False, False, None)
self.ui.status(_("updating tags\n"))
date = "%s 0" % int(time.mktime(time.gmtime()))
@@ -253,7 +253,11 @@
destmarks[bookmark] = bin(updatedbookmark[bookmark])
destmarks.write()
- def hascommit(self, rev):
+ def hascommitfrommap(self, rev):
+ # the exact semantics of clonebranches is unclear so we can't say no
+ return rev in self.repo or self.clonebranches
+
+ def hascommitforsplicemap(self, rev):
if rev not in self.repo and self.clonebranches:
raise util.Abort(_('revision %s not found in destination '
'repository (lookups with clonebranches=true '
@@ -394,7 +398,9 @@
sortkey=ctx.rev())
def gettags(self):
- tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
+ # This will get written to .hgtags, filter non global tags out.
+ tags = [t for t in self.repo.tagslist()
+ if self.repo.tagtype(t[0]) == 'global']
return dict([(name, hex(node)) for name, node in tags
if self.keep(node)])
--- a/hgext/convert/subversion.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/convert/subversion.py Mon Jun 09 13:53:23 2014 -0500
@@ -1300,7 +1300,12 @@
self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
return None, None
- def hascommit(self, rev):
+ def hascommitfrommap(self, rev):
+ # We trust that revisions referenced in a map still is present
+ # TODO: implement something better if necessary and feasible
+ return True
+
+ def hascommitforsplicemap(self, rev):
# This is not correct as one can convert to an existing subversion
# repository and childmap would not list all revisions. Too bad.
if rev in self.childmap:
--- a/hgext/extdiff.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/extdiff.py Mon Jun 09 13:53:23 2014 -0500
@@ -63,9 +63,11 @@
from mercurial.i18n import _
from mercurial.node import short, nullid
-from mercurial import scmutil, scmutil, util, commands, encoding
+from mercurial import cmdutil, scmutil, scmutil, util, commands, encoding
import os, shlex, shutil, tempfile, re
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
def snapshot(ui, repo, files, node, tmproot):
@@ -238,6 +240,15 @@
ui.note(_('cleaning up temp directory\n'))
shutil.rmtree(tmproot)
+@command('extdiff',
+ [('p', 'program', '',
+ _('comparison program to run'), _('CMD')),
+ ('o', 'option', [],
+ _('pass option to comparison program'), _('OPT')),
+ ('r', 'rev', [], _('revision'), _('REV')),
+ ('c', 'change', '', _('change made by revision'), _('REV')),
+ ] + commands.walkopts,
+ _('hg extdiff [OPT]... [FILE]...'))
def extdiff(ui, repo, *pats, **opts):
'''use external program to diff repository (or selected files)
@@ -262,21 +273,6 @@
option = option or ['-Npru']
return dodiff(ui, repo, program, option, pats, opts)
-cmdtable = {
- "extdiff":
- (extdiff,
- [('p', 'program', '',
- _('comparison program to run'), _('CMD')),
- ('o', 'option', [],
- _('pass option to comparison program'), _('OPT')),
- ('r', 'rev', [],
- _('revision'), _('REV')),
- ('c', 'change', '',
- _('change made by revision'), _('REV')),
- ] + commands.walkopts,
- _('hg extdiff [OPT]... [FILE]...')),
- }
-
def uisetup(ui):
for cmd, path in ui.configitems('extdiff'):
if cmd.startswith('cmd.'):
--- a/hgext/factotum.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/factotum.py Mon Jun 09 13:53:23 2014 -0500
@@ -52,6 +52,8 @@
ERRMAX = 128
+_executable = _mountpoint = _service = None
+
def auth_getkey(self, params):
if not self.ui.interactive():
raise util.Abort(_('factotum not interactive'))
--- a/hgext/fetch.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/fetch.py Mon Jun 09 13:53:23 2014 -0500
@@ -12,8 +12,18 @@
from mercurial import commands, cmdutil, hg, util, error
from mercurial.lock import release
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
+@command('fetch',
+ [('r', 'rev', [],
+ _('a specific revision you would like to pull'), _('REV')),
+ ('e', 'edit', None, _('edit commit message')),
+ ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
+ ('', 'switch-parent', None, _('switch parents when merging')),
+ ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
+ _('hg fetch [SOURCE]'))
def fetch(ui, repo, source='default', **opts):
'''pull changes from a remote repository, merge new changes if needed.
@@ -132,10 +142,9 @@
message = (cmdutil.logmessage(ui, opts) or
('Automated merge with %s' %
util.removeauth(other.url())))
- editor = cmdutil.commiteditor
- if opts.get('force_editor') or opts.get('edit'):
- editor = cmdutil.commitforceeditor
- n = repo.commit(message, opts['user'], opts['date'], editor=editor)
+ editopt = opts.get('edit') or opts.get('force_editor')
+ n = repo.commit(message, opts['user'], opts['date'],
+ editor=cmdutil.getcommiteditor(edit=editopt))
ui.status(_('new changeset %d:%s merges remote changes '
'with local\n') % (repo.changelog.rev(n),
short(n)))
@@ -144,15 +153,3 @@
finally:
release(lock, wlock)
-
-cmdtable = {
- 'fetch':
- (fetch,
- [('r', 'rev', [],
- _('a specific revision you would like to pull'), _('REV')),
- ('e', 'edit', None, _('edit commit message')),
- ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
- ('', 'switch-parent', None, _('switch parents when merging')),
- ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
- _('hg fetch [SOURCE]')),
-}
--- a/hgext/gpg.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/gpg.py Mon Jun 09 13:53:23 2014 -0500
@@ -204,6 +204,7 @@
_('the key id to sign with'), _('ID')),
('m', 'message', '',
_('commit message'), _('TEXT')),
+ ('e', 'edit', False, _('invoke editor on commit messages')),
] + commands.commitopts2,
_('hg sign [OPTION]... [REV]...'))
def sign(ui, repo, *revs, **opts):
@@ -276,7 +277,8 @@
% hgnode.short(n)
for n in nodes])
try:
- repo.commit(message, opts['user'], opts['date'], match=msigs)
+ repo.commit(message, opts['user'], opts['date'], match=msigs,
+ editor=cmdutil.getcommiteditor(**opts))
except ValueError, inst:
raise util.Abort(str(inst))
--- a/hgext/hgk.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/hgk.py Mon Jun 09 13:53:23 2014 -0500
@@ -35,12 +35,22 @@
'''
import os
-from mercurial import commands, util, patch, revlog, scmutil
+from mercurial import cmdutil, commands, util, patch, revlog, scmutil
from mercurial.node import nullid, nullrev, short
from mercurial.i18n import _
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
+@command('debug-diff-tree',
+ [('p', 'patch', None, _('generate patch')),
+ ('r', 'recursive', None, _('recursive')),
+ ('P', 'pretty', None, _('pretty')),
+ ('s', 'stdin', None, _('stdin')),
+ ('C', 'copy', None, _('detect copies')),
+ ('S', 'search', "", _('search'))],
+ ('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'))
def difftree(ui, repo, node1=None, node2=None, *files, **opts):
"""diff trees from two commits"""
def __difftree(repo, node1, node2, files=[]):
@@ -125,6 +135,7 @@
if prefix:
ui.write('\0')
+@command('debug-merge-base', [], _('hg debug-merge-base REV REV'))
def base(ui, repo, node1, node2):
"""output common ancestor information"""
node1 = repo.lookup(node1)
@@ -132,6 +143,9 @@
n = repo.changelog.ancestor(node1, node2)
ui.write(short(n) + "\n")
+@command('debug-cat-file',
+ [('s', 'stdin', None, _('stdin'))],
+ _('hg debug-cat-file [OPTION]... TYPE FILE'))
def catfile(ui, repo, type=None, r=None, **opts):
"""cat a specific revision"""
# in stdin mode, every line except the commit is prefixed with two
@@ -276,6 +290,9 @@
break
count += 1
+@command('debug-rev-parse',
+ [('', 'default', '', _('ignored'))],
+ _('hg debug-rev-parse REV'))
def revparse(ui, repo, *revs, **opts):
"""parse given revisions"""
def revstr(rev):
@@ -292,6 +309,12 @@
# git rev-list tries to order things by date, and has the ability to stop
# at a given commit without walking the whole repo. TODO add the stop
# parameter
+@command('debug-rev-list',
+ [('H', 'header', None, _('header')),
+ ('t', 'topo-order', None, _('topo-order')),
+ ('p', 'parents', None, _('parents')),
+ ('n', 'max-count', 0, _('max-count'))],
+ ('hg debug-rev-list [OPTION]... REV...'))
def revlist(ui, repo, *revs, **opts):
"""print revisions"""
if opts['header']:
@@ -301,6 +324,7 @@
copy = [x for x in revs]
revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
+@command('debug-config', [], _('hg debug-config'))
def config(ui, repo, **opts):
"""print extension options"""
def writeopt(name, value):
@@ -309,6 +333,10 @@
writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
+@command('view',
+ [('l', 'limit', '',
+ _('limit number of changes displayed'), _('NUM'))],
+ _('hg view [-l LIMIT] [REVRANGE]'))
def view(ui, repo, *etc, **opts):
"start interactive history viewer"
os.chdir(repo.root)
@@ -317,40 +345,4 @@
ui.debug("running %s\n" % cmd)
util.system(cmd)
-cmdtable = {
- "^view":
- (view,
- [('l', 'limit', '',
- _('limit number of changes displayed'), _('NUM'))],
- _('hg view [-l LIMIT] [REVRANGE]')),
- "debug-diff-tree":
- (difftree,
- [('p', 'patch', None, _('generate patch')),
- ('r', 'recursive', None, _('recursive')),
- ('P', 'pretty', None, _('pretty')),
- ('s', 'stdin', None, _('stdin')),
- ('C', 'copy', None, _('detect copies')),
- ('S', 'search', "", _('search'))],
- _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
- "debug-cat-file":
- (catfile,
- [('s', 'stdin', None, _('stdin'))],
- _('hg debug-cat-file [OPTION]... TYPE FILE')),
- "debug-config":
- (config, [], _('hg debug-config')),
- "debug-merge-base":
- (base, [], _('hg debug-merge-base REV REV')),
- "debug-rev-parse":
- (revparse,
- [('', 'default', '', _('ignored'))],
- _('hg debug-rev-parse REV')),
- "debug-rev-list":
- (revlist,
- [('H', 'header', None, _('header')),
- ('t', 'topo-order', None, _('topo-order')),
- ('p', 'parents', None, _('parents')),
- ('n', 'max-count', 0, _('max-count'))],
- _('hg debug-rev-list [OPTION]... REV...')),
-}
-
commands.inferrepo += " debug-diff-tree debug-cat-file"
--- a/hgext/histedit.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/histedit.py Mon Jun 09 13:53:23 2014 -0500
@@ -275,7 +275,8 @@
if path in headmf:
fctx = last[path]
flags = fctx.flags()
- mctx = context.memfilectx(fctx.path(), fctx.data(),
+ mctx = context.memfilectx(repo,
+ fctx.path(), fctx.data(),
islink='l' in flags,
isexec='x' in flags,
copied=copied.get(path))
@@ -298,9 +299,8 @@
filectxfn=filectxfn,
user=user,
date=date,
- extra=extra)
- new._text = cmdutil.commitforceeditor(repo, new, [])
- repo.savecommitmessage(new.description())
+ extra=extra,
+ editor=cmdutil.getcommiteditor(edit=True))
return repo.commitctx(new)
def pick(ui, repo, ctx, ha, opts):
@@ -402,12 +402,11 @@
if stats and stats[3] > 0:
raise error.InterventionRequired(
_('Fix up the change and run hg histedit --continue'))
- message = oldctx.description() + '\n'
- message = ui.edit(message, ui.username())
- repo.savecommitmessage(message)
+ message = oldctx.description()
commit = commitfuncfor(repo, oldctx)
new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
- extra=oldctx.extra())
+ extra=oldctx.extra(),
+ editor=cmdutil.getcommiteditor(edit=True))
newctx = repo[new]
if oldctx.node() != newctx.node():
return newctx, [(oldctx.node(), (new,))]
@@ -682,11 +681,9 @@
if action in ('f', 'fold'):
message = 'fold-temp-revision %s' % currentnode
else:
- message = ctx.description() + '\n'
- if action in ('e', 'edit', 'm', 'mess'):
- editor = cmdutil.commitforceeditor
- else:
- editor = False
+ message = ctx.description()
+ editopt = action in ('e', 'edit', 'm', 'mess')
+ editor = cmdutil.getcommiteditor(edit=editopt)
commit = commitfuncfor(repo, ctx)
new = commit(text=message, user=ctx.user(),
date=ctx.date(), extra=ctx.extra(),
--- a/hgext/largefiles/lfcommands.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/largefiles/lfcommands.py Mon Jun 09 13:53:23 2014 -0500
@@ -21,6 +21,18 @@
# -- Commands ----------------------------------------------------------
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+commands.inferrepo += " lfconvert"
+
+@command('lfconvert',
+ [('s', 'size', '',
+ _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
+ ('', 'to-normal', False,
+ _('convert from a largefiles repo to a normal repo')),
+ ],
+ _('hg lfconvert SOURCE DEST [FILE ...]'))
def lfconvert(ui, src, dest, *pats, **opts):
'''convert a normal repository to a largefiles repository
@@ -160,10 +172,10 @@
finally:
if fd:
fd.close()
- return context.memfilectx(f, data, 'l' in fctx.flags(),
+ return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
'x' in fctx.flags(), renamed)
else:
- return _getnormalcontext(repo.ui, ctx, f, revmap)
+ return _getnormalcontext(repo, ctx, f, revmap)
dstfiles = []
for file in files:
@@ -243,10 +255,11 @@
# doesn't change after rename or copy
renamed = lfutil.standin(renamed[0])
- return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
- fctx.flags(), 'x' in fctx.flags(), renamed)
+ return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
+ 'l' in fctx.flags(), 'x' in fctx.flags(),
+ renamed)
else:
- return _getnormalcontext(repo.ui, ctx, f, revmap)
+ return _getnormalcontext(repo, ctx, f, revmap)
# Commit
_commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
@@ -281,7 +294,7 @@
return parents
# Get memfilectx for a normal file
-def _getnormalcontext(ui, ctx, f, revmap):
+def _getnormalcontext(repo, ctx, f, revmap):
try:
fctx = ctx.filectx(f)
except error.LookupError:
@@ -292,8 +305,8 @@
data = fctx.data()
if f == '.hgtags':
- data = _converttags (ui, revmap, data)
- return context.memfilectx(f, data, 'l' in fctx.flags(),
+ data = _converttags (repo.ui, revmap, data)
+ return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
'x' in fctx.flags(), renamed)
# Remap tag data using a revision map
@@ -519,6 +532,10 @@
finally:
wlock.release()
+@command('lfpull',
+ [('r', 'rev', [], _('pull largefiles for these revisions'))
+ ] + commands.remoteopts,
+ _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
def lfpull(ui, repo, source="default", **opts):
"""pull largefiles for the specified revisions from the specified source
@@ -553,24 +570,3 @@
(cached, missing) = cachelfiles(ui, repo, rev)
numcached += len(cached)
ui.status(_("%d largefiles cached\n") % numcached)
-
-# -- hg commands declarations ------------------------------------------------
-
-cmdtable = {
- 'lfconvert': (lfconvert,
- [('s', 'size', '',
- _('minimum size (MB) for files to be converted '
- 'as largefiles'),
- 'SIZE'),
- ('', 'to-normal', False,
- _('convert from a largefiles repo to a normal repo')),
- ],
- _('hg lfconvert SOURCE DEST [FILE ...]')),
- 'lfpull': (lfpull,
- [('r', 'rev', [], _('pull largefiles for these revisions'))
- ] + commands.remoteopts,
- _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]')
- ),
- }
-
-commands.inferrepo += " lfconvert"
--- a/hgext/largefiles/overrides.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/largefiles/overrides.py Mon Jun 09 13:53:23 2014 -0500
@@ -282,6 +282,8 @@
standin = lfutil.standin(m._files[i])
if standin in repo[ctx.node()]:
m._files[i] = standin
+ elif m._files[i] not in repo[ctx.node()]:
+ m._files.append(standin)
pats.add(standin)
m._fmap = set(m._files)
@@ -408,14 +410,13 @@
if overwrite:
return actions
- removes = set(a[0] for a in actions if a[1] == 'r')
- processed = []
+ removes = set(a[0] for a in actions['r'])
- for action in actions:
- f, m, args, msg = action
-
+ newglist = []
+ for action in actions['g']:
+ f, args, msg = action
splitstandin = f and lfutil.splitstandin(f)
- if (m == "g" and splitstandin is not None and
+ if (splitstandin is not None and
splitstandin in p1 and splitstandin not in removes):
# Case 1: normal file in the working copy, largefile in
# the second parent
@@ -425,12 +426,11 @@
'use (l)argefile or keep (n)ormal file?'
'$$ &Largefile $$ &Normal file') % lfile
if repo.ui.promptchoice(msg, 0) == 0:
- processed.append((lfile, "r", None, msg))
- processed.append((standin, "g", (p2.flags(standin),), msg))
+ actions['r'].append((lfile, None, msg))
+ newglist.append((standin, (p2.flags(standin),), msg))
else:
- processed.append((standin, "r", None, msg))
- elif (m == "g" and
- lfutil.standin(f) in p1 and lfutil.standin(f) not in removes):
+ actions['r'].append((standin, None, msg))
+ elif lfutil.standin(f) in p1 and lfutil.standin(f) not in removes:
# Case 2: largefile in the working copy, normal file in
# the second parent
standin = lfutil.standin(f)
@@ -439,20 +439,23 @@
'keep (l)argefile or use (n)ormal file?'
'$$ &Largefile $$ &Normal file') % lfile
if repo.ui.promptchoice(msg, 0) == 0:
- processed.append((lfile, "r", None, msg))
+ actions['r'].append((lfile, None, msg))
else:
- processed.append((standin, "r", None, msg))
- processed.append((lfile, "g", (p2.flags(lfile),), msg))
+ actions['r'].append((standin, None, msg))
+ newglist.append((lfile, (p2.flags(lfile),), msg))
else:
- processed.append(action)
+ newglist.append(action)
- return processed
+ newglist.sort()
+ actions['g'] = newglist
+
+ return actions
# Override filemerge to prompt the user about how they wish to merge
# largefiles. This will handle identical edits without prompting the user.
-def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
+def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None):
if not lfutil.isstandin(orig):
- return origfn(repo, mynode, orig, fcd, fco, fca)
+ return origfn(repo, mynode, orig, fcd, fco, fca, labels=labels)
ahash = fca.data().strip().lower()
dhash = fcd.data().strip().lower()
--- a/hgext/mq.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/mq.py Mon Jun 09 13:53:23 2014 -0500
@@ -1026,6 +1026,7 @@
msg: a string or a no-argument function returning a string
"""
msg = opts.get('msg')
+ edit = opts.get('edit')
user = opts.get('user')
date = opts.get('date')
if date:
@@ -1078,12 +1079,25 @@
p.write("# User " + user + "\n")
if date:
p.write("# Date %s %s\n\n" % date)
- if util.safehasattr(msg, '__call__'):
- msg = msg()
- repo.savecommitmessage(msg)
- commitmsg = msg and msg or ("[mq]: %s" % patchfn)
+
+ defaultmsg = "[mq]: %s" % patchfn
+ editor = cmdutil.getcommiteditor()
+ if edit:
+ def finishdesc(desc):
+ if desc.rstrip():
+ return desc
+ else:
+ return defaultmsg
+ # i18n: this message is shown in editor with "HG: " prefix
+ extramsg = _('Leave message empty to use default message.')
+ editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
+ extramsg=extramsg)
+ commitmsg = msg
+ else:
+ commitmsg = msg or defaultmsg
+
n = newcommit(repo, None, commitmsg, user, date, match=match,
- force=True)
+ force=True, editor=editor)
if n is None:
raise util.Abort(_("repo commit failed"))
try:
@@ -1092,8 +1106,9 @@
self.parseseries()
self.seriesdirty = True
self.applieddirty = True
- if msg:
- msg = msg + "\n\n"
+ nctx = repo[n]
+ if nctx.description() != defaultmsg.rstrip():
+ msg = nctx.description() + "\n\n"
p.write(msg)
if commitfiles:
parent = self.qparents(repo, n)
@@ -1471,6 +1486,7 @@
self.ui.write(_("no patches applied\n"))
return 1
msg = opts.get('msg', '').rstrip()
+ edit = opts.get('edit')
newuser = opts.get('user')
newdate = opts.get('date')
if newdate:
@@ -1495,8 +1511,6 @@
ph = patchheader(self.join(patchfn), self.plainmode)
diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
- if msg:
- ph.setmessage(msg)
if newuser:
ph.setuser(newuser)
if newdate:
@@ -1506,10 +1520,6 @@
# only commit new patch when write is complete
patchf = self.opener(patchfn, 'w', atomictemp=True)
- comments = str(ph)
- if comments:
- patchf.write(comments)
-
# update the dirstate in place, strip off the qtip commit
# and then commit.
#
@@ -1629,14 +1639,6 @@
for f in forget:
repo.dirstate.drop(f)
- if not msg:
- if not ph.message:
- message = "[mq]: %s\n" % patchfn
- else:
- message = "\n".join(ph.message)
- else:
- message = msg
-
user = ph.user or changes[1]
oldphase = repo[top].phase()
@@ -1653,16 +1655,41 @@
try:
# might be nice to attempt to roll back strip after this
+ defaultmsg = "[mq]: %s" % patchfn
+ editor = cmdutil.getcommiteditor()
+ if edit:
+ def finishdesc(desc):
+ if desc.rstrip():
+ ph.setmessage(desc)
+ return desc
+ return defaultmsg
+ # i18n: this message is shown in editor with "HG: " prefix
+ extramsg = _('Leave message empty to use default message.')
+ editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
+ extramsg=extramsg)
+ message = msg or "\n".join(ph.message)
+ elif not msg:
+ if not ph.message:
+ message = defaultmsg
+ else:
+ message = "\n".join(ph.message)
+ else:
+ message = msg
+ ph.setmessage(msg)
+
# Ensure we create a new changeset in the same phase than
# the old one.
n = newcommit(repo, oldphase, message, user, ph.date,
- match=match, force=True)
+ match=match, force=True, editor=editor)
# only write patch after a successful commit
c = [list(x) for x in refreshchanges]
if inclsubs:
self.putsubstate2changes(substatestate, c)
chunks = patchmod.diff(repo, patchparent,
changes=c, opts=diffopts)
+ comments = str(ph)
+ if comments:
+ patchf.write(comments)
for chunk in chunks:
patchf.write(chunk)
patchf.close()
@@ -2417,14 +2444,8 @@
Returns 0 on successful creation of a new patch.
"""
msg = cmdutil.logmessage(ui, opts)
- def getmsg():
- return ui.edit(msg, opts.get('user') or ui.username())
q = repo.mq
opts['msg'] = msg
- if opts.get('edit'):
- opts['msg'] = getmsg
- else:
- opts['msg'] = msg
setupheaderopts(ui, opts)
q.new(repo, patch, *args, **opts)
q.savedirty()
@@ -2468,17 +2489,6 @@
"""
q = repo.mq
message = cmdutil.logmessage(ui, opts)
- if opts.get('edit'):
- if not q.applied:
- ui.write(_("no patches applied\n"))
- return 1
- if message:
- raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
- patch = q.applied[-1].name
- ph = patchheader(q.join(patch), q.plainmode)
- message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
- # We don't want to lose the patch message if qrefresh fails (issue2062)
- repo.savecommitmessage(message)
setupheaderopts(ui, opts)
wlock = repo.wlock()
try:
@@ -2536,9 +2546,6 @@
q.checklocalchanges(repo)
message = cmdutil.logmessage(ui, opts)
- if opts.get('edit'):
- if message:
- raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
parent = q.lookup('qtip')
patches = []
@@ -2564,7 +2571,7 @@
if not message:
ph = patchheader(q.join(parent), q.plainmode)
- message, user = ph.message, ph.user
+ message = ph.message
for msg in messages:
if msg:
if message:
@@ -2572,14 +2579,10 @@
message.extend(msg)
message = '\n'.join(message)
- if opts.get('edit'):
- message = ui.edit(message, user or ui.username())
- repo.savecommitmessage(message)
-
diffopts = q.patchopts(q.diffopts(), *patches)
wlock = repo.wlock()
try:
- q.refresh(repo, msg=message, git=diffopts.git)
+ q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'))
q.delete(repo, patches, opts)
q.savedirty()
finally:
--- a/hgext/pager.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/pager.py Mon Jun 09 13:53:23 2014 -0500
@@ -39,12 +39,20 @@
If pager.attend is present, pager.ignore will be ignored.
+Lastly, you can enable and disable paging for individual commands with
+the attend-<command> option. This setting takes precedence over
+existing attend and ignore options and defaults::
+
+ [pager]
+ attend-cat = false
+
To ignore global commands like :hg:`version` or :hg:`help`, you have
to specify them in your user configuration file.
The --pager=... option can also be used to control when the pager is
used. Use a boolean value like yes, no, on, off, or use auto for
normal behavior.
+
'''
import atexit, sys, os, signal, subprocess, errno, shlex
@@ -116,25 +124,37 @@
def pagecmd(orig, ui, options, cmd, cmdfunc):
p = ui.config("pager", "pager", os.environ.get("PAGER"))
+ usepager = False
+ always = util.parsebool(options['pager'])
+ auto = options['pager'] == 'auto'
- if p:
+ if not p:
+ pass
+ elif always:
+ usepager = True
+ elif not auto:
+ usepager = False
+ else:
attend = ui.configlist('pager', 'attend', attended)
- auto = options['pager'] == 'auto'
- always = util.parsebool(options['pager'])
-
+ ignore = ui.configlist('pager', 'ignore')
cmds, _ = cmdutil.findcmd(cmd, commands.table)
- ignore = ui.configlist('pager', 'ignore')
for cmd in cmds:
- if (always or auto and
- (cmd in attend or
- (cmd not in ignore and not attend))):
- ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
- ui.setconfig('ui', 'interactive', False, 'pager')
- if util.safehasattr(signal, "SIGPIPE"):
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
- _runpager(ui, p)
+ var = 'attend-%s' % cmd
+ if ui.config('pager', var):
+ usepager = ui.configbool('pager', var)
+ break
+ if (cmd in attend or
+ (cmd not in ignore and not attend)):
+ usepager = True
break
+
+ if usepager:
+ ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
+ ui.setconfig('ui', 'interactive', False, 'pager')
+ if util.safehasattr(signal, "SIGPIPE"):
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ _runpager(ui, p)
return orig(ui, options, cmd, cmdfunc)
extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
--- a/hgext/patchbomb.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/patchbomb.py Mon Jun 09 13:53:23 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 = [
--- a/hgext/rebase.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/rebase.py Mon Jun 09 13:53:23 2014 -0500
@@ -138,9 +138,7 @@
skipped = set()
targetancestors = set()
- editor = None
- if opts.get('edit'):
- editor = cmdutil.commitforceeditor
+ editor = cmdutil.getcommiteditor(**opts)
lock = wlock = None
try:
@@ -376,7 +374,7 @@
for rebased in state:
if rebased not in skipped and state[rebased] > nullmerge:
commitmsg += '\n* %s' % repo[rebased].description()
- editor = cmdutil.commitforceeditor
+ editor = cmdutil.getcommiteditor(edit=True)
newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
extrafn=extrafn, editor=editor)
for oldrev in state.iterkeys():
@@ -533,7 +531,8 @@
repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
# When collapsing in-place, the parent is the common ancestor, we
# have to allow merging with it.
- return merge.update(repo, rev, True, True, False, base, collapse)
+ return merge.update(repo, rev, True, True, False, base, collapse,
+ labels=['dest', 'source'])
def nearestrebased(repo, rev, state):
"""return the nearest ancestors of rev in the rebase result"""
--- a/hgext/record.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/record.py Mon Jun 09 13:53:23 2014 -0500
@@ -459,6 +459,8 @@
# backup all changed files
dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
+# This command registration is replaced during uisetup().
+@command('qrecord', [], _('hg qrecord [OPTION]... PATCH [FILE]...'))
def qrecord(ui, repo, patch, *pats, **opts):
'''interactively record a new patch
@@ -637,10 +639,6 @@
finally:
ui.write = oldwrite
-cmdtable["qrecord"] = \
- (qrecord, [], # placeholder until mq is available
- _('hg qrecord [OPTION]... PATCH [FILE]...'))
-
def uisetup(ui):
try:
mq = extensions.find('mq')
--- a/hgext/relink.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/relink.py Mon Jun 09 13:53:23 2014 -0500
@@ -7,12 +7,15 @@
"""recreates hardlinks between repository clones"""
-from mercurial import hg, util
+from mercurial import cmdutil, hg, util
from mercurial.i18n import _
import os, stat
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
+@command('relink', [], _('[ORIGIN]'))
def relink(ui, repo, origin=None, **opts):
"""recreate hardlinks between two repositories
@@ -178,11 +181,3 @@
ui.status(_('relinked %d files (%s reclaimed)\n') %
(relinked, util.bytecount(savedbytes)))
-
-cmdtable = {
- 'relink': (
- relink,
- [],
- _('[ORIGIN]')
- )
-}
--- a/hgext/share.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/share.py Mon Jun 09 13:53:23 2014 -0500
@@ -6,10 +6,15 @@
'''share a common history between several working directories'''
from mercurial.i18n import _
-from mercurial import hg, commands, util
+from mercurial import cmdutil, hg, commands, util
+cmdtable = {}
+command = cmdutil.command(cmdtable)
testedwith = 'internal'
+@command('share',
+ [('U', 'noupdate', None, _('do not create a working copy'))],
+ _('[-U] SOURCE [DEST]'))
def share(ui, source, dest=None, noupdate=False):
"""create a new shared repository
@@ -30,6 +35,7 @@
return hg.share(ui, source, dest, not noupdate)
+@command('unshare', [], '')
def unshare(ui, repo):
"""convert a shared repository to a normal one
@@ -61,15 +67,4 @@
# update store, spath, sopener and sjoin of repo
repo.unfiltered().__init__(repo.baseui, repo.root)
-cmdtable = {
- "share":
- (share,
- [('U', 'noupdate', None, _('do not create a working copy'))],
- _('[-U] SOURCE [DEST]')),
- "unshare":
- (unshare,
- [],
- ''),
-}
-
commands.norepo += " share"
--- a/hgext/transplant.py Mon May 26 19:02:11 2014 +0200
+++ b/hgext/transplant.py Mon Jun 09 13:53:23 2014 -0500
@@ -80,13 +80,13 @@
self.dirty = True
class transplanter(object):
- def __init__(self, ui, repo):
+ def __init__(self, ui, repo, opts):
self.ui = ui
self.path = repo.join('transplant')
self.opener = scmutil.opener(self.path)
self.transplants = transplants(self.path, 'transplants',
opener=self.opener)
- self.editor = None
+ self.editor = cmdutil.getcommiteditor(**opts)
def applied(self, repo, node, parent):
'''returns True if a node is already an ancestor of parent
@@ -599,9 +599,7 @@
if not opts.get('filter'):
opts['filter'] = ui.config('transplant', 'filter')
- tp = transplanter(ui, repo)
- if opts.get('edit'):
- tp.editor = cmdutil.commitforceeditor
+ tp = transplanter(ui, repo, opts)
cmdutil.checkunfinished(repo)
p1, p2 = repo.dirstate.parents()
--- a/mercurial/bundle2.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/bundle2.py Mon Jun 09 13:53:23 2014 -0500
@@ -113,6 +113,8 @@
Mandatory parameters comes first, then the advisory ones.
+ Each parameter's key MUST be unique within the part.
+
:payload:
payload is a series of `<chunksize><chunkdata>`.
@@ -144,6 +146,7 @@
import struct
import urllib
import string
+import pushkey
import changegroup, error
from i18n import _
@@ -170,18 +173,14 @@
"""
return '>'+('BB'*nbparams)
-class UnknownPartError(KeyError):
- """error raised when no handler is found for a Mandatory part"""
- pass
-
parthandlermapping = {}
-def parthandler(parttype):
+def parthandler(parttype, params=()):
"""decorator that register a function as a bundle2 part handler
eg::
- @parthandler('myparttype')
+ @parthandler('myparttype', ('mandatory', 'param', 'handled'))
def myparttypehandler(...):
'''process a part of type "my part".'''
...
@@ -190,6 +189,7 @@
lparttype = parttype.lower() # enforce lower case matching.
assert lparttype not in parthandlermapping
parthandlermapping[lparttype] = func
+ func.params = frozenset(params)
return func
return _decorator
@@ -295,18 +295,25 @@
# part key are matched lower case
key = parttype.lower()
try:
- handler = parthandlermapping[key]
+ handler = parthandlermapping.get(key)
+ if handler is None:
+ raise error.BundleValueError(parttype=key)
op.ui.debug('found a handler for part %r\n' % parttype)
- except KeyError:
+ unknownparams = part.mandatorykeys - handler.params
+ if unknownparams:
+ unknownparams = list(unknownparams)
+ unknownparams.sort()
+ raise error.BundleValueError(parttype=key,
+ params=unknownparams)
+ except error.BundleValueError, exc:
if key != parttype: # mandatory parts
- # todo:
- # - use a more precise exception
- raise UnknownPartError(key)
- op.ui.debug('ignoring unknown advisory part %r\n' % key)
+ raise
+ op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
# consuming the part
part.read()
continue
+
# handler is called outside the above try block so that we don't
# risk catching KeyErrors from anything other than the
# parthandlermapping lookup (any KeyError raised by handler()
@@ -321,11 +328,8 @@
if output is not None:
output = op.ui.popbuffer()
if output:
- outpart = bundlepart('b2x:output',
- advisoryparams=[('in-reply-to',
- str(part.id))],
- data=output)
- op.reply.addpart(outpart)
+ outpart = op.reply.newpart('b2x:output', data=output)
+ outpart.addparam('in-reply-to', str(part.id), mandatory=False)
part.read()
except Exception, exc:
if part is not None:
@@ -381,7 +385,7 @@
class bundle20(object):
"""represent an outgoing bundle2 container
- Use the `addparam` method to add stream level parameter. and `addpart` to
+ Use the `addparam` method to add stream level parameter. and `newpart` to
populate it. Then call `getchunks` to retrieve all the binary chunks of
data that compose the bundle2 container."""
@@ -391,6 +395,7 @@
self._parts = []
self.capabilities = dict(capabilities)
+ # methods used to defines the bundle2 content
def addparam(self, name, value=None):
"""add a stream level parameter"""
if not name:
@@ -407,6 +412,20 @@
part.id = len(self._parts) # very cheap counter
self._parts.append(part)
+ def newpart(self, typeid, *args, **kwargs):
+ """create a new part and add it to the containers
+
+ As the part is directly added to the containers. For now, this means
+ that any failure to properly initialize the part after calling
+ ``newpart`` should result in a failure of the whole bundling process.
+
+ You can still fall back to manually create and add if you need better
+ control."""
+ part = bundlepart(typeid, *args, **kwargs)
+ self.addpart(part)
+ return part
+
+ # methods used to generate the bundle2 stream
def getchunks(self):
self.ui.debug('start emission of %s stream\n' % _magicstring)
yield _magicstring
@@ -505,7 +524,7 @@
if name[0].islower():
self.ui.debug("ignoring unknown parameter %r\n" % name)
else:
- raise KeyError(name)
+ raise error.BundleValueError(params=(name,))
def iterparts(self):
@@ -536,17 +555,71 @@
The part `type` is used to route the part to the application level
handler.
+
+ The part payload is contained in ``part.data``. It could be raw bytes or a
+ generator of byte chunks.
+
+ You can add parameters to the part using the ``addparam`` method.
+ Parameters can be either mandatory (default) or advisory. Remote side
+ should be able to safely ignore the advisory ones.
+
+ Both data and parameters cannot be modified after the generation has begun.
"""
def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
data=''):
self.id = None
self.type = parttype
- self.data = data
- self.mandatoryparams = mandatoryparams
- self.advisoryparams = advisoryparams
+ self._data = data
+ self._mandatoryparams = list(mandatoryparams)
+ self._advisoryparams = list(advisoryparams)
+ # checking for duplicated entries
+ self._seenparams = set()
+ for pname, __ in self._mandatoryparams + self._advisoryparams:
+ if pname in self._seenparams:
+ raise RuntimeError('duplicated params: %s' % pname)
+ self._seenparams.add(pname)
+ # status of the part's generation:
+ # - None: not started,
+ # - False: currently generated,
+ # - True: generation done.
+ self._generated = None
+
+ # methods used to defines the part content
+ def __setdata(self, data):
+ if self._generated is not None:
+ raise error.ReadOnlyPartError('part is being generated')
+ self._data = data
+ def __getdata(self):
+ return self._data
+ data = property(__getdata, __setdata)
+ @property
+ def mandatoryparams(self):
+ # make it an immutable tuple to force people through ``addparam``
+ return tuple(self._mandatoryparams)
+
+ @property
+ def advisoryparams(self):
+ # make it an immutable tuple to force people through ``addparam``
+ return tuple(self._advisoryparams)
+
+ def addparam(self, name, value='', mandatory=True):
+ if self._generated is not None:
+ raise error.ReadOnlyPartError('part is being generated')
+ if name in self._seenparams:
+ raise ValueError('duplicated params: %s' % name)
+ self._seenparams.add(name)
+ params = self._advisoryparams
+ if mandatory:
+ params = self._mandatoryparams
+ params.append((name, value))
+
+ # methods used to generates the bundle2 stream
def getchunks(self):
+ if self._generated is not None:
+ raise RuntimeError('part can only be consumed once')
+ self._generated = False
#### header
## parttype
header = [_pack(_fparttypesize, len(self.type)),
@@ -584,6 +657,7 @@
yield chunk
# end of payload
yield _pack(_fpayloadsize, 0)
+ self._generated = True
def _payloadchunks(self):
"""yield chunks of a the part payload
@@ -616,6 +690,8 @@
self.type = None
self.mandatoryparams = None
self.advisoryparams = None
+ self.params = None
+ self.mandatorykeys = ()
self._payloadstream = None
self._readheader()
@@ -633,6 +709,16 @@
data = self._fromheader(struct.calcsize(format))
return _unpack(format, data)
+ def _initparams(self, mandatoryparams, advisoryparams):
+ """internal function to setup all logic related parameters"""
+ # make it read only to prevent people touching it by mistake.
+ self.mandatoryparams = tuple(mandatoryparams)
+ self.advisoryparams = tuple(advisoryparams)
+ # user friendly UI
+ self.params = dict(self.mandatoryparams)
+ self.params.update(dict(self.advisoryparams))
+ self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
+
def _readheader(self):
"""read the header and setup the object"""
typesize = self._unpackheader(_fparttypesize)[0]
@@ -659,8 +745,7 @@
advparams = []
for key, value in advsizes:
advparams.append((self._fromheader(key), self._fromheader(value)))
- self.mandatoryparams = manparams
- self.advisoryparams = advparams
+ self._initparams(manparams, advparams)
## part payload
def payloadchunks():
payloadsize = self._unpack(_fpayloadsize)[0]
@@ -685,6 +770,13 @@
self.consumed = True
return data
+def bundle2caps(remote):
+ """return the bundlecapabilities of a peer as dict"""
+ raw = remote.capable('bundle2-exp')
+ if not raw and raw != '':
+ return {}
+ capsblob = urllib.unquote(remote.capable('bundle2-exp'))
+ return decodecaps(capsblob)
@parthandler('b2x:changegroup')
def handlechangegroup(op, inpart):
@@ -705,17 +797,16 @@
if op.reply is not None:
# This is definitly not the final form of this
# return. But one need to start somewhere.
- part = bundlepart('b2x:reply:changegroup', (),
- [('in-reply-to', str(inpart.id)),
- ('return', '%i' % ret)])
- op.reply.addpart(part)
+ part = op.reply.newpart('b2x:reply:changegroup')
+ part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+ part.addparam('return', '%i' % ret, mandatory=False)
assert not inpart.read()
-@parthandler('b2x:reply:changegroup')
+@parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
def handlechangegroup(op, inpart):
- p = dict(inpart.advisoryparams)
- ret = int(p['return'])
- op.records.add('changegroup', {'return': ret}, int(p['in-reply-to']))
+ ret = int(inpart.params['return'])
+ replyto = int(inpart.params['in-reply-to'])
+ op.records.add('changegroup', {'return': ret}, replyto)
@parthandler('b2x:check:heads')
def handlechangegroup(op, inpart):
@@ -748,21 +839,58 @@
if op.reply is None:
op.reply = bundle20(op.ui, caps)
-@parthandler('b2x:error:abort')
+@parthandler('b2x:error:abort', ('message', 'hint'))
def handlereplycaps(op, inpart):
"""Used to transmit abort error over the wire"""
- manargs = dict(inpart.mandatoryparams)
- advargs = dict(inpart.advisoryparams)
- raise util.Abort(manargs['message'], hint=advargs.get('hint'))
+ raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
-@parthandler('b2x:error:unknownpart')
+@parthandler('b2x:error:unsupportedcontent', ('parttype', 'params'))
def handlereplycaps(op, inpart):
- """Used to transmit unknown part error over the wire"""
- manargs = dict(inpart.mandatoryparams)
- raise UnknownPartError(manargs['parttype'])
+ """Used to transmit unknown content error over the wire"""
+ kwargs = {}
+ parttype = inpart.params.get('parttype')
+ if parttype is not None:
+ kwargs['parttype'] = parttype
+ params = inpart.params.get('params')
+ if params is not None:
+ kwargs['params'] = params.split('\0')
-@parthandler('b2x:error:pushraced')
+ raise error.BundleValueError(**kwargs)
+
+@parthandler('b2x:error:pushraced', ('message',))
def handlereplycaps(op, inpart):
"""Used to transmit push race error over the wire"""
- manargs = dict(inpart.mandatoryparams)
- raise error.ResponseError(_('push failed:'), manargs['message'])
+ raise error.ResponseError(_('push failed:'), inpart.params['message'])
+
+@parthandler('b2x:listkeys', ('namespace',))
+def handlelistkeys(op, inpart):
+ """retrieve pushkey namespace content stored in a bundle2"""
+ namespace = inpart.params['namespace']
+ r = pushkey.decodekeys(inpart.read())
+ op.records.add('listkeys', (namespace, r))
+
+@parthandler('b2x:pushkey', ('namespace', 'key', 'old', 'new'))
+def handlepushkey(op, inpart):
+ """process a pushkey request"""
+ dec = pushkey.decode
+ namespace = dec(inpart.params['namespace'])
+ key = dec(inpart.params['key'])
+ old = dec(inpart.params['old'])
+ new = dec(inpart.params['new'])
+ ret = op.repo.pushkey(namespace, key, old, new)
+ record = {'namespace': namespace,
+ 'key': key,
+ 'old': old,
+ 'new': new}
+ op.records.add('pushkey', record)
+ if op.reply is not None:
+ rpart = op.reply.newpart('b2x:reply:pushkey')
+ rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+ rpart.addparam('return', '%i' % ret, mandatory=False)
+
+@parthandler('b2x:reply:pushkey', ('return', 'in-reply-to'))
+def handlepushkeyreply(op, inpart):
+ """retrieve the result of a pushkey request"""
+ ret = int(inpart.params['return'])
+ partid = int(inpart.params['in-reply-to'])
+ op.records.add('pushkey', {'return': ret}, partid)
--- a/mercurial/changegroup.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/changegroup.py Mon Jun 09 13:53:23 2014 -0500
@@ -493,6 +493,25 @@
bundler = bundle10(repo, bundlecaps)
return getsubset(repo, outgoing, bundler, source)
+def _computeoutgoing(repo, heads, common):
+ """Computes which revs are outgoing given a set of common
+ and a set of heads.
+
+ This is a separate function so extensions can have access to
+ the logic.
+
+ Returns a discovery.outgoing object.
+ """
+ cl = repo.changelog
+ if common:
+ hasnode = cl.hasnode
+ common = [n for n in common if hasnode(n)]
+ else:
+ common = [nullid]
+ if not heads:
+ heads = cl.heads()
+ return discovery.outgoing(cl, common, heads)
+
def getbundle(repo, source, heads=None, common=None, bundlecaps=None):
"""Like changegroupsubset, but returns the set difference between the
ancestors of heads and the ancestors common.
@@ -502,15 +521,7 @@
The nodes in common might not all be known locally due to the way the
current discovery protocol works.
"""
- cl = repo.changelog
- if common:
- hasnode = cl.hasnode
- common = [n for n in common if hasnode(n)]
- else:
- common = [nullid]
- if not heads:
- heads = cl.heads()
- outgoing = discovery.outgoing(cl, common, heads)
+ outgoing = _computeoutgoing(repo, heads, common)
return getlocalbundle(repo, source, outgoing, bundlecaps=bundlecaps)
def changegroup(repo, basenodes, source):
--- a/mercurial/cmdutil.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/cmdutil.py Mon Jun 09 13:53:23 2014 -0500
@@ -109,6 +109,30 @@
(logfile, inst.strerror))
return message
+def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
+ """get appropriate commit message editor according to '--edit' option
+
+ 'finishdesc' is a function to be called with edited commit message
+ (= 'description' of the new changeset) just after editing, but
+ before checking empty-ness. It should return actual text to be
+ stored into history. This allows to change description before
+ storing.
+
+ 'extramsg' is a extra message to be shown in the editor instead of
+ 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
+ is automatically added.
+
+ 'getcommiteditor' returns 'commitforceeditor' regardless of
+ 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
+ they are specific for usage in MQ.
+ """
+ if edit or finishdesc or extramsg:
+ return lambda r, c, s: commitforceeditor(r, c, s,
+ finishdesc=finishdesc,
+ extramsg=extramsg)
+ else:
+ return commiteditor
+
def loglimit(opts):
"""get the log limit according to option -l/--limit"""
limit = opts.get('limit')
@@ -562,16 +586,16 @@
tmpname, message, user, date, branch, nodeid, p1, p2 = \
patch.extract(ui, hunk)
- editor = commiteditor
- if opts.get('edit'):
- editor = commitforceeditor
+ editor = getcommiteditor(**opts)
update = not opts.get('bypass')
strip = opts["strip"]
sim = float(opts.get('similarity') or 0)
if not tmpname:
- return (None, None)
+ return (None, None, False)
msg = _('applied to working directory')
+ rejects = False
+
try:
cmdline_message = logmessage(ui, opts)
if cmdline_message:
@@ -617,9 +641,17 @@
if opts.get('exact') or opts.get('import_branch'):
repo.dirstate.setbranch(branch or 'default')
+ partial = opts.get('partial', False)
files = set()
- patch.patch(ui, repo, tmpname, strip=strip, files=files,
- eolmode=None, similarity=sim / 100.0)
+ try:
+ patch.patch(ui, repo, tmpname, strip=strip, files=files,
+ eolmode=None, similarity=sim / 100.0)
+ except patch.PatchError, e:
+ if not partial:
+ raise util.Abort(str(e))
+ if partial:
+ rejects = True
+
files = list(files)
if opts.get('no_commit'):
if message:
@@ -634,7 +666,7 @@
m = scmutil.matchfiles(repo, files or [])
n = repo.commit(message, opts.get('user') or user,
opts.get('date') or date, match=m,
- editor=editor)
+ editor=editor, force=partial)
else:
if opts.get('exact') or opts.get('import_branch'):
branch = branch or 'default'
@@ -653,8 +685,7 @@
opts.get('user') or user,
opts.get('date') or date,
branch, files, store,
- editor=commiteditor)
- repo.savecommitmessage(memctx.description())
+ editor=getcommiteditor())
n = memctx.commit()
finally:
store.close()
@@ -663,7 +694,7 @@
if n:
# i18n: refers to a short changeset id
msg = _('created %s') % short(n)
- return (msg, n)
+ return (msg, n, rejects)
finally:
os.unlink(tmpname)
@@ -2026,7 +2057,8 @@
try:
fctx = ctx[path]
flags = fctx.flags()
- mctx = context.memfilectx(fctx.path(), fctx.data(),
+ mctx = context.memfilectx(repo,
+ fctx.path(), fctx.data(),
islink='l' in flags,
isexec='x' in flags,
copied=copied.get(path))
@@ -2045,12 +2077,10 @@
user = opts.get('user') or old.user()
date = opts.get('date') or old.date()
- editmsg = False
+ editor = getcommiteditor(**opts)
if not message:
- editmsg = True
+ editor = getcommiteditor(edit=True)
message = old.description()
- elif opts.get('edit'):
- editmsg = True
pureextra = extra.copy()
extra['amend_source'] = old.hex()
@@ -2062,10 +2092,8 @@
filectxfn=filectxfn,
user=user,
date=date,
- extra=extra)
- if editmsg:
- new._text = commitforceeditor(repo, new, [])
- repo.savecommitmessage(new.description())
+ extra=extra,
+ editor=editor)
newdesc = changelog.stripdesc(new.description())
if ((not node)
@@ -2130,7 +2158,7 @@
return ctx.description()
return commitforceeditor(repo, ctx, subs)
-def commitforceeditor(repo, ctx, subs):
+def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
edittext = []
modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
if ctx.description():
@@ -2139,7 +2167,10 @@
edittext.append("") # Empty line between message and comments.
edittext.append(_("HG: Enter commit message."
" Lines beginning with 'HG:' are removed."))
- edittext.append(_("HG: Leave message empty to abort commit."))
+ if extramsg:
+ edittext.append("HG: %s" % extramsg)
+ else:
+ edittext.append(_("HG: Leave message empty to abort commit."))
edittext.append("HG: --")
edittext.append(_("HG: user: %s") % ctx.user())
if ctx.p2():
@@ -2162,6 +2193,8 @@
text = re.sub("(?m)^HG:.*(\n|$)", "", text)
os.chdir(olddir)
+ if finishdesc:
+ text = finishdesc(text)
if not text.strip():
raise util.Abort(_("empty commit message"))
@@ -2218,6 +2251,8 @@
node = ctx.node()
mf = ctx.manifest()
+ if node == p2:
+ parent = p2
if node == parent:
pmf = mf
else:
@@ -2227,18 +2262,22 @@
# so have to walk both. do not print errors if files exist in one
# but not other.
+ # `names` is a mapping for all elements in working copy and target revision
+ # The mapping is in the form:
+ # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
names = {}
wlock = repo.wlock()
try:
- # walk dirstate.
+ ## filling of the `names` mapping
+ # walk dirstate to fill `names`
m = scmutil.match(repo[None], pats, opts)
m.bad = lambda x, y: False
for abs in repo.walk(m):
names[abs] = m.rel(abs), m.exact(abs)
- # walk target manifest.
+ # walk target manifest to fill `names`
def badfn(path, msg):
if path in names:
@@ -2259,11 +2298,13 @@
# get the list of subrepos that must be reverted
targetsubs = sorted(s for s in ctx.substate if m(s))
+
+ # Find status of all file in `names`. (Against working directory parent)
m = scmutil.matchfiles(repo, names)
- changes = repo.status(match=m)[:4]
+ changes = repo.status(node1=parent, match=m)[:4]
modified, added, removed, deleted = map(set, changes)
- # if f is a rename, also revert the source
+ # if f is a rename, update `names` to also revert the source
cwd = repo.getcwd()
for f in added:
src = repo.dirstate.copied(f)
@@ -2271,15 +2312,19 @@
removed.add(src)
names[src] = (repo.pathto(src, cwd), True)
+ ## computation of the action to performs on `names` content.
+
def removeforget(abs):
if repo.dirstate[abs] == 'a':
return _('forgetting %s\n')
return _('removing %s\n')
- revert = ([], _('reverting %s\n'))
- add = ([], _('adding %s\n'))
- remove = ([], removeforget)
- undelete = ([], _('undeleting %s\n'))
+ # action to be actually performed by revert
+ # (<list of file>, message>) tuple
+ actions = {'revert': ([], _('reverting %s\n')),
+ 'add': ([], _('adding %s\n')),
+ 'remove': ([], removeforget),
+ 'undelete': ([], _('undeleting %s\n'))}
disptable = (
# dispatch table:
@@ -2288,14 +2333,20 @@
# action if not in target manifest
# make backup if in target manifest
# make backup if not in target manifest
- (modified, revert, remove, True, True),
- (added, revert, remove, True, False),
- (removed, undelete, None, True, False),
- (deleted, revert, remove, False, False),
+ (modified, (actions['revert'], True),
+ (actions['remove'], True)),
+ (added, (actions['revert'], True),
+ (actions['remove'], False)),
+ (removed, (actions['undelete'], True),
+ (None, False)),
+ (deleted, (actions['revert'], False),
+ (actions['remove'], False)),
)
for abs, (rel, exact) in sorted(names.items()):
+ # hash on file in target manifest (or None if missing from target)
mfentry = mf.get(abs)
+ # target file to be touch on disk (relative to cwd)
target = repo.wjoin(abs)
def handle(xlist, dobackup):
xlist[0].append(abs)
@@ -2312,27 +2363,35 @@
if not isinstance(msg, basestring):
msg = msg(abs)
ui.status(msg % rel)
- for table, hitlist, misslist, backuphit, backupmiss in disptable:
+ # search the entry in the dispatch table.
+ # if the file is in any of this sets, it was touched in the working
+ # directory parent and we are sure it needs to be reverted.
+ for table, hit, miss in disptable:
if abs not in table:
continue
# file has changed in dirstate
if mfentry:
- handle(hitlist, backuphit)
- elif misslist is not None:
- handle(misslist, backupmiss)
+ handle(*hit)
+ elif miss[0] is not None:
+ handle(*miss)
break
else:
+ # Not touched in current dirstate.
+
+ # file is unknown in parent, restore older version or ignore.
if abs not in repo.dirstate:
if mfentry:
- handle(add, True)
+ handle(actions['add'], True)
elif exact:
ui.warn(_('file not managed: %s\n') % rel)
continue
- # file has not changed in dirstate
+
+ # parent is target, no changes mean no changes
if node == parent:
if exact:
ui.warn(_('no changes needed to %s\n') % rel)
continue
+ # no change in dirstate but parent and target may differ
if pmf is None:
# only need parent manifest in this unlikely case,
# so do not read by default
@@ -2342,11 +2401,12 @@
# manifests, do nothing
if (pmf[abs] != mfentry or
pmf.flags(abs) != mf.flags(abs)):
- handle(revert, False)
+ handle(actions['revert'], False)
else:
- handle(remove, False)
+ handle(actions['remove'], False)
+
if not opts.get('dry_run'):
- _performrevert(repo, parents, ctx, revert, add, remove, undelete)
+ _performrevert(repo, parents, ctx, actions)
if targetsubs:
# Revert the subrepos on the revert list
@@ -2355,8 +2415,8 @@
finally:
wlock.release()
-def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
- """function that actually perform all the action computed for revert
+def _performrevert(repo, parents, ctx, actions):
+ """function that actually perform all the actions computed for revert
This is an independent function to let extension to plug in and react to
the imminent revert.
@@ -2370,7 +2430,7 @@
repo.wwrite(f, fc.data(), fc.flags())
audit_path = pathutil.pathauditor(repo.root)
- for f in remove[0]:
+ for f in actions['remove'][0]:
if repo.dirstate[f] == 'a':
repo.dirstate.drop(f)
continue
@@ -2390,25 +2450,25 @@
normal = repo.dirstate.normallookup
else:
normal = repo.dirstate.normal
- for f in revert[0]:
+ for f in actions['revert'][0]:
checkout(f)
if normal:
normal(f)
- for f in add[0]:
+ for f in actions['add'][0]:
checkout(f)
repo.dirstate.add(f)
normal = repo.dirstate.normallookup
if node == parent and p2 == nullid:
normal = repo.dirstate.normal
- for f in undelete[0]:
+ for f in actions['undelete'][0]:
checkout(f)
normal(f)
copied = copies.pathcopies(repo[parent], ctx)
- for f in add[0] + undelete[0] + revert[0]:
+ for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
if f in copied:
repo.dirstate.copy(copied[f], f)
--- a/mercurial/commands.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/commands.py Mon Jun 09 13:53:23 2014 -0500
@@ -386,6 +386,7 @@
('', 'parent', '',
_('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
('r', 'rev', '', _('revision to backout'), _('REV')),
+ ('e', 'edit', False, _('invoke editor on commit messages')),
] + mergetoolopts + walkopts + commitopts + commitopts2,
_('[OPTION]... [-r] REV'))
def backout(ui, repo, node=None, rev=None, **opts):
@@ -487,13 +488,12 @@
cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
- e = cmdutil.commiteditor
- if not opts['message'] and not opts['logfile']:
- # we don't translate commit messages
- opts['message'] = "Backed out changeset %s" % short(node)
- e = cmdutil.commitforceeditor
-
def commitfunc(ui, repo, message, match, opts):
+ e = cmdutil.getcommiteditor(**opts)
+ if not message:
+ # we don't translate commit messages
+ message = "Backed out changeset %s" % short(node)
+ e = cmdutil.getcommiteditor(edit=True)
return repo.commit(message, opts.get('user'), opts.get('date'),
match, editor=e)
newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
@@ -1347,8 +1347,6 @@
Returns 0 on success, 1 if nothing changed.
"""
- forceeditor = opts.get('edit')
-
if opts.get('subrepos'):
if opts.get('amend'):
raise util.Abort(_('cannot amend with --subrepos'))
@@ -1410,10 +1408,6 @@
bookmarks.setcurrent(repo, bm)
newmarks.write()
else:
- e = cmdutil.commiteditor
- if forceeditor:
- e = cmdutil.commitforceeditor
-
def commitfunc(ui, repo, message, match, opts):
try:
if opts.get('secret'):
@@ -1423,7 +1417,9 @@
'commit')
return repo.commit(message, opts.get('user'), opts.get('date'),
- match, editor=e, extra=extra)
+ match,
+ editor=cmdutil.getcommiteditor(**opts),
+ extra=extra)
finally:
ui.setconfig('phases', 'new-commit', oldcommitphase, 'commit')
repo.baseui.setconfig('phases', 'new-commit', oldcommitphase,
@@ -1686,17 +1682,17 @@
ml[id * linesperrev] += " r%i" % id
mergedtext = "\n".join(ml)
files.append(fn)
- fctxs[fn] = context.memfilectx(fn, mergedtext)
+ fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
if overwritten_file:
fn = "of"
files.append(fn)
- fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
+ fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
if new_file:
fn = "nf%i" % id
files.append(fn)
- fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
+ fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
if len(ps) > 1:
if not p2:
p2 = repo[ps[1]]
@@ -3077,9 +3073,7 @@
if not opts.get('date') and opts.get('currentdate'):
opts['date'] = "%d %d" % util.makedate()
- editor = None
- if opts.get('edit'):
- editor = cmdutil.commitforceeditor
+ editor = cmdutil.getcommiteditor(**opts)
cont = False
if opts['continue']:
@@ -3186,7 +3180,8 @@
repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
'graft')
stats = mergemod.update(repo, ctx.node(), True, True, False,
- ctx.p1().node())
+ ctx.p1().node(),
+ labels=['local', 'graft'])
finally:
repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
# report any conflicts
@@ -3692,6 +3687,8 @@
_("don't commit, just update the working directory")),
('', 'bypass', None,
_("apply patch without touching the working directory")),
+ ('', 'partial', None,
+ _('commit even if some hunks fail')),
('', 'exact', None,
_('apply patch to the nodes from which it was generated')),
('', 'import-branch', None,
@@ -3733,6 +3730,16 @@
With -s/--similarity, hg will attempt to discover renames and
copies in the patch in the same way as :hg:`addremove`.
+ Use --partial to ensure a changeset will be created from the patch
+ even if some hunks fail to apply. Hunks that fail to apply will be
+ written to a <target-file>.rej file. Conflicts can then be resolved
+ by hand before :hg:`commit --amend` is run to update the created
+ changeset. This flag exists to let people import patches that
+ partially apply without losing the associated metadata (author,
+ date, description, ...), Note that when none of the hunk applies
+ cleanly, :hg:`import --partial` will create an empty changeset,
+ importing only the patch metadata.
+
To read a patch from standard input, use "-" as the patch name. If
a URL is specified, the patch will be downloaded from it.
See :hg:`help dates` for a list of formats valid for -d/--date.
@@ -3758,7 +3765,7 @@
hg import --exact proposed-fix.patch
- Returns 0 on success.
+ Returns 0 on success, 1 on partial success (see --partial).
"""
if not patch1:
@@ -3790,6 +3797,7 @@
base = opts["base"]
wlock = lock = tr = None
msgs = []
+ ret = 0
try:
@@ -3811,8 +3819,9 @@
haspatch = False
for hunk in patch.split(patchfile):
- (msg, node) = cmdutil.tryimportone(ui, repo, hunk, parents,
- opts, msgs, hg.clean)
+ (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
+ parents, opts,
+ msgs, hg.clean)
if msg:
haspatch = True
ui.note(msg + '\n')
@@ -3820,6 +3829,12 @@
parents = repo.parents()
else:
parents = [repo[node]]
+ if rej:
+ ui.write_err(_("patch applied partially\n"))
+ ui.write_err(("(fix the .rej files and run "
+ "`hg commit --amend`)\n"))
+ ret = 1
+ break
if not haspatch:
raise util.Abort(_('%s: no diffs found') % patchurl)
@@ -3828,6 +3843,7 @@
tr.close()
if msgs:
repo.savecommitmessage('\n* * *\n'.join(msgs))
+ return ret
except: # re-raises
# wlock.release() indirectly calls dirstate.write(): since
# we're crashing, we do not want to change the working dir
@@ -4930,46 +4946,66 @@
wlock = repo.wlock()
try:
ms = mergemod.mergestate(repo)
+
+ if not ms.active() and not show:
+ raise util.Abort(
+ _('resolve command not applicable when not merging'))
+
m = scmutil.match(repo[None], pats, opts)
ret = 0
+ didwork = False
for f in ms:
- if m(f):
- if show:
- if nostatus:
- ui.write("%s\n" % f)
- else:
- ui.write("%s %s\n" % (ms[f].upper(), f),
- label='resolve.' +
- {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
- elif mark:
- ms.mark(f, "r")
- elif unmark:
- ms.mark(f, "u")
+ if not m(f):
+ continue
+
+ didwork = True
+
+ if show:
+ if nostatus:
+ ui.write("%s\n" % f)
else:
- wctx = repo[None]
-
- # backup pre-resolve (merge uses .orig for its own purposes)
- a = repo.wjoin(f)
- util.copyfile(a, a + ".resolve")
-
- try:
- # resolve file
- ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
- 'resolve')
- if ms.resolve(f, wctx):
- ret = 1
- finally:
- ui.setconfig('ui', 'forcemerge', '', 'resolve')
- ms.commit()
-
- # replace filemerge's .orig file with our resolve file
- util.rename(a + ".resolve", a + ".orig")
+ ui.write("%s %s\n" % (ms[f].upper(), f),
+ label='resolve.' +
+ {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
+ elif mark:
+ ms.mark(f, "r")
+ elif unmark:
+ ms.mark(f, "u")
+ else:
+ wctx = repo[None]
+
+ # backup pre-resolve (merge uses .orig for its own purposes)
+ a = repo.wjoin(f)
+ util.copyfile(a, a + ".resolve")
+
+ try:
+ # resolve file
+ ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
+ 'resolve')
+ if ms.resolve(f, wctx):
+ ret = 1
+ finally:
+ ui.setconfig('ui', 'forcemerge', '', 'resolve')
+ ms.commit()
+
+ # replace filemerge's .orig file with our resolve file
+ util.rename(a + ".resolve", a + ".orig")
ms.commit()
+
+ if not didwork and pats:
+ ui.warn(_("arguments do not match paths that need resolved\n"))
+
finally:
wlock.release()
+ # Nudge users into finishing an unfinished operation. We don't print
+ # this with the list/show operation because we want list/show to remain
+ # machine readable.
+ if not list(ms.unresolved()) and not show:
+ ui.status(_('no more unresolved files\n'))
+
return ret
@command('revert',
@@ -5689,16 +5725,15 @@
if date:
date = util.parsedate(date)
- if opts.get('edit'):
- message = ui.edit(message, ui.username())
- repo.savecommitmessage(message)
+ editor = cmdutil.getcommiteditor(**opts)
# don't allow tagging the null rev
if (not opts.get('remove') and
scmutil.revsingle(repo, rev_).rev() == nullrev):
raise util.Abort(_("cannot tag null revision"))
- repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
+ repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
+ editor=editor)
finally:
release(lock, wlock)
@@ -5791,9 +5826,11 @@
('c', 'check', None,
_('update across branches if no uncommitted changes')),
('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
- ('r', 'rev', '', _('revision'), _('REV'))],
+ ('r', 'rev', '', _('revision'), _('REV'))
+ ] + mergetoolopts,
_('[-c] [-C] [-d DATE] [[-r] REV]'))
-def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
+def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
+ tool=None):
"""update working directory (or switch revisions)
Update the repository's working directory to the specified
@@ -5874,6 +5911,8 @@
rev = repo[repo[None].branch()].rev()
mergemod._checkunknown(repo, repo[None], repo[rev])
+ repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
+
if clean:
ret = hg.clean(repo, rev)
else:
@@ -5884,7 +5923,11 @@
ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
elif brev in repo._bookmarks:
bookmarks.setcurrent(repo, brev)
+ ui.status(_("(activating bookmark %s)\n") % brev)
elif brev:
+ if repo._bookmarkcurrent:
+ ui.status(_("(leaving bookmark %s)\n") %
+ repo._bookmarkcurrent)
bookmarks.unsetcurrent(repo)
return ret
--- a/mercurial/context.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/context.py Mon Jun 09 13:53:23 2014 -0500
@@ -63,10 +63,81 @@
for f in sorted(self._manifest):
yield f
+ def _manifestmatches(self, match, s):
+ """generate a new manifest filtered by the match argument
+
+ This method is for internal use only and mainly exists to provide an
+ object oriented way for other contexts to customize the manifest
+ generation.
+ """
+ mf = self.manifest().copy()
+ if match.always():
+ return mf
+ for fn in mf.keys():
+ if not match(fn):
+ del mf[fn]
+ return mf
+
+ def _matchstatus(self, other, s, match, listignored, listclean,
+ listunknown):
+ """return match.always if match is none
+
+ This internal method provides a way for child objects to override the
+ match operator.
+ """
+ return match or matchmod.always(self._repo.root, self._repo.getcwd())
+
+ def _prestatus(self, other, s, match, listignored, listclean, listunknown):
+ """provide a hook to allow child objects to preprocess status results
+
+ For example, this allows other contexts, such as workingctx, to query
+ the dirstate before comparing the manifests.
+ """
+ # load earliest manifest first for caching reasons
+ if self.rev() < other.rev():
+ self.manifest()
+ return s
+
+ def _poststatus(self, other, s, match, listignored, listclean, listunknown):
+ """provide a hook to allow child objects to postprocess status results
+
+ For example, this allows other contexts, such as workingctx, to filter
+ suspect symlinks in the case of FAT32 and NTFS filesytems.
+ """
+ return s
+
+ def _buildstatus(self, other, s, match, listignored, listclean,
+ listunknown):
+ """build a status with respect to another context"""
+ mf1 = other._manifestmatches(match, s)
+ mf2 = self._manifestmatches(match, s)
+
+ modified, added, clean = [], [], []
+ deleted, unknown, ignored = s[3], [], []
+ withflags = mf1.withflags() | mf2.withflags()
+ for fn, mf2node in mf2.iteritems():
+ if fn in mf1:
+ if (fn not in deleted and
+ ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
+ (mf1[fn] != mf2node and
+ (mf2node or self[fn].cmp(other[fn]))))):
+ modified.append(fn)
+ elif listclean:
+ clean.append(fn)
+ del mf1[fn]
+ elif fn not in deleted:
+ added.append(fn)
+ removed = mf1.keys()
+
+ return [modified, added, removed, deleted, unknown, ignored, clean]
+
@propertycache
def substate(self):
return subrepo.state(self, self._repo.ui)
+ def subrev(self, subpath):
+ return self.substate[subpath][1]
+
def rev(self):
return self._rev
def node(self):
@@ -198,19 +269,79 @@
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.
+ """
+
+ 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 +954,7 @@
if user:
self._user = user
if changes:
- self._status = list(changes[:4])
- self._unknown = changes[4]
- self._ignored = changes[5]
- self._clean = changes[6]
- else:
- self._unknown = None
- self._ignored = None
- self._clean = None
+ self._status = changes
self._extra = {}
if extra:
@@ -891,7 +1015,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 +1029,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 +1047,7 @@
@propertycache
def _status(self):
- return self._repo.status()[:4]
+ return self._repo.status()
@propertycache
def _user(self):
@@ -933,21 +1057,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 +1078,11 @@
def deleted(self):
return self._status[3]
def unknown(self):
- assert self._unknown is not None # must call status first
- return self._unknown
+ return self._status[4]
def ignored(self):
- assert self._ignored is not None # must call status first
- return self._ignored
+ return self._status[5]
def clean(self):
- assert self._clean is not None # must call status first
- return self._clean
+ return self._status[6]
def branch(self):
return encoding.tolocal(self._extra['branch'])
def closesbranch(self):
@@ -1180,6 +1288,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 +1531,7 @@
# invert comparison to reuse the same code path
return fctx.cmp(self)
-class memctx(object):
+class memctx(committablectx):
"""Use memctx to perform in-memory commits via localrepo.commitctx().
Revision information is supplied at initialization time while
@@ -1287,13 +1559,10 @@
is a dictionary of metadata or is left empty.
"""
def __init__(self, repo, parents, text, files, filectxfn, user=None,
- date=None, extra=None):
- self._repo = repo
+ date=None, extra=None, editor=False):
+ super(memctx, self).__init__(repo, text, user, date, extra)
self._rev = None
self._node = None
- self._text = text
- self._date = date and util.parsedate(date) or util.makedate()
- self._user = user
parents = [(p or nullid) for p in parents]
p1, p2 = parents
self._parents = [changectx(self._repo, p) for p in (p1, p2)]
@@ -1305,55 +1574,9 @@
if self._extra.get('branch', '') == '':
self._extra['branch'] = 'default'
- def __str__(self):
- return str(self._parents[0]) + "+"
-
- def __int__(self):
- return self._rev
-
- def __nonzero__(self):
- return True
-
- def __getitem__(self, key):
- return self.filectx(key)
-
- def p1(self):
- return self._parents[0]
- def p2(self):
- return self._parents[1]
-
- def user(self):
- return self._user or self._repo.ui.username()
- def date(self):
- return self._date
- def description(self):
- return self._text
- def files(self):
- return self.modified()
- def modified(self):
- return self._status[0]
- def added(self):
- return self._status[1]
- def removed(self):
- return self._status[2]
- def deleted(self):
- return self._status[3]
- def unknown(self):
- return self._status[4]
- def ignored(self):
- return self._status[5]
- def clean(self):
- return self._status[6]
- def branch(self):
- return encoding.tolocal(self._extra['branch'])
- def extra(self):
- return self._extra
- def flags(self, f):
- return self[f].flags()
-
- def parents(self):
- """return contexts for each parent changeset"""
- return self._parents
+ if editor:
+ self._text = editor(self._repo, self, [])
+ self._repo.savecommitmessage(self._text)
def filectx(self, path, filelog=None):
"""get a file context from the working directory"""
@@ -1363,12 +1586,13 @@
"""commit context to the repo"""
return self._repo.commitctx(self)
-class memfilectx(object):
+class memfilectx(committablefilectx):
"""memfilectx represents an in-memory file to commit.
- See memctx for more details.
+ See memctx and commitablefilectx for more details.
"""
- def __init__(self, path, data, islink=False, isexec=False, copied=None):
+ def __init__(self, repo, path, data, islink=False,
+ isexec=False, copied=None, memctx=None):
"""
path is the normalized file path relative to repository root.
data is the file content as a string.
@@ -1376,21 +1600,17 @@
isexec is True if the file is executable.
copied is the source file path if current file was copied in the
revision being committed, or None."""
- self._path = path
+ super(memfilectx, self).__init__(repo, path, None, memctx)
self._data = data
self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
self._copied = None
if copied:
self._copied = (copied, nullid)
- def __nonzero__(self):
- return True
- def __str__(self):
- return "%s@%s" % (self.path(), self._changectx)
- def path(self):
- return self._path
def data(self):
return self._data
+ def size(self):
+ return len(self.data())
def flags(self):
return self._flags
def isexec(self):
--- a/mercurial/demandimport.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/demandimport.py Mon Jun 09 13:53:23 2014 -0500
@@ -24,13 +24,17 @@
b = __import__(a)
'''
-import __builtin__, os
+import __builtin__, os, sys
_origimport = __import__
nothing = object()
try:
- _origimport(__builtin__.__name__, {}, {}, None, -1)
+ # Python 3 doesn't have relative imports nor level -1.
+ level = -1
+ if sys.version_info[0] >= 3:
+ level = 0
+ _origimport(__builtin__.__name__, {}, {}, None, level)
except TypeError: # no level argument
def _import(name, globals, locals, fromlist, level):
"call _origimport with no level argument"
@@ -55,7 +59,7 @@
class _demandmod(object):
"""module demand-loader and proxy"""
- def __init__(self, name, globals, locals, level=-1):
+ def __init__(self, name, globals, locals, level=level):
if '.' in name:
head, rest = name.split('.', 1)
after = [rest]
@@ -105,7 +109,7 @@
self._load()
setattr(self._module, attr, val)
-def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
+def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
if not locals or name in ignore or fromlist == ('*',):
# these cases we can't really delay
return _hgextimport(_import, name, globals, locals, fromlist, level)
--- a/mercurial/discovery.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/discovery.py Mon Jun 09 13:53:23 2014 -0500
@@ -341,6 +341,10 @@
if branch not in ('default', None):
error = _("push creates new remote head %s "
"on branch '%s'!") % (short(dhs[0]), branch)
+ elif repo[dhs[0]].bookmarks():
+ error = _("push creates new remote head %s "
+ "with bookmark '%s'!") % (
+ short(dhs[0]), repo[dhs[0]].bookmarks()[0])
else:
error = _("push creates new remote head %s!"
) % short(dhs[0])
--- a/mercurial/error.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/error.py Mon Jun 09 13:53:23 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))
+ super(BundleValueError, self).__init__(msg)
+
+class ReadOnlyPartError(RuntimeError):
+ """error raised when code tries to alter a part being generated"""
+ pass
+
--- a/mercurial/exchange.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/exchange.py Mon Jun 09 13:53:23 2014 -0500
@@ -9,7 +9,7 @@
from node import hex, nullid
import errno, urllib
import util, scmutil, changegroup, base85, error
-import discovery, phases, obsolete, bookmarks, bundle2
+import discovery, phases, obsolete, bookmarks, bundle2, pushkey
def readbundle(ui, fh, fname, vfs=None):
header = changegroup.readexactly(fh, 4)
@@ -208,30 +208,25 @@
The only currently supported type of data is changegroup but this will
evolve in the future."""
- # Send known head to the server for race detection.
- capsblob = urllib.unquote(pushop.remote.capable('bundle2-exp'))
- caps = bundle2.decodecaps(capsblob)
- bundler = bundle2.bundle20(pushop.ui, caps)
+ bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
# create reply capability
capsblob = bundle2.encodecaps(pushop.repo.bundle2caps)
- bundler.addpart(bundle2.bundlepart('b2x:replycaps', data=capsblob))
+ bundler.newpart('b2x:replycaps', data=capsblob)
+ # Send known heads to the server for race detection.
if not pushop.force:
- part = bundle2.bundlepart('B2X:CHECK:HEADS',
- data=iter(pushop.remoteheads))
- bundler.addpart(part)
+ bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads))
extrainfo = _pushbundle2extraparts(pushop, bundler)
# add the changegroup bundle
cg = changegroup.getlocalbundle(pushop.repo, 'push', pushop.outgoing)
- cgpart = bundle2.bundlepart('B2X:CHANGEGROUP', data=cg.getchunks())
- bundler.addpart(cgpart)
+ cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg.getchunks())
stream = util.chunkbuffer(bundler.getchunks())
try:
reply = pushop.remote.unbundle(stream, ['force'], 'push')
- except bundle2.UnknownPartError, exc:
+ except error.BundleValueError, exc:
raise util.Abort('missing support for %s' % exc)
try:
op = bundle2.processbundle(pushop.repo, reply)
- except bundle2.UnknownPartError, exc:
+ except error.BundleValueError, exc:
raise util.Abort('missing support for %s' % exc)
cgreplies = op.records.getreplies(cgpart.id)
assert len(cgreplies['changegroup']) == 1
@@ -330,37 +325,6 @@
"""synchronise phase information locally and remotely"""
unfi = pushop.repo.unfiltered()
cheads = pushop.commonheads
- if pushop.ret:
- # push succeed, synchronize target of the push
- cheads = pushop.outgoing.missingheads
- elif pushop.revs is None:
- # All out push fails. synchronize all common
- cheads = pushop.outgoing.commonheads
- else:
- # I want cheads = heads(::missingheads and ::commonheads)
- # (missingheads is revs with secret changeset filtered out)
- #
- # This can be expressed as:
- # cheads = ( (missingheads and ::commonheads)
- # + (commonheads and ::missingheads))"
- # )
- #
- # while trying to push we already computed the following:
- # common = (::commonheads)
- # missing = ((commonheads::missingheads) - commonheads)
- #
- # We can pick:
- # * missingheads part of common (::commonheads)
- common = set(pushop.outgoing.common)
- nm = pushop.repo.changelog.nodemap
- cheads = [node for node in pushop.revs if nm[node] in common]
- # and
- # * commonheads parents on missing
- revset = unfi.set('%ln and parents(roots(%ln))',
- pushop.outgoing.commonheads,
- pushop.outgoing.missing)
- cheads.extend(c.node() for c in revset)
- pushop.commonheads = cheads
# even when we don't push, exchanging phase data is useful
remotephases = pushop.remote.listkeys('phases')
if (pushop.ui.configbool('ui', '_usedassubrepo', False)
@@ -395,16 +359,54 @@
# Get the list of all revs draft on remote by public here.
# XXX Beware that revset break if droots is not strictly
# XXX root we may want to ensure it is but it is costly
- outdated = unfi.set('heads((%ln::%ln) and public())',
- droots, cheads)
- for newremotehead in outdated:
- r = pushop.remote.pushkey('phases',
- newremotehead.hex(),
- str(phases.draft),
- str(phases.public))
- if not r:
- pushop.ui.warn(_('updating %s to public failed!\n')
- % newremotehead)
+ outdated = unfi.set('heads((%ln::%ln) and public())',
+ droots, cheads)
+
+ b2caps = bundle2.bundle2caps(pushop.remote)
+ if 'b2x:pushkey' in b2caps:
+ # server supports bundle2, let's do a batched push through it
+ #
+ # This will eventually be unified with the changesets bundle2 push
+ bundler = bundle2.bundle20(pushop.ui, b2caps)
+ capsblob = bundle2.encodecaps(pushop.repo.bundle2caps)
+ bundler.newpart('b2x:replycaps', data=capsblob)
+ part2node = []
+ enc = pushkey.encode
+ for newremotehead in outdated:
+ part = bundler.newpart('b2x:pushkey')
+ part.addparam('namespace', enc('phases'))
+ part.addparam('key', enc(newremotehead.hex()))
+ part.addparam('old', enc(str(phases.draft)))
+ part.addparam('new', enc(str(phases.public)))
+ part2node.append((part.id, newremotehead))
+ stream = util.chunkbuffer(bundler.getchunks())
+ try:
+ reply = pushop.remote.unbundle(stream, ['force'], 'push')
+ op = bundle2.processbundle(pushop.repo, reply)
+ except error.BundleValueError, exc:
+ raise util.Abort('missing support for %s' % exc)
+ for partid, node in part2node:
+ partrep = op.records.getreplies(partid)
+ results = partrep['pushkey']
+ assert len(results) <= 1
+ msg = None
+ if not results:
+ msg = _('server ignored update of %s to public!\n') % node
+ elif not int(results[0]['return']):
+ msg = _('updating %s to public failed!\n') % node
+ if msg is not None:
+ pushop.ui.warn(msg)
+
+ else:
+ # fallback to independant pushkey command
+ for newremotehead in outdated:
+ r = pushop.remote.pushkey('phases',
+ newremotehead.hex(),
+ str(phases.draft),
+ str(phases.public))
+ if not r:
+ pushop.ui.warn(_('updating %s to public failed!\n')
+ % newremotehead)
def _localphasemove(pushop, nodes, phase=phases.public):
"""move <nodes> to <phase> in the local source repo"""
@@ -568,14 +570,15 @@
"""pull data using bundle2
For now, the only supported data are changegroup."""
- kwargs = {'bundlecaps': set(['HG2X'])}
- capsblob = bundle2.encodecaps(pullop.repo.bundle2caps)
- kwargs['bundlecaps'].add('bundle2=' + urllib.quote(capsblob))
+ remotecaps = bundle2.bundle2caps(pullop.remote)
+ kwargs = {'bundlecaps': caps20to10(pullop.repo)}
# pulling changegroup
pullop.todosteps.remove('changegroup')
kwargs['common'] = pullop.common
kwargs['heads'] = pullop.heads or pullop.rheads
+ if 'b2x:listkeys' in remotecaps:
+ kwargs['listkeys'] = ['phase']
if not pullop.fetch:
pullop.repo.ui.status(_("no changes found\n"))
pullop.cgresult = 0
@@ -588,13 +591,18 @@
bundle = pullop.remote.getbundle('pull', **kwargs)
try:
op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
- except bundle2.UnknownPartError, exc:
+ except error.BundleValueError, exc:
raise util.Abort('missing support for %s' % exc)
if pullop.fetch:
assert len(op.records['changegroup']) == 1
pullop.cgresult = op.records['changegroup'][0]['return']
+ # processing phases change
+ for namespace, value in op.records['listkeys']:
+ if namespace == 'phases':
+ _pullapplyphases(pullop, value)
+
def _pullbundle2extraprepare(pullop, kwargs):
"""hook function so that extensions can extend the getbundle call"""
pass
@@ -624,8 +632,8 @@
cg = pullop.remote.changegroup(pullop.fetch, 'pull')
elif not pullop.remote.capable('changegroupsubset'):
raise util.Abort(_("partial pull cannot be done because "
- "other repository doesn't support "
- "changegroupsubset."))
+ "other repository doesn't support "
+ "changegroupsubset."))
else:
cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
@@ -633,8 +641,12 @@
def _pullphase(pullop):
# Get remote phases data from remote
+ remotephases = pullop.remote.listkeys('phases')
+ _pullapplyphases(pullop, remotephases)
+
+def _pullapplyphases(pullop, remotephases):
+ """apply phase movement from observed remote state"""
pullop.todosteps.remove('phases')
- remotephases = pullop.remote.listkeys('phases')
publishing = bool(remotephases.get('publishing', False))
if remotephases and not publishing:
# remote is new and unpublishing
@@ -672,6 +684,13 @@
pullop.repo.invalidatevolatilesets()
return tr
+def caps20to10(repo):
+ """return a set with appropriate options to use bundle20 during getbundle"""
+ caps = set(['HG2X'])
+ capsblob = bundle2.encodecaps(repo.bundle2caps)
+ caps.add('bundle2=' + urllib.quote(capsblob))
+ return caps
+
def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
**kwargs):
"""return a full bundle (with potentially multiple kind of parts)
@@ -691,6 +710,9 @@
cg = changegroup.getbundle(repo, source, heads=heads,
common=common, bundlecaps=bundlecaps)
if bundlecaps is None or 'HG2X' not in bundlecaps:
+ if kwargs:
+ raise ValueError(_('unsupported getbundle arguments: %s')
+ % ', '.join(sorted(kwargs.keys())))
return cg
# very crude first implementation,
# the bundle API will change and the generation will be done lazily.
@@ -701,8 +723,13 @@
b2caps.update(bundle2.decodecaps(blob))
bundler = bundle2.bundle20(repo.ui, b2caps)
if cg:
- part = bundle2.bundlepart('b2x:changegroup', data=cg.getchunks())
- bundler.addpart(part)
+ bundler.newpart('b2x:changegroup', data=cg.getchunks())
+ listkeys = kwargs.get('listkeys', ())
+ for namespace in listkeys:
+ part = bundler.newpart('b2x:listkeys')
+ part.addparam('namespace', namespace)
+ keys = repo.listkeys(namespace).items()
+ part.data = pushkey.encodekeys(keys)
_getbundleextrapart(bundler, repo, source, heads=heads, common=common,
bundlecaps=bundlecaps, **kwargs)
return util.chunkbuffer(bundler.getchunks())
--- a/mercurial/filemerge.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/filemerge.py Mon Jun 09 13:53:23 2014 -0500
@@ -7,7 +7,7 @@
from node import short
from i18n import _
-import util, simplemerge, match, error
+import util, simplemerge, match, error, templater, templatekw
import os, tempfile, re, filecmp
def _toolstr(ui, tool, part, default=""):
@@ -169,7 +169,7 @@
used to resolve these conflicts."""
return 1
-def _premerge(repo, toolconf, files):
+def _premerge(repo, toolconf, files, labels=None):
tool, toolpath, binary, symlink = toolconf
if symlink:
return 1
@@ -190,7 +190,7 @@
(tool, premerge, _valid))
if premerge:
- r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
+ r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
if not r:
ui.debug(" premerge successful\n")
return 0
@@ -201,7 +201,7 @@
@internaltool('merge', True,
_("merging %s incomplete! "
"(edit conflicts, then use 'hg resolve --mark')\n"))
-def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files):
+def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
"""
Uses the internal non-interactive simple merge algorithm for merging
files. It will fail if there are any conflicts and leave markers in
@@ -211,19 +211,18 @@
repo.ui.warn(_('warning: internal:merge cannot merge symlinks '
'for %s\n') % fcd.path())
return False, 1
-
- r = _premerge(repo, toolconf, files)
+ r = _premerge(repo, toolconf, files, labels=labels)
if r:
a, b, c, back = files
ui = repo.ui
- r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
+ r = simplemerge.simplemerge(ui, a, b, c, label=labels)
return True, r
return False, 0
@internaltool('dump', True)
-def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files):
+def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
"""
Creates three versions of the files to merge, containing the
contents of local, other and base. These files can then be used to
@@ -231,7 +230,7 @@
``a.txt``, these files will accordingly be named ``a.txt.local``,
``a.txt.other`` and ``a.txt.base`` and they will be placed in the
same directory as ``a.txt``."""
- r = _premerge(repo, toolconf, files)
+ r = _premerge(repo, toolconf, files, labels=labels)
if r:
a, b, c, back = files
@@ -242,8 +241,8 @@
repo.wwrite(fd + ".base", fca.data(), fca.flags())
return False, r
-def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files):
- r = _premerge(repo, toolconf, files)
+def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
+ r = _premerge(repo, toolconf, files, labels=labels)
if r:
tool, toolpath, binary, symlink = toolconf
a, b, c, back = files
@@ -270,7 +269,58 @@
return True, r
return False, 0
-def filemerge(repo, mynode, orig, fcd, fco, fca):
+def _formatconflictmarker(repo, ctx, template, label, pad):
+ """Applies the given template to the ctx, prefixed by the label.
+
+ Pad is the minimum width of the label prefix, so that multiple markers
+ can have aligned templated parts.
+ """
+ if ctx.node() is None:
+ ctx = ctx.p1()
+
+ props = templatekw.keywords.copy()
+ props['templ'] = template
+ props['ctx'] = ctx
+ props['repo'] = repo
+ templateresult = template('conflictmarker', **props)
+
+ label = ('%s:' % label).ljust(pad + 1)
+ mark = '%s %s' % (label, templater.stringify(templateresult))
+
+ # The <<< marks add 8 to the length, and '...' adds three, so max
+ # length of the actual marker is 69.
+ maxlength = 80 - 8 - 3
+ if len(mark) > maxlength:
+ mark = mark[:maxlength] + '...'
+ return mark
+
+_defaultconflictmarker = ('{node|short} ' +
+ '{ifeq(tags, "tip", "", "{tags} ")}' +
+ '{if(bookmarks, "{bookmarks} ")}' +
+ '{ifeq(branch, "default", "", "{branch} ")}' +
+ '- {author|user}: {desc|firstline}')
+
+_defaultconflictlabels = ['local', 'other']
+
+def _formatlabels(repo, fcd, fco, labels):
+ """Formats the given labels using the conflict marker template.
+
+ Returns a list of formatted labels.
+ """
+ cd = fcd.changectx()
+ co = fco.changectx()
+
+ ui = repo.ui
+ template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
+ template = templater.parsestring(template, quoted=False)
+ tmpl = templater.templater(None, cache={ 'conflictmarker' : template })
+
+ pad = max(len(labels[0]), len(labels[1]))
+
+ return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
+ _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
+
+def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
"""perform a 3-way merge in the working directory
mynode = parent node before merge
@@ -327,8 +377,17 @@
ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
+ markerstyle = ui.config('ui', 'mergemarkers', 'detailed')
+ if markerstyle == 'basic':
+ formattedlabels = _defaultconflictlabels
+ else:
+ if not labels:
+ labels = _defaultconflictlabels
+
+ formattedlabels = _formatlabels(repo, fcd, fco, labels)
+
needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
- (a, b, c, back))
+ (a, b, c, back), labels=formattedlabels)
if not needcheck:
if r:
if onfailure:
--- a/mercurial/help.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/help.py Mon Jun 09 13:53:23 2014 -0500
@@ -481,8 +481,11 @@
rst.append('%s:\n\n' % title)
rst.extend(minirst.maketable(sorted(matches[t]), 1))
rst.append('\n')
+ if not rst:
+ msg = _('no matches')
+ hint = _('try "hg help" for a list of topics')
+ raise util.Abort(msg, hint=hint)
elif name and name != 'shortlist':
- i = None
if unknowncmd:
queries = (helpextcmd,)
elif opts.get('extension'):
@@ -494,12 +497,16 @@
for f in queries:
try:
rst = f(name)
- i = None
break
- except error.UnknownCommand, inst:
- i = inst
- if i:
- raise i
+ except error.UnknownCommand:
+ pass
+ else:
+ if unknowncmd:
+ raise error.UnknownCommand(name)
+ else:
+ msg = _('no such help topic: %s') % name
+ hint = _('try "hg help --keyword %s"') % name
+ raise util.Abort(msg, hint=hint)
else:
# program name
if not ui.quiet:
--- a/mercurial/help/config.txt Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/help/config.txt Mon Jun 09 13:53:23 2014 -0500
@@ -807,7 +807,9 @@
---------------
This section configures external merge tools to use for file-level
-merges.
+merges. This section has likely been preconfigured at install time.
+Use :hg:`config merge-tools` to check the existing configuration.
+Also see :hg:`help merge-tools` for more details.
Example ``~/.hgrc``::
@@ -819,6 +821,9 @@
# Give higher priority
kdiff3.priority = 1
+ # Changing the priority of preconfigured tool
+ vimdiff.priority = 0
+
# Define new tool
myHtmlTool.args = -m $local $other $base $output
myHtmlTool.regkey = Software\FooSoftware\HtmlMerge
@@ -838,7 +843,13 @@
``args``
The arguments to pass to the tool executable. You can refer to the
files being merged as well as the output file through these
- variables: ``$base``, ``$local``, ``$other``, ``$output``.
+ variables: ``$base``, ``$local``, ``$other``, ``$output``. The meaning
+ of ``$local`` and ``$other`` can vary depending on which action is being
+ performed. During and update or merge, ``$local`` represents the original
+ state of the file, while ``$other`` represents the commit you are updating
+ to or the commit you are merging with. During a rebase ``$local``
+ represents the destination of the rebase, and ``$other`` represents the
+ commit being rebased.
Default: ``$local $base $other``
``premerge``
@@ -1203,6 +1214,20 @@
For more information on merge tools see :hg:`help merge-tools`.
For configuring merge tools see the ``[merge-tools]`` section.
+``mergemarkers``
+ Sets the merge conflict marker label styling. The default ``detailed``
+ style uses the ``mergemarkertemplate`` setting to style the labels.
+ The ``basic`` style just uses 'local' and 'other' as the marker label.
+ One of ``basic`` or ``detailed``.
+ Default is ``detailed``.
+
+``mergemarkertemplate``
+ The template used to print the commit description next to each conflict
+ marker during merge conflicts. See :hg:`help templates` for the template
+ format.
+ Defaults to showing the hash, tags, branches, bookmarks, author, and
+ the first line of the commit description.
+
``portablefilenames``
Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``.
Default is ``warn``.
--- a/mercurial/hg.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/hg.py Mon Jun 09 13:53:23 2014 -0500
@@ -483,7 +483,8 @@
When overwrite is set, changes are clobbered, merged else
returns stats (see pydoc mercurial.merge.applyupdates)"""
- return mergemod.update(repo, node, False, overwrite, None)
+ return mergemod.update(repo, node, False, overwrite, None,
+ labels=['working copy', 'destination'])
def update(repo, node):
"""update the working directory to node, merging linear changes"""
--- a/mercurial/localrepo.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/localrepo.py Mon Jun 09 13:53:23 2014 -0500
@@ -180,7 +180,9 @@
requirements = ['revlogv1']
filtername = None
- bundle2caps = {'HG2X': ()}
+ bundle2caps = {'HG2X': (),
+ 'b2x:listkeys': (),
+ 'b2x:pushkey': ()}
# a list of (ui, featureset) functions.
# only functions defined in module of enabled extensions are invoked
@@ -479,7 +481,8 @@
return hook.hook(self.ui, self, name, throw, **args)
@unfilteredmethod
- def _tag(self, names, node, message, local, user, date, extra={}):
+ def _tag(self, names, node, message, local, user, date, extra={},
+ editor=False):
if isinstance(names, str):
names = (names,)
@@ -539,14 +542,15 @@
self[None].add(['.hgtags'])
m = matchmod.exact(self.root, '', ['.hgtags'])
- tagnode = self.commit(message, user, date, extra=extra, match=m)
+ tagnode = self.commit(message, user, date, extra=extra, match=m,
+ editor=editor)
for name in names:
self.hook('tag', node=hex(node), tag=name, local=local)
return tagnode
- def tag(self, names, node, message, local, user, date):
+ def tag(self, names, node, message, local, user, date, editor=False):
'''tag a revision with one or more symbolic names.
names is a list of strings or, when adding a single tag, names may be a
@@ -574,7 +578,7 @@
'(please commit .hgtags manually)'))
self.tags() # instantiate the cache
- self._tag(names, node, message, local, user, date)
+ self._tag(names, node, message, local, user, date, editor=editor)
@filteredpropertycache
def _tagscache(self):
@@ -855,7 +859,8 @@
# abort here if the journal already exists
if self.svfs.exists("journal"):
raise error.RepoError(
- _("abandoned transaction found - run hg recover"))
+ _("abandoned transaction found"),
+ hint=_("run 'hg recover' to clean up transaction"))
def onclose():
self.store.write(self._transref())
@@ -1501,149 +1506,9 @@
def status(self, node1='.', node2=None, match=None,
ignored=False, clean=False, unknown=False,
listsubrepos=False):
- """return status of files between two nodes or node and working
- directory.
-
- If node1 is None, use the first dirstate parent instead.
- If node2 is None, compare node1 with working directory.
- """
-
- def mfmatches(ctx):
- mf = ctx.manifest().copy()
- if match.always():
- return mf
- for fn in mf.keys():
- if not match(fn):
- del mf[fn]
- return mf
-
- ctx1 = self[node1]
- ctx2 = self[node2]
-
- working = ctx2.rev() is None
- parentworking = working and ctx1 == self['.']
- match = match or matchmod.always(self.root, self.getcwd())
- listignored, listclean, listunknown = ignored, clean, unknown
-
- # load earliest manifest first for caching reasons
- if not working and ctx2.rev() < ctx1.rev():
- ctx2.manifest()
-
- if not parentworking:
- def bad(f, msg):
- # 'f' may be a directory pattern from 'match.files()',
- # so 'f not in ctx1' is not enough
- if f not in ctx1 and f not in ctx1.dirs():
- self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
- match.bad = bad
-
- if working: # we need to scan the working dir
- subrepos = []
- if '.hgsub' in self.dirstate:
- subrepos = sorted(ctx2.substate)
- s = self.dirstate.status(match, subrepos, listignored,
- listclean, listunknown)
- cmp, modified, added, removed, deleted, unknown, ignored, clean = s
-
- # check for any possibly clean files
- if parentworking and cmp:
- fixup = []
- # do a full compare of any files that might have changed
- for f in sorted(cmp):
- if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
- or ctx1[f].cmp(ctx2[f])):
- modified.append(f)
- else:
- fixup.append(f)
-
- # update dirstate for files that are actually clean
- if fixup:
- if listclean:
- clean += fixup
-
- try:
- # updating the dirstate is optional
- # so we don't wait on the lock
- wlock = self.wlock(False)
- try:
- for f in fixup:
- self.dirstate.normal(f)
- finally:
- wlock.release()
- except error.LockError:
- pass
-
- if not parentworking:
- mf1 = mfmatches(ctx1)
- if working:
- # we are comparing working dir against non-parent
- # generate a pseudo-manifest for the working dir
- mf2 = mfmatches(self['.'])
- for f in cmp + modified + added:
- mf2[f] = None
- mf2.set(f, ctx2.flags(f))
- for f in removed:
- if f in mf2:
- del mf2[f]
- else:
- # we are comparing two revisions
- deleted, unknown, ignored = [], [], []
- mf2 = mfmatches(ctx2)
-
- modified, added, clean = [], [], []
- withflags = mf1.withflags() | mf2.withflags()
- for fn, mf2node in mf2.iteritems():
- if fn in mf1:
- if (fn not in deleted and
- ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
- (mf1[fn] != mf2node and
- (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
- modified.append(fn)
- elif listclean:
- clean.append(fn)
- del mf1[fn]
- elif fn not in deleted:
- added.append(fn)
- removed = mf1.keys()
-
- if working and modified and not self.dirstate._checklink:
- # Symlink placeholders may get non-symlink-like contents
- # via user error or dereferencing by NFS or Samba servers,
- # so we filter out any placeholders that don't look like a
- # symlink
- sane = []
- for f in modified:
- if ctx2.flags(f) == 'l':
- d = ctx2[f].data()
- if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
- self.ui.debug('ignoring suspect symlink placeholder'
- ' "%s"\n' % f)
- continue
- sane.append(f)
- modified = sane
-
- r = modified, added, removed, deleted, unknown, ignored, clean
-
- if listsubrepos:
- for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
- if working:
- rev2 = None
- else:
- rev2 = ctx2.substate[subpath][1]
- try:
- submatch = matchmod.narrowmatcher(subpath, match)
- s = sub.status(rev2, match=submatch, ignored=listignored,
- clean=listclean, unknown=listunknown,
- listsubrepos=True)
- for rfiles, sfiles in zip(r, s):
- rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
- except error.LookupError:
- self.ui.status(_("skipping missing subrepository: %s\n")
- % subpath)
-
- for l in r:
- l.sort()
- return r
+ '''a convenience method that calls node1.status(node2)'''
+ return self[node1].status(node2, match, ignored, clean, unknown,
+ listsubrepos)
def heads(self, start=None):
heads = self.changelog.heads(start)
--- a/mercurial/merge.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/merge.py Mon Jun 09 13:53:23 2014 -0500
@@ -55,6 +55,8 @@
def reset(self, node=None, other=None):
self._state = {}
+ self._local = None
+ self._other = None
if node:
self._local = node
self._other = other
@@ -68,6 +70,8 @@
of on disk file.
"""
self._state = {}
+ self._local = None
+ self._other = None
records = self._readrecords()
for rtype, record in records:
if rtype == 'L':
@@ -171,6 +175,18 @@
raise
return records
+ def active(self):
+ """Whether mergestate is active.
+
+ Returns True if there appears to be mergestate. This is a rough proxy
+ for "is a merge in progress."
+ """
+ # Check local variables before looking at filesystem for performance
+ # reasons.
+ return bool(self._local) or bool(self._state) or \
+ self._repo.opener.exists(self.statepathv1) or \
+ self._repo.opener.exists(self.statepathv2)
+
def commit(self):
"""Write current state on disk (if necessary)"""
if self._dirty:
@@ -232,10 +248,7 @@
return self._state[dfile][0]
def __iter__(self):
- l = self._state.keys()
- l.sort()
- for f in l:
- yield f
+ return iter(sorted(self._state))
def files(self):
return self._state.keys()
@@ -244,7 +257,14 @@
self._state[dfile][0] = state
self._dirty = True
- def resolve(self, dfile, wctx):
+ def unresolved(self):
+ """Obtain the paths of unresolved files."""
+
+ for f, entry in self._state.items():
+ if entry[0] == 'u':
+ yield f
+
+ def resolve(self, dfile, wctx, labels=None):
"""rerun merge process for file path `dfile`"""
if self[dfile] == 'r':
return 0
@@ -267,7 +287,8 @@
f = self._repo.opener("merge/" + hash)
self._repo.wwrite(dfile, f.read(), flags)
f.close()
- r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
+ r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
+ labels=labels)
if r is None:
# no real conflict
del self._state[dfile]
@@ -310,62 +331,44 @@
as removed.
"""
- actions = []
- state = branchmerge and 'r' or 'f'
+ ractions = []
+ factions = xactions = []
+ if branchmerge:
+ xactions = ractions
for f in wctx.deleted():
if f not in mctx:
- actions.append((f, state, None, "forget deleted"))
+ xactions.append((f, None, "forget deleted"))
if not branchmerge:
for f in wctx.removed():
if f not in mctx:
- actions.append((f, "f", None, "forget removed"))
+ factions.append((f, None, "forget removed"))
- return actions
+ return ractions, factions
def _checkcollision(repo, wmf, actions):
# build provisional merged manifest up
pmmf = set(wmf)
- def addop(f, args):
- pmmf.add(f)
- def removeop(f, args):
- pmmf.discard(f)
- def nop(f, args):
- pass
-
- def renamemoveop(f, args):
- f2, flags = args
- pmmf.discard(f2)
- pmmf.add(f)
- def renamegetop(f, args):
- f2, flags = args
- pmmf.add(f)
- def mergeop(f, args):
- f1, f2, fa, move, anc = args
- if move:
- pmmf.discard(f1)
- pmmf.add(f)
-
- opmap = {
- "a": addop,
- "dm": renamemoveop,
- "dg": renamegetop,
- "dr": nop,
- "e": nop,
- "k": nop,
- "f": addop, # untracked file should be kept in working directory
- "g": addop,
- "m": mergeop,
- "r": removeop,
- "rd": nop,
- "cd": addop,
- "dc": addop,
- }
- for f, m, args, msg in actions:
- op = opmap.get(m)
- assert op, m
- op(f, args)
+ if actions:
+ # k, dr, e and rd are no-op
+ for m in 'a', 'f', 'g', 'cd', 'dc':
+ for f, args, msg in actions[m]:
+ pmmf.add(f)
+ for f, args, msg in actions['r']:
+ pmmf.discard(f)
+ for f, args, msg in actions['dm']:
+ f2, flags = args
+ pmmf.discard(f2)
+ pmmf.add(f)
+ for f, args, msg in actions['dg']:
+ f2, flags = args
+ pmmf.add(f)
+ for f, args, msg in actions['m']:
+ f1, f2, fa, move, anc = args
+ if move:
+ pmmf.discard(f1)
+ pmmf.add(f)
# check case-folding collision in provisional merged manifest
foldmap = {}
@@ -386,7 +389,8 @@
acceptremote = accept the incoming changes without prompting
"""
- actions, copy, movewithdir = [], {}, {}
+ actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
+ copy, movewithdir = {}, {}
# manifests fetched in order are going to be faster, so prime the caches
[x.manifest() for x in
@@ -396,9 +400,9 @@
ret = copies.mergecopies(repo, wctx, p2, pa)
copy, movewithdir, diverge, renamedelete = ret
for of, fl in diverge.iteritems():
- actions.append((of, "dr", (fl,), "divergent renames"))
+ actions['dr'].append((of, (fl,), "divergent renames"))
for of, fl in renamedelete.iteritems():
- actions.append((of, "rd", (fl,), "rename and delete"))
+ actions['rd'].append((of, (fl,), "rename and delete"))
repo.ui.note(_("resolving manifests\n"))
repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
@@ -450,50 +454,50 @@
fla = ma.flags(fa)
nol = 'l' not in fl1 + fl2 + fla
if n2 == a and fl2 == fla:
- actions.append((f, "k", (), "keep")) # remote unchanged
+ actions['k'].append((f, (), "keep")) # remote unchanged
elif n1 == a and fl1 == fla: # local unchanged - use remote
if n1 == n2: # optimization: keep local content
- actions.append((f, "e", (fl2,), "update permissions"))
+ actions['e'].append((f, (fl2,), "update permissions"))
else:
- actions.append((f, "g", (fl2,), "remote is newer"))
+ actions['g'].append((f, (fl2,), "remote is newer"))
elif nol and n2 == a: # remote only changed 'x'
- actions.append((f, "e", (fl2,), "update permissions"))
+ actions['e'].append((f, (fl2,), "update permissions"))
elif nol and n1 == a: # local only changed 'x'
- actions.append((f, "g", (fl1,), "remote is newer"))
+ actions['g'].append((f, (fl1,), "remote is newer"))
else: # both changed something
- actions.append((f, "m", (f, f, fa, False, pa.node()),
+ actions['m'].append((f, (f, f, fa, False, pa.node()),
"versions differ"))
elif f in copied: # files we'll deal with on m2 side
pass
elif n1 and f in movewithdir: # directory rename, move local
f2 = movewithdir[f]
- actions.append((f2, "dm", (f, fl1),
+ actions['dm'].append((f2, (f, fl1),
"remote directory rename - move from " + f))
elif n1 and f in copy:
f2 = copy[f]
- actions.append((f, "m", (f, f2, f2, False, pa.node()),
+ actions['m'].append((f, (f, f2, f2, False, pa.node()),
"local copied/moved from " + f2))
elif n1 and f in ma: # clean, a different, no remote
if n1 != ma[f]:
if acceptremote:
- actions.append((f, "r", None, "remote delete"))
+ actions['r'].append((f, None, "remote delete"))
else:
- actions.append((f, "cd", None, "prompt changed/deleted"))
+ actions['cd'].append((f, None, "prompt changed/deleted"))
elif n1[20:] == "a": # added, no remote
- actions.append((f, "f", None, "remote deleted"))
+ actions['f'].append((f, None, "remote deleted"))
else:
- actions.append((f, "r", None, "other deleted"))
+ actions['r'].append((f, None, "other deleted"))
elif n2 and f in movewithdir:
f2 = movewithdir[f]
- actions.append((f2, "dg", (f, fl2),
+ actions['dg'].append((f2, (f, fl2),
"local directory rename - get from " + f))
elif n2 and f in copy:
f2 = copy[f]
if f2 in m2:
- actions.append((f, "m", (f2, f, f2, False, pa.node()),
+ actions['m'].append((f, (f2, f, f2, False, pa.node()),
"remote copied from " + f2))
else:
- actions.append((f, "m", (f2, f, f2, True, pa.node()),
+ actions['m'].append((f, (f2, f, f2, True, pa.node()),
"remote moved from " + f2))
elif n2 and f not in ma:
# local unknown, remote created: the logic is described by the
@@ -509,17 +513,17 @@
# Checking whether the files are different is expensive, so we
# don't do that when we can avoid it.
if force and not branchmerge:
- actions.append((f, "g", (fl2,), "remote created"))
+ actions['g'].append((f, (fl2,), "remote created"))
else:
different = _checkunknownfile(repo, wctx, p2, f)
if force and branchmerge and different:
# FIXME: This is wrong - f is not in ma ...
- actions.append((f, "m", (f, f, f, False, pa.node()),
+ actions['m'].append((f, (f, f, f, False, pa.node()),
"remote differs from untracked local"))
elif not force and different:
aborts.append((f, "ud"))
else:
- actions.append((f, "g", (fl2,), "remote created"))
+ actions['g'].append((f, (fl2,), "remote created"))
elif n2 and n2 != ma[f]:
different = _checkunknownfile(repo, wctx, p2, f)
if not force and different:
@@ -527,10 +531,10 @@
else:
# if different: old untracked f may be overwritten and lost
if acceptremote:
- actions.append((f, "g", (m2.flags(f),),
+ actions['g'].append((f, (m2.flags(f),),
"remote recreating"))
else:
- actions.append((f, "dc", (m2.flags(f),),
+ actions['dc'].append((f, (m2.flags(f),),
"prompt deleted/changed"))
for f, m in sorted(aborts):
@@ -545,44 +549,32 @@
# check collision between files only in p2 for clean update
if (not branchmerge and
(force or not wctx.dirty(missing=True, branch=False))):
- _checkcollision(repo, m2, [])
+ _checkcollision(repo, m2, None)
else:
_checkcollision(repo, m1, actions)
return actions
-def actionkey(a):
- return a[1] in "rf" and -1 or 0, a
-
-def getremove(repo, mctx, overwrite, args):
- """apply usually-non-interactive updates to the working directory
-
- mctx is the context to be merged into the working copy
+def batchremove(repo, actions):
+ """apply removes to the working directory
yields tuples for progress updates
"""
verbose = repo.ui.verbose
unlink = util.unlinkpath
wjoin = repo.wjoin
- fctx = mctx.filectx
- wwrite = repo.wwrite
audit = repo.wopener.audit
i = 0
- for arg in args:
- f = arg[0]
- if arg[1] == 'r':
- if verbose:
- repo.ui.note(_("removing %s\n") % f)
- audit(f)
- try:
- unlink(wjoin(f), ignoremissing=True)
- except OSError, inst:
- repo.ui.warn(_("update failed to remove %s: %s!\n") %
- (f, inst.strerror))
- else:
- if verbose:
- repo.ui.note(_("getting %s\n") % f)
- wwrite(f, fctx(f).data(), arg[2][0])
+ for f, args, msg in actions:
+ repo.ui.debug(" %s: %s -> r\n" % (f, msg))
+ if verbose:
+ repo.ui.note(_("removing %s\n") % f)
+ audit(f)
+ try:
+ unlink(wjoin(f), ignoremissing=True)
+ except OSError, inst:
+ repo.ui.warn(_("update failed to remove %s: %s!\n") %
+ (f, inst.strerror))
if i == 100:
yield i, f
i = 0
@@ -590,7 +582,30 @@
if i > 0:
yield i, f
-def applyupdates(repo, actions, wctx, mctx, overwrite):
+def batchget(repo, mctx, actions):
+ """apply gets to the working directory
+
+ mctx is the context to get from
+
+ yields tuples for progress updates
+ """
+ verbose = repo.ui.verbose
+ fctx = mctx.filectx
+ wwrite = repo.wwrite
+ i = 0
+ for f, args, msg in actions:
+ repo.ui.debug(" %s: %s -> g\n" % (f, msg))
+ if verbose:
+ repo.ui.note(_("getting %s\n") % f)
+ wwrite(f, fctx(f).data(), args[0])
+ if i == 100:
+ yield i, f
+ i = 0
+ i += 1
+ if i > 0:
+ yield i, f
+
+def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
"""apply the merge action list to the working directory
wctx is the working copy context
@@ -604,29 +619,30 @@
ms = mergestate(repo)
ms.reset(wctx.p1().node(), mctx.node())
moves = []
- actions.sort(key=actionkey)
+ for m, l in actions.items():
+ l.sort()
# prescan for merges
- for a in actions:
- f, m, args, msg = a
- repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
- if m == "m": # merge
- f1, f2, fa, move, anc = args
- if f == '.hgsubstate': # merged internally
- continue
- repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
- fcl = wctx[f1]
- fco = mctx[f2]
- actx = repo[anc]
- if fa in actx:
- fca = actx[fa]
- else:
- fca = repo.filectx(f1, fileid=nullrev)
- ms.add(fcl, fco, fca, f)
- if f1 != f and move:
- moves.append(f1)
+ for f, args, msg in actions['m']:
+ f1, f2, fa, move, anc = args
+ if f == '.hgsubstate': # merged internally
+ continue
+ repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
+ fcl = wctx[f1]
+ fco = mctx[f2]
+ actx = repo[anc]
+ if fa in actx:
+ fca = actx[fa]
+ else:
+ fca = repo.filectx(f1, fileid=nullrev)
+ ms.add(fcl, fco, fca, f)
+ if f1 != f and move:
+ moves.append(f1)
audit = repo.wopener.audit
+ _updating = _('updating')
+ _files = _('files')
+ progress = repo.ui.progress
# remove renamed files after safely stored
for f in moves:
@@ -635,86 +651,120 @@
audit(f)
util.unlinkpath(repo.wjoin(f))
- numupdates = len([a for a in actions if a[1] != 'k'])
- workeractions = [a for a in actions if a[1] in 'gr']
- updateactions = [a for a in workeractions if a[1] == 'g']
- updated = len(updateactions)
- removeactions = [a for a in workeractions if a[1] == 'r']
- removed = len(removeactions)
- actions = [a for a in actions if a[1] not in 'grk']
+ numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
- hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
- if hgsub and hgsub[0] == 'r':
+ if [a for a in actions['r'] if a[0] == '.hgsubstate']:
subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
+ # remove in parallel (must come first)
z = 0
- prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
- removeactions)
+ prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
for i, item in prog:
z += i
- repo.ui.progress(_('updating'), z, item=item, total=numupdates,
- unit=_('files'))
- prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
- updateactions)
+ progress(_updating, z, item=item, total=numupdates, unit=_files)
+ removed = len(actions['r'])
+
+ # get in parallel
+ prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
for i, item in prog:
z += i
- repo.ui.progress(_('updating'), z, item=item, total=numupdates,
- unit=_('files'))
+ progress(_updating, z, item=item, total=numupdates, unit=_files)
+ updated = len(actions['g'])
- if hgsub and hgsub[0] == 'g':
+ if [a for a in actions['g'] if a[0] == '.hgsubstate']:
subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
- _updating = _('updating')
- _files = _('files')
- progress = repo.ui.progress
+ # forget (manifest only, just log it) (must come first)
+ for f, args, msg in actions['f']:
+ repo.ui.debug(" %s: %s -> f\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+
+ # re-add (manifest only, just log it)
+ for f, args, msg in actions['a']:
+ repo.ui.debug(" %s: %s -> a\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+
+ # keep (noop, just log it)
+ for f, args, msg in actions['k']:
+ repo.ui.debug(" %s: %s -> k\n" % (f, msg))
+ # no progress
- for i, a in enumerate(actions):
- f, m, args, msg = a
- progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
- if m == "m": # merge
- f1, f2, fa, move, anc = args
- if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
- overwrite)
- continue
- audit(f)
- r = ms.resolve(f, wctx)
- if r is not None and r > 0:
- unresolved += 1
+ # merge
+ for f, args, msg in actions['m']:
+ repo.ui.debug(" %s: %s -> m\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+ f1, f2, fa, move, anc = args
+ if f == '.hgsubstate': # subrepo states need updating
+ subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
+ overwrite)
+ continue
+ audit(f)
+ r = ms.resolve(f, wctx, labels=labels)
+ if r is not None and r > 0:
+ unresolved += 1
+ else:
+ if r is None:
+ updated += 1
else:
- if r is None:
- updated += 1
- else:
- merged += 1
- elif m == "dm": # directory rename, move local
- f0, flags = args
- repo.ui.note(_("moving %s to %s\n") % (f0, f))
- audit(f)
- repo.wwrite(f, wctx.filectx(f0).data(), flags)
- util.unlinkpath(repo.wjoin(f0))
- updated += 1
- elif m == "dg": # local directory rename, get
- f0, flags = args
- repo.ui.note(_("getting %s to %s\n") % (f0, f))
- repo.wwrite(f, mctx.filectx(f0).data(), flags)
- updated += 1
- elif m == "dr": # divergent renames
- fl, = args
- repo.ui.warn(_("note: possible conflict - %s was renamed "
- "multiple times to:\n") % f)
- for nf in fl:
- repo.ui.warn(" %s\n" % nf)
- elif m == "rd": # rename and delete
- fl, = args
- repo.ui.warn(_("note: possible conflict - %s was deleted "
- "and renamed to:\n") % f)
- for nf in fl:
- repo.ui.warn(" %s\n" % nf)
- elif m == "e": # exec
- flags, = args
- audit(f)
- util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
- updated += 1
+ merged += 1
+
+ # directory rename, move local
+ for f, args, msg in actions['dm']:
+ repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+ f0, flags = args
+ repo.ui.note(_("moving %s to %s\n") % (f0, f))
+ audit(f)
+ repo.wwrite(f, wctx.filectx(f0).data(), flags)
+ util.unlinkpath(repo.wjoin(f0))
+ updated += 1
+
+ # local directory rename, get
+ for f, args, msg in actions['dg']:
+ repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+ f0, flags = args
+ repo.ui.note(_("getting %s to %s\n") % (f0, f))
+ repo.wwrite(f, mctx.filectx(f0).data(), flags)
+ updated += 1
+
+ # divergent renames
+ for f, args, msg in actions['dr']:
+ repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+ fl, = args
+ repo.ui.warn(_("note: possible conflict - %s was renamed "
+ "multiple times to:\n") % f)
+ for nf in fl:
+ repo.ui.warn(" %s\n" % nf)
+
+ # rename and delete
+ for f, args, msg in actions['rd']:
+ repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+ fl, = args
+ repo.ui.warn(_("note: possible conflict - %s was deleted "
+ "and renamed to:\n") % f)
+ for nf in fl:
+ repo.ui.warn(" %s\n" % nf)
+
+ # exec
+ for f, args, msg in actions['e']:
+ repo.ui.debug(" %s: %s -> e\n" % (f, msg))
+ z += 1
+ progress(_updating, z, item=f, total=numupdates, unit=_files)
+ flags, = args
+ audit(f)
+ util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
+ updated += 1
+
ms.commit()
progress(_updating, None, total=numupdates, unit=_files)
@@ -735,163 +785,173 @@
(wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
# Call for bids
- fbids = {} # mapping filename to list af action bids
+ fbids = {} # mapping filename to bids (action method to list af actions)
for ancestor in ancestors:
repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
actions = manifestmerge(repo, wctx, mctx, ancestor,
branchmerge, force,
partial, acceptremote, followcopies)
- for a in sorted(actions):
- repo.ui.debug(' %s: %s\n' % (a[0], a[1]))
- f = a[0]
- if f in fbids:
- fbids[f].append(a)
- else:
- fbids[f] = [a]
+ for m, l in sorted(actions.items()):
+ for a in l:
+ f, args, msg = a
+ repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
+ if f in fbids:
+ d = fbids[f]
+ if m in d:
+ d[m].append(a)
+ else:
+ d[m] = [a]
+ else:
+ fbids[f] = {m: [a]}
# Pick the best bid for each file
repo.ui.note(_('\nauction for merging merge bids\n'))
- actions = []
- for f, bidsl in sorted(fbids.items()):
+ actions = dict((m, []) for m in actions.keys())
+ for f, bids in sorted(fbids.items()):
+ # bids is a mapping from action method to list af actions
# Consensus?
- a0 = bidsl[0]
- if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1
- repo.ui.note(" %s: consensus for %s\n" % (f, a0[1]))
- actions.append(a0)
- continue
- # Group bids by kind of action
- bids = {}
- for a in bidsl:
- m = a[1]
- if m in bids:
- bids[m].append(a)
- else:
- bids[m] = [a]
+ if len(bids) == 1: # all bids are the same kind of method
+ m, l = bids.items()[0]
+ if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
+ repo.ui.note(" %s: consensus for %s\n" % (f, m))
+ actions[m].append(l[0])
+ continue
# If keep is an option, just do it.
if "k" in bids:
repo.ui.note(" %s: picking 'keep' action\n" % f)
- actions.append(bids["k"][0])
+ actions['k'].append(bids["k"][0])
continue
- # If all gets agree [how could they not?], just do it.
+ # If there are gets and they all agree [how could they not?], do it.
if "g" in bids:
ga0 = bids["g"][0]
if util.all(a == ga0 for a in bids["g"][1:]):
repo.ui.note(" %s: picking 'get' action\n" % f)
- actions.append(ga0)
+ actions['g'].append(ga0)
continue
# TODO: Consider other simple actions such as mode changes
# Handle inefficient democrazy.
repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
- for _f, m, args, msg in bidsl:
- repo.ui.note(' %s -> %s\n' % (msg, m))
+ for m, l in sorted(bids.items()):
+ for _f, args, msg in l:
+ repo.ui.note(' %s -> %s\n' % (msg, m))
# Pick random action. TODO: Instead, prompt user when resolving
- a0 = bidsl[0]
+ m, l = bids.items()[0]
repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
- (f, a0[1]))
- actions.append(a0)
+ (f, m))
+ actions[m].append(l[0])
continue
repo.ui.note(_('end of auction\n\n'))
- # Filter out prompts.
- newactions, prompts = [], []
- for a in actions:
- if a[1] in ("cd", "dc"):
- prompts.append(a)
+ # Prompt and create actions. TODO: Move this towards resolve phase.
+ for f, args, msg in actions['cd']:
+ if repo.ui.promptchoice(
+ _("local changed %s which remote deleted\n"
+ "use (c)hanged version or (d)elete?"
+ "$$ &Changed $$ &Delete") % f, 0):
+ actions['r'].append((f, None, "prompt delete"))
else:
- newactions.append(a)
- # Prompt and create actions. TODO: Move this towards resolve phase.
- for f, m, args, msg in sorted(prompts):
- if m == "cd":
- if repo.ui.promptchoice(
- _("local changed %s which remote deleted\n"
- "use (c)hanged version or (d)elete?"
- "$$ &Changed $$ &Delete") % f, 0):
- newactions.append((f, "r", None, "prompt delete"))
- else:
- newactions.append((f, "a", None, "prompt keep"))
- elif m == "dc":
- flags, = args
- if repo.ui.promptchoice(
- _("remote changed %s which local deleted\n"
- "use (c)hanged version or leave (d)eleted?"
- "$$ &Changed $$ &Deleted") % f, 0) == 0:
- newactions.append((f, "g", (flags,), "prompt recreating"))
- else: assert False, m
+ actions['a'].append((f, None, "prompt keep"))
+ del actions['cd'][:]
+
+ for f, args, msg in actions['dc']:
+ flags, = args
+ if repo.ui.promptchoice(
+ _("remote changed %s which local deleted\n"
+ "use (c)hanged version or leave (d)eleted?"
+ "$$ &Changed $$ &Deleted") % f, 0) == 0:
+ actions['g'].append((f, (flags,), "prompt recreating"))
+ del actions['dc'][:]
if wctx.rev() is None:
- newactions += _forgetremoved(wctx, mctx, branchmerge)
+ ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
+ actions['r'].extend(ractions)
+ actions['f'].extend(factions)
- return newactions
+ return actions
def recordupdates(repo, actions, branchmerge):
"record merge actions to the dirstate"
-
- for a in actions:
- f, m, args, msg = a
- if m == "r": # remove
- if branchmerge:
- repo.dirstate.remove(f)
- else:
- repo.dirstate.drop(f)
- elif m == "a": # re-add
- if not branchmerge:
- repo.dirstate.add(f)
- elif m == "f": # forget
+ # remove (must come first)
+ for f, args, msg in actions['r']:
+ if branchmerge:
+ repo.dirstate.remove(f)
+ else:
repo.dirstate.drop(f)
- elif m == "e": # exec change
- repo.dirstate.normallookup(f)
- elif m == "k": # keep
- pass
- elif m == "g": # get
- if branchmerge:
- repo.dirstate.otherparent(f)
- else:
- repo.dirstate.normal(f)
- elif m == "m": # merge
- f1, f2, fa, move, anc = args
- if branchmerge:
- # We've done a branch merge, mark this file as merged
- # so that we properly record the merger later
- repo.dirstate.merge(f)
- if f1 != f2: # copy/rename
- if move:
- repo.dirstate.remove(f1)
- if f1 != f:
- repo.dirstate.copy(f1, f)
- else:
- repo.dirstate.copy(f2, f)
- else:
- # We've update-merged a locally modified file, so
- # we set the dirstate to emulate a normal checkout
- # of that file some time in the past. Thus our
- # merge will appear as a normal local file
- # modification.
- if f2 == f: # file not locally copied/moved
- repo.dirstate.normallookup(f)
+
+ # forget (must come first)
+ for f, args, msg in actions['f']:
+ repo.dirstate.drop(f)
+
+ # re-add
+ for f, args, msg in actions['a']:
+ if not branchmerge:
+ repo.dirstate.add(f)
+
+ # exec change
+ for f, args, msg in actions['e']:
+ repo.dirstate.normallookup(f)
+
+ # keep
+ for f, args, msg in actions['k']:
+ pass
+
+ # get
+ for f, args, msg in actions['g']:
+ if branchmerge:
+ repo.dirstate.otherparent(f)
+ else:
+ repo.dirstate.normal(f)
+
+ # merge
+ for f, args, msg in actions['m']:
+ f1, f2, fa, move, anc = args
+ if branchmerge:
+ # We've done a branch merge, mark this file as merged
+ # so that we properly record the merger later
+ repo.dirstate.merge(f)
+ if f1 != f2: # copy/rename
if move:
- repo.dirstate.drop(f1)
- elif m == "dm": # directory rename, move local
- f0, flag = args
- if f0 not in repo.dirstate:
- # untracked file moved
- continue
- if branchmerge:
- repo.dirstate.add(f)
- repo.dirstate.remove(f0)
- repo.dirstate.copy(f0, f)
- else:
- repo.dirstate.normal(f)
- repo.dirstate.drop(f0)
- elif m == "dg": # directory rename, get
- f0, flag = args
- if branchmerge:
- repo.dirstate.add(f)
- repo.dirstate.copy(f0, f)
- else:
- repo.dirstate.normal(f)
+ repo.dirstate.remove(f1)
+ if f1 != f:
+ repo.dirstate.copy(f1, f)
+ else:
+ repo.dirstate.copy(f2, f)
+ else:
+ # We've update-merged a locally modified file, so
+ # we set the dirstate to emulate a normal checkout
+ # of that file some time in the past. Thus our
+ # merge will appear as a normal local file
+ # modification.
+ if f2 == f: # file not locally copied/moved
+ repo.dirstate.normallookup(f)
+ if move:
+ repo.dirstate.drop(f1)
+
+ # directory rename, move local
+ for f, args, msg in actions['dm']:
+ f0, flag = args
+ if f0 not in repo.dirstate:
+ # untracked file moved
+ continue
+ if branchmerge:
+ repo.dirstate.add(f)
+ repo.dirstate.remove(f0)
+ repo.dirstate.copy(f0, f)
+ else:
+ repo.dirstate.normal(f)
+ repo.dirstate.drop(f0)
+
+ # directory rename, get
+ for f, args, msg in actions['dg']:
+ f0, flag = args
+ if branchmerge:
+ repo.dirstate.add(f)
+ repo.dirstate.copy(f0, f)
+ else:
+ repo.dirstate.normal(f)
def update(repo, node, branchmerge, force, partial, ancestor=None,
- mergeancestor=False):
+ mergeancestor=False, labels=None):
"""
Perform a merge between the working directory and the given node
@@ -1071,7 +1131,7 @@
# note that we're in the middle of an update
repo.vfs.write('updatestate', p2.hex())
- stats = applyupdates(repo, actions, wc, p2, overwrite)
+ stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
if not partial:
repo.setparents(fp1, fp2)
--- a/mercurial/patch.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/patch.py Mon Jun 09 13:53:23 2014 -0500
@@ -417,12 +417,12 @@
return os.path.join(self.opener.base, f)
def getfile(self, fname):
- path = self._join(fname)
- if os.path.islink(path):
- return (os.readlink(path), (True, False))
+ if self.opener.islink(fname):
+ return (self.opener.readlink(fname), (True, False))
+
isexec = False
try:
- isexec = os.lstat(path).st_mode & 0100 != 0
+ isexec = self.opener.lstat(fname).st_mode & 0100 != 0
except OSError, e:
if e.errno != errno.ENOENT:
raise
@@ -431,17 +431,17 @@
def setfile(self, fname, data, mode, copysource):
islink, isexec = mode
if data is None:
- util.setflags(self._join(fname), islink, isexec)
+ self.opener.setflags(fname, islink, isexec)
return
if islink:
self.opener.symlink(data, fname)
else:
self.opener.write(fname, data)
if isexec:
- util.setflags(self._join(fname), False, True)
+ self.opener.setflags(fname, False, True)
def unlink(self, fname):
- util.unlinkpath(self._join(fname), ignoremissing=True)
+ self.opener.unlinkpath(fname, ignoremissing=True)
def writerej(self, fname, failed, total, lines):
fname = fname + ".rej"
@@ -453,7 +453,7 @@
fp.close()
def exists(self, fname):
- return os.path.lexists(self._join(fname))
+ return self.opener.lexists(fname)
class workingbackend(fsbackend):
def __init__(self, ui, repo, similarity):
@@ -1521,14 +1521,11 @@
patcher = ui.config('ui', 'patch')
if files is None:
files = set()
- try:
- if patcher:
- return _externalpatch(ui, repo, patcher, patchname, strip,
- files, similarity)
- return internalpatch(ui, repo, patchname, strip, files, eolmode,
- similarity)
- except PatchError, err:
- raise util.Abort(str(err))
+ if patcher:
+ return _externalpatch(ui, repo, patcher, patchname, strip,
+ files, similarity)
+ return internalpatch(ui, repo, patchname, strip, files, eolmode,
+ similarity)
def changedfiles(ui, repo, patchpath, strip=1):
backend = fsbackend(ui, repo.root)
--- a/mercurial/pushkey.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/pushkey.py Mon Jun 09 13:53:23 2014 -0500
@@ -5,7 +5,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import bookmarks, phases, obsolete
+import bookmarks, phases, obsolete, encoding
def _nslist(repo):
n = {}
@@ -37,3 +37,18 @@
lk = _get(namespace)[1]
return lk(repo)
+encode = encoding.fromlocal
+
+decode = encoding.tolocal
+
+def encodekeys(keys):
+ """encode the content of a pushkey namespace for exchange over the wire"""
+ return '\n'.join(['%s\t%s' % (encode(k), encode(v)) for k, v in keys])
+
+def decodekeys(data):
+ """decode the content of a pushkey namespace from exchange over the wire"""
+ result = {}
+ for l in data.splitlines():
+ k, v = l.split('\t')
+ result[decode(k)] = decode(v)
+ return result
--- a/mercurial/py3kcompat.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/py3kcompat.py Mon Jun 09 13:53:23 2014 -0500
@@ -5,7 +5,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import os, builtins
+import builtins
from numbers import Number
@@ -52,13 +52,6 @@
return ret.encode('utf-8', 'surrogateescape')
builtins.bytesformatter = bytesformatter
-# Create bytes equivalents for os.environ values
-for key in list(os.environ.keys()):
- # UTF-8 is fine for us
- bkey = key.encode('utf-8', 'surrogateescape')
- bvalue = os.environ[key].encode('utf-8', 'surrogateescape')
- os.environ[bkey] = bvalue
-
origord = builtins.ord
def fakeord(char):
if isinstance(char, int):
--- a/mercurial/revset.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/revset.py Mon Jun 09 13:53:23 2014 -0500
@@ -2764,10 +2764,6 @@
if self._start < self._end:
self.reverse()
- def _contained(self, rev):
- return (rev <= self._start and rev > self._end) or (rev >= self._start
- and rev < self._end)
-
def __iter__(self):
if self._start <= self._end:
iterrange = xrange(self._start, self._end)
@@ -2825,7 +2821,7 @@
start = self._start
end = self._end
for rev in self._hiddenrevs:
- if (end < rev <= start) or (start <= rev and rev < end):
+ if (end < rev <= start) or (start <= rev < end):
count += 1
return abs(self._end - self._start) - count
--- a/mercurial/scmutil.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/scmutil.py Mon Jun 09 13:53:23 2014 -0500
@@ -178,6 +178,9 @@
def islink(self, path=None):
return os.path.islink(self.join(path))
+ def lexists(self, path=None):
+ return os.path.lexists(self.join(path))
+
def lstat(self, path=None):
return os.lstat(self.join(path))
@@ -223,6 +226,9 @@
def unlink(self, path=None):
return util.unlink(self.join(path))
+ def unlinkpath(self, path=None, ignoremissing=False):
+ return util.unlinkpath(self.join(path), ignoremissing)
+
def utime(self, path=None, t=None):
return os.utime(self.join(path), t)
--- a/mercurial/simplemerge.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/simplemerge.py Mon Jun 09 13:53:23 2014 -0500
@@ -416,11 +416,11 @@
name_a = local
name_b = other
labels = opts.get('label', [])
- if labels:
- name_a = labels.pop(0)
- if labels:
- name_b = labels.pop(0)
- if labels:
+ if len(labels) > 0:
+ name_a = labels[0]
+ if len(labels) > 1:
+ name_b = labels[1]
+ if len(labels) > 2:
raise util.Abort(_("can only specify two labels."))
try:
--- a/mercurial/subrepo.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/subrepo.py Mon Jun 09 13:53:23 2014 -0500
@@ -205,12 +205,13 @@
sm[s] = r
else:
debug(s, "both sides changed")
+ srepo = wctx.sub(s)
option = repo.ui.promptchoice(
_(' subrepository %s diverged (local revision: %s, '
'remote revision: %s)\n'
'(M)erge, keep (l)ocal or keep (r)emote?'
'$$ &Merge $$ &Local $$ &Remote')
- % (s, l[1][:12], r[1][:12]), 0)
+ % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
if option == 0:
wctx.sub(s).merge(r)
sm[s] = l
@@ -502,6 +503,9 @@
% (substate[0], substate[2]))
return []
+ def shortid(self, revid):
+ return revid
+
class hgsubrepo(abstractsubrepo):
def __init__(self, ctx, path, state):
self._path = path
@@ -867,6 +871,9 @@
pats = []
cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
+ def shortid(self, revid):
+ return revid[:12]
+
class svnsubrepo(abstractsubrepo):
def __init__(self, ctx, path, state):
self._path = path
@@ -1563,6 +1570,9 @@
deleted = unknown = ignored = clean = []
return modified, added, removed, deleted, unknown, ignored, clean
+ def shortid(self, revid):
+ return revid[:7]
+
types = {
'hg': hgsubrepo,
'svn': svnsubrepo,
--- a/mercurial/templates/atom/changelogentry.tmpl Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/templates/atom/changelogentry.tmpl Mon Jun 09 13:53:23 2014 -0500
@@ -32,7 +32,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>{desc|strip|escape|addbreaks|nonempty}</td>
+ <td>{desc|strip|escape|websub|addbreaks|nonempty}</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
--- a/mercurial/templates/rss/changelogentry.tmpl Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/templates/rss/changelogentry.tmpl Mon Jun 09 13:53:23 2014 -0500
@@ -27,7 +27,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>{desc|strip|escape|addbreaks|nonempty}</td>
+ <td>{desc|strip|escape|websub|addbreaks|nonempty}</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
--- a/mercurial/templates/rss/filelogentry.tmpl Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/templates/rss/filelogentry.tmpl Mon Jun 09 13:53:23 2014 -0500
@@ -1,7 +1,7 @@
<item>
<title>{desc|strip|firstline|strip|escape}</title>
<link>{urlbase}{url|urlescape}log{node|short}/{file|urlescape}</link>
- <description><![CDATA[{desc|strip|escape|addbreaks|nonempty}]]></description>
+ <description><![CDATA[{desc|strip|escape|websub|addbreaks|nonempty}]]></description>
<author>{author|obfuscate}</author>
<pubDate>{date|rfc822date}</pubDate>
</item>
--- a/mercurial/wireproto.py Mon May 26 19:02:11 2014 +0200
+++ b/mercurial/wireproto.py Mon Jun 09 13:53:23 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:
@@ -627,12 +645,16 @@
@wireprotocommand('getbundle', '*')
def getbundle(repo, proto, others):
- opts = options('getbundle', gboptslist, others)
+ opts = options('getbundle', gboptsmap.keys(), others)
for k, v in opts.iteritems():
- if k in ('heads', 'common'):
+ keytype = gboptsmap[k]
+ if keytype == 'nodes':
opts[k] = decodelist(v)
- elif k == 'bundlecaps':
+ elif keytype == 'csv':
opts[k] = set(v.split(','))
+ elif keytype != 'plain':
+ raise KeyError('unknown getbundle option type %s'
+ % keytype)
cg = exchange.getbundle(repo, 'serve', **opts)
return streamres(proto.groupchunks(cg))
@@ -655,9 +677,7 @@
@wireprotocommand('listkeys', 'namespace')
def listkeys(repo, proto, namespace):
d = repo.listkeys(encoding.tolocal(namespace)).items()
- t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
- for k, v in d])
- return t
+ return pushkeymod.encodekeys(d)
@wireprotocommand('lookup', 'key')
def lookup(repo, proto, key):
@@ -809,11 +829,13 @@
finally:
fp.close()
os.unlink(tempname)
- except bundle2.UnknownPartError, exc:
+ except error.BundleValueError, exc:
bundler = bundle2.bundle20(repo.ui)
- part = bundle2.bundlepart('B2X:ERROR:UNKNOWNPART',
- [('parttype', str(exc))])
- bundler.addpart(part)
+ errpart = bundler.newpart('B2X:ERROR:UNSUPPORTEDCONTENT')
+ if exc.parttype is not None:
+ errpart.addparam('parttype', exc.parttype)
+ if exc.params:
+ errpart.addparam('params', '\0'.join(exc.params))
return streamres(bundler.getchunks())
except util.Abort, inst:
# The old code we moved used sys.stderr directly.
@@ -835,9 +857,7 @@
except error.PushRaced, exc:
if getattr(exc, 'duringunbundle2', False):
bundler = bundle2.bundle20(repo.ui)
- part = bundle2.bundlepart('B2X:ERROR:PUSHRACED',
- [('message', str(exc))])
- bundler.addpart(part)
+ bundler.newpart('B2X:ERROR:PUSHRACED', [('message', str(exc))])
return streamres(bundler.getchunks())
else:
return pusherr(str(exc))
--- a/setup.py Mon May 26 19:02:11 2014 +0200
+++ b/setup.py Mon Jun 09 13:53:23 2014 -0500
@@ -513,7 +513,7 @@
version = version[0]
xcode4 = (version.startswith('Xcode') and
StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
- xcode51 = re.match(r'^Xcode\s+5\.1\.', version) is not None
+ xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
else:
# xcodebuild returns empty on OS X Lion with XCode 4.3 not
# installed, but instead with only command-line tools. Assume
--- a/tests/autodiff.py Mon May 26 19:02:11 2014 +0200
+++ b/tests/autodiff.py Mon Jun 09 13:53:23 2014 -0500
@@ -1,8 +1,14 @@
# Extension dedicated to test patch.diff() upgrade modes
#
#
-from mercurial import scmutil, patch, util
+from mercurial import cmdutil, scmutil, patch, util
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+@command('autodiff',
+ [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)')],
+ '[OPTION]... [FILE]...')
def autodiff(ui, repo, *pats, **opts):
diffopts = patch.diffopts(ui, opts)
git = opts.get('git', 'no')
@@ -36,11 +42,3 @@
ui.write(chunk)
for fn in sorted(brokenfiles):
ui.write(('data lost for: %s\n' % fn))
-
-cmdtable = {
- "autodiff":
- (autodiff,
- [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)'),
- ],
- '[OPTION]... [FILE]...'),
-}
--- a/tests/filterpyflakes.py Mon May 26 19:02:11 2014 +0200
+++ b/tests/filterpyflakes.py Mon Jun 09 13:53:23 2014 -0500
@@ -29,12 +29,15 @@
for line in sys.stdin:
# We whitelist tests (see more messages in pyflakes.messages)
pats = [
- r"imported but unused",
- r"local variable '.*' is assigned to but never used",
- r"unable to detect undefined names",
+ (r"imported but unused", None),
+ (r"local variable '.*' is assigned to but never used", None),
+ (r"unable to detect undefined names", None),
+ (r"undefined name '.*'",
+ r"undefined name '(WindowsError|memoryview)'")
]
- for msgtype, pat in enumerate(pats):
- if re.search(pat, line):
+
+ for msgtype, (pat, excl) in enumerate(pats):
+ if re.search(pat, line) and (not excl or not re.search(excl, line)):
break # pattern matches
else:
continue # no pattern matched, next line
@@ -49,3 +52,7 @@
for msgtype, line in sorted(lines, key=makekey):
sys.stdout.write(line)
print
+
+# self test of "undefined name" detection for other than 'memoryview'
+if False:
+ print undefinedname
--- a/tests/run-tests.py Mon May 26 19:02:11 2014 +0200
+++ b/tests/run-tests.py Mon Jun 09 13:53:23 2014 -0500
@@ -57,6 +57,7 @@
import threading
import killdaemons as killmod
import Queue as queue
+import unittest
processlock = threading.Lock()
@@ -92,18 +93,12 @@
return p
-# reserved exit code to skip test (used by hghave)
-SKIPPED_STATUS = 80
-SKIPPED_PREFIX = 'skipped: '
-FAILED_PREFIX = 'hghave check failed: '
PYTHON = sys.executable.replace('\\', '/')
IMPL_PATH = 'PYTHONPATH'
if 'java' in sys.platform:
IMPL_PATH = 'JYTHONPATH'
-requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
- "gunzip", "bunzip2", "sed"]
-createdfiles = []
+TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
defaults = {
'jobs': ('HGTEST_JOBS', 1),
@@ -134,6 +129,7 @@
return entries
def getparser():
+ """Obtain the OptionParser used by the CLI."""
parser = optparse.OptionParser("%prog [options] [tests]")
# keep these sorted
@@ -214,6 +210,7 @@
return parser
def parseargs(args, parser):
+ """Parse arguments with our OptionParser and validate results."""
(options, args) = parser.parse_args(args)
# jython is always pure
@@ -285,47 +282,30 @@
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)
+ 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 +315,6 @@
sys.stdout.flush()
iolock.release()
-def findprogram(program):
- """Search PATH for a executable program"""
- for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
- name = os.path.join(p, program)
- if os.name == 'nt' or os.access(name, os.X_OK):
- return name
- return None
-
-def createhgrc(path, options):
- # create a fresh hgrc
- hgrc = open(path, 'w')
- hgrc.write('[ui]\n')
- hgrc.write('slash = True\n')
- hgrc.write('interactive = False\n')
- hgrc.write('[defaults]\n')
- hgrc.write('backout = -d "0 0"\n')
- hgrc.write('commit = -d "0 0"\n')
- hgrc.write('shelve = --date "0 0"\n')
- hgrc.write('tag = -d "0 0"\n')
- if options.extra_config_opt:
- for opt in options.extra_config_opt:
- section, key = opt.split('.', 1)
- assert '=' in key, ('extra config opt %s must '
- 'have an = for assignment' % opt)
- hgrc.write('[%s]\n%s\n' % (section, key))
- hgrc.close()
-
-def createenv(options, testtmp, threadtmp, port):
- env = os.environ.copy()
- env['TESTTMP'] = testtmp
- env['HOME'] = testtmp
- env["HGPORT"] = str(port)
- env["HGPORT1"] = str(port + 1)
- env["HGPORT2"] = str(port + 2)
- env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc')
- env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids')
- env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
- env["HGMERGE"] = "internal:merge"
- env["HGUSER"] = "test"
- env["HGENCODING"] = "ascii"
- env["HGENCODINGMODE"] = "strict"
-
- # Reset some environment variables to well-known values so that
- # the tests produce repeatable output.
- env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
- env['TZ'] = 'GMT'
- env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
- env['COLUMNS'] = '80'
- env['TERM'] = 'xterm'
-
- for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
- 'NO_PROXY').split():
- if k in env:
- del env[k]
-
- # unset env related to hooks
- for k in env.keys():
- if k.startswith('HG_'):
- del env[k]
-
- return env
-
-def checktools():
- # Before we go any further, check for pre-requisite tools
- # stuff from coreutils (cat, rm, etc) are not tested
- for p in requiredtools:
- if os.name == 'nt' and not p.endswith('.exe'):
- p += '.exe'
- found = findprogram(p)
- if found:
- vlog("# Found prerequisite", p, "at", found)
- else:
- print "WARNING: Did not find prerequisite tool: "+p
-
def terminate(proc):
"""Terminate subprocess (with fallback for Python versions < 2.6)"""
vlog('# Terminating process %d' % proc.pid)
@@ -421,264 +327,409 @@
return killmod.killdaemons(pidfile, tryhard=False, remove=True,
logfn=vlog)
-def cleanup(options):
- if not options.keep_tmpdir:
- vlog("# Cleaning up HGTMP", HGTMP)
- shutil.rmtree(HGTMP, True)
- for f in createdfiles:
- try:
- os.remove(f)
- except OSError:
- pass
+class Test(unittest.TestCase):
+ """Encapsulates a single, runnable test.
+
+ While this class conforms to the unittest.TestCase API, it differs in that
+ instances need to be instantiated manually. (Typically, unittest.TestCase
+ classes are instantiated automatically by scanning modules.)
+ """
+
+ # Status code reserved for skipped tests (used by hghave).
+ SKIPPED_STATUS = 80
+
+ def __init__(self, path, tmpdir, keeptmpdir=False,
+ debug=False,
+ timeout=defaults['timeout'],
+ startport=defaults['port'], extraconfigopts=None,
+ py3kwarnings=False, shell=None):
+ """Create a test from parameters.
-def usecorrectpython():
- # some tests run python interpreter. they must use same
- # interpreter we use or bad things will happen.
- pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
- if getattr(os, 'symlink', None):
- vlog("# Making python executable in test path a symlink to '%s'" %
- sys.executable)
- mypython = os.path.join(TMPBINDIR, pyexename)
- try:
- if os.readlink(mypython) == sys.executable:
- return
- os.unlink(mypython)
- except OSError, err:
- if err.errno != errno.ENOENT:
- raise
- if findprogram(pyexename) != sys.executable:
- try:
- os.symlink(sys.executable, mypython)
- createdfiles.append(mypython)
- except OSError, err:
- # child processes may race, which is harmless
- if err.errno != errno.EEXIST:
- raise
- else:
- exedir, exename = os.path.split(sys.executable)
- vlog("# Modifying search path to find %s as %s in '%s'" %
- (exename, pyexename, exedir))
- path = os.environ['PATH'].split(os.pathsep)
- while exedir in path:
- path.remove(exedir)
- os.environ['PATH'] = os.pathsep.join([exedir] + path)
- if not findprogram(pyexename):
- print "WARNING: Cannot find %s in search path" % pyexename
+ path is the full path to the file defining the test.
+
+ tmpdir is the main temporary directory to use for this test.
+
+ keeptmpdir determines whether to keep the test's temporary directory
+ after execution. It defaults to removal (False).
-def installhg(options):
- vlog("# Performing temporary installation of HG")
- installerrs = os.path.join("tests", "install.err")
- compiler = ''
- if options.compiler:
- compiler = '--compiler ' + options.compiler
- pure = options.pure and "--pure" or ""
- py3 = ''
- if sys.version_info[0] == 3:
- py3 = '--c2to3'
+ debug mode will make the test execute verbosely, with unfiltered
+ output.
+
+ timeout controls the maximum run time of the test. It is ignored when
+ debug is True.
+
+ startport controls the starting port number to use for this test. Each
+ test will reserve 3 port numbers for execution. It is the caller's
+ responsibility to allocate a non-overlapping port range to Test
+ instances.
- # Run installer in hg root
- script = os.path.realpath(sys.argv[0])
- hgroot = os.path.dirname(os.path.dirname(script))
- os.chdir(hgroot)
- nohome = '--home=""'
- if os.name == 'nt':
- # The --home="" trick works only on OS where os.sep == '/'
- # because of a distutils convert_path() fast-path. Avoid it at
- # least on Windows for now, deal with .pydistutils.cfg bugs
- # when they happen.
- nohome = ''
- cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
- ' build %(compiler)s --build-base="%(base)s"'
- ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
- ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
- % {'exe': sys.executable, 'py3': py3, 'pure': pure,
- 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
- 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
- 'nohome': nohome, 'logfile': installerrs})
- vlog("# Running", cmd)
- if os.system(cmd) == 0:
- if not options.verbose:
- os.remove(installerrs)
- else:
- f = open(installerrs)
- for line in f:
- print line,
- f.close()
- sys.exit(1)
- os.chdir(TESTDIR)
+ extraconfigopts is an iterable of extra hgrc config options. Values
+ must have the form "key=value" (something understood by hgrc). Values
+ of the form "foo.key=value" will result in "[foo] key=value".
+
+ py3kwarnings enables Py3k warnings.
+
+ shell is the shell to execute tests in.
+ """
+
+ self.path = path
+ self.name = os.path.basename(path)
+ self._testdir = os.path.dirname(path)
+ self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
- usecorrectpython()
-
- if options.py3k_warnings and not options.anycoverage:
- vlog("# Updating hg command to enable Py3k Warnings switch")
- f = open(os.path.join(BINDIR, 'hg'), 'r')
- lines = [line.rstrip() for line in f]
- lines[0] += ' -3'
- f.close()
- f = open(os.path.join(BINDIR, 'hg'), 'w')
- for line in lines:
- f.write(line + '\n')
- f.close()
+ self._threadtmp = tmpdir
+ self._keeptmpdir = keeptmpdir
+ self._debug = debug
+ self._timeout = timeout
+ self._startport = startport
+ self._extraconfigopts = extraconfigopts or []
+ self._py3kwarnings = py3kwarnings
+ self._shell = shell
- hgbat = os.path.join(BINDIR, 'hg.bat')
- if os.path.isfile(hgbat):
- # hg.bat expects to be put in bin/scripts while run-tests.py
- # installation layout put it in bin/ directly. Fix it
- f = open(hgbat, 'rb')
- data = f.read()
- f.close()
- if '"%~dp0..\python" "%~dp0hg" %*' in data:
- data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
- '"%~dp0python" "%~dp0hg" %*')
- f = open(hgbat, 'wb')
- f.write(data)
+ self._aborted = False
+ self._daemonpids = []
+ self._finished = None
+ self._ret = None
+ self._out = None
+ self._skipped = None
+ self._testtmp = None
+
+ # If we're not in --debug mode and reference output file exists,
+ # check test output against it.
+ if debug:
+ self._refout = None # to match "out is None"
+ elif os.path.exists(self.refpath):
+ f = open(self.refpath, 'r')
+ self._refout = f.read().splitlines(True)
f.close()
else:
- print 'WARNING: cannot fix hg.bat reference to python.exe'
+ self._refout = []
+
+ def __str__(self):
+ return self.name
+
+ def shortDescription(self):
+ return self.name
- if options.anycoverage:
- custom = os.path.join(TESTDIR, 'sitecustomize.py')
- target = os.path.join(PYTHONDIR, 'sitecustomize.py')
- vlog('# Installing coverage trigger to %s' % target)
- shutil.copyfile(custom, target)
- rc = os.path.join(TESTDIR, '.coveragerc')
- vlog('# Installing coverage rc to %s' % rc)
- os.environ['COVERAGE_PROCESS_START'] = rc
- fn = os.path.join(INST, '..', '.coverage')
- os.environ['COVERAGE_FILE'] = fn
+ def setUp(self):
+ """Tasks to perform before run()."""
+ self._finished = False
+ self._ret = None
+ self._out = None
+ self._skipped = None
+
+ try:
+ os.mkdir(self._threadtmp)
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ self._testtmp = os.path.join(self._threadtmp,
+ os.path.basename(self.path))
+ os.mkdir(self._testtmp)
+
+ # Remove any previous output files.
+ if os.path.exists(self.errpath):
+ os.remove(self.errpath)
-def outputtimes(options):
- vlog('# Producing time report')
- times.sort(key=lambda t: (t[1], t[0]), reverse=True)
- cols = '%7.3f %s'
- print '\n%-7s %s' % ('Time', 'Test')
- for test, timetaken in times:
- print cols % (timetaken, test)
+ def run(self, result):
+ """Run this test and report results against a TestResult instance."""
+ # This function is extremely similar to unittest.TestCase.run(). Once
+ # we require Python 2.7 (or at least its version of unittest), this
+ # function can largely go away.
+ self._result = result
+ result.startTest(self)
+ try:
+ try:
+ self.setUp()
+ except (KeyboardInterrupt, SystemExit):
+ self._aborted = True
+ raise
+ except Exception:
+ result.addError(self, sys.exc_info())
+ return
-def outputcoverage(options):
-
- vlog('# Producing coverage report')
- os.chdir(PYTHONDIR)
+ success = False
+ try:
+ self.runTest()
+ except KeyboardInterrupt:
+ self._aborted = True
+ raise
+ except SkipTest, e:
+ result.addSkip(self, str(e))
+ except IgnoreTest, e:
+ result.addIgnore(self, str(e))
+ except WarnTest, e:
+ result.addWarn(self, str(e))
+ except self.failureException, e:
+ # This differs from unittest in that we don't capture
+ # the stack trace. This is for historical reasons and
+ # this decision could be revisted in the future,
+ # especially for PythonTest instances.
+ result.addFailure(self, str(e))
+ except Exception:
+ result.addError(self, sys.exc_info())
+ else:
+ success = True
- def covrun(*args):
- cmd = 'coverage %s' % ' '.join(args)
- vlog('# Running: %s' % cmd)
- os.system(cmd)
+ try:
+ self.tearDown()
+ except (KeyboardInterrupt, SystemExit):
+ self._aborted = True
+ raise
+ except Exception:
+ result.addError(self, sys.exc_info())
+ success = False
- covrun('-c')
- omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
- covrun('-i', '-r', '"--omit=%s"' % omit) # report
- if options.htmlcov:
- htmldir = os.path.join(TESTDIR, 'htmlcov')
- covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
- if options.annotate:
- adir = os.path.join(TESTDIR, 'annotated')
- if not os.path.isdir(adir):
- os.mkdir(adir)
- covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
+ if success:
+ result.addSuccess(self)
+ finally:
+ result.stopTest(self, interrupted=self._aborted)
+
+ def runTest(self):
+ """Run this test instance.
+
+ This will return a tuple describing the result of the test.
+ """
+ replacements = self._getreplacements()
+ env = self._getenv()
+ self._daemonpids.append(env['DAEMON_PIDS'])
+ self._createhgrc(env['HGRCPATH'])
+
+ vlog('# Test', self.name)
+
+ ret, out = self._run(replacements, env)
+ self._finished = True
+ self._ret = ret
+ self._out = out
+
+ def describe(ret):
+ if ret < 0:
+ return 'killed by signal: %d' % -ret
+ return 'returned error code %d' % ret
+
+ self._skipped = False
+
+ if ret == self.SKIPPED_STATUS:
+ if out is None: # Debug mode, nothing to parse.
+ missing = ['unknown']
+ failed = None
+ else:
+ missing, failed = TTest.parsehghaveoutput(out)
+
+ if not missing:
+ missing = ['irrelevant']
-def pytest(test, wd, options, replacements, env):
- py3kswitch = options.py3k_warnings and ' -3' or ''
- cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
- vlog("# Running", cmd)
- if os.name == 'nt':
- replacements.append((r'\r\n', '\n'))
- return run(cmd, wd, options, replacements, env)
+ if failed:
+ self.fail('hg have failed checking for %s' % failed[-1])
+ else:
+ self._skipped = True
+ raise SkipTest(missing[-1])
+ elif ret == 'timeout':
+ self.fail('timed out')
+ elif ret is False:
+ raise WarnTest('no result code from test')
+ elif out != self._refout:
+ # Diff generation may rely on written .err file.
+ if (ret != 0 or out != self._refout) and not self._skipped \
+ and not self._debug:
+ f = open(self.errpath, 'wb')
+ for line in out:
+ f.write(line)
+ f.close()
+
+ # The result object handles diff calculation for us.
+ self._result.addOutputMismatch(self, ret, out, self._refout)
-needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
-escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
-escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
-escapemap.update({'\\': '\\\\', '\r': r'\r'})
-def escapef(m):
- return escapemap[m.group(0)]
-def stringescape(s):
- return escapesub(escapef, s)
+ if ret:
+ msg = 'output changed and ' + describe(ret)
+ else:
+ msg = 'output changed'
+
+ self.fail(msg)
+ elif ret:
+ self.fail(describe(ret))
-def rematch(el, l):
- try:
- # use \Z to ensure that the regex matches to the end of the string
- if os.name == 'nt':
- return re.match(el + r'\r?\n\Z', l)
- return re.match(el + r'\n\Z', l)
- except re.error:
- # el is an invalid regex
- return False
+ def tearDown(self):
+ """Tasks to perform after run()."""
+ for entry in self._daemonpids:
+ killdaemons(entry)
+ self._daemonpids = []
+
+ if not self._keeptmpdir:
+ shutil.rmtree(self._testtmp, True)
+ shutil.rmtree(self._threadtmp, True)
+
+ if (self._ret != 0 or self._out != self._refout) and not self._skipped \
+ and not self._debug and self._out:
+ f = open(self.errpath, 'wb')
+ for line in self._out:
+ f.write(line)
+ f.close()
-def globmatch(el, l):
- # The only supported special characters are * and ? plus / which also
- # matches \ on windows. Escaping of these characters is supported.
- if el + '\n' == l:
- if os.altsep:
- # matching on "/" is not needed for this line
- return '-glob'
- return True
- i, n = 0, len(el)
- res = ''
- while i < n:
- c = el[i]
- i += 1
- if c == '\\' and el[i] in '*?\\/':
- res += el[i - 1:i + 1]
- i += 1
- elif c == '*':
- res += '.*'
- elif c == '?':
- res += '.'
- elif c == '/' and os.altsep:
- res += '[/\\\\]'
+ vlog("# Ret was:", self._ret)
+
+ def _run(self, replacements, env):
+ # This should be implemented in child classes to run tests.
+ raise SkipTest('unknown test type')
+
+ def abort(self):
+ """Terminate execution of this test."""
+ self._aborted = True
+
+ def _getreplacements(self):
+ """Obtain a mapping of text replacements to apply to test output.
+
+ Test output needs to be normalized so it can be compared to expected
+ output. This function defines how some of that normalization will
+ occur.
+ """
+ r = [
+ (r':%s\b' % self._startport, ':$HGPORT'),
+ (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
+ (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
+ ]
+
+ if os.name == 'nt':
+ r.append(
+ (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
+ c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
+ for c in self._testtmp), '$TESTTMP'))
else:
- res += re.escape(c)
- return rematch(res, l)
+ r.append((re.escape(self._testtmp), '$TESTTMP'))
+
+ return r
+
+ def _getenv(self):
+ """Obtain environment variables to use during test execution."""
+ env = os.environ.copy()
+ env['TESTTMP'] = self._testtmp
+ env['HOME'] = self._testtmp
+ env["HGPORT"] = str(self._startport)
+ env["HGPORT1"] = str(self._startport + 1)
+ env["HGPORT2"] = str(self._startport + 2)
+ env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
+ env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
+ env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
+ env["HGMERGE"] = "internal:merge"
+ env["HGUSER"] = "test"
+ env["HGENCODING"] = "ascii"
+ env["HGENCODINGMODE"] = "strict"
+
+ # Reset some environment variables to well-known values so that
+ # the tests produce repeatable output.
+ env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
+ env['TZ'] = 'GMT'
+ env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
+ env['COLUMNS'] = '80'
+ env['TERM'] = 'xterm'
+
+ for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
+ 'NO_PROXY').split():
+ if k in env:
+ del env[k]
+
+ # unset env related to hooks
+ for k in env.keys():
+ if k.startswith('HG_'):
+ del env[k]
+
+ return env
-def linematch(el, l):
- if el == l: # perfect match (fast)
- return True
- if el:
- if el.endswith(" (esc)\n"):
- el = el[:-7].decode('string-escape') + '\n'
- if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
- return True
- if el.endswith(" (re)\n"):
- return rematch(el[:-6], l)
- if el.endswith(" (glob)\n"):
- return globmatch(el[:-8], l)
- if os.altsep and l.replace('\\', '/') == el:
- return '+glob'
- return False
+ def _createhgrc(self, path):
+ """Create an hgrc file for this test."""
+ hgrc = open(path, 'w')
+ hgrc.write('[ui]\n')
+ hgrc.write('slash = True\n')
+ hgrc.write('interactive = False\n')
+ hgrc.write('[defaults]\n')
+ hgrc.write('backout = -d "0 0"\n')
+ hgrc.write('commit = -d "0 0"\n')
+ hgrc.write('shelve = --date "0 0"\n')
+ hgrc.write('tag = -d "0 0"\n')
+ for opt in self._extraconfigopts:
+ section, key = opt.split('.', 1)
+ assert '=' in key, ('extra config opt %s must '
+ 'have an = for assignment' % opt)
+ hgrc.write('[%s]\n%s\n' % (section, key))
+ hgrc.close()
+
+ def fail(self, msg):
+ # unittest differentiates between errored and failed.
+ # Failed is denoted by AssertionError (by default at least).
+ raise AssertionError(msg)
+
+class PythonTest(Test):
+ """A Python-based test."""
+
+ @property
+ def refpath(self):
+ return os.path.join(self._testdir, '%s.out' % self.name)
+
+ def _run(self, replacements, env):
+ py3kswitch = self._py3kwarnings and ' -3' or ''
+ cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
+ vlog("# Running", cmd)
+ if os.name == 'nt':
+ replacements.append((r'\r\n', '\n'))
+ result = run(cmd, self._testtmp, replacements, env,
+ debug=self._debug, timeout=self._timeout)
+ if self._aborted:
+ raise KeyboardInterrupt()
+
+ return result
+
+class TTest(Test):
+ """A "t test" is a test backed by a .t file."""
-def tsttest(test, wd, options, replacements, env):
- # We generate a shell script which outputs unique markers to line
- # up script results with our source. These markers include input
- # line number and the last return code
- salt = "SALT" + str(time.time())
- def addsalt(line, inpython):
- if inpython:
- script.append('%s %d 0\n' % (salt, line))
- else:
- script.append('echo %s %s $?\n' % (salt, line))
+ SKIPPED_PREFIX = 'skipped: '
+ FAILED_PREFIX = 'hghave check failed: '
+ NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
+
+ ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
+ ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
+ ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
+
+ @property
+ def refpath(self):
+ return os.path.join(self._testdir, self.name)
+
+ def _run(self, replacements, env):
+ f = open(self.path)
+ lines = f.readlines()
+ f.close()
+
+ salt, script, after, expected = self._parsetest(lines)
- # After we run the shell script, we re-unify the script output
- # with non-active parts of the source, with synchronization by our
- # SALT line number markers. The after table contains the
- # non-active components, ordered by line number
- after = {}
- pos = prepos = -1
+ # Write out the generated script.
+ fname = '%s.sh' % self._testtmp
+ f = open(fname, 'w')
+ for l in script:
+ f.write(l)
+ f.close()
- # Expected shell script output
- expected = {}
+ cmd = '%s "%s"' % (self._shell, fname)
+ vlog("# Running", cmd)
+
+ exitcode, output = run(cmd, self._testtmp, replacements, env,
+ debug=self._debug, timeout=self._timeout)
- # We keep track of whether or not we're in a Python block so we
- # can generate the surrounding doctest magic
- inpython = False
+ if self._aborted:
+ raise KeyboardInterrupt()
+
+ # Do not merge output if skipped. Return hghave message instead.
+ # Similarly, with --debug, output is None.
+ if exitcode == self.SKIPPED_STATUS or output is None:
+ return exitcode, output
- # True or False when in a true or false conditional section
- skipping = None
+ return self._processoutput(exitcode, output, salt, after, expected)
- def hghave(reqs):
- # TODO: do something smarter when all other uses of hghave is gone
- tdir = TESTDIR.replace('\\', '/')
+ def _hghave(self, reqs):
+ # TODO do something smarter when all other uses of hghave are gone.
+ tdir = self._testdir.replace('\\', '/')
proc = Popen4('%s -c "%s/hghave %s"' %
- (options.shell, tdir, ' '.join(reqs)), wd, 0)
+ (self._shell, tdir, ' '.join(reqs)),
+ self._testtmp, 0)
stdout, stderr = proc.communicate()
ret = proc.wait()
if wifexited(ret):
@@ -686,172 +737,271 @@
if ret == 2:
print stdout
sys.exit(1)
+
return ret == 0
- f = open(test)
- t = f.readlines()
- f.close()
+ def _parsetest(self, lines):
+ # We generate a shell script which outputs unique markers to line
+ # up script results with our source. These markers include input
+ # line number and the last return code.
+ salt = "SALT" + str(time.time())
+ def addsalt(line, inpython):
+ if inpython:
+ script.append('%s %d 0\n' % (salt, line))
+ else:
+ script.append('echo %s %s $?\n' % (salt, line))
+
+ script = []
+
+ # After we run the shell script, we re-unify the script output
+ # with non-active parts of the source, with synchronization by our
+ # SALT line number markers. The after table contains the non-active
+ # components, ordered by line number.
+ after = {}
+
+ # Expected shell script output.
+ expected = {}
+
+ pos = prepos = -1
+
+ # True or False when in a true or false conditional section
+ skipping = None
+
+ # We keep track of whether or not we're in a Python block so we
+ # can generate the surrounding doctest magic.
+ inpython = False
+
+ if self._debug:
+ script.append('set -x\n')
+ if os.getenv('MSYSTEM'):
+ script.append('alias pwd="pwd -W"\n')
- script = []
- if options.debug:
- script.append('set -x\n')
- if os.getenv('MSYSTEM'):
- script.append('alias pwd="pwd -W"\n')
- n = 0
- for n, l in enumerate(t):
- if not l.endswith('\n'):
- l += '\n'
- if l.startswith('#if'):
- lsplit = l.split()
- if len(lsplit) < 2 or lsplit[0] != '#if':
- after.setdefault(pos, []).append(' !!! invalid #if\n')
- if skipping is not None:
- after.setdefault(pos, []).append(' !!! nested #if\n')
- skipping = not hghave(lsplit[1:])
- after.setdefault(pos, []).append(l)
- elif l.startswith('#else'):
- if skipping is None:
- after.setdefault(pos, []).append(' !!! missing #if\n')
- skipping = not skipping
- after.setdefault(pos, []).append(l)
- elif l.startswith('#endif'):
- if skipping is None:
- after.setdefault(pos, []).append(' !!! missing #if\n')
- skipping = None
- after.setdefault(pos, []).append(l)
- elif skipping:
- after.setdefault(pos, []).append(l)
- elif l.startswith(' >>> '): # python inlines
- after.setdefault(pos, []).append(l)
- prepos = pos
- pos = n
- if not inpython:
- # we've just entered a Python block, add the header
- inpython = True
- addsalt(prepos, False) # make sure we report the exit code
- script.append('%s -m heredoctest <<EOF\n' % PYTHON)
- addsalt(n, True)
- script.append(l[2:])
- elif l.startswith(' ... '): # python inlines
- after.setdefault(prepos, []).append(l)
- script.append(l[2:])
- elif l.startswith(' $ '): # commands
- if inpython:
- script.append("EOF\n")
- inpython = False
- after.setdefault(pos, []).append(l)
- prepos = pos
- pos = n
- addsalt(n, False)
- cmd = l[4:].split()
- if len(cmd) == 2 and cmd[0] == 'cd':
- l = ' $ cd %s || exit 1\n' % cmd[1]
- script.append(l[4:])
- elif l.startswith(' > '): # continuations
- after.setdefault(prepos, []).append(l)
- script.append(l[4:])
- elif l.startswith(' '): # results
- # queue up a list of expected results
- expected.setdefault(pos, []).append(l[2:])
- else:
- if inpython:
- script.append("EOF\n")
- inpython = False
- # non-command/result - queue up for merged output
- after.setdefault(pos, []).append(l)
+ for n, l in enumerate(lines):
+ if not l.endswith('\n'):
+ l += '\n'
+ if l.startswith('#if'):
+ lsplit = l.split()
+ if len(lsplit) < 2 or lsplit[0] != '#if':
+ after.setdefault(pos, []).append(' !!! invalid #if\n')
+ if skipping is not None:
+ after.setdefault(pos, []).append(' !!! nested #if\n')
+ skipping = not self._hghave(lsplit[1:])
+ after.setdefault(pos, []).append(l)
+ elif l.startswith('#else'):
+ if skipping is None:
+ after.setdefault(pos, []).append(' !!! missing #if\n')
+ skipping = not skipping
+ after.setdefault(pos, []).append(l)
+ elif l.startswith('#endif'):
+ if skipping is None:
+ after.setdefault(pos, []).append(' !!! missing #if\n')
+ skipping = None
+ after.setdefault(pos, []).append(l)
+ elif skipping:
+ after.setdefault(pos, []).append(l)
+ elif l.startswith(' >>> '): # python inlines
+ after.setdefault(pos, []).append(l)
+ prepos = pos
+ pos = n
+ if not inpython:
+ # We've just entered a Python block. Add the header.
+ inpython = True
+ addsalt(prepos, False) # Make sure we report the exit code.
+ script.append('%s -m heredoctest <<EOF\n' % PYTHON)
+ addsalt(n, True)
+ script.append(l[2:])
+ elif l.startswith(' ... '): # python inlines
+ after.setdefault(prepos, []).append(l)
+ script.append(l[2:])
+ elif l.startswith(' $ '): # commands
+ if inpython:
+ script.append('EOF\n')
+ inpython = False
+ after.setdefault(pos, []).append(l)
+ prepos = pos
+ pos = n
+ addsalt(n, False)
+ cmd = l[4:].split()
+ if len(cmd) == 2 and cmd[0] == 'cd':
+ l = ' $ cd %s || exit 1\n' % cmd[1]
+ script.append(l[4:])
+ elif l.startswith(' > '): # continuations
+ after.setdefault(prepos, []).append(l)
+ script.append(l[4:])
+ elif l.startswith(' '): # results
+ # Queue up a list of expected results.
+ expected.setdefault(pos, []).append(l[2:])
+ else:
+ if inpython:
+ script.append('EOF\n')
+ inpython = False
+ # Non-command/result. Queue up for merged output.
+ after.setdefault(pos, []).append(l)
+
+ if inpython:
+ script.append('EOF\n')
+ if skipping is not None:
+ after.setdefault(pos, []).append(' !!! missing #endif\n')
+ addsalt(n + 1, False)
+
+ return salt, script, after, expected
+
+ def _processoutput(self, exitcode, output, salt, after, expected):
+ # Merge the script output back into a unified test.
+ warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
+ if exitcode != 0:
+ warnonly = 3
+
+ pos = -1
+ postout = []
+ for l in output:
+ lout, lcmd = l, None
+ if salt in l:
+ lout, lcmd = l.split(salt, 1)
+
+ if lout:
+ if not lout.endswith('\n'):
+ lout += ' (no-eol)\n'
- if inpython:
- script.append("EOF\n")
- if skipping is not None:
- after.setdefault(pos, []).append(' !!! missing #endif\n')
- addsalt(n + 1, False)
+ # Find the expected output at the current position.
+ el = None
+ if expected.get(pos, None):
+ el = expected[pos].pop(0)
- # Write out the script and execute it
- name = wd + '.sh'
- f = open(name, 'w')
- for l in script:
- f.write(l)
- f.close()
+ r = TTest.linematch(el, lout)
+ if isinstance(r, str):
+ if r == '+glob':
+ lout = el[:-1] + ' (glob)\n'
+ r = '' # Warn only this line.
+ elif r == '-glob':
+ lout = ''.join(el.rsplit(' (glob)', 1))
+ r = '' # Warn only this line.
+ else:
+ log('\ninfo, unknown linematch result: %r\n' % r)
+ r = False
+ if r:
+ postout.append(' ' + el)
+ else:
+ if self.NEEDESCAPE(lout):
+ lout = TTest._stringescape('%s (esc)\n' %
+ lout.rstrip('\n'))
+ postout.append(' ' + lout) # Let diff deal with it.
+ if r != '': # If line failed.
+ warnonly = 3 # for sure not
+ elif warnonly == 1: # Is "not yet" and line is warn only.
+ warnonly = 2 # Yes do warn.
- cmd = '%s "%s"' % (options.shell, name)
- vlog("# Running", cmd)
- exitcode, output = run(cmd, wd, options, replacements, env)
- # do not merge output if skipped, return hghave message instead
- # similarly, with --debug, output is None
- if exitcode == SKIPPED_STATUS or output is None:
- return exitcode, output
+ if lcmd:
+ # Add on last return code.
+ ret = int(lcmd.split()[1])
+ if ret != 0:
+ postout.append(' [%s]\n' % ret)
+ if pos in after:
+ # Merge in non-active test bits.
+ postout += after.pop(pos)
+ pos = int(lcmd.split()[0])
- # Merge the script output back into a unified test
+ if pos in after:
+ postout += after.pop(pos)
- warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
- if exitcode != 0: # failure has been reported
- warnonly = 3 # set to "for sure not"
- pos = -1
- postout = []
- for l in output:
- lout, lcmd = l, None
- if salt in l:
- lout, lcmd = l.split(salt, 1)
+ if warnonly == 2:
+ exitcode = False # Set exitcode to warned.
+
+ return exitcode, postout
- if lout:
- if not lout.endswith('\n'):
- lout += ' (no-eol)\n'
+ @staticmethod
+ def rematch(el, l):
+ try:
+ # use \Z to ensure that the regex matches to the end of the string
+ if os.name == 'nt':
+ return re.match(el + r'\r?\n\Z', l)
+ return re.match(el + r'\n\Z', l)
+ except re.error:
+ # el is an invalid regex
+ return False
- # find the expected output at the current position
- el = None
- if pos in expected and expected[pos]:
- el = expected[pos].pop(0)
-
- r = linematch(el, lout)
- if isinstance(r, str):
- if r == '+glob':
- lout = el[:-1] + ' (glob)\n'
- r = '' # warn only this line
- elif r == '-glob':
- lout = ''.join(el.rsplit(' (glob)', 1))
- r = '' # warn only this line
- else:
- log('\ninfo, unknown linematch result: %r\n' % r)
- r = False
- if r:
- postout.append(" " + el)
+ @staticmethod
+ def globmatch(el, l):
+ # The only supported special characters are * and ? plus / which also
+ # matches \ on windows. Escaping of these characters is supported.
+ if el + '\n' == l:
+ if os.altsep:
+ # matching on "/" is not needed for this line
+ return '-glob'
+ return True
+ i, n = 0, len(el)
+ res = ''
+ while i < n:
+ c = el[i]
+ i += 1
+ if c == '\\' and el[i] in '*?\\/':
+ res += el[i - 1:i + 1]
+ i += 1
+ elif c == '*':
+ res += '.*'
+ elif c == '?':
+ res += '.'
+ elif c == '/' and os.altsep:
+ res += '[/\\\\]'
else:
- if needescape(lout):
- lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
- postout.append(" " + lout) # let diff deal with it
- if r != '': # if line failed
- warnonly = 3 # set to "for sure not"
- elif warnonly == 1: # is "not yet" (and line is warn only)
- warnonly = 2 # set to "yes" do warn
+ res += re.escape(c)
+ return TTest.rematch(res, l)
+
+ @staticmethod
+ def linematch(el, l):
+ if el == l: # perfect match (fast)
+ return True
+ if el:
+ if el.endswith(" (esc)\n"):
+ el = el[:-7].decode('string-escape') + '\n'
+ if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
+ return True
+ if el.endswith(" (re)\n"):
+ return TTest.rematch(el[:-6], l)
+ if el.endswith(" (glob)\n"):
+ return TTest.globmatch(el[:-8], l)
+ if os.altsep and l.replace('\\', '/') == el:
+ return '+glob'
+ return False
+
+ @staticmethod
+ def parsehghaveoutput(lines):
+ '''Parse hghave log lines.
- if lcmd:
- # add on last return code
- ret = int(lcmd.split()[1])
- if ret != 0:
- postout.append(" [%s]\n" % ret)
- if pos in after:
- # merge in non-active test bits
- postout += after.pop(pos)
- pos = int(lcmd.split()[0])
+ Return tuple of lists (missing, failed):
+ * the missing/unknown features
+ * the features for which existence check failed'''
+ missing = []
+ failed = []
+ for line in lines:
+ if line.startswith(TTest.SKIPPED_PREFIX):
+ line = line.splitlines()[0]
+ missing.append(line[len(TTest.SKIPPED_PREFIX):])
+ elif line.startswith(TTest.FAILED_PREFIX):
+ line = line.splitlines()[0]
+ failed.append(line[len(TTest.FAILED_PREFIX):])
- if pos in after:
- postout += after.pop(pos)
+ return missing, failed
- if warnonly == 2:
- exitcode = False # set exitcode to warned
- return exitcode, postout
+ @staticmethod
+ def _escapef(m):
+ return TTest.ESCAPEMAP[m.group(0)]
+
+ @staticmethod
+ def _stringescape(s):
+ return TTest.ESCAPESUB(TTest._escapef, s)
+
wifexited = getattr(os, "WIFEXITED", lambda x: False)
-def run(cmd, wd, options, replacements, env):
+def run(cmd, wd, replacements, env, debug=False, timeout=None):
"""Run command in a sub-process, capturing the output (stdout and stderr).
Return a tuple (exitcode, output). output is None in debug mode."""
- # TODO: Use subprocess.Popen if we're running on Python 2.4
- if options.debug:
+ if debug:
proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
ret = proc.wait()
return (ret, None)
- proc = Popen4(cmd, wd, options.timeout, env)
+ proc = Popen4(cmd, wd, timeout, env)
def cleanup():
terminate(proc)
ret = proc.wait()
@@ -880,442 +1030,764 @@
if ret:
killdaemons(env['DAEMON_PIDS'])
- if abort:
- raise KeyboardInterrupt()
-
for s, r in replacements:
output = re.sub(s, r, output)
return ret, output.splitlines(True)
-def runone(options, test, count):
- '''returns a result element: (code, test, msg)'''
+iolock = threading.Lock()
+
+class SkipTest(Exception):
+ """Raised to indicate that a test is to be skipped."""
+
+class IgnoreTest(Exception):
+ """Raised to indicate that a test is to be ignored."""
+
+class WarnTest(Exception):
+ """Raised to indicate that a test warned."""
+
+class TestResult(unittest._TextTestResult):
+ """Holds results when executing via unittest."""
+ # Don't worry too much about accessing the non-public _TextTestResult.
+ # It is relatively common in Python testing tools.
+ def __init__(self, options, *args, **kwargs):
+ super(TestResult, self).__init__(*args, **kwargs)
+
+ self._options = options
- def skip(msg):
- if options.verbose:
- log("\nSkipping %s: %s" % (testpath, msg))
- return 's', test, msg
+ # unittest.TestResult didn't have skipped until 2.7. We need to
+ # polyfill it.
+ self.skipped = []
+
+ # We have a custom "ignored" result that isn't present in any Python
+ # unittest implementation. It is very similar to skipped. It may make
+ # sense to map it into skip some day.
+ self.ignored = []
+
+ # We have a custom "warned" result that isn't present in any Python
+ # unittest implementation. It is very similar to failed. It may make
+ # sense to map it into fail some day.
+ self.warned = []
+
+ self.times = []
+ self._started = {}
+
+ def addFailure(self, test, reason):
+ self.failures.append((test, reason))
+
+ if self._options.first:
+ self.stop()
- 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
+ def addError(self, *args, **kwargs):
+ super(TestResult, self).addError(*args, **kwargs)
+
+ if self._options.first:
+ self.stop()
+
+ # Polyfill.
+ def addSkip(self, test, reason):
+ self.skipped.append((test, reason))
+
+ if self.showAll:
+ self.stream.writeln('skipped %s' % reason)
+ else:
+ self.stream.write('s')
+ self.stream.flush()
+
+ def addIgnore(self, test, reason):
+ self.ignored.append((test, reason))
- def success():
- return '.', test, ''
+ if self.showAll:
+ self.stream.writeln('ignored %s' % reason)
+ else:
+ self.stream.write('i')
+ self.stream.flush()
- def ignore(msg):
- return 'i', test, msg
+ def addWarn(self, test, reason):
+ self.warned.append((test, reason))
- def describe(ret):
- if ret < 0:
- return 'killed by signal %d' % -ret
- return 'returned error code %d' % ret
+ if self._options.first:
+ self.stop()
- testpath = os.path.join(TESTDIR, test)
- err = os.path.join(TESTDIR, test + ".err")
- lctest = test.lower()
+ if self.showAll:
+ self.stream.writeln('warned %s' % reason)
+ else:
+ self.stream.write('~')
+ self.stream.flush()
- if not os.path.exists(testpath):
- return skip("doesn't exist")
+ def addOutputMismatch(self, test, ret, got, expected):
+ """Record a mismatch in test output for a particular test."""
- if not (options.whitelisted and test in options.whitelisted):
- if options.blacklist and test in options.blacklist:
- return skip("blacklisted")
+ if self._options.nodiff:
+ return
- if options.retest and not os.path.exists(test + ".err"):
- return ignore("not retesting")
+ if self._options.view:
+ os.system("%s %s %s" % (self._view, test.refpath, test.errpath))
+ else:
+ failed, lines = getdiff(expected, got,
+ test.refpath, test.errpath)
+ if failed:
+ self.addFailure(test, 'diff generation failed')
+ else:
+ self.stream.write('\n')
+ for line in lines:
+ self.stream.write(line)
+ self.stream.flush()
+
+ if ret or not self._options.interactive or \
+ not os.path.exists(test.errpath):
+ return
- if options.keywords:
- fp = open(test)
- t = fp.read().lower() + test.lower()
- fp.close()
- for k in options.keywords.lower().split():
- if k in t:
- break
- else:
- return ignore("doesn't match keyword")
+ iolock.acquire()
+ print 'Accept this change? [n] ',
+ answer = sys.stdin.readline().strip()
+ iolock.release()
+ if answer.lower() in ('y', 'yes'):
+ if test.name.endswith('.t'):
+ rename(test.errpath, test.path)
+ else:
+ rename(test.errpath, '%s.out' % test.path)
+
+ def startTest(self, test):
+ super(TestResult, self).startTest(test)
- 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")
+ self._started[test.name] = time.time()
+
+ def stopTest(self, test, interrupted=False):
+ super(TestResult, self).stopTest(test)
- vlog("# Test", test)
+ self.times.append((test.name, time.time() - self._started[test.name]))
+ del self._started[test.name]
+
+ 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."""
- if os.path.exists(err):
- os.remove(err) # Remove any previous output files
+ 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.
- # Make a tmp subdirectory to work in
- threadtmp = os.path.join(HGTMP, "child%d" % count)
- testtmp = os.path.join(threadtmp, os.path.basename(test))
- os.mkdir(threadtmp)
- os.mkdir(testtmp)
+ whitelist and blacklist denote tests that have been whitelisted and
+ blacklisted, respectively. These arguments don't belong in TestSuite.
+ Instead, whitelist and blacklist should be handled by the thing that
+ populates the TestSuite with tests. They are present to preserve
+ backwards compatible behavior which reports skipped tests as part
+ of the results.
+
+ retest denotes whether to retest failed tests. This arguably belongs
+ outside of TestSuite.
+
+ keywords denotes key words that will be used to filter which tests
+ to execute. This arguably belongs outside of TestSuite.
- 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'))
+ loop denotes whether to loop over tests forever.
+ """
+ super(TestSuite, self).__init__(*args, **kwargs)
+
+ self._jobs = jobs
+ self._whitelist = whitelist
+ self._blacklist = blacklist
+ self._retest = retest
+ self._keywords = keywords
+ self._loop = loop
- env = createenv(options, testtmp, threadtmp, port)
- createhgrc(env['HGRCPATH'], options)
+ def run(self, result):
+ # We have a number of filters that need to be applied. We do this
+ # here instead of inside Test because it makes the running logic for
+ # Test simpler.
+ tests = []
+ for test in self._tests:
+ if not os.path.exists(test.path):
+ result.addSkip(test, "Doesn't exist")
+ continue
+
+ if not (self._whitelist and test.name in self._whitelist):
+ if self._blacklist and test.name in self._blacklist:
+ result.addSkip(test, 'blacklisted')
+ continue
- 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._retest and not os.path.exists(test.errpath):
+ result.addIgnore(test, 'not retesting')
+ continue
+
+ if self._keywords:
+ f = open(test.path)
+ t = f.read().lower() + test.name.lower()
+ f.close()
+ ignored = False
+ for k in self._keywords.lower().split():
+ if k not in t:
+ result.addIgnore(test, "doesn't match keyword")
+ ignored = True
+ break
+
+ if ignored:
+ continue
+
+ tests.append(test)
+
+ runtests = list(tests)
+ done = queue.Queue()
+ running = 0
+
+ def job(test, result):
+ try:
+ test(result)
+ done.put(None)
+ except KeyboardInterrupt:
+ pass
+ except: # re-raises
+ done.put(('!', test, 'run-test raised an error, see traceback'))
+ raise
- killdaemons(env['DAEMON_PIDS'])
+ try:
+ while tests or running:
+ if not done.empty() or running == self._jobs or not tests:
+ try:
+ done.get(True, 1)
+ if result and result.shouldStop:
+ break
+ except queue.Empty:
+ continue
+ running -= 1
+ if tests and not running == self._jobs:
+ test = tests.pop(0)
+ if self._loop:
+ tests.append(test)
+ t = threading.Thread(target=job, name=test.name,
+ args=(test, result))
+ t.start()
+ running += 1
+ except KeyboardInterrupt:
+ for test in runtests:
+ test.abort()
- skipped = (ret == SKIPPED_STATUS)
+ return result
+
+class TextTestRunner(unittest.TextTestRunner):
+ """Custom unittest test runner that uses appropriate settings."""
+
+ def __init__(self, runner, *args, **kwargs):
+ super(TextTestRunner, self).__init__(*args, **kwargs)
+
+ self._runner = runner
+
+ def run(self, test):
+ result = TestResult(self._runner.options, self.stream,
+ self.descriptions, self.verbosity)
+
+ test(result)
+
+ failed = len(result.failures)
+ warned = len(result.warned)
+ skipped = len(result.skipped)
+ ignored = len(result.ignored)
+
+ self.stream.writeln('')
- # If we're not in --debug mode and reference output file exists,
- # check test output against it.
- if options.debug:
- refout = None # to match "out is None"
- elif os.path.exists(ref):
- f = open(ref, "r")
- refout = f.read().splitlines(True)
- f.close()
- else:
- refout = []
+ if not self._runner.options.noskips:
+ for test, msg in result.skipped:
+ self.stream.writeln('Skipped %s: %s' % (test.name, msg))
+ for test, msg in result.warned:
+ self.stream.writeln('Warned %s: %s' % (test.name, msg))
+ for test, msg in result.failures:
+ self.stream.writeln('Failed %s: %s' % (test.name, msg))
+ for test, msg in result.errors:
+ self.stream.writeln('Errored %s: %s' % (test.name, msg))
+
+ self._runner._checkhglib('Tested')
+
+ # This differs from unittest's default output in that we don't count
+ # skipped and ignored tests as part of the total test count.
+ self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
+ % (result.testsRun - skipped - ignored,
+ skipped + ignored, warned, failed))
+ if failed:
+ self.stream.writeln('python hash seed: %s' %
+ os.environ['PYTHONHASHSEED'])
+ if self._runner.options.time:
+ self.printtimes(result.times)
+
+ return result
+
+ def printtimes(self, times):
+ self.stream.writeln('# Producing time report')
+ times.sort(key=lambda t: (t[1], t[0]), reverse=True)
+ cols = '%7.3f %s'
+ self.stream.writeln('%-7s %s' % ('Time', 'Test'))
+ for test, timetaken in times:
+ self.stream.writeln(cols % (timetaken, test))
+
+class TestRunner(object):
+ """Holds context for executing tests.
+
+ Tests rely on a lot of state. This object holds it for them.
+ """
- if (ret != 0 or out != refout) and not skipped and not options.debug:
- # Save errors to a file for diagnosis
- f = open(err, "wb")
- for line in out:
- f.write(line)
- f.close()
+ # Programs required to run tests.
+ REQUIREDTOOLS = [
+ os.path.basename(sys.executable),
+ 'diff',
+ 'grep',
+ 'unzip',
+ 'gunzip',
+ 'bunzip2',
+ 'sed',
+ ]
+
+ # Maps file extensions to test class.
+ TESTTYPES = [
+ ('.py', PythonTest),
+ ('.t', TTest),
+ ]
- if skipped:
- if out is None: # debug mode: nothing to parse
- missing = ['unknown']
- failed = None
- else:
- missing, failed = parsehghaveoutput(out)
- if not missing:
- missing = ['irrelevant']
- if failed:
- result = fail("hghave failed checking for %s" % failed[-1], ret)
- skipped = False
+ def __init__(self):
+ self.options = None
+ self._testdir = None
+ self._hgtmp = None
+ self._installdir = None
+ self._bindir = None
+ self._tmpbinddir = None
+ self._pythondir = None
+ self._coveragefile = None
+ self._createdfiles = []
+ self._hgpath = None
+
+ def run(self, args, parser=None):
+ """Run the test suite."""
+ oldmask = os.umask(022)
+ try:
+ parser = parser or getparser()
+ options, args = parseargs(args, parser)
+ self.options = options
+
+ self._checktools()
+ tests = self.findtests(args)
+ return self._run(tests)
+ finally:
+ os.umask(oldmask)
+
+ def _run(self, tests):
+ if self.options.random:
+ random.shuffle(tests)
else:
- result = skip(missing[-1])
- elif ret == 'timeout':
- result = fail("timed out", ret)
- elif out != refout:
- info = {}
- if not options.nodiff:
- iolock.acquire()
- if options.view:
- os.system("%s %s %s" % (options.view, ref, err))
- else:
- info = showdiff(refout, out, ref, err)
- iolock.release()
- msg = ""
- if info.get('servefail'): msg += "serve failed and "
- if ret:
- msg += "output changed and " + describe(ret)
- else:
- msg += "output changed"
- result = fail(msg, ret)
- elif ret:
- result = fail(describe(ret), ret)
- else:
- result = success()
-
- if not options.verbose:
- iolock.acquire()
- sys.stdout.write(result[0])
- sys.stdout.flush()
- iolock.release()
+ # keywords for slow tests
+ slow = 'svn gendoc check-code-hg'.split()
+ def sortkey(f):
+ # run largest tests first, as they tend to take the longest
+ try:
+ val = -os.stat(f).st_size
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ return -1e9 # file does not exist, tell early
+ for kw in slow:
+ if kw in f:
+ val *= 10
+ return val
+ tests.sort(key=sortkey)
- if not options.keep_tmpdir:
- shutil.rmtree(threadtmp, True)
- return result
-
-_hgpath = None
-
-def _gethgpath():
- """Return the path to the mercurial package that is actually found by
- the current Python interpreter."""
- global _hgpath
- if _hgpath is not None:
- return _hgpath
+ self._testdir = os.environ['TESTDIR'] = os.getcwd()
- cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
- pipe = os.popen(cmd % PYTHON)
- try:
- _hgpath = pipe.read().strip()
- finally:
- pipe.close()
- return _hgpath
-
-def _checkhglib(verb):
- """Ensure that the 'mercurial' package imported by python is
- the one we expect it to be. If not, print a warning to stderr."""
- expecthg = os.path.join(PYTHONDIR, 'mercurial')
- actualhg = _gethgpath()
- if os.path.abspath(actualhg) != os.path.abspath(expecthg):
- sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
- ' (expected %s)\n'
- % (verb, actualhg, expecthg))
-
-results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
-times = []
-iolock = threading.Lock()
-abort = False
+ if 'PYTHONHASHSEED' not in os.environ:
+ # use a random python hash seed all the time
+ # we do the randomness ourself to know what seed is used
+ os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
-def scheduletests(options, tests):
- jobs = options.jobs
- done = queue.Queue()
- running = 0
- count = 0
- global abort
-
- def job(test, count):
- try:
- done.put(runone(options, test, count))
- except KeyboardInterrupt:
- pass
- except: # re-raises
- done.put(('!', test, 'run-test raised an error, see traceback'))
- raise
-
- try:
- while tests or running:
- if not done.empty() or running == jobs or not tests:
- try:
- code, test, msg = done.get(True, 1)
- results[code].append((test, msg))
- if options.first and code not in '.si':
- break
- except queue.Empty:
- continue
- running -= 1
- if tests and not running == jobs:
- test = tests.pop(0)
- if options.loop:
- tests.append(test)
- t = threading.Thread(target=job, name=test, args=(test, count))
- t.start()
- running += 1
- count += 1
- except KeyboardInterrupt:
- abort = True
-
-def runtests(options, tests):
- try:
- if INST:
- installhg(options)
- _checkhglib("Testing")
- else:
- usecorrectpython()
+ if self.options.tmpdir:
+ self.options.keep_tmpdir = True
+ tmpdir = self.options.tmpdir
+ if os.path.exists(tmpdir):
+ # Meaning of tmpdir has changed since 1.3: we used to create
+ # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
+ # tmpdir already exists.
+ print "error: temp dir %r already exists" % tmpdir
+ return 1
- if options.restart:
- orig = list(tests)
- while tests:
- if os.path.exists(tests[0] + ".err"):
- break
- tests.pop(0)
- if not tests:
- print "running all tests"
- tests = orig
-
- scheduletests(options, tests)
-
- failed = len(results['!'])
- warned = len(results['~'])
- tested = len(results['.']) + failed + warned
- skipped = len(results['s'])
- ignored = len(results['i'])
+ # Automatically removing tmpdir sounds convenient, but could
+ # really annoy anyone in the habit of using "--tmpdir=/tmp"
+ # or "--tmpdir=$HOME".
+ #vlog("# Removing temp dir", tmpdir)
+ #shutil.rmtree(tmpdir)
+ os.makedirs(tmpdir)
+ else:
+ d = None
+ if os.name == 'nt':
+ # without this, we get the default temp dir location, but
+ # in all lowercase, which causes troubles with paths (issue3490)
+ d = os.getenv('TMP')
+ tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
+ self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
- print
- if not options.noskips:
- for s in results['s']:
- print "Skipped %s: %s" % s
- for s in results['~']:
- print "Warned %s: %s" % s
- for s in results['!']:
- print "Failed %s: %s" % s
- _checkhglib("Tested")
- print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
- tested, skipped + ignored, warned, failed)
- if results['!']:
- print 'python hash seed:', os.environ['PYTHONHASHSEED']
- if options.time:
- outputtimes(options)
-
- if options.anycoverage:
- outputcoverage(options)
- except KeyboardInterrupt:
- failed = True
- print "\ninterrupted!"
+ if self.options.with_hg:
+ self._installdir = None
+ self._bindir = os.path.dirname(os.path.realpath(
+ self.options.with_hg))
+ self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
+ os.makedirs(self._tmpbindir)
- if failed:
- return 1
- if warned:
- return 80
-
-testtypes = [('.py', pytest, '.out'),
- ('.t', tsttest, '')]
-
-def main(args, parser=None):
- parser = parser or getparser()
- (options, args) = parseargs(args, parser)
- os.umask(022)
-
- checktools()
-
- if not args:
- if options.changed:
- proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
- None, 0)
- stdout, stderr = proc.communicate()
- args = stdout.strip('\0').split('\0')
+ # This looks redundant with how Python initializes sys.path from
+ # the location of the script being executed. Needed because the
+ # "hg" specified by --with-hg is not the only Python script
+ # executed in the test suite that needs to import 'mercurial'
+ # ... which means it's not really redundant at all.
+ self._pythondir = self._bindir
else:
- args = os.listdir(".")
+ self._installdir = os.path.join(self._hgtmp, "install")
+ self._bindir = os.environ["BINDIR"] = \
+ os.path.join(self._installdir, "bin")
+ self._tmpbindir = self._bindir
+ self._pythondir = os.path.join(self._installdir, "lib", "python")
+
+ os.environ["BINDIR"] = self._bindir
+ os.environ["PYTHON"] = PYTHON
+
+ path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
+ if self._tmpbindir != self._bindir:
+ path = [self._tmpbindir] + path
+ os.environ["PATH"] = os.pathsep.join(path)
- tests = [t for t in args
- if os.path.basename(t).startswith("test-")
- and (t.endswith(".py") or t.endswith(".t"))]
+ # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
+ # can run .../tests/run-tests.py test-foo where test-foo
+ # adds an extension to HGRC. Also include run-test.py directory to
+ # import modules like heredoctest.
+ pypath = [self._pythondir, self._testdir,
+ os.path.abspath(os.path.dirname(__file__))]
+ # We have to augment PYTHONPATH, rather than simply replacing
+ # it, in case external libraries are only available via current
+ # PYTHONPATH. (In particular, the Subversion bindings on OS X
+ # are in /opt/subversion.)
+ oldpypath = os.environ.get(IMPL_PATH)
+ if oldpypath:
+ pypath.append(oldpypath)
+ os.environ[IMPL_PATH] = os.pathsep.join(pypath)
+
+ self._coveragefile = os.path.join(self._testdir, '.coverage')
+
+ vlog("# Using TESTDIR", self._testdir)
+ vlog("# Using HGTMP", self._hgtmp)
+ vlog("# Using PATH", os.environ["PATH"])
+ vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
+
+ try:
+ return self._runtests(tests) or 0
+ finally:
+ time.sleep(.1)
+ self._cleanup()
+
+ def findtests(self, args):
+ """Finds possible test files from arguments.
- if options.random:
- random.shuffle(tests)
- else:
- # keywords for slow tests
- slow = 'svn gendoc check-code-hg'.split()
- def sortkey(f):
- # run largest tests first, as they tend to take the longest
- try:
- val = -os.stat(f).st_size
- except OSError, e:
- if e.errno != errno.ENOENT:
- raise
- return -1e9 # file does not exist, tell early
- for kw in slow:
- if kw in f:
- val *= 10
- return val
- tests.sort(key=sortkey)
+ If you wish to inject custom tests into the test harness, this would
+ be a good function to monkeypatch or override in a derived class.
+ """
+ if not args:
+ if self.options.changed:
+ proc = Popen4('hg st --rev "%s" -man0 .' %
+ self.options.changed, None, 0)
+ stdout, stderr = proc.communicate()
+ args = stdout.strip('\0').split('\0')
+ else:
+ args = os.listdir('.')
+
+ return [t for t in args
+ if os.path.basename(t).startswith('test-')
+ and (t.endswith('.py') or t.endswith('.t'))]
+
+ def _runtests(self, tests):
+ try:
+ if self._installdir:
+ self._installhg()
+ self._checkhglib("Testing")
+ else:
+ self._usecorrectpython()
+
+ if self.options.restart:
+ orig = list(tests)
+ while tests:
+ if os.path.exists(tests[0] + ".err"):
+ break
+ tests.pop(0)
+ if not tests:
+ print "running all tests"
+ tests = orig
+
+ tests = [self._gettest(t, i) for i, t in enumerate(tests)]
+
+ failed = False
+ warned = False
+
+ suite = TestSuite(self._testdir,
+ jobs=self.options.jobs,
+ whitelist=self.options.whitelisted,
+ blacklist=self.options.blacklist,
+ retest=self.options.retest,
+ keywords=self.options.keywords,
+ loop=self.options.loop,
+ tests=tests)
+ verbosity = 1
+ if self.options.verbose:
+ verbosity = 2
+ runner = TextTestRunner(self, verbosity=verbosity)
+ result = runner.run(suite)
+
+ if result.failures:
+ failed = True
+ if result.warned:
+ warned = True
- if 'PYTHONHASHSEED' not in os.environ:
- # use a random python hash seed all the time
- # we do the randomness ourself to know what seed is used
- os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
+ if self.options.anycoverage:
+ self._outputcoverage()
+ except KeyboardInterrupt:
+ failed = True
+ print "\ninterrupted!"
+
+ if failed:
+ return 1
+ if warned:
+ return 80
+
+ def _gettest(self, test, count):
+ """Obtain a Test by looking at its filename.
+
+ Returns a Test instance. The Test may not be runnable if it doesn't
+ map to a known type.
+ """
+ lctest = test.lower()
+ testcls = Test
+
+ for ext, cls in self.TESTTYPES:
+ if lctest.endswith(ext):
+ testcls = cls
+ break
+
+ refpath = os.path.join(self._testdir, test)
+ tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
+
+ return testcls(refpath, tmpdir,
+ keeptmpdir=self.options.keep_tmpdir,
+ debug=self.options.debug,
+ timeout=self.options.timeout,
+ startport=self.options.port + count * 3,
+ extraconfigopts=self.options.extra_config_opt,
+ py3kwarnings=self.options.py3k_warnings,
+ shell=self.options.shell)
+
+ def _cleanup(self):
+ """Clean up state from this test invocation."""
+
+ if self.options.keep_tmpdir:
+ return
+
+ vlog("# Cleaning up HGTMP", self._hgtmp)
+ shutil.rmtree(self._hgtmp, True)
+ for f in self._createdfiles:
+ try:
+ os.remove(f)
+ except OSError:
+ pass
- global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
- TESTDIR = os.environ["TESTDIR"] = os.getcwd()
- if options.tmpdir:
- options.keep_tmpdir = True
- tmpdir = options.tmpdir
- if os.path.exists(tmpdir):
- # Meaning of tmpdir has changed since 1.3: we used to create
- # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
- # tmpdir already exists.
- print "error: temp dir %r already exists" % tmpdir
- return 1
+ def _usecorrectpython(self):
+ """Configure the environment to use the appropriate Python in tests."""
+ # Tests must use the same interpreter as us or bad things will happen.
+ pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
+ if getattr(os, 'symlink', None):
+ vlog("# Making python executable in test path a symlink to '%s'" %
+ sys.executable)
+ mypython = os.path.join(self._tmpbindir, pyexename)
+ try:
+ if os.readlink(mypython) == sys.executable:
+ return
+ os.unlink(mypython)
+ except OSError, err:
+ if err.errno != errno.ENOENT:
+ raise
+ if self._findprogram(pyexename) != sys.executable:
+ try:
+ os.symlink(sys.executable, mypython)
+ self._createdfiles.append(mypython)
+ except OSError, err:
+ # child processes may race, which is harmless
+ if err.errno != errno.EEXIST:
+ raise
+ else:
+ exedir, exename = os.path.split(sys.executable)
+ vlog("# Modifying search path to find %s as %s in '%s'" %
+ (exename, pyexename, exedir))
+ path = os.environ['PATH'].split(os.pathsep)
+ while exedir in path:
+ path.remove(exedir)
+ os.environ['PATH'] = os.pathsep.join([exedir] + path)
+ if not self._findprogram(pyexename):
+ print "WARNING: Cannot find %s in search path" % pyexename
- # Automatically removing tmpdir sounds convenient, but could
- # really annoy anyone in the habit of using "--tmpdir=/tmp"
- # or "--tmpdir=$HOME".
- #vlog("# Removing temp dir", tmpdir)
- #shutil.rmtree(tmpdir)
- os.makedirs(tmpdir)
- else:
- d = None
+ def _installhg(self):
+ """Install hg into the test environment.
+
+ This will also configure hg with the appropriate testing settings.
+ """
+ vlog("# Performing temporary installation of HG")
+ installerrs = os.path.join("tests", "install.err")
+ compiler = ''
+ if self.options.compiler:
+ compiler = '--compiler ' + self.options.compiler
+ pure = self.options.pure and "--pure" or ""
+ py3 = ''
+ if sys.version_info[0] == 3:
+ py3 = '--c2to3'
+
+ # Run installer in hg root
+ script = os.path.realpath(sys.argv[0])
+ hgroot = os.path.dirname(os.path.dirname(script))
+ os.chdir(hgroot)
+ nohome = '--home=""'
if os.name == 'nt':
- # without this, we get the default temp dir location, but
- # in all lowercase, which causes troubles with paths (issue3490)
- d = os.getenv('TMP')
- tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
- HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
-
- 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)
+ # The --home="" trick works only on OS where os.sep == '/'
+ # because of a distutils convert_path() fast-path. Avoid it at
+ # least on Windows for now, deal with .pydistutils.cfg bugs
+ # when they happen.
+ nohome = ''
+ cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
+ ' build %(compiler)s --build-base="%(base)s"'
+ ' install --force --prefix="%(prefix)s"'
+ ' --install-lib="%(libdir)s"'
+ ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
+ % {'exe': sys.executable, 'py3': py3, 'pure': pure,
+ 'compiler': compiler,
+ 'base': os.path.join(self._hgtmp, "build"),
+ 'prefix': self._installdir, 'libdir': self._pythondir,
+ 'bindir': self._bindir,
+ 'nohome': nohome, 'logfile': installerrs})
+ vlog("# Running", cmd)
+ if os.system(cmd) == 0:
+ if not self.options.verbose:
+ os.remove(installerrs)
+ else:
+ f = open(installerrs)
+ for line in f:
+ print line,
+ f.close()
+ sys.exit(1)
+ os.chdir(self._testdir)
- # 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")
+ 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'), 'r')
+ lines = [line.rstrip() for line in f]
+ lines[0] += ' -3'
+ f.close()
+ f = open(os.path.join(self._bindir, 'hg'), 'w')
+ for line in lines:
+ f.write(line + '\n')
+ f.close()
- os.environ["BINDIR"] = BINDIR
- os.environ["PYTHON"] = PYTHON
+ hgbat = os.path.join(self._bindir, 'hg.bat')
+ if os.path.isfile(hgbat):
+ # hg.bat expects to be put in bin/scripts while run-tests.py
+ # installation layout put it in bin/ directly. Fix it
+ f = open(hgbat, 'rb')
+ data = f.read()
+ f.close()
+ if '"%~dp0..\python" "%~dp0hg" %*' in data:
+ data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
+ '"%~dp0python" "%~dp0hg" %*')
+ f = open(hgbat, 'wb')
+ f.write(data)
+ f.close()
+ else:
+ print 'WARNING: cannot fix hg.bat reference to python.exe'
- 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."""
+ expecthg = os.path.join(self._pythondir, 'mercurial')
+ actualhg = self._gethgpath()
+ if os.path.abspath(actualhg) != os.path.abspath(expecthg):
+ sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
+ ' (expected %s)\n'
+ % (verb, actualhg, expecthg))
+ def _gethgpath(self):
+ """Return the path to the mercurial package that is actually found by
+ the current Python interpreter."""
+ if self._hgpath is not None:
+ return self._hgpath
+
+ cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
+ pipe = os.popen(cmd % PYTHON)
+ try:
+ self._hgpath = pipe.read().strip()
+ finally:
+ pipe.close()
+
+ return self._hgpath
- # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
- # can run .../tests/run-tests.py test-foo where test-foo
- # adds an extension to HGRC. Also include run-test.py directory to import
- # modules like heredoctest.
- pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
- # We have to augment PYTHONPATH, rather than simply replacing
- # it, in case external libraries are only available via current
- # PYTHONPATH. (In particular, the Subversion bindings on OS X
- # are in /opt/subversion.)
- oldpypath = os.environ.get(IMPL_PATH)
- if oldpypath:
- pypath.append(oldpypath)
- os.environ[IMPL_PATH] = os.pathsep.join(pypath)
+ def _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:]))
--- a/tests/test-add.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-add.t Mon Jun 09 13:53:23 2014 -0500
@@ -107,6 +107,7 @@
M a
? a.orig
$ hg resolve -m a
+ no more unresolved files
$ hg ci -m merge
Issue683: peculiarity with hg revert of an removed then added file
--- a/tests/test-backout.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-backout.t Mon Jun 09 13:53:23 2014 -0500
@@ -11,6 +11,8 @@
[255]
basic operation
+(this also tests that editor is invoked if the commit message is not
+specified explicitly)
$ echo a > a
$ hg commit -d '0 0' -A -m a
@@ -18,8 +20,19 @@
$ echo b >> a
$ hg commit -d '1 0' -m b
- $ hg backout -d '2 0' tip --tool=true
+ $ hg status --rev tip --rev "tip^1"
+ M a
+ $ HGEDITOR=cat hg backout -d '2 0' tip --tool=true
reverting a
+ Backed out changeset a820f4f40a57
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: changed a
changeset 2:2929462c3dff backs out changeset 1:a820f4f40a57
$ cat a
a
@@ -31,6 +44,8 @@
update: (current)
file that was removed is recreated
+(this also tests that editor is not invoked if the commit message is
+specified explicitly)
$ cd ..
$ hg init remove
@@ -43,7 +58,7 @@
$ hg rm a
$ hg commit -d '1 0' -m b
- $ hg backout -d '2 0' tip --tool=true
+ $ HGEDITOR=cat hg backout -d '2 0' tip --tool=true -m "Backed out changeset 76862dcce372"
adding a
changeset 2:de31bdc76c0d backs out changeset 1:76862dcce372
$ cat a
@@ -340,9 +355,21 @@
update: (current)
with --merge
+(this also tests that editor is invoked if '--edit' is specified
+explicitly regardless of '--message')
+
$ hg update -qC
- $ hg backout --merge -d '3 0' -r 1 -m 'backout on branch1' --tool=true
+ $ HGEDITOR=cat hg backout --merge -d '3 0' -r 1 -m 'backout on branch1' --tool=true --edit
removing file1
+ backout on branch1
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'branch2'
+ HG: removed file1
created new head
changeset 3:d4e8f6db59fb backs out changeset 1:bf1602f437f3
merging with changeset 3:d4e8f6db59fb
@@ -490,6 +517,7 @@
merging foo
my foo@b71750c4b0fd+ other foo@a30dd8addae3 ancestor foo@913609522437
premerge successful
+ no more unresolved files
$ hg status
M foo
? foo.orig
--- a/tests/test-bookmarks-current.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-bookmarks-current.t Mon Jun 09 13:53:23 2014 -0500
@@ -24,6 +24,7 @@
$ hg update X
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark X)
list bookmarks
@@ -71,6 +72,7 @@
Verify that switching to Z updates the current bookmark:
$ hg update Z
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (activating bookmark Z)
$ hg bookmark
Y 0:719295282060
* Z -1:000000000000
@@ -78,6 +80,7 @@
Switch back to Y for the remaining tests in this file:
$ hg update Y
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark Y)
delete bookmarks
@@ -152,6 +155,7 @@
$ hg bookmark X@2 -r 2
$ hg update X
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (activating bookmark X)
$ hg bookmarks
* X 0:719295282060
X@1 1:cc586d725fbe
--- a/tests/test-bookmarks-merge.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-bookmarks-merge.t Mon Jun 09 13:53:23 2014 -0500
@@ -32,6 +32,7 @@
$ hg up -C 3
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark c)
$ echo d > d
$ hg add d
$ hg commit -m'd'
@@ -54,6 +55,7 @@
$ hg up -C 4
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark e)
$ hg merge
abort: heads are bookmarked - please merge with an explicit rev
(run 'hg heads' to see all heads)
@@ -63,6 +65,7 @@
$ hg up -C e
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (activating bookmark e)
$ hg merge
abort: no matching bookmark to merge - please merge with an explicit rev or bookmark
(run 'hg heads' to see all heads)
@@ -72,6 +75,7 @@
$ hg up -C 4
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark e)
$ echo f > f
$ hg commit -Am "f"
adding f
@@ -96,6 +100,7 @@
$ hg up -C e
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (activating bookmark e)
$ hg bookmarks
b 1:d2ae7f538514
c 3:b8f96cf4688b
@@ -114,6 +119,7 @@
$ hg up -C 6
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark e)
$ echo g > g
$ hg commit -Am 'g'
adding g
--- a/tests/test-bookmarks-pushpull.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-bookmarks-pushpull.t Mon Jun 09 13:53:23 2014 -0500
@@ -274,7 +274,7 @@
$ hg push http://localhost:$HGPORT2/
pushing to http://localhost:$HGPORT2/
searching for changes
- abort: push creates new remote head c922c0139ca0!
+ abort: push creates new remote head c922c0139ca0 with bookmark 'Y'!
(merge or see "hg help push" for details about pushing new heads)
[255]
$ hg -R ../a book
@@ -290,7 +290,7 @@
$ hg push http://localhost:$HGPORT2/
pushing to http://localhost:$HGPORT2/
searching for changes
- abort: push creates new remote head c922c0139ca0!
+ abort: push creates new remote head c922c0139ca0 with bookmark 'Y'!
(merge or see "hg help push" for details about pushing new heads)
[255]
$ hg -R ../a book
@@ -411,6 +411,7 @@
$ hg commit -m 'add bar'
$ hg co "tip^"
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark @)
$ hg book add-foo
$ hg book -r tip add-bar
Note: this push *must* push only a single changeset, as that's the point
--- a/tests/test-bookmarks-strip.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-bookmarks-strip.t Mon Jun 09 13:53:23 2014 -0500
@@ -38,6 +38,7 @@
$ hg update -r -2
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark test2)
$ echo eee>>qqq.txt
--- a/tests/test-bookmarks.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-bookmarks.t Mon Jun 09 13:53:23 2014 -0500
@@ -118,6 +118,7 @@
$ hg update X
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (activating bookmark X)
$ echo c > c
$ hg add c
$ hg commit -m 2
@@ -501,6 +502,7 @@
$ hg update
updating to active bookmark Z
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark Z)
$ hg bookmarks
X2 1:925d80f479bb
Y 2:db815d6d32e6
@@ -513,6 +515,7 @@
moving bookmark 'Y' forward from db815d6d32e6
$ hg -R cloned-bookmarks-update update Y
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark Y)
$ hg -R cloned-bookmarks-update pull --update .
pulling from .
searching for changes
@@ -582,6 +585,7 @@
$ hg book should-end-on-two
$ hg co --clean 4
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark should-end-on-two)
$ hg book four
$ hg --config extensions.mq= strip 3
saved backup bundle to * (glob)
--- a/tests/test-bundle2.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-bundle2.t Mon Jun 09 13:53:23 2014 -0500
@@ -47,9 +47,7 @@
> op.ui.write('received ping request (id %i)\n' % part.id)
> if op.reply is not None and 'ping-pong' in op.reply.capabilities:
> op.ui.write_err('replying to ping request (id %i)\n' % part.id)
- > rpart = bundle2.bundlepart('test:pong',
- > [('in-reply-to', str(part.id))])
- > op.reply.addpart(rpart)
+ > op.reply.newpart('test:pong', [('in-reply-to', str(part.id))])
>
> @bundle2.parthandler('test:debugreply')
> def debugreply(op, part):
@@ -66,6 +64,7 @@
> @command('bundle2',
> [('', 'param', [], 'stream level parameter'),
> ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
+ > ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
> ('', 'parts', False, 'include some arbitrary parts to the bundle'),
> ('', 'reply', False, 'produce a reply bundle'),
> ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
@@ -83,11 +82,12 @@
>
> if opts['reply']:
> capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
- > bundler.addpart(bundle2.bundlepart('b2x:replycaps', data=capsstring))
+ > bundler.newpart('b2x:replycaps', data=capsstring)
>
> if opts['pushrace']:
- > dummynode = '01234567890123456789'
- > bundler.addpart(bundle2.bundlepart('b2x:check:heads', data=dummynode))
+ > # also serve to test the assignement of data outside of init
+ > part = bundler.newpart('b2x:check:heads')
+ > part.data = '01234567890123456789'
>
> revs = opts['rev']
> if 'rev' in opts:
@@ -99,31 +99,27 @@
> headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
> outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
> cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
- > part = bundle2.bundlepart('b2x:changegroup', data=cg.getchunks())
- > bundler.addpart(part)
+ > bundler.newpart('b2x:changegroup', data=cg.getchunks())
>
> if opts['parts']:
- > part = bundle2.bundlepart('test:empty')
- > bundler.addpart(part)
+ > bundler.newpart('test:empty')
> # add a second one to make sure we handle multiple parts
- > part = bundle2.bundlepart('test:empty')
- > bundler.addpart(part)
- > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
- > bundler.addpart(part)
- > part = bundle2.bundlepart('test:debugreply')
- > bundler.addpart(part)
- > part = bundle2.bundlepart('test:math',
- > [('pi', '3.14'), ('e', '2.72')],
- > [('cooking', 'raw')],
- > '42')
- > bundler.addpart(part)
+ > bundler.newpart('test:empty')
+ > bundler.newpart('test:song', data=ELEPHANTSSONG)
+ > bundler.newpart('test:debugreply')
+ > mathpart = bundler.newpart('test:math')
+ > mathpart.addparam('pi', '3.14')
+ > mathpart.addparam('e', '2.72')
+ > mathpart.addparam('cooking', 'raw', mandatory=False)
+ > mathpart.data = '42'
+ > # advisory known part with unknown mandatory param
+ > bundler.newpart('test:song', [('randomparam','')])
> if opts['unknown']:
- > part = bundle2.bundlepart('test:UNKNOWN',
- > data='some random content')
- > bundler.addpart(part)
+ > bundler.newpart('test:UNKNOWN', data='some random content')
+ > if opts['unknownparams']:
+ > bundler.newpart('test:SONG', [('randomparams', '')])
> if opts['parts']:
- > part = bundle2.bundlepart('test:ping')
- > bundler.addpart(part)
+ > bundler.newpart('test:ping')
>
> if path is None:
> file = sys.stdout
@@ -144,7 +140,7 @@
> unbundler = bundle2.unbundle20(ui, sys.stdin)
> op = bundle2.processbundle(repo, unbundler, lambda: tr)
> tr.close()
- > except KeyError, exc:
+ > except error.BundleValueError, exc:
> raise util.Abort('missing support for %s' % exc)
> except error.PushRaced, exc:
> raise util.Abort('push race: %s' % exc)
@@ -170,7 +166,7 @@
> unbundler = bundle2.unbundle20(ui, sys.stdin)
> try:
> params = unbundler.params
- > except KeyError, exc:
+ > except error.BundleValueError, exc:
> raise util.Abort('unknown parameters: %s' % exc)
> ui.write('options count: %i\n' % len(params))
> for key in sorted(params):
@@ -194,9 +190,12 @@
> bundle2-exp=True
> [ui]
> ssh=python "$TESTDIR/dummyssh"
+ > logtemplate={rev}:{node|short} {phase} {author} {desc|firstline}
> [web]
> push_ssl = false
> allow_push = *
+ > [phases]
+ > publish=False
> EOF
The extension requires a repo (currently unused)
@@ -308,7 +307,7 @@
---------------------------------------------------
$ hg bundle2 --param 'Gravity' | hg statbundle2
- abort: unknown parameters: 'Gravity'
+ abort: unknown parameters: Stream Parameter - Gravity
[255]
Test debug output
@@ -372,6 +371,7 @@
bundle part: "test:song"
bundle part: "test:debugreply"
bundle part: "test:math"
+ bundle part: "test:song"
bundle part: "test:ping"
end of bundle
@@ -380,7 +380,7 @@
test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
- Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+ Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x1d test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
$ hg statbundle2 < ../parts.hg2
@@ -405,11 +405,15 @@
mandatory: 2
advisory: 1
payload: 2 bytes
+ :test:song:
+ mandatory: 1
+ advisory: 0
+ payload: 0 bytes
:test:ping:
mandatory: 0
advisory: 0
payload: 0 bytes
- parts count: 6
+ parts count: 7
$ hg statbundle2 --debug < ../parts.hg2
start processing of HG2X stream
@@ -463,9 +467,18 @@
payload chunk size: 2
payload chunk size: 0
payload: 2 bytes
+ part header size: 29
+ part type: "test:song"
+ part id: "5"
+ part parameters: 1
+ :test:song:
+ mandatory: 1
+ advisory: 0
+ payload chunk size: 0
+ payload: 0 bytes
part header size: 16
part type: "test:ping"
- part id: "5"
+ part id: "6"
part parameters: 0
:test:ping:
mandatory: 0
@@ -474,7 +487,7 @@
payload: 0 bytes
part header size: 0
end of bundle2 stream
- parts count: 6
+ parts count: 7
Test actual unbundling of test part
=======================================
@@ -489,13 +502,13 @@
part type: "test:empty"
part id: "0"
part parameters: 0
- ignoring unknown advisory part 'test:empty'
+ ignoring unsupported advisory part test:empty
payload chunk size: 0
part header size: 17
part type: "test:empty"
part id: "1"
part parameters: 0
- ignoring unknown advisory part 'test:empty'
+ ignoring unsupported advisory part test:empty
payload chunk size: 0
part header size: 16
part type: "test:song"
@@ -519,15 +532,22 @@
part type: "test:math"
part id: "4"
part parameters: 3
- ignoring unknown advisory part 'test:math'
+ ignoring unsupported advisory part test:math
payload chunk size: 2
payload chunk size: 0
+ part header size: 29
+ part type: "test:song"
+ part id: "5"
+ part parameters: 1
+ found a handler for part 'test:song'
+ ignoring unsupported advisory part test:song - randomparam
+ payload chunk size: 0
part header size: 16
part type: "test:ping"
- part id: "5"
+ part id: "6"
part parameters: 0
found a handler for part 'test:ping'
- received ping request (id 5)
+ received ping request (id 6)
payload chunk size: 0
part header size: 0
end of bundle2 stream
@@ -546,7 +566,17 @@
Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
debugreply: no reply
0 unread bytes
- abort: missing support for 'test:unknown'
+ abort: missing support for test:unknown
+ [255]
+
+Unbundle with an unknown mandatory part parameters
+(should abort)
+
+ $ hg bundle2 --unknownparams ../unknown.hg2
+
+ $ hg unbundle2 < ../unknown.hg2
+ 0 unread bytes
+ abort: missing support for test:song - randomparams
[255]
unbundle with a reply
@@ -572,9 +602,9 @@
debugreply: 'babar'
debugreply: 'celeste'
debugreply: 'ping-pong'
- \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1f (esc)
- b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc)
- replying to ping request (id 6)
+ \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x1f (esc)
+ b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
+ replying to ping request (id 7)
\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
The reply is valid
@@ -613,8 +643,8 @@
remote: debugreply: 'babar'
remote: debugreply: 'celeste'
remote: debugreply: 'ping-pong'
- remote: received ping request (id 6)
- remote: replying to ping request (id 6)
+ remote: received ping request (id 7)
+ remote: replying to ping request (id 7)
0 unread bytes
Test push race detection
@@ -637,57 +667,23 @@
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg log -G
- o changeset: 8:02de42196ebe
- | tag: tip
- | parent: 6:24b6387c8c8c
- | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: H
- |
- | o changeset: 7:eea13746799a
- |/| parent: 6:24b6387c8c8c
- | | parent: 5:9520eea781bc
- | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | date: Sat Apr 30 15:24:48 2011 +0200
- | | summary: G
- | |
- o | changeset: 6:24b6387c8c8c
- | | parent: 1:cd010b8cd998
- | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | date: Sat Apr 30 15:24:48 2011 +0200
- | | summary: F
- | |
- | o changeset: 5:9520eea781bc
- |/ parent: 1:cd010b8cd998
- | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: E
+ o 8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
|
- | o changeset: 4:32af7686d403
- | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | date: Sat Apr 30 15:24:48 2011 +0200
- | | summary: D
+ | o 7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G
+ |/|
+ o | 6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
| |
- | o changeset: 3:5fddd98957c8
- | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | date: Sat Apr 30 15:24:48 2011 +0200
- | | summary: C
+ | o 5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ | o 4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D
| |
- | o changeset: 2:42ccdea3bb16
- |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: B
- |
- o changeset: 1:cd010b8cd998
- parent: -1:000000000000
- user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- date: Sat Apr 30 15:24:48 2011 +0200
- summary: A
+ | o 3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
+ | |
+ | o 2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
+ |/
+ o 1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A
- @ changeset: 0:3903775176ed
- user: test
- date: Thu Jan 01 00:00:00 1970 +0000
- summary: a
+ @ 0:3903775176ed draft test a
$ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
@@ -768,6 +764,7 @@
clone --pull
$ cd ..
+ $ hg -R main phase --public cd010b8cd998
$ hg clone main other --pull --rev 9520eea781bc
adding changesets
adding manifests
@@ -776,20 +773,14 @@
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg -R other log -G
- @ changeset: 1:9520eea781bc
- | tag: tip
- | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: E
+ @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
|
- o changeset: 0:cd010b8cd998
- user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- date: Sat Apr 30 15:24:48 2011 +0200
- summary: A
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
pull
+ $ hg -R main phase --public 9520eea781bc
$ hg -R other pull -r 24b6387c8c8c
pulling from $TESTTMP/main (glob)
searching for changes
@@ -798,15 +789,43 @@
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
+ $ hg -R other log -G
+ o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
+ |
+ | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
+
+pull empty (with phase movement)
+
+ $ hg -R main phase --public 24b6387c8c8c
+ $ hg -R other pull -r 24b6387c8c8c
+ pulling from $TESTTMP/main (glob)
+ no changes found
+ $ hg -R other log -G
+ o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
+ |
+ | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
+
pull empty
$ hg -R other pull -r 24b6387c8c8c
pulling from $TESTTMP/main (glob)
no changes found
+ $ hg -R other log -G
+ o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
+ |
+ | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
+
push
+ $ hg -R main phase --public eea13746799a
$ hg -R main push other --rev eea13746799a
pushing to other
searching for changes
@@ -814,6 +833,15 @@
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 0 changes to 0 files (-1 heads)
+ $ hg -R other log -G
+ o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G
+ |\
+ | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
+ | |
+ @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
+
pull over ssh
@@ -850,12 +878,28 @@
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
+ $ hg -R other log -G
+ o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
+ |
+ o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
+ |
+ | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
+ | |
+ | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G
+ | |/|
+ | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
+ |/ /
+ | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
+
push over http
$ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
$ cat other.pid >> $DAEMON_PIDS
+ $ hg -R main phase --public 32af7686d403
$ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
pushing to http://localhost:$HGPORT2/
searching for changes
@@ -868,51 +912,21 @@
Check final content.
$ hg -R other log -G
- o changeset: 7:32af7686d403
- | tag: tip
- | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: D
+ o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> D
|
- o changeset: 6:5fddd98957c8
- | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: C
+ o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> C
|
- o changeset: 5:42ccdea3bb16
- | parent: 0:cd010b8cd998
- | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: B
+ o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> B
|
- | o changeset: 4:02de42196ebe
- | | parent: 2:24b6387c8c8c
- | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | date: Sat Apr 30 15:24:48 2011 +0200
- | | summary: H
+ | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
| |
- | | o changeset: 3:eea13746799a
- | |/| parent: 2:24b6387c8c8c
- | | | parent: 1:9520eea781bc
- | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | | date: Sat Apr 30 15:24:48 2011 +0200
- | | | summary: G
- | | |
- | o | changeset: 2:24b6387c8c8c
- |/ / parent: 0:cd010b8cd998
- | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | | date: Sat Apr 30 15:24:48 2011 +0200
- | | summary: F
- | |
- | @ changeset: 1:9520eea781bc
- |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- | date: Sat Apr 30 15:24:48 2011 +0200
- | summary: E
- |
- o changeset: 0:cd010b8cd998
- user: Nicolas Dumazet <nicdumz.commits@gmail.com>
- date: Sat Apr 30 15:24:48 2011 +0200
- summary: A
+ | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G
+ | |/|
+ | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
+ |/ /
+ | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
+ |/
+ o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
Error Handling
@@ -938,14 +952,12 @@
> reason = pushop.ui.config('failpush', 'reason', None)
> part = None
> if reason == 'abort':
- > part = bundle2.bundlepart('test:abort')
+ > bundler.newpart('test:abort')
> if reason == 'unknown':
- > part = bundle2.bundlepart('TEST:UNKNOWN')
+ > bundler.newpart('TEST:UNKNOWN')
> if reason == 'race':
> # 20 Bytes of crap
- > part = bundle2.bundlepart('b2x:check:heads', data='01234567890123456789')
- > if part is not None:
- > bundler.addpart(part)
+ > bundler.newpart('b2x:check:heads', data='01234567890123456789')
> return extradata
>
> @bundle2.parthandler("test:abort")
@@ -1015,19 +1027,19 @@
$ hg -R main push other -r e7ec4e813ba6
pushing to other
searching for changes
- abort: missing support for 'test:unknown'
+ abort: missing support for test:unknown
[255]
$ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
pushing to ssh://user@dummy/other
searching for changes
- abort: missing support for "'test:unknown'"
+ abort: missing support for test:unknown
[255]
$ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
pushing to http://localhost:$HGPORT2/
searching for changes
- abort: missing support for "'test:unknown'"
+ abort: missing support for test:unknown
[255]
Doing the actual push: race
--- a/tests/test-check-code-hg.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-check-code-hg.t Mon Jun 09 13:53:23 2014 -0500
@@ -1,36 +1,17 @@
+#if test-repo
+
$ check_code="$TESTDIR"/../contrib/check-code.py
$ cd "$TESTDIR"/..
- $ if hg identify -q > /dev/null 2>&1; then :
- > else
- > echo "skipped: not a Mercurial working dir" >&2
- > exit 80
- > fi
-
-Prepare check for Python files without py extension
-
- $ cp \
- > hg \
- > hgweb.cgi \
- > contrib/convert-repo \
- > contrib/dumprevlog \
- > contrib/hgweb.fcgi \
- > contrib/hgweb.wsgi \
- > contrib/simplemerge \
- > contrib/undumprevlog \
- > i18n/hggettext \
- > i18n/posplit \
- > tests/hghave \
- > tests/dummyssh \
- > "$TESTTMP"/
- $ for f in "$TESTTMP"/*; do mv "$f" "$f.py"; done
New errors are not allowed. Warnings are strongly discouraged.
(The writing "no-che?k-code" is for not skipping this file when checking.)
- $ { hg manifest 2>/dev/null; ls "$TESTTMP"/*.py | sed 's-\\-/-g'; } |
+ $ hg locate | sed 's-\\-/-g' |
> xargs "$check_code" --warnings --per-file=0 || false
Skipping hgext/zeroconf/Zeroconf.py it has no-che?k-code (glob)
Skipping i18n/polib.py it has no-che?k-code (glob)
Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
Skipping mercurial/httpclient/socketutil.py it has no-che?k-code (glob)
+
+#endif
--- a/tests/test-check-code.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-check-code.t Mon Jun 09 13:53:23 2014 -0500
@@ -284,3 +284,19 @@
> print _(
don't use % inside _()
[1]
+
+web templates
+
+ $ mkdir -p mercurial/templates
+ $ cat > mercurial/templates/example.tmpl <<EOF
+ > {desc}
+ > {desc|escape}
+ > {desc|firstline}
+ > {desc|websub}
+ > EOF
+
+ $ "$check_code" --warnings mercurial/templates/example.tmpl
+ mercurial/templates/example.tmpl:2:
+ > {desc|escape}
+ warning: follow desc keyword with either firstline or websub
+ [1]
--- a/tests/test-check-pyflakes.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-check-pyflakes.t Mon Jun 09 13:53:23 2014 -0500
@@ -5,7 +5,7 @@
run pyflakes on all tracked files ending in .py or without a file ending
(skipping binary file random-seed)
- $ hg manifest 2>/dev/null | egrep "\.py$|^[^.]*$" | grep -v /random_seed$ \
+ $ hg locate 'set:**.py or grep("^!#.*python")' 2>/dev/null \
> | xargs pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
contrib/win32/hgwebdir_wsgi.py:*: 'win32traceutil' imported but unused (glob)
setup.py:*: 'sha' imported but unused (glob)
@@ -17,5 +17,6 @@
tests/hghave.py:*: 'pygments' imported but unused (glob)
tests/hghave.py:*: 'ssl' imported but unused (glob)
contrib/win32/hgwebdir_wsgi.py:93: 'from isapi.install import *' used; unable to detect undefined names (glob)
+ tests/filterpyflakes.py:58: undefined name 'undefinedname'
#endif
--- a/tests/test-commandserver.py.out Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-commandserver.py.out Mon Jun 09 13:53:23 2014 -0500
@@ -177,6 +177,7 @@
runcommand update -C 0
1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+(leaving bookmark bm3)
runcommand commit -Am. a
created new head
runcommand log -Gq
--- a/tests/test-commit-amend.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-commit-amend.t Mon Jun 09 13:53:23 2014 -0500
@@ -586,9 +586,10 @@
merging cc incomplete! (edit conflicts, then use 'hg resolve --mark')
[1]
$ hg resolve -m cc
+ no more unresolved files
$ hg ci -m 'merge bar'
$ hg log --config diff.git=1 -pr .
- changeset: 23:d51446492733
+ changeset: 23:93cd4445f720
tag: tip
parent: 22:30d96aeaf27b
parent: 21:1aa437659d19
@@ -603,11 +604,11 @@
--- a/cc
+++ b/cc
@@ -1,1 +1,5 @@
- +<<<<<<< local
+ +<<<<<<< local: 30d96aeaf27b - test: aa
dd
+=======
+cc
- +>>>>>>> other
+ +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
diff --git a/z b/zz
rename from z
rename to zz
@@ -620,7 +621,7 @@
cc not renamed
$ hg ci --amend -m 'merge bar (amend message)'
$ hg log --config diff.git=1 -pr .
- changeset: 24:59de3dce7a79
+ changeset: 24:832b50f2c271
tag: tip
parent: 22:30d96aeaf27b
parent: 21:1aa437659d19
@@ -635,11 +636,11 @@
--- a/cc
+++ b/cc
@@ -1,1 +1,5 @@
- +<<<<<<< local
+ +<<<<<<< local: 30d96aeaf27b - test: aa
dd
+=======
+cc
- +>>>>>>> other
+ +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
diff --git a/z b/zz
rename from z
rename to zz
@@ -653,7 +654,7 @@
$ hg mv zz z
$ hg ci --amend -m 'merge bar (undo rename)'
$ hg log --config diff.git=1 -pr .
- changeset: 26:7fb89c461f81
+ changeset: 26:bdafc5c72f74
tag: tip
parent: 22:30d96aeaf27b
parent: 21:1aa437659d19
@@ -668,11 +669,11 @@
--- a/cc
+++ b/cc
@@ -1,1 +1,5 @@
- +<<<<<<< local
+ +<<<<<<< local: 30d96aeaf27b - test: aa
dd
+=======
+cc
- +>>>>>>> other
+ +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
$ hg debugrename z
z not renamed
@@ -689,9 +690,9 @@
$ echo aa >> aaa
$ hg ci -m 'merge bar again'
$ hg log --config diff.git=1 -pr .
- changeset: 28:982d7a34ffee
+ changeset: 28:32f19415b634
tag: tip
- parent: 26:7fb89c461f81
+ parent: 26:bdafc5c72f74
parent: 27:4c94d5bc65f5
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -724,9 +725,9 @@
$ hg mv aaa aa
$ hg ci --amend -m 'merge bar again (undo rename)'
$ hg log --config diff.git=1 -pr .
- changeset: 30:522688c0e71b
+ changeset: 30:1e2a06b3d312
tag: tip
- parent: 26:7fb89c461f81
+ parent: 26:bdafc5c72f74
parent: 27:4c94d5bc65f5
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -764,9 +765,9 @@
use (c)hanged version or (d)elete? c
$ hg ci -m 'merge bar (with conflicts)'
$ hg log --config diff.git=1 -pr .
- changeset: 33:5f9904c491b8
+ changeset: 33:97a298b0c59f
tag: tip
- parent: 32:01780b896f58
+ parent: 32:3d78ce4226b8
parent: 31:67db8847a540
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -776,9 +777,9 @@
$ hg rm aa
$ hg ci --amend -m 'merge bar (with conflicts, amended)'
$ hg log --config diff.git=1 -pr .
- changeset: 35:6ce0c89781a3
+ changeset: 35:6de0c1bde1c8
tag: tip
- parent: 32:01780b896f58
+ parent: 32:3d78ce4226b8
parent: 31:67db8847a540
user: test
date: Thu Jan 01 00:00:00 1970 +0000
--- a/tests/test-commit-unresolved.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-commit-unresolved.t Mon Jun 09 13:53:23 2014 -0500
@@ -41,6 +41,7 @@
Mark the conflict as resolved and commit
$ hg resolve -m A
+ no more unresolved files
$ hg commit -m "Merged"
$ cd ..
--- a/tests/test-completion.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-completion.t Mon Jun 09 13:53:23 2014 -0500
@@ -212,10 +212,10 @@
serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
summary: remote
- update: clean, check, date, rev
+ update: clean, check, date, rev, tool
addremove: similarity, include, exclude, dry-run
archive: no-decode, prefix, rev, type, subrepos, include, exclude
- backout: merge, parent, rev, tool, include, exclude, message, logfile, date, user
+ backout: merge, parent, rev, edit, tool, include, exclude, message, logfile, date, user
bisect: reset, good, bad, skip, extend, command, noupdate
bookmarks: force, rev, delete, rename, inactive
branch: force, clean
@@ -262,7 +262,7 @@
heads: rev, topo, active, closed, style, template
help: extension, command, keyword
identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
- import: strip, base, edit, force, no-commit, bypass, exact, import-branch, message, logfile, date, user, similarity
+ import: strip, base, edit, force, no-commit, bypass, partial, exact, import-branch, message, logfile, date, user, similarity
incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
locate: rev, print0, fullpath, include, exclude
manifest: rev, all
--- a/tests/test-conflict.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-conflict.t Mon Jun 09 13:53:23 2014 -0500
@@ -22,12 +22,52 @@
32e80765d7fe+75234512624c+ tip
$ cat a
+ <<<<<<< local: 32e80765d7fe - test: branch2
+ something else
+ =======
+ something
+ >>>>>>> other: 75234512624c - test: branch1
+
+ $ hg status
+ M a
+ ? a.orig
+
+Verify custom conflict markers
+
+ $ hg up -q --clean .
+ $ printf "\n[ui]\nmergemarkertemplate={author} {rev}\n" >> .hg/hgrc
+
+ $ hg merge 1
+ merging a
+ warning: conflicts during merge.
+ merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+ [1]
+
+ $ cat a
+ <<<<<<< local: test 2
+ something else
+ =======
+ something
+ >>>>>>> other: test 1
+
+Verify basic conflict markers
+
+ $ hg up -q --clean .
+ $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
+
+ $ hg merge 1
+ merging a
+ warning: conflicts during merge.
+ merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+ [1]
+
+ $ cat a
<<<<<<< local
something else
=======
something
>>>>>>> other
-
- $ hg status
- M a
- ? a.orig
--- a/tests/test-context.py Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-context.py Mon Jun 09 13:53:23 2014 -0500
@@ -21,7 +21,7 @@
# test memctx with non-ASCII commit message
def filectxfn(repo, memctx, path):
- return context.memfilectx("foo", "")
+ return context.memfilectx(repo, "foo", "")
ctx = context.memctx(repo, ['tip', None],
encoding.tolocal("Gr\xc3\xbcezi!"),
--- a/tests/test-convert-hg-sink.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-convert-hg-sink.t Mon Jun 09 13:53:23 2014 -0500
@@ -16,8 +16,10 @@
$ echo file > foo/file
$ hg ci -qAm 'add foo/file'
$ hg tag some-tag
+ $ hg tag -l local-tag
$ hg log
changeset: 3:593cbf6fb2b4
+ tag: local-tag
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -390,3 +392,148 @@
o 0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a
$ cd ..
+
+Two way tests
+
+ $ hg init 0
+ $ echo f > 0/f
+ $ echo a > 0/a-only
+ $ echo b > 0/b-only
+ $ hg -R 0 ci -Aqm0
+
+ $ cat << EOF > filemap-a
+ > exclude b-only
+ > EOF
+ $ cat << EOF > filemap-b
+ > exclude a-only
+ > EOF
+ $ hg convert --filemap filemap-a 0 a
+ initializing destination a repository
+ scanning source...
+ sorting...
+ converting...
+ 0 0
+ $ hg -R a up -q
+ $ echo a > a/f
+ $ hg -R a ci -ma
+
+ $ hg convert --filemap filemap-b 0 b
+ initializing destination b repository
+ scanning source...
+ sorting...
+ converting...
+ 0 0
+ $ hg -R b up -q
+ $ echo b > b/f
+ $ hg -R b ci -mb
+
+ $ tail */.hg/shamap
+ ==> 0/.hg/shamap <==
+ 86f3f774ffb682bffb5dc3c1d3b3da637cb9a0d6 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a
+ dd9f218eb91fb857f2a62fe023e1d64a4e7812fe 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a
+
+ ==> a/.hg/shamap <==
+ 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a 86f3f774ffb682bffb5dc3c1d3b3da637cb9a0d6
+
+ ==> b/.hg/shamap <==
+ 8a028c7c77f6c7bd6d63bc3f02ca9f779eabf16a dd9f218eb91fb857f2a62fe023e1d64a4e7812fe
+
+ $ hg convert a 0
+ scanning source...
+ sorting...
+ converting...
+ 0 a
+
+ $ hg convert b 0
+ scanning source...
+ sorting...
+ converting...
+ 0 b
+
+ $ hg -R 0 log -G
+ o changeset: 2:637fbbbe96b6
+ | tag: tip
+ | parent: 0:8a028c7c77f6
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: b
+ |
+ | o changeset: 1:ec7b9c96e692
+ |/ user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: a
+ |
+ @ changeset: 0:8a028c7c77f6
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 0
+
+ $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1::
+ scanning source...
+ sorting...
+ converting...
+
+ $ hg -R 0 up -r1
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo f >> 0/f
+ $ hg -R 0 ci -mx
+
+ $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1::
+ scanning source...
+ sorting...
+ converting...
+ 0 x
+
+ $ hg -R a log -G -T '{rev} {desc|firstline} ({files})\n'
+ o 2 x (f)
+ |
+ @ 1 a (f)
+ |
+ o 0 0 (a-only f)
+
+ $ hg -R a mani -r tip
+ a-only
+ f
+
+An additional round, demonstrating that unchanged files don't get converted
+
+ $ echo f >> 0/f
+ $ echo f >> 0/a-only
+ $ hg -R 0 ci -m "extra f+a-only change"
+
+ $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1::
+ scanning source...
+ sorting...
+ converting...
+ 0 extra f+a-only change
+
+ $ hg -R a log -G -T '{rev} {desc|firstline} ({files})\n'
+ o 3 extra f+a-only change (f)
+ |
+ o 2 x (f)
+ |
+ @ 1 a (f)
+ |
+ o 0 0 (a-only f)
+
+
+Conversion after rollback
+
+ $ hg -R a rollback -f
+ repository tip rolled back to revision 2 (undo commit)
+
+ $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1::
+ scanning source...
+ sorting...
+ converting...
+ 0 extra f+a-only change
+
+ $ hg -R a log -G -T '{rev} {desc|firstline} ({files})\n'
+ o 3 extra f+a-only change (f)
+ |
+ o 2 x (f)
+ |
+ @ 1 a (f)
+ |
+ o 0 0 (a-only f)
+
--- a/tests/test-convert-hg-source.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-convert-hg-source.t Mon Jun 09 13:53:23 2014 -0500
@@ -24,6 +24,7 @@
$ hg ci -m 'merge local copy' -d '3 0'
$ hg up -C 1
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark premerge1)
$ hg bookmark premerge2
$ hg merge 2
merging foo and baz to baz
--- a/tests/test-convert-svn-sink.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-convert-svn-sink.t Mon Jun 09 13:53:23 2014 -0500
@@ -352,6 +352,7 @@
[1]
$ hg --cwd b revert -r 2 b
$ hg --cwd b resolve -m b
+ no more unresolved files
$ hg --cwd b ci -d '5 0' -m 'merge'
Expect 4 changes
--- a/tests/test-copy-move-merge.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-copy-move-merge.t Mon Jun 09 13:53:23 2014 -0500
@@ -31,16 +31,16 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6
+ preserving a for resolve of b
+ preserving a for resolve of c
+ removing a
b: remote moved from a -> m
- preserving a for resolve of b
- c: remote moved from a -> m
- preserving a for resolve of c
- removing a
updating: b 1/2 files (50.00%)
picked tool 'internal:merge' for b (binary False symlink False)
merging a and b to b
my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
premerge successful
+ c: remote moved from a -> m
updating: c 2/2 files (100.00%)
picked tool 'internal:merge' for c (binary False symlink False)
merging a and c to c
--- a/tests/test-double-merge.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-double-merge.t Mon Jun 09 13:53:23 2014 -0500
@@ -35,15 +35,15 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: e6dc8efe11cc, local: 6a0df1dad128+, remote: 484bf6903104
+ preserving foo for resolve of bar
+ preserving foo for resolve of foo
bar: remote copied from foo -> m
- preserving foo for resolve of bar
- foo: versions differ -> m
- preserving foo for resolve of foo
updating: bar 1/2 files (50.00%)
picked tool 'internal:merge' for bar (binary False symlink False)
merging foo and bar to bar
my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc
premerge successful
+ foo: versions differ -> m
updating: foo 2/2 files (100.00%)
picked tool 'internal:merge' for foo (binary False symlink False)
merging foo
--- a/tests/test-encoding-align.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-encoding-align.t Mon Jun 09 13:53:23 2014 -0500
@@ -16,19 +16,18 @@
> f = file('l', 'w'); f.write(l); f.close()
> # instant extension to show list of options
> f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8
+ > from mercurial import cmdutil
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
+ >
+ > @command('showoptlist',
+ > [('s', 'opt1', '', 'short width' + ' %(s)s' * 8, '%(s)s'),
+ > ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'),
+ > ('l', 'opt3', '', 'long width' + ' %(l)s' * 8, '%(l)s')],
+ > '')
> def showoptlist(ui, repo, *pats, **opts):
> '''dummy command to show option descriptions'''
> return 0
- > cmdtable = {
- > 'showoptlist':
- > (showoptlist,
- > [('s', 'opt1', '', 'short width' + ' %(s)s' * 8, '%(s)s'),
- > ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'),
- > ('l', 'opt3', '', 'long width' + ' %(l)s' * 8, '%(l)s')
- > ],
- > ""
- > )
- > }
> """ % globals())
> f.close()
> EOF
--- a/tests/test-encoding-textwrap.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-encoding-textwrap.t Mon Jun 09 13:53:23 2014 -0500
@@ -6,7 +6,13 @@
define commands to display help text
$ cat << EOF > show.py
+ > from mercurial import cmdutil
+ >
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
+ >
> # Japanese full-width characters:
+ > @command('show_full_ja', [], '')
> def show_full_ja(ui, **opts):
> u'''\u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051
>
@@ -16,6 +22,7 @@
> '''
>
> # Japanese half-width characters:
+ > @command('show_half_ja', [], '')
> def show_half_ja(ui, *opts):
> u'''\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79
>
@@ -25,6 +32,7 @@
> '''
>
> # Japanese ambiguous-width characters:
+ > @command('show_ambig_ja', [], '')
> def show_ambig_ja(ui, **opts):
> u'''\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb
>
@@ -34,6 +42,7 @@
> '''
>
> # Russian ambiguous-width characters:
+ > @command('show_ambig_ru', [], '')
> def show_ambig_ru(ui, **opts):
> u'''\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
>
@@ -41,13 +50,6 @@
>
> \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
> '''
- >
- > cmdtable = {
- > 'show_full_ja': (show_full_ja, [], ""),
- > 'show_half_ja': (show_half_ja, [], ""),
- > 'show_ambig_ja': (show_ambig_ja, [], ""),
- > 'show_ambig_ru': (show_ambig_ru, [], ""),
- > }
> EOF
"COLUMNS=60" means that there is no lines which has grater than 58 width
--- a/tests/test-extension.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-extension.t Mon Jun 09 13:53:23 2014 -0500
@@ -2,7 +2,10 @@
$ cat > foobar.py <<EOF
> import os
- > from mercurial import commands
+ > from mercurial import cmdutil, commands
+ >
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
>
> def uisetup(ui):
> ui.write("uisetup called\\n")
@@ -11,17 +14,14 @@
> ui.write("reposetup called for %s\\n" % os.path.basename(repo.root))
> ui.write("ui %s= repo.ui\\n" % (ui == repo.ui and "=" or "!"))
>
+ > @command('foo', [], 'hg foo')
> def foo(ui, *args, **kwargs):
> ui.write("Foo\\n")
>
+ > @command('bar', [], 'hg bar')
> def bar(ui, *args, **kwargs):
> ui.write("Bar\\n")
>
- > cmdtable = {
- > "foo": (foo, [], "hg foo"),
- > "bar": (bar, [], "hg bar"),
- > }
- >
> commands.norepo += ' bar'
> EOF
$ abspath=`pwd`/foobar.py
@@ -288,21 +288,22 @@
$ cat > debugextension.py <<EOF
> '''only debugcommands
> '''
+ > from mercurial import cmdutil
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
+ >
+ > @command('debugfoobar', [], 'hg debugfoobar')
> def debugfoobar(ui, repo, *args, **opts):
> "yet another debug command"
> pass
>
+ > @command('foo', [], 'hg foo')
> def foo(ui, repo, *args, **opts):
> """yet another foo command
>
> This command has been DEPRECATED since forever.
> """
> pass
- >
- > cmdtable = {
- > "debugfoobar": (debugfoobar, (), "hg debugfoobar"),
- > "foo": (foo, (), "hg foo")
- > }
> EOF
$ debugpath=`pwd`/debugextension.py
$ echo "debugextension = $debugpath" >> $HGRCPATH
@@ -475,15 +476,15 @@
Test help topic with same name as extension
$ cat > multirevs.py <<EOF
- > from mercurial import commands
+ > from mercurial import cmdutil, commands
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
> """multirevs extension
> Big multi-line module docstring."""
+ > @command('multirevs', [], 'ARG')
> def multirevs(ui, repo, arg, *args, **opts):
> """multirevs command"""
> pass
- > cmdtable = {
- > "multirevs": (multirevs, [], 'ARG')
- > }
> commands.norepo += ' multirevs'
> EOF
$ echo "multirevs = multirevs.py" >> $HGRCPATH
@@ -532,13 +533,15 @@
$ cat > debugissue811.py <<EOF
> '''show all loaded extensions
> '''
- > from mercurial import extensions, commands
+ > from mercurial import cmdutil, commands, extensions
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
>
+ > @command('debugextensions', [], 'hg debugextensions')
> def debugextensions(ui):
> "yet another debug command"
> ui.write("%s\n" % '\n'.join([x for x, y in extensions.extensions()]))
>
- > cmdtable = {"debugextensions": (debugextensions, (), "hg debugextensions")}
> commands.norepo += " debugextensions"
> EOF
$ echo "debugissue811 = $debugpath" >> $HGRCPATH
@@ -618,8 +621,8 @@
> EOF
$ hg --config extensions.path=./path.py help foo > /dev/null
warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
- hg: unknown command 'foo'
- warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
+ abort: no such help topic: foo
+ (try "hg help --keyword foo")
[255]
$ cat > throw.py <<EOF
--- a/tests/test-fetch.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-fetch.t Mon Jun 09 13:53:23 2014 -0500
@@ -68,8 +68,9 @@
$ cat a/hg.pid >> "$DAEMON_PIDS"
fetch over http, no auth
+(this also tests that editor is invoked if '--edit' is specified)
- $ hg --cwd d fetch http://localhost:$HGPORT/
+ $ HGEDITOR=cat hg --cwd d fetch --edit http://localhost:$HGPORT/
pulling from http://localhost:$HGPORT/
searching for changes
adding changesets
@@ -80,13 +81,29 @@
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
merging with 1:d36c0562f908
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ Automated merge with http://localhost:$HGPORT/
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch merge
+ HG: branch 'default'
+ HG: changed c
new changeset 3:* merges remote changes with local (glob)
$ hg --cwd d tip --template '{desc}\n'
Automated merge with http://localhost:$HGPORT/
+ $ hg --cwd d status --rev 'tip^1' --rev tip
+ A c
+ $ hg --cwd d status --rev 'tip^2' --rev tip
+ A b
fetch over http with auth (should be hidden in desc)
+(this also tests that editor is not invoked if '--edit' is not
+specified, even though commit message is not specified explicitly)
- $ hg --cwd e fetch http://user:password@localhost:$HGPORT/
+ $ HGEDITOR=cat hg --cwd e fetch http://user:password@localhost:$HGPORT/
pulling from http://user:***@localhost:$HGPORT/
searching for changes
adding changesets
--- a/tests/test-fileset.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-fileset.t Mon Jun 09 13:53:23 2014 -0500
@@ -154,6 +154,7 @@
b2
$ echo e > b2
$ hg resolve -m b2
+ no more unresolved files
$ fileset 'resolved()'
b2
$ fileset 'unresolved()'
--- a/tests/test-gpg.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-gpg.t Mon Jun 09 13:53:23 2014 -0500
@@ -16,8 +16,17 @@
$ hg sigs
- $ hg sign 0
+ $ HGEDITOR=cat hg sign -e 0
signing 0:e63c23eaa88a
+ Added signature for changeset e63c23eaa88a
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added .hgsigs
$ hg sigs
hgtest 0:e63c23eaa88ae77967edcf4ea194d31167c478b0
--- a/tests/test-graft.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-graft.t Mon Jun 09 13:53:23 2014 -0500
@@ -76,10 +76,24 @@
$ hg revert a
Graft a rename:
+(this also tests that editor is invoked if '--edit' is specified)
- $ hg graft 2 -u foo
+ $ hg status --rev "2^1" --rev 2
+ A b
+ R a
+ $ HGEDITOR=cat hg graft 2 -u foo --edit
grafting revision 2
merging a and b to b
+ 2
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: foo
+ HG: branch 'default'
+ HG: changed b
+ HG: removed a
$ hg export tip --git
# HG changeset patch
# User foo
@@ -114,6 +128,7 @@
Graft out of order, skipping a merge and a duplicate
+(this also tests that editor is not invoked if '--edit' is not specified)
$ hg graft 1 5 4 3 'merge()' 2 -n
skipping ungraftable merge revision 6
@@ -123,7 +138,7 @@
grafting revision 4
grafting revision 3
- $ hg graft 1 5 4 3 'merge()' 2 --debug
+ $ HGEDITOR=cat hg graft 1 5 4 3 'merge()' 2 --debug
skipping ungraftable merge revision 6
scanning for duplicate grafts
skipping revision 2 (already grafted to 7)
@@ -137,8 +152,8 @@
resolving manifests
branchmerge: True, force: True, partial: False
ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
+ preserving b for resolve of b
b: local copied/moved from a -> m
- preserving b for resolve of b
updating: b 1/1 files (100.00%)
picked tool 'internal:merge' for b (binary False symlink False)
merging b and a to b
@@ -150,22 +165,22 @@
resolving manifests
branchmerge: True, force: True, partial: False
ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
- b: keep -> k
e: remote is newer -> g
getting e
updating: e 1/1 files (100.00%)
+ b: keep -> k
e
grafting revision 4
searching for copies back to rev 1
resolving manifests
branchmerge: True, force: True, partial: False
ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
- b: keep -> k
+ preserving e for resolve of e
d: remote is newer -> g
- e: versions differ -> m
- preserving e for resolve of e
getting d
updating: d 1/2 files (50.00%)
+ b: keep -> k
+ e: versions differ -> m
updating: e 2/2 files (100.00%)
picked tool 'internal:merge' for e (binary False symlink False)
merging e
@@ -220,6 +235,7 @@
$ echo b > e
$ hg resolve -m e
+ no more unresolved files
Continue with a revision should fail:
@@ -354,6 +370,7 @@
[255]
$ hg resolve --all
merging a
+ no more unresolved files
$ hg graft -c
grafting revision 1
$ hg export tip --git
@@ -382,6 +399,7 @@
[255]
$ hg resolve --all
merging a and b to b
+ no more unresolved files
$ hg graft -c
grafting revision 2
$ hg export tip --git
--- a/tests/test-help.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-help.t Mon Jun 09 13:53:23 2014 -0500
@@ -596,30 +596,8 @@
show changed files in the working directory
$ hg help foo
- hg: unknown command 'foo'
- Mercurial Distributed SCM
-
- basic commands:
-
- add add the specified files on the next commit
- annotate show changeset information by line for each file
- clone make a copy of an existing repository
- commit commit the specified files or all outstanding changes
- diff diff repository (or selected files)
- export dump the header and diffs for one or more changesets
- forget forget the specified files on the next commit
- init create a new repository in the given directory
- log show revision history of entire repository or files
- merge merge working directory with another revision
- pull pull changes from the specified source
- push push changes to the specified destination
- remove remove the specified files on the next commit
- serve start stand-alone webserver
- status show changed files in the working directory
- summary summarize working directory state
- update update working directory (or switch revisions)
-
- use "hg help" for the full list of commands or "hg -v" for details
+ abort: no such help topic: foo
+ (try "hg help --keyword foo")
[255]
$ hg skjdfks
@@ -652,19 +630,20 @@
$ cat > helpext.py <<EOF
> import os
- > from mercurial import commands
+ > from mercurial import cmdutil, commands
+ >
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
>
+ > @command('nohelp',
+ > [('', 'longdesc', 3, 'x'*90),
+ > ('n', '', None, 'normal desc'),
+ > ('', 'newline', '', 'line1\nline2')],
+ > 'hg nohelp')
+ > @command('debugoptDEP', [('', 'dopt', None, 'option is DEPRECATED')])
> def nohelp(ui, *args, **kwargs):
> pass
>
- > cmdtable = {
- > "debugoptDEP": (nohelp, [('', 'dopt', None, 'option is DEPRECATED')],),
- > "nohelp": (nohelp, [('', 'longdesc', 3, 'x'*90),
- > ('n', '', None, 'normal desc'),
- > ('', 'newline', '', 'line1\nline2'),
- > ], "hg nohelp"),
- > }
- >
> commands.norepo += ' nohelp'
> EOF
$ echo '[extensions]' >> $HGRCPATH
@@ -987,6 +966,20 @@
qclone clone main and patch repository at same time
+Test unfound topic
+
+ $ hg help nonexistingtopicthatwillneverexisteverever
+ abort: no such help topic: nonexistingtopicthatwillneverexisteverever
+ (try "hg help --keyword nonexistingtopicthatwillneverexisteverever")
+ [255]
+
+Test unfound keyword
+
+ $ hg help --keyword nonexistingwordthatwillneverexisteverever
+ abort: no matches
+ (try "hg help" for a list of topics)
+ [255]
+
Test omit indicating for help
$ cat > addverboseitems.py <<EOF
--- a/tests/test-hgweb-commands.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-hgweb-commands.t Mon Jun 09 13:53:23 2014 -0500
@@ -31,10 +31,15 @@
$ hg ci -l msg
$ rm msg
- $ echo [graph] >> .hg/hgrc
- $ echo default.width = 3 >> .hg/hgrc
- $ echo stable.width = 3 >> .hg/hgrc
- $ echo stable.color = FF0000 >> .hg/hgrc
+ $ cat > .hg/hgrc <<EOF
+ > [graph]
+ > default.width = 3
+ > stable.width = 3
+ > stable.color = FF0000
+ > [websub]
+ > append = s|(.*)|\1(websub)|
+ > EOF
+
$ hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
$ cat hg.pid >> $DAEMON_PIDS
$ hg log -G --template '{rev}:{node|short} {desc}\n'
@@ -95,7 +100,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>branch commit with null character: </td>
+ <td>branch commit with null character: (websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -138,7 +143,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>branch</td>
+ <td>branch(websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -181,7 +186,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>Added tag 1.0 for changeset 2ef0ac749a14</td>
+ <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -224,7 +229,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>base</td>
+ <td>base(websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -235,6 +240,180 @@
</entry>
</feed>
+ $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=rss'
+ 200 Script output follows
+
+ <?xml version="1.0" encoding="ascii"?>
+ <rss version="2.0">
+ <channel>
+ <link>http://*:$HGPORT/</link> (glob)
+ <language>en-us</language>
+
+ <title>test Changelog</title>
+ <description>test Changelog</description>
+ <item>
+ <title>[unstable] branch commit with null character: </title>
+ <guid isPermaLink="true">http://*:$HGPORT/rev/cad8025a2e87</guid> (glob)
+ <link>http://*:$HGPORT/rev/cad8025a2e87</link> (glob)
+ <description>
+ <![CDATA[
+ <table>
+ <tr>
+ <th style="text-align:left;">changeset</th>
+ <td>cad8025a2e87</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">branch</th>
+ <td>unstable</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">bookmark</th>
+ <td>something</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">tag</th>
+ <td>tip</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">user</th>
+ <td>test</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">description</th>
+ <td>branch commit with null character: (websub)</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">files</th>
+ <td></td>
+ </tr>
+ </table>
+ ]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+ <item>
+ <title>[stable] branch</title>
+ <guid isPermaLink="true">http://*:$HGPORT/rev/1d22e65f027e</guid> (glob)
+ <link>http://*:$HGPORT/rev/1d22e65f027e</link> (glob)
+ <description>
+ <![CDATA[
+ <table>
+ <tr>
+ <th style="text-align:left;">changeset</th>
+ <td>1d22e65f027e</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">branch</th>
+ <td>stable</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">bookmark</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">tag</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">user</th>
+ <td>test</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">description</th>
+ <td>branch(websub)</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">files</th>
+ <td>foo<br /></td>
+ </tr>
+ </table>
+ ]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+ <item>
+ <title>[default] Added tag 1.0 for changeset 2ef0ac749a14</title>
+ <guid isPermaLink="true">http://*:$HGPORT/rev/a4f92ed23982</guid> (glob)
+ <link>http://*:$HGPORT/rev/a4f92ed23982</link> (glob)
+ <description>
+ <![CDATA[
+ <table>
+ <tr>
+ <th style="text-align:left;">changeset</th>
+ <td>a4f92ed23982</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">branch</th>
+ <td>default</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">bookmark</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">tag</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">user</th>
+ <td>test</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">description</th>
+ <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">files</th>
+ <td>.hgtags<br /></td>
+ </tr>
+ </table>
+ ]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+ <item>
+ <title>base</title>
+ <guid isPermaLink="true">http://*:$HGPORT/rev/2ef0ac749a14</guid> (glob)
+ <link>http://*:$HGPORT/rev/2ef0ac749a14</link> (glob)
+ <description>
+ <![CDATA[
+ <table>
+ <tr>
+ <th style="text-align:left;">changeset</th>
+ <td>2ef0ac749a14</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">branch</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">bookmark</th>
+ <td>anotherthing</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">tag</th>
+ <td>1.0</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">user</th>
+ <td>test</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">description</th>
+ <td>base(websub)</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">files</th>
+ <td>da/foo<br />foo<br /></td>
+ </tr>
+ </table>
+ ]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+
+ </channel>
+ </rss> (no-eol)
$ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=atom'
200 Script output follows
@@ -281,7 +460,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>Added tag 1.0 for changeset 2ef0ac749a14</td>
+ <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -324,7 +503,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>base</td>
+ <td>base(websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -335,6 +514,100 @@
</entry>
</feed>
+ $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=rss'
+ 200 Script output follows
+
+ <?xml version="1.0" encoding="ascii"?>
+ <rss version="2.0">
+ <channel>
+ <link>http://*:$HGPORT/</link> (glob)
+ <language>en-us</language>
+
+ <title>test Changelog</title>
+ <description>test Changelog</description>
+ <item>
+ <title>[default] Added tag 1.0 for changeset 2ef0ac749a14</title>
+ <guid isPermaLink="true">http://*:$HGPORT/rev/a4f92ed23982</guid> (glob)
+ <link>http://*:$HGPORT/rev/a4f92ed23982</link> (glob)
+ <description>
+ <![CDATA[
+ <table>
+ <tr>
+ <th style="text-align:left;">changeset</th>
+ <td>a4f92ed23982</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">branch</th>
+ <td>default</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">bookmark</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">tag</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">user</th>
+ <td>test</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">description</th>
+ <td>Added tag 1.0 for changeset 2ef0ac749a14(websub)</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">files</th>
+ <td>.hgtags<br /></td>
+ </tr>
+ </table>
+ ]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+ <item>
+ <title>base</title>
+ <guid isPermaLink="true">http://*:$HGPORT/rev/2ef0ac749a14</guid> (glob)
+ <link>http://*:$HGPORT/rev/2ef0ac749a14</link> (glob)
+ <description>
+ <![CDATA[
+ <table>
+ <tr>
+ <th style="text-align:left;">changeset</th>
+ <td>2ef0ac749a14</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">branch</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">bookmark</th>
+ <td>anotherthing</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;">tag</th>
+ <td>1.0</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">user</th>
+ <td>test</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">description</th>
+ <td>base(websub)</td>
+ </tr>
+ <tr>
+ <th style="text-align:left;vertical-align:top;">files</th>
+ <td>da/foo<br />foo<br /></td>
+ </tr>
+ </table>
+ ]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+
+ </channel>
+ </rss> (no-eol)
$ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=atom'
200 Script output follows
@@ -379,7 +652,7 @@
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">description</th>
- <td>base</td>
+ <td>base(websub)</td>
</tr>
<tr>
<th style="text-align:left;vertical-align:top;">files</th>
@@ -390,6 +663,27 @@
</entry>
</feed>
+ $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=rss'
+ 200 Script output follows
+
+ <?xml version="1.0" encoding="ascii"?>
+ <rss version="2.0">
+ <channel>
+ <link>http://*:$HGPORT/</link> (glob)
+ <language>en-us</language>
+
+ <title>test: foo history</title>
+ <description>foo revision history</description>
+ <item>
+ <title>base</title>
+ <link>http://*:$HGPORT/log2ef0ac749a14/foo</link> (glob)
+ <description><![CDATA[base(websub)]]></description>
+ <author>test</author>
+ <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
+ </item>
+
+ </channel>
+ </rss>
$ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/'
200 Script output follows
@@ -570,7 +864,7 @@
number or hash, or <a href="/help/revsets">revset expression</a>.</div>
</form>
- <div class="description">base</div>
+ <div class="description">base(websub)</div>
<table id="changesetEntry">
<tr>
@@ -992,7 +1286,7 @@
number or hash, or <a href="/help/revsets">revset expression</a>.</div>
</form>
- <div class="description">Added tag 1.0 for changeset 2ef0ac749a14</div>
+ <div class="description">Added tag 1.0 for changeset 2ef0ac749a14(websub)</div>
<table id="changesetEntry">
<tr>
@@ -1116,7 +1410,7 @@
number or hash, or <a href="/help/revsets">revset expression</a>.</div>
</form>
- <div class="description">branch</div>
+ <div class="description">branch(websub)</div>
<table id="changesetEntry">
<tr>
--- a/tests/test-histedit-edit.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-histedit-edit.t Mon Jun 09 13:53:23 2014 -0500
@@ -156,7 +156,19 @@
update: 1 new changesets (update)
hist: 1 remaining (histedit --continue)
- $ HGEDITOR='true' hg histedit --continue
+(test also that editor is invoked if histedit is continued for
+"edit" action)
+
+ $ HGEDITOR='cat' hg histedit --continue
+ f
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added f
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-backup.hg (glob)
@@ -200,8 +212,9 @@
> raise util.Abort('emulating unexpected abort')
> repo.__class__ = commitfailure
> EOF
- $ cat > .hg/hgrc <<EOF
+ $ cat >> .hg/hgrc <<EOF
> [extensions]
+ > # this failure occurs before editor invocation
> commitfailure = $TESTTMP/commitfailure.py
> EOF
@@ -211,22 +224,87 @@
> echo "===="
> echo "check saving last-message.txt" >> \$1
> EOF
+
+(test that editor is not invoked before transaction starting)
+
$ rm -f .hg/last-message.txt
$ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
> mess 1fd3b2fe7754 f
> EOF
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ abort: emulating unexpected abort
+ $ cat .hg/last-message.txt
+ cat: .hg/last-message.txt: No such file or directory
+ [1]
+
+ $ cat >> .hg/hgrc <<EOF
+ > [extensions]
+ > commitfailure = !
+ > EOF
+ $ hg histedit --abort -q
+
+(test that editor is invoked and commit message is saved into
+"last-message.txt")
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > # this failure occurs after editor invocation
+ > pretxncommit.unexpectedabort = false
+ > EOF
+
+ $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
+ A f
+
+ $ rm -f .hg/last-message.txt
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
+ > mess 1fd3b2fe7754 f
+ > EOF
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ adding f
==== before editing
f
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added f
====
- abort: emulating unexpected abort
+ transaction abort!
+ rollback completed
+ note: commit message saved in .hg/last-message.txt
+ abort: pretxncommit.unexpectedabort hook exited with status 1
+ [255]
$ cat .hg/last-message.txt
f
+
+
check saving last-message.txt
- $ cat > .hg/hgrc <<EOF
- > [extensions]
- > commitfailure = !
+(test also that editor is invoked if histedit is continued for "message"
+action)
+
+ $ HGEDITOR=cat hg histedit --continue
+ f
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added f
+ transaction abort!
+ rollback completed
+ note: commit message saved in .hg/last-message.txt
+ abort: pretxncommit.unexpectedabort hook exited with status 1
+ [255]
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > pretxncommit.unexpectedabort =
> EOF
$ hg histedit --abort -q
--- a/tests/test-histedit-fold-non-commute.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-histedit-fold-non-commute.t Mon Jun 09 13:53:23 2014 -0500
@@ -95,6 +95,7 @@
fix up
$ echo 'I can haz no commute' > e
$ hg resolve --mark e
+ no more unresolved files
$ cat > cat.py <<EOF
> import sys
> print open(sys.argv[1]).read()
@@ -129,6 +130,7 @@
just continue this time
$ hg revert -r 'p1()' e
$ hg resolve --mark e
+ no more unresolved files
$ hg histedit --continue 2>&1 | fixbundle
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-histedit-fold.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-histedit-fold.t Mon Jun 09 13:53:23 2014 -0500
@@ -217,6 +217,7 @@
U file
$ hg revert -r 'p1()' file
$ hg resolve --mark file
+ no more unresolved files
$ hg histedit --continue
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
saved backup bundle to $TESTTMP/*-backup.hg (glob)
@@ -276,6 +277,7 @@
> 5
> EOF
$ hg resolve --mark file
+ no more unresolved files
$ hg commit -m '+5.2'
created new head
$ echo 6 >> file
--- a/tests/test-histedit-non-commute.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-histedit-non-commute.t Mon Jun 09 13:53:23 2014 -0500
@@ -154,6 +154,7 @@
fix up
$ echo 'I can haz no commute' > e
$ hg resolve --mark e
+ no more unresolved files
$ hg histedit --continue 2>&1 | fixbundle
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
merging e
@@ -167,6 +168,7 @@
just continue this time
$ hg revert -r 'p1()' e
$ hg resolve --mark e
+ no more unresolved files
$ hg histedit --continue 2>&1 | fixbundle
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -239,6 +241,7 @@
$ echo 'I can haz no commute' > e
$ hg resolve --mark e
+ no more unresolved files
$ hg histedit --continue 2>&1 | fixbundle
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
merging e
@@ -248,6 +251,7 @@
second edit also fails, but just continue
$ hg revert -r 'p1()' e
$ hg resolve --mark e
+ no more unresolved files
$ hg histedit --continue 2>&1 | fixbundle
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-import-bypass.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-import-bypass.t Mon Jun 09 13:53:23 2014 -0500
@@ -22,8 +22,10 @@
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
Test importing an existing revision
+(this also tests that editor is not invoked for '--bypass', if the
+patch contains the commit message, regardless of '--edit')
- $ hg import --bypass --exact ../test.diff
+ $ HGEDITOR=cat hg import --bypass --exact --edit ../test.diff
applying ../test.diff
$ shortlog
o 1:4e322f7ce8e3 test 0 0 - foo - changea
@@ -107,6 +109,8 @@
[255]
Test commit editor
+(this also tests that editor is invoked, if the patch doesn't contain
+the commit message, regardless of '--edit')
$ cat > ../test.diff <<EOF
> diff -r 07f494440405 -r 4e322f7ce8e3 a
@@ -131,10 +135,12 @@
[255]
Test patch.eol is handled
+(this also tests that editor is not invoked for '--bypass', if the
+commit message is explicitly specified, regardless of '--edit')
$ python -c 'file("a", "wb").write("a\r\n")'
$ hg ci -m makeacrlf
- $ hg import -m 'should fail because of eol' --bypass ../test.diff
+ $ HGEDITOR=cat hg import -m 'should fail because of eol' --edit --bypass ../test.diff
applying ../test.diff
patching file a
Hunk #1 FAILED at 0
--- a/tests/test-import.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-import.t Mon Jun 09 13:53:23 2014 -0500
@@ -23,6 +23,8 @@
import exported patch
+(this also tests that editor is not invoked, if the patch contains the
+commit message and '--edit' is not specified)
$ hg clone -r0 a b
adding changesets
@@ -31,7 +33,7 @@
added 1 changesets with 2 changes to 2 files
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg --cwd b import ../exported-tip.patch
+ $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
applying ../exported-tip.patch
message and committer and date should be same
@@ -47,6 +49,8 @@
import exported patch with external patcher
+(this also tests that editor is invoked, if the '--edit' is specified,
+regardless of the commit message in the patch)
$ cat > dummypatch.py <<EOF
> print 'patching file a'
@@ -59,14 +63,25 @@
added 1 changesets with 2 changes to 2 files
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
+ $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
applying ../exported-tip.patch
+ second change
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: someone
+ HG: branch 'default'
+ HG: changed a
$ cat b/a
line2
$ rm -r b
import of plain diff should fail without message
+(this also tests that editor is invoked, if the patch doesn't contain
+the commit message, regardless of '--edit')
$ hg clone -r0 a b
adding changesets
@@ -75,8 +90,16 @@
added 1 changesets with 2 changes to 2 files
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg --cwd b import ../diffed-tip.patch
+ $ HGEDITOR=cat hg --cwd b import ../diffed-tip.patch
applying ../diffed-tip.patch
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: changed a
abort: empty commit message
[255]
$ rm -r b
@@ -97,6 +120,8 @@
import of plain diff with specific date and user
+(this also tests that editor is not invoked, if
+'--message'/'--logfile' is specified and '--edit' is not)
$ hg clone -r0 a b
adding changesets
@@ -128,6 +153,8 @@
import of plain diff should be ok with --no-commit
+(this also tests that editor is not invoked, if '--no-commit' is
+specified, regardless of '--edit')
$ hg clone -r0 a b
adding changesets
@@ -136,7 +163,7 @@
added 1 changesets with 2 changes to 2 files
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg --cwd b import --no-commit ../diffed-tip.patch
+ $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
applying ../diffed-tip.patch
$ hg --cwd b diff --nodates
diff -r 80971e65b431 a
@@ -1154,5 +1181,272 @@
3
4
line
+ $ cd ..
- $ cd ..
+Test partial application
+------------------------
+
+prepare a stack of patches depending on each other
+
+ $ hg init partial
+ $ cd partial
+ $ cat << EOF > a
+ > one
+ > two
+ > three
+ > four
+ > five
+ > six
+ > seven
+ > EOF
+ $ hg add a
+ $ echo 'b' > b
+ $ hg add b
+ $ hg commit -m 'initial' -u Babar
+ $ cat << EOF > a
+ > one
+ > two
+ > 3
+ > four
+ > five
+ > six
+ > seven
+ > EOF
+ $ hg commit -m 'three' -u Celeste
+ $ cat << EOF > a
+ > one
+ > two
+ > 3
+ > 4
+ > five
+ > six
+ > seven
+ > EOF
+ $ hg commit -m 'four' -u Rataxes
+ $ cat << EOF > a
+ > one
+ > two
+ > 3
+ > 4
+ > 5
+ > six
+ > seven
+ > EOF
+ $ echo bb >> b
+ $ hg commit -m 'five' -u Arthur
+ $ echo 'Babar' > jungle
+ $ hg add jungle
+ $ hg ci -m 'jungle' -u Zephir
+ $ echo 'Celeste' >> jungle
+ $ hg ci -m 'extended jungle' -u Cornelius
+ $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
+ @ extended jungle [Cornelius] 1: +1/-0
+ |
+ o jungle [Zephir] 1: +1/-0
+ |
+ o five [Arthur] 2: +2/-1
+ |
+ o four [Rataxes] 1: +1/-1
+ |
+ o three [Celeste] 1: +1/-1
+ |
+ o initial [Babar] 2: +8/-0
+
+
+Importing with some success and some errors:
+
+ $ hg update --rev 'desc(initial)'
+ 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg export --rev 'desc(five)' | hg import --partial -
+ applying patch from stdin
+ patching file a
+ Hunk #1 FAILED at 1
+ 1 out of 1 hunks FAILED -- saving rejects to file a.rej
+ patch applied partially
+ (fix the .rej files and run `hg commit --amend`)
+ [1]
+
+ $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
+ @ five [Arthur] 1: +1/-0
+ |
+ | o extended jungle [Cornelius] 1: +1/-0
+ | |
+ | o jungle [Zephir] 1: +1/-0
+ | |
+ | o five [Arthur] 2: +2/-1
+ | |
+ | o four [Rataxes] 1: +1/-1
+ | |
+ | o three [Celeste] 1: +1/-1
+ |/
+ o initial [Babar] 2: +8/-0
+
+ $ hg export
+ # HG changeset patch
+ # User Arthur
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
+ # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
+ five
+
+ diff -r 8e4f0351909e -r 26e6446bb252 b
+ --- a/b Thu Jan 01 00:00:00 1970 +0000
+ +++ b/b Thu Jan 01 00:00:00 1970 +0000
+ @@ -1,1 +1,2 @@
+ b
+ +bb
+ $ hg status -c .
+ C a
+ C b
+ $ ls
+ a
+ a.rej
+ b
+
+Importing with zero success:
+
+ $ hg update --rev 'desc(initial)'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg export --rev 'desc(four)' | hg import --partial -
+ applying patch from stdin
+ patching file a
+ Hunk #1 FAILED at 0
+ 1 out of 1 hunks FAILED -- saving rejects to file a.rej
+ patch applied partially
+ (fix the .rej files and run `hg commit --amend`)
+ [1]
+
+ $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
+ @ four [Rataxes] 0: +0/-0
+ |
+ | o five [Arthur] 1: +1/-0
+ |/
+ | o extended jungle [Cornelius] 1: +1/-0
+ | |
+ | o jungle [Zephir] 1: +1/-0
+ | |
+ | o five [Arthur] 2: +2/-1
+ | |
+ | o four [Rataxes] 1: +1/-1
+ | |
+ | o three [Celeste] 1: +1/-1
+ |/
+ o initial [Babar] 2: +8/-0
+
+ $ hg export
+ # HG changeset patch
+ # User Rataxes
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
+ # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
+ four
+
+ $ hg status -c .
+ C a
+ C b
+ $ ls
+ a
+ a.rej
+ b
+
+Importing with unknown file:
+
+ $ hg update --rev 'desc(initial)'
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg export --rev 'desc("extended jungle")' | hg import --partial -
+ applying patch from stdin
+ unable to find 'jungle' for patching
+ 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
+ patch applied partially
+ (fix the .rej files and run `hg commit --amend`)
+ [1]
+
+ $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
+ @ extended jungle [Cornelius] 0: +0/-0
+ |
+ | o four [Rataxes] 0: +0/-0
+ |/
+ | o five [Arthur] 1: +1/-0
+ |/
+ | o extended jungle [Cornelius] 1: +1/-0
+ | |
+ | o jungle [Zephir] 1: +1/-0
+ | |
+ | o five [Arthur] 2: +2/-1
+ | |
+ | o four [Rataxes] 1: +1/-1
+ | |
+ | o three [Celeste] 1: +1/-1
+ |/
+ o initial [Babar] 2: +8/-0
+
+ $ hg export
+ # HG changeset patch
+ # User Cornelius
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
+ # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
+ extended jungle
+
+ $ hg status -c .
+ C a
+ C b
+ $ ls
+ a
+ a.rej
+ b
+ jungle.rej
+
+Importing multiple failing patches:
+
+ $ hg update --rev 'desc(initial)'
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo 'B' > b # just to make another commit
+ $ hg commit -m "a new base"
+ created new head
+ $ hg export --rev 'desc("extended jungle") + desc("four")' | hg import --partial -
+ applying patch from stdin
+ patching file a
+ Hunk #1 FAILED at 0
+ 1 out of 1 hunks FAILED -- saving rejects to file a.rej
+ patch applied partially
+ (fix the .rej files and run `hg commit --amend`)
+ [1]
+ $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
+ @ four [Rataxes] 0: +0/-0
+ |
+ o a new base [test] 1: +1/-1
+ |
+ | o extended jungle [Cornelius] 0: +0/-0
+ |/
+ | o four [Rataxes] 0: +0/-0
+ |/
+ | o five [Arthur] 1: +1/-0
+ |/
+ | o extended jungle [Cornelius] 1: +1/-0
+ | |
+ | o jungle [Zephir] 1: +1/-0
+ | |
+ | o five [Arthur] 2: +2/-1
+ | |
+ | o four [Rataxes] 1: +1/-1
+ | |
+ | o three [Celeste] 1: +1/-1
+ |/
+ o initial [Babar] 2: +8/-0
+
+ $ hg export
+ # HG changeset patch
+ # User Rataxes
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
+ # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
+ four
+
+ $ hg status -c .
+ C a
+ C b
--- a/tests/test-issue1877.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-issue1877.t Mon Jun 09 13:53:23 2014 -0500
@@ -34,6 +34,7 @@
$ hg up 1e6c11564562
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark main)
$ hg merge main
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
--- a/tests/test-issue672.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-issue672.t Mon Jun 09 13:53:23 2014 -0500
@@ -35,12 +35,12 @@
branchmerge: True, force: False, partial: False
ancestor: 81f4b099af3d, local: c64f439569a9+, remote: c12dcd37c90a
1: other deleted -> r
- 1a: remote created -> g
- 2: keep -> k
removing 1
updating: 1 1/2 files (50.00%)
+ 1a: remote created -> g
getting 1a
updating: 1a 2/2 files (100.00%)
+ 2: keep -> k
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
(branch merge, don't forget to commit)
@@ -66,8 +66,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: c64f439569a9, local: e327dca35ac8+, remote: 746e9549ea96
+ preserving 1a for resolve of 1a
1a: local copied/moved from 1 -> m
- preserving 1a for resolve of 1a
updating: 1a 1/1 files (100.00%)
picked tool 'internal:merge' for 1a (binary False symlink False)
merging 1a and 1 to 1a
@@ -89,9 +89,9 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: c64f439569a9, local: 746e9549ea96+, remote: e327dca35ac8
+ preserving 1 for resolve of 1a
+ removing 1
1a: remote moved from 1 -> m
- preserving 1 for resolve of 1a
- removing 1
updating: 1a 1/1 files (100.00%)
picked tool 'internal:merge' for 1a (binary False symlink False)
merging 1 and 1a to 1a
--- a/tests/test-journal-exists.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-journal-exists.t Mon Jun 09 13:53:23 2014 -0500
@@ -9,7 +9,8 @@
$ echo foo > a
$ hg ci -Am0
- abort: abandoned transaction found - run hg recover!
+ abort: abandoned transaction found!
+ (run 'hg recover' to clean up transaction)
[255]
$ hg recover
--- a/tests/test-keyword.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-keyword.t Mon Jun 09 13:53:23 2014 -0500
@@ -1049,15 +1049,16 @@
[1]
$ cat m
$Id$
- <<<<<<< local
+ <<<<<<< local: 88a80c8d172e - test: 8bar
bar
=======
foo
- >>>>>>> other
+ >>>>>>> other: 85d2d2d732a5 - test: simplemerge
resolve to local
$ HGMERGE=internal:local hg resolve -a
+ no more unresolved files
$ hg commit -m localresolve
$ cat m
$Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-largefiles-misc.t Mon Jun 09 13:53:23 2014 -0500
@@ -0,0 +1,696 @@
+This file contains testcases that tend to be related to special cases or less
+common commands affecting largefile.
+
+Each sections should be independent of each others.
+
+ $ USERCACHE="$TESTTMP/cache"; export USERCACHE
+ $ mkdir "${USERCACHE}"
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > largefiles=
+ > purge=
+ > rebase=
+ > transplant=
+ > [phases]
+ > publish=False
+ > [largefiles]
+ > minsize=2
+ > patterns=glob:**.dat
+ > usercache=${USERCACHE}
+ > [hooks]
+ > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
+ > EOF
+
+
+
+Test copies and moves from a directory other than root (issue3516)
+=========================================================================
+
+ $ hg init lf_cpmv
+ $ cd lf_cpmv
+ $ mkdir dira
+ $ mkdir dira/dirb
+ $ touch dira/dirb/largefile
+ $ hg add --large dira/dirb/largefile
+ $ hg commit -m "added"
+ Invoking status precommit hook
+ A dira/dirb/largefile
+ $ cd dira
+ $ hg cp dirb/largefile foo/largefile
+ $ hg ci -m "deep copy"
+ Invoking status precommit hook
+ A dira/foo/largefile
+ $ find . | sort
+ .
+ ./dirb
+ ./dirb/largefile
+ ./foo
+ ./foo/largefile
+ $ hg mv foo/largefile baz/largefile
+ $ hg ci -m "moved"
+ Invoking status precommit hook
+ A dira/baz/largefile
+ R dira/foo/largefile
+ $ find . | sort
+ .
+ ./baz
+ ./baz/largefile
+ ./dirb
+ ./dirb/largefile
+ $ cd ..
+ $ hg mv dira dirc
+ moving .hglf/dira/baz/largefile to .hglf/dirc/baz/largefile (glob)
+ moving .hglf/dira/dirb/largefile to .hglf/dirc/dirb/largefile (glob)
+ $ find * | sort
+ dirc
+ dirc/baz
+ dirc/baz/largefile
+ dirc/dirb
+ dirc/dirb/largefile
+ $ hg up -qC
+ $ cd ..
+
+Clone a local repository owned by another user
+===================================================
+
+#if unix-permissions
+
+We have to simulate that here by setting $HOME and removing write permissions
+ $ ORIGHOME="$HOME"
+ $ mkdir alice
+ $ HOME="`pwd`/alice"
+ $ cd alice
+ $ hg init pubrepo
+ $ cd pubrepo
+ $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null
+ $ hg add --large a-large-file
+ $ hg commit -m "Add a large file"
+ Invoking status precommit hook
+ A a-large-file
+ $ cd ..
+ $ chmod -R a-w pubrepo
+ $ cd ..
+ $ mkdir bob
+ $ HOME="`pwd`/bob"
+ $ cd bob
+ $ hg clone --pull ../alice/pubrepo pubrepo
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ updating to branch default
+ getting changed largefiles
+ 1 largefiles updated, 0 removed
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd ..
+ $ chmod -R u+w alice/pubrepo
+ $ HOME="$ORIGHOME"
+
+#endif
+
+
+Symlink to a large largefile should behave the same as a symlink to a normal file
+=====================================================================================
+
+#if symlink
+
+ $ hg init largesymlink
+ $ cd largesymlink
+ $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null
+ $ hg add --large largefile
+ $ hg commit -m "commit a large file"
+ Invoking status precommit hook
+ A largefile
+ $ ln -s largefile largelink
+ $ hg add largelink
+ $ hg commit -m "commit a large symlink"
+ Invoking status precommit hook
+ A largelink
+ $ rm -f largelink
+ $ hg up >/dev/null
+ $ test -f largelink
+ [1]
+ $ test -L largelink
+ [1]
+ $ rm -f largelink # make next part of the test independent of the previous
+ $ hg up -C >/dev/null
+ $ test -f largelink
+ $ test -L largelink
+ $ cd ..
+
+#endif
+
+
+test for pattern matching on 'hg status':
+==============================================
+
+
+to boost performance, largefiles checks whether specified patterns are
+related to largefiles in working directory (NOT to STANDIN) or not.
+
+ $ hg init statusmatch
+ $ cd statusmatch
+
+ $ mkdir -p a/b/c/d
+ $ echo normal > a/b/c/d/e.normal.txt
+ $ hg add a/b/c/d/e.normal.txt
+ $ echo large > a/b/c/d/e.large.txt
+ $ hg add --large a/b/c/d/e.large.txt
+ $ mkdir -p a/b/c/x
+ $ echo normal > a/b/c/x/y.normal.txt
+ $ hg add a/b/c/x/y.normal.txt
+ $ hg commit -m 'add files'
+ Invoking status precommit hook
+ A a/b/c/d/e.large.txt
+ A a/b/c/d/e.normal.txt
+ A a/b/c/x/y.normal.txt
+
+(1) no pattern: no performance boost
+ $ hg status -A
+ C a/b/c/d/e.large.txt
+ C a/b/c/d/e.normal.txt
+ C a/b/c/x/y.normal.txt
+
+(2) pattern not related to largefiles: performance boost
+ $ hg status -A a/b/c/x
+ C a/b/c/x/y.normal.txt
+
+(3) pattern related to largefiles: no performance boost
+ $ hg status -A a/b/c/d
+ C a/b/c/d/e.large.txt
+ C a/b/c/d/e.normal.txt
+
+(4) pattern related to STANDIN (not to largefiles): performance boost
+ $ hg status -A .hglf/a
+ C .hglf/a/b/c/d/e.large.txt
+
+(5) mixed case: no performance boost
+ $ hg status -A a/b/c/x a/b/c/d
+ C a/b/c/d/e.large.txt
+ C a/b/c/d/e.normal.txt
+ C a/b/c/x/y.normal.txt
+
+verify that largefiles doesn't break filesets
+
+ $ hg log --rev . --exclude "set:binary()"
+ changeset: 0:41bd42f10efa
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: add files
+
+verify that large files in subrepos handled properly
+ $ hg init subrepo
+ $ echo "subrepo = subrepo" > .hgsub
+ $ hg add .hgsub
+ $ hg ci -m "add subrepo"
+ Invoking status precommit hook
+ A .hgsub
+ ? .hgsubstate
+ $ echo "rev 1" > subrepo/large.txt
+ $ hg -R subrepo add --large subrepo/large.txt
+ $ hg sum
+ parent: 1:8ee150ea2e9c tip
+ add subrepo
+ branch: default
+ commit: 1 subrepos
+ update: (current)
+ $ hg st
+ $ hg st -S
+ A subrepo/large.txt
+ $ hg ci -S -m "commit top repo"
+ committing subrepository subrepo
+ Invoking status precommit hook
+ A large.txt
+ Invoking status precommit hook
+ M .hgsubstate
+# No differences
+ $ hg st -S
+ $ hg sum
+ parent: 2:ce4cd0c527a6 tip
+ commit top repo
+ branch: default
+ commit: (clean)
+ update: (current)
+ $ echo "rev 2" > subrepo/large.txt
+ $ hg st -S
+ M subrepo/large.txt
+ $ hg sum
+ parent: 2:ce4cd0c527a6 tip
+ commit top repo
+ branch: default
+ commit: 1 subrepos
+ update: (current)
+ $ hg ci -m "this commit should fail without -S"
+ abort: uncommitted changes in subrepo subrepo
+ (use --subrepos for recursive commit)
+ [255]
+
+Add a normal file to the subrepo, then test archiving
+
+ $ echo 'normal file' > subrepo/normal.txt
+ $ hg -R subrepo add subrepo/normal.txt
+
+Lock in subrepo, otherwise the change isn't archived
+
+ $ hg ci -S -m "add normal file to top level"
+ committing subrepository subrepo
+ Invoking status precommit hook
+ M large.txt
+ A normal.txt
+ Invoking status precommit hook
+ M .hgsubstate
+ $ hg archive -S ../lf_subrepo_archive
+ $ find ../lf_subrepo_archive | sort
+ ../lf_subrepo_archive
+ ../lf_subrepo_archive/.hg_archival.txt
+ ../lf_subrepo_archive/.hgsub
+ ../lf_subrepo_archive/.hgsubstate
+ ../lf_subrepo_archive/a
+ ../lf_subrepo_archive/a/b
+ ../lf_subrepo_archive/a/b/c
+ ../lf_subrepo_archive/a/b/c/d
+ ../lf_subrepo_archive/a/b/c/d/e.large.txt
+ ../lf_subrepo_archive/a/b/c/d/e.normal.txt
+ ../lf_subrepo_archive/a/b/c/x
+ ../lf_subrepo_archive/a/b/c/x/y.normal.txt
+ ../lf_subrepo_archive/subrepo
+ ../lf_subrepo_archive/subrepo/large.txt
+ ../lf_subrepo_archive/subrepo/normal.txt
+
+Test update with subrepos.
+
+ $ hg update 0
+ getting changed largefiles
+ 0 largefiles updated, 1 removed
+ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg status -S
+ $ hg update tip
+ getting changed largefiles
+ 1 largefiles updated, 0 removed
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg status -S
+# modify a large file
+ $ echo "modified" > subrepo/large.txt
+ $ hg st -S
+ M subrepo/large.txt
+# update -C should revert the change.
+ $ hg update -C
+ getting changed largefiles
+ 1 largefiles updated, 0 removed
+ getting changed largefiles
+ 0 largefiles updated, 0 removed
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg status -S
+
+Test archiving a revision that references a subrepo that is not yet
+cloned (see test-subrepo-recursion.t):
+
+ $ hg clone -U . ../empty
+ $ cd ../empty
+ $ hg archive --subrepos -r tip ../archive.tar.gz
+ cloning subrepo subrepo from $TESTTMP/statusmatch/subrepo
+ $ cd ..
+
+
+
+
+
+
+Test addremove, forget and others
+==============================================
+
+Test that addremove picks up largefiles prior to the initial commit (issue3541)
+
+ $ hg init addrm2
+ $ cd addrm2
+ $ touch large.dat
+ $ touch large2.dat
+ $ touch normal
+ $ hg add --large large.dat
+ $ hg addremove -v
+ adding large2.dat as a largefile
+ adding normal
+
+Test that forgetting all largefiles reverts to islfilesrepo() == False
+(addremove will add *.dat as normal files now)
+ $ hg forget large.dat
+ $ hg forget large2.dat
+ $ hg addremove -v
+ adding large.dat
+ adding large2.dat
+
+Test commit's addremove option prior to the first commit
+ $ hg forget large.dat
+ $ hg forget large2.dat
+ $ hg add --large large.dat
+ $ hg ci -Am "commit"
+ adding large2.dat as a largefile
+ Invoking status precommit hook
+ A large.dat
+ A large2.dat
+ A normal
+ $ find .hglf | sort
+ .hglf
+ .hglf/large.dat
+ .hglf/large2.dat
+
+Test actions on largefiles using relative paths from subdir
+
+ $ mkdir sub
+ $ cd sub
+ $ echo anotherlarge > anotherlarge
+ $ hg add --large anotherlarge
+ $ hg st
+ A sub/anotherlarge
+ $ hg st anotherlarge
+ A anotherlarge
+ $ hg commit -m anotherlarge anotherlarge
+ Invoking status precommit hook
+ A sub/anotherlarge
+ $ hg log anotherlarge
+ changeset: 1:9627a577c5e9
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: anotherlarge
+
+ $ hg log -G anotherlarge
+ @ changeset: 1:9627a577c5e9
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: anotherlarge
+ |
+ $ echo more >> anotherlarge
+ $ hg st .
+ M anotherlarge
+ $ hg cat anotherlarge
+ anotherlarge
+ $ hg revert anotherlarge
+ $ hg st
+ ? sub/anotherlarge.orig
+ $ cd ..
+
+ $ cd ..
+
+Check error message while exchange
+=========================================================
+
+issue3651: summary/outgoing with largefiles shows "no remote repo"
+unexpectedly
+
+ $ mkdir issue3651
+ $ cd issue3651
+
+ $ hg init src
+ $ echo a > src/a
+ $ hg -R src add --large src/a
+ $ hg -R src commit -m '#0'
+ Invoking status precommit hook
+ A a
+
+check messages when no remote repository is specified:
+"no remote repo" route for "hg outgoing --large" is not tested here,
+because it can't be reproduced easily.
+
+ $ hg init clone1
+ $ hg -R clone1 -q pull src
+ $ hg -R clone1 -q update
+ $ hg -R clone1 paths | grep default
+ [1]
+
+ $ hg -R clone1 summary --large
+ parent: 0:fc0bd45326d3 tip
+ #0
+ branch: default
+ commit: (clean)
+ update: (current)
+ largefiles: (no remote repo)
+
+check messages when there is no files to upload:
+
+ $ hg -q clone src clone2
+ $ hg -R clone2 paths | grep default
+ default = $TESTTMP/issue3651/src (glob)
+
+ $ hg -R clone2 summary --large
+ parent: 0:fc0bd45326d3 tip
+ #0
+ branch: default
+ commit: (clean)
+ update: (current)
+ largefiles: (no files to upload)
+ $ hg -R clone2 outgoing --large
+ comparing with $TESTTMP/issue3651/src (glob)
+ searching for changes
+ no changes found
+ largefiles: no files to upload
+ [1]
+
+ $ hg -R clone2 outgoing --large --graph --template "{rev}"
+ comparing with $TESTTMP/issue3651/src (glob)
+ searching for changes
+ no changes found
+ largefiles: no files to upload
+
+check messages when there are files to upload:
+
+ $ echo b > clone2/b
+ $ hg -R clone2 add --large clone2/b
+ $ hg -R clone2 commit -m '#1'
+ Invoking status precommit hook
+ A b
+ $ hg -R clone2 summary --large
+ parent: 1:1acbe71ce432 tip
+ #1
+ branch: default
+ commit: (clean)
+ update: (current)
+ largefiles: 1 to upload
+ $ hg -R clone2 outgoing --large
+ comparing with $TESTTMP/issue3651/src (glob)
+ searching for changes
+ changeset: 1:1acbe71ce432
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: #1
+
+ largefiles to upload:
+ b
+
+ $ hg -R clone2 outgoing --large --graph --template "{rev}"
+ comparing with $TESTTMP/issue3651/src
+ searching for changes
+ @ 1
+
+ largefiles to upload:
+ b
+
+
+ $ cd ..
+
+merge action 'd' for 'local renamed directory to d2/g' which has no filename
+==================================================================================
+
+ $ hg init merge-action
+ $ cd merge-action
+ $ touch l
+ $ hg add --large l
+ $ mkdir d1
+ $ touch d1/f
+ $ hg ci -Aqm0
+ Invoking status precommit hook
+ A d1/f
+ A l
+ $ echo > d1/f
+ $ touch d1/g
+ $ hg ci -Aqm1
+ Invoking status precommit hook
+ M d1/f
+ A d1/g
+ $ hg up -qr0
+ $ hg mv d1 d2
+ moving d1/f to d2/f (glob)
+ $ hg ci -qm2
+ Invoking status precommit hook
+ A d2/f
+ R d1/f
+ $ hg merge
+ merging d2/f and d1/f to d2/f
+ 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ getting changed largefiles
+ 0 largefiles updated, 0 removed
+ $ cd ..
+
+
+Merge conflicts:
+=====================
+
+ $ hg init merge
+ $ cd merge
+ $ echo 0 > f-different
+ $ echo 0 > f-same
+ $ echo 0 > f-unchanged-1
+ $ echo 0 > f-unchanged-2
+ $ hg add --large *
+ $ hg ci -m0
+ Invoking status precommit hook
+ A f-different
+ A f-same
+ A f-unchanged-1
+ A f-unchanged-2
+ $ echo tmp1 > f-unchanged-1
+ $ echo tmp1 > f-unchanged-2
+ $ echo tmp1 > f-same
+ $ hg ci -m1
+ Invoking status precommit hook
+ M f-same
+ M f-unchanged-1
+ M f-unchanged-2
+ $ echo 2 > f-different
+ $ echo 0 > f-unchanged-1
+ $ echo 1 > f-unchanged-2
+ $ echo 1 > f-same
+ $ hg ci -m2
+ Invoking status precommit hook
+ M f-different
+ M f-same
+ M f-unchanged-1
+ M f-unchanged-2
+ $ hg up -qr0
+ $ echo tmp2 > f-unchanged-1
+ $ echo tmp2 > f-unchanged-2
+ $ echo tmp2 > f-same
+ $ hg ci -m3
+ Invoking status precommit hook
+ M f-same
+ M f-unchanged-1
+ M f-unchanged-2
+ created new head
+ $ echo 1 > f-different
+ $ echo 1 > f-unchanged-1
+ $ echo 0 > f-unchanged-2
+ $ echo 1 > f-same
+ $ hg ci -m4
+ Invoking status precommit hook
+ M f-different
+ M f-same
+ M f-unchanged-1
+ M f-unchanged-2
+ $ hg merge
+ largefile f-different has a merge conflict
+ ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7
+ keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or
+ take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l
+ 0 files updated, 4 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ getting changed largefiles
+ 1 largefiles updated, 0 removed
+ $ cat f-different
+ 1
+ $ cat f-same
+ 1
+ $ cat f-unchanged-1
+ 1
+ $ cat f-unchanged-2
+ 1
+ $ cd ..
+
+Test largefile insulation (do not enabled a side effect
+========================================================
+
+Check whether "largefiles" feature is supported only in repositories
+enabling largefiles extension.
+
+ $ mkdir individualenabling
+ $ cd individualenabling
+
+ $ hg init enabledlocally
+ $ echo large > enabledlocally/large
+ $ hg -R enabledlocally add --large enabledlocally/large
+ $ hg -R enabledlocally commit -m '#0'
+ Invoking status precommit hook
+ A large
+
+ $ hg init notenabledlocally
+ $ echo large > notenabledlocally/large
+ $ hg -R notenabledlocally add --large notenabledlocally/large
+ $ hg -R notenabledlocally commit -m '#0'
+ Invoking status precommit hook
+ A large
+
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > # disable globally
+ > largefiles=!
+ > EOF
+ $ cat >> enabledlocally/.hg/hgrc <<EOF
+ > [extensions]
+ > # enable locally
+ > largefiles=
+ > EOF
+ $ hg -R enabledlocally root
+ $TESTTMP/individualenabling/enabledlocally (glob)
+ $ hg -R notenabledlocally root
+ abort: repository requires features unknown to this Mercurial: largefiles!
+ (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
+ [255]
+
+ $ hg init push-dst
+ $ hg -R enabledlocally push push-dst
+ pushing to push-dst
+ abort: required features are not supported in the destination: largefiles
+ [255]
+
+ $ hg init pull-src
+ $ hg -R pull-src pull enabledlocally
+ pulling from enabledlocally
+ abort: required features are not supported in the destination: largefiles
+ [255]
+
+ $ hg clone enabledlocally clone-dst
+ abort: repository requires features unknown to this Mercurial: largefiles!
+ (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
+ [255]
+ $ test -d clone-dst
+ [1]
+ $ hg clone --pull enabledlocally clone-pull-dst
+ abort: required features are not supported in the destination: largefiles
+ [255]
+ $ test -d clone-pull-dst
+ [1]
+
+#if serve
+
+Test largefiles specific peer setup, when largefiles is enabled
+locally (issue4109)
+
+ $ hg showconfig extensions | grep largefiles
+ extensions.largefiles=!
+ $ mkdir -p $TESTTMP/individualenabling/usercache
+
+ $ hg serve -R enabledlocally -d -p $HGPORT --pid-file hg.pid
+ $ cat hg.pid >> $DAEMON_PIDS
+
+ $ hg init pull-dst
+ $ cat > pull-dst/.hg/hgrc <<EOF
+ > [extensions]
+ > # enable locally
+ > largefiles=
+ > [largefiles]
+ > # ignore system cache to force largefiles specific wire proto access
+ > usercache=$TESTTMP/individualenabling/usercache
+ > EOF
+ $ hg -R pull-dst -q pull -u http://localhost:$HGPORT
+
+ $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+#endif
+
+ $ cd ..
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-largefiles-wireproto.t Mon Jun 09 13:53:23 2014 -0500
@@ -0,0 +1,293 @@
+This file contains testcases that tend to be related to the wireprotocol part of
+largefile.
+
+ $ USERCACHE="$TESTTMP/cache"; export USERCACHE
+ $ mkdir "${USERCACHE}"
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > largefiles=
+ > purge=
+ > rebase=
+ > transplant=
+ > [phases]
+ > publish=False
+ > [largefiles]
+ > minsize=2
+ > patterns=glob:**.dat
+ > usercache=${USERCACHE}
+ > [hooks]
+ > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
+ > EOF
+
+
+#if serve
+vanilla clients not locked out from largefiles servers on vanilla repos
+ $ mkdir r1
+ $ cd r1
+ $ hg init
+ $ echo c1 > f1
+ $ hg add f1
+ $ hg commit -m "m1"
+ Invoking status precommit hook
+ A f1
+ $ cd ..
+ $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid
+ $ cat hg.pid >> $DAEMON_PIDS
+ $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+largefiles clients still work with vanilla servers
+ $ hg --config extensions.largefiles=! serve -R r1 -d -p $HGPORT1 --pid-file hg.pid
+ $ cat hg.pid >> $DAEMON_PIDS
+ $ hg clone http://localhost:$HGPORT1 r3
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+vanilla clients locked out from largefiles http repos
+ $ mkdir r4
+ $ cd r4
+ $ hg init
+ $ echo c1 > f1
+ $ hg add --large f1
+ $ hg commit -m "m1"
+ Invoking status precommit hook
+ A f1
+ $ cd ..
+
+largefiles can be pushed locally (issue3583)
+ $ hg init dest
+ $ cd r4
+ $ hg outgoing ../dest
+ comparing with ../dest
+ searching for changes
+ changeset: 0:639881c12b4c
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: m1
+
+ $ hg push ../dest
+ pushing to ../dest
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+
+exit code with nothing outgoing (issue3611)
+ $ hg outgoing ../dest
+ comparing with ../dest
+ searching for changes
+ no changes found
+ [1]
+ $ cd ..
+
+#if serve
+ $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid
+ $ cat hg.pid >> $DAEMON_PIDS
+ $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5
+ abort: remote error:
+
+ This repository uses the largefiles extension.
+
+ Please enable it in your Mercurial config file.
+ [255]
+
+used all HGPORTs, kill all daemons
+ $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+#endif
+
+vanilla clients locked out from largefiles ssh repos
+ $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
+ abort: remote error:
+
+ This repository uses the largefiles extension.
+
+ Please enable it in your Mercurial config file.
+ [255]
+
+#if serve
+
+largefiles clients refuse to push largefiles repos to vanilla servers
+ $ mkdir r6
+ $ cd r6
+ $ hg init
+ $ echo c1 > f1
+ $ hg add f1
+ $ hg commit -m "m1"
+ Invoking status precommit hook
+ A f1
+ $ cat >> .hg/hgrc <<!
+ > [web]
+ > push_ssl = false
+ > allow_push = *
+ > !
+ $ cd ..
+ $ hg clone r6 r7
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd r7
+ $ echo c2 > f2
+ $ hg add --large f2
+ $ hg commit -m "m2"
+ Invoking status precommit hook
+ A f2
+ $ hg --config extensions.largefiles=! -R ../r6 serve -d -p $HGPORT --pid-file ../hg.pid
+ $ cat ../hg.pid >> $DAEMON_PIDS
+ $ hg push http://localhost:$HGPORT
+ pushing to http://localhost:$HGPORT/
+ searching for changes
+ abort: http://localhost:$HGPORT/ does not appear to be a largefile store
+ [255]
+ $ cd ..
+
+putlfile errors are shown (issue3123)
+Corrupt the cached largefile in r7 and move it out of the servers usercache
+ $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 .
+ $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
+ $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8"
+ $ hg init empty
+ $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \
+ > --config 'web.allow_push=*' --config web.push_ssl=False
+ $ cat hg.pid >> $DAEMON_PIDS
+ $ hg push -R r7 http://localhost:$HGPORT1
+ pushing to http://localhost:$HGPORT1/
+ searching for changes
+ remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash
+ abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/ (glob)
+ [255]
+ $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
+Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic
+ $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
+ $ hg push -R r7 http://localhost:$HGPORT1
+ pushing to http://localhost:$HGPORT1/
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 2 changesets with 2 changes to 2 files
+ $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
+ server side corruption
+ $ rm -rf empty
+
+Push a largefiles repository to a served empty repository
+ $ hg init r8
+ $ echo c3 > r8/f1
+ $ hg add --large r8/f1 -R r8
+ $ hg commit -m "m1" -R r8
+ Invoking status precommit hook
+ A f1
+ $ hg init empty
+ $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \
+ > --config 'web.allow_push=*' --config web.push_ssl=False
+ $ cat hg.pid >> $DAEMON_PIDS
+ $ rm "${USERCACHE}"/*
+ $ hg push -R r8 http://localhost:$HGPORT2/#default
+ pushing to http://localhost:$HGPORT2/
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files
+ $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
+ $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
+
+Clone over http, no largefiles pulled on clone.
+
+ $ hg clone http://localhost:$HGPORT2/#default http-clone -U
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+
+test 'verify' with remotestore:
+
+ $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90
+ $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
+ $ hg -R http-clone verify --large --lfa
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 1 files, 1 changesets, 1 total revisions
+ searching 1 changesets for largefiles
+ changeset 0:cf03e5bb9936: f1 missing
+ verified existence of 1 revisions of 1 largefiles
+ [1]
+ $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
+ $ hg -R http-clone -q verify --large --lfa
+
+largefiles pulled on update - a largefile missing on the server:
+ $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
+ $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
+ getting changed largefiles
+ f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/
+ 0 largefiles updated, 0 removed
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg -R http-clone st
+ ! f1
+ $ hg -R http-clone up -Cqr null
+
+largefiles pulled on update - a largefile corrupted on the server:
+ $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90
+ $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
+ getting changed largefiles
+ f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27)
+ 0 largefiles updated, 0 removed
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg -R http-clone st
+ ! f1
+ $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
+ $ [ ! -f http-clone/f1 ]
+ $ [ ! -f http-clone-usercache ]
+ $ hg -R http-clone verify --large --lfc
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 1 files, 1 changesets, 1 total revisions
+ searching 1 changesets for largefiles
+ verified contents of 1 revisions of 1 largefiles
+ $ hg -R http-clone up -Cqr null
+
+largefiles pulled on update - no server side problems:
+ $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
+ $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache
+ resolving manifests
+ branchmerge: False, force: False, partial: False
+ ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936
+ .hglf/f1: remote created -> g
+ getting .hglf/f1
+ updating: .hglf/f1 1/1 files (100.00%)
+ getting changed largefiles
+ using http://localhost:$HGPORT2/
+ sending capabilities command
+ sending batch command
+ getting largefiles: 0/1 lfile (0.00%)
+ getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90
+ sending getlfile command
+ found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store
+ 1 largefiles updated, 0 removed
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ ls http-clone-usercache/*
+ http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90
+
+ $ rm -rf empty http-clone*
+
+used all HGPORTs, kill all daemons
+ $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+
+#endif
--- a/tests/test-largefiles.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-largefiles.t Mon Jun 09 13:53:23 2014 -0500
@@ -1,3 +1,8 @@
+This file used to contains all largefile tests.
+Do not add any new tests in this file as it his already far too long to run.
+
+It contains all the testing of the basic concepts of large file in a single block.
+
$ USERCACHE="$TESTTMP/cache"; export USERCACHE
$ mkdir "${USERCACHE}"
$ cat >> $HGRCPATH <<EOF
@@ -180,52 +185,6 @@
$ cat sub/large4
large22
-Test copies and moves from a directory other than root (issue3516)
-
- $ cd ..
- $ hg init lf_cpmv
- $ cd lf_cpmv
- $ mkdir dira
- $ mkdir dira/dirb
- $ touch dira/dirb/largefile
- $ hg add --large dira/dirb/largefile
- $ hg commit -m "added"
- Invoking status precommit hook
- A dira/dirb/largefile
- $ cd dira
- $ hg cp dirb/largefile foo/largefile
- $ hg ci -m "deep copy"
- Invoking status precommit hook
- A dira/foo/largefile
- $ find . | sort
- .
- ./dirb
- ./dirb/largefile
- ./foo
- ./foo/largefile
- $ hg mv foo/largefile baz/largefile
- $ hg ci -m "moved"
- Invoking status precommit hook
- A dira/baz/largefile
- R dira/foo/largefile
- $ find . | sort
- .
- ./baz
- ./baz/largefile
- ./dirb
- ./dirb/largefile
- $ cd ..
- $ hg mv dira dirc
- moving .hglf/dira/baz/largefile to .hglf/dirc/baz/largefile (glob)
- moving .hglf/dira/dirb/largefile to .hglf/dirc/dirb/largefile (glob)
- $ find * | sort
- dirc
- dirc/baz
- dirc/baz/largefile
- dirc/dirb
- dirc/dirb/largefile
- $ hg up -qC
- $ cd ../a
#if serve
Test display of largefiles in hgweb
@@ -390,6 +349,14 @@
A sub2/large7
A z/y/x/large2
A z/y/x/m/large1
+
+(and a bit of log testing)
+
+ $ hg log -T '{rev}\n' z/y/x/m/large1
+ 7
+ $ hg log -T '{rev}\n' z/y/x/m # with only a largefile
+ 7
+
$ hg rollback --quiet
$ touch z/y/x/m/normal
$ hg add z/y/x/m/normal
@@ -1665,873 +1632,5 @@
(use 'hg revert new-largefile' to cancel the pending addition)
$ cd ..
-#if serve
-vanilla clients not locked out from largefiles servers on vanilla repos
- $ mkdir r1
- $ cd r1
- $ hg init
- $ echo c1 > f1
- $ hg add f1
- $ hg commit -m "m1"
- Invoking status precommit hook
- A f1
- $ cd ..
- $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid
- $ cat hg.pid >> $DAEMON_PIDS
- $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2
- requesting all changes
- adding changesets
- adding manifests
- adding file changes
- added 1 changesets with 1 changes to 1 files
- updating to branch default
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
-largefiles clients still work with vanilla servers
- $ hg --config extensions.largefiles=! serve -R r1 -d -p $HGPORT1 --pid-file hg.pid
- $ cat hg.pid >> $DAEMON_PIDS
- $ hg clone http://localhost:$HGPORT1 r3
- requesting all changes
- adding changesets
- adding manifests
- adding file changes
- added 1 changesets with 1 changes to 1 files
- updating to branch default
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-#endif
-
-
-vanilla clients locked out from largefiles http repos
- $ mkdir r4
- $ cd r4
- $ hg init
- $ echo c1 > f1
- $ hg add --large f1
- $ hg commit -m "m1"
- Invoking status precommit hook
- A f1
- $ cd ..
-
-largefiles can be pushed locally (issue3583)
- $ hg init dest
- $ cd r4
- $ hg outgoing ../dest
- comparing with ../dest
- searching for changes
- changeset: 0:639881c12b4c
- tag: tip
- user: test
- date: Thu Jan 01 00:00:00 1970 +0000
- summary: m1
-
- $ hg push ../dest
- pushing to ../dest
- searching for changes
- adding changesets
- adding manifests
- adding file changes
- added 1 changesets with 1 changes to 1 files
-
-exit code with nothing outgoing (issue3611)
- $ hg outgoing ../dest
- comparing with ../dest
- searching for changes
- no changes found
- [1]
- $ cd ..
-
-#if serve
- $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid
- $ cat hg.pid >> $DAEMON_PIDS
- $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5
- abort: remote error:
-
- This repository uses the largefiles extension.
-
- Please enable it in your Mercurial config file.
- [255]
-
-used all HGPORTs, kill all daemons
- $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
-#endif
-
-vanilla clients locked out from largefiles ssh repos
- $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
- abort: remote error:
-
- This repository uses the largefiles extension.
-
- Please enable it in your Mercurial config file.
- [255]
-
-#if serve
-
-largefiles clients refuse to push largefiles repos to vanilla servers
- $ mkdir r6
- $ cd r6
- $ hg init
- $ echo c1 > f1
- $ hg add f1
- $ hg commit -m "m1"
- Invoking status precommit hook
- A f1
- $ cat >> .hg/hgrc <<!
- > [web]
- > push_ssl = false
- > allow_push = *
- > !
- $ cd ..
- $ hg clone r6 r7
- updating to branch default
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ cd r7
- $ echo c2 > f2
- $ hg add --large f2
- $ hg commit -m "m2"
- Invoking status precommit hook
- A f2
- $ hg --config extensions.largefiles=! -R ../r6 serve -d -p $HGPORT --pid-file ../hg.pid
- $ cat ../hg.pid >> $DAEMON_PIDS
- $ hg push http://localhost:$HGPORT
- pushing to http://localhost:$HGPORT/
- searching for changes
- abort: http://localhost:$HGPORT/ does not appear to be a largefile store
- [255]
- $ cd ..
-
-putlfile errors are shown (issue3123)
-Corrupt the cached largefile in r7 and move it out of the servers usercache
- $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 .
- $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
- $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8"
- $ hg init empty
- $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \
- > --config 'web.allow_push=*' --config web.push_ssl=False
- $ cat hg.pid >> $DAEMON_PIDS
- $ hg push -R r7 http://localhost:$HGPORT1
- pushing to http://localhost:$HGPORT1/
- searching for changes
- remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash
- abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/ (glob)
- [255]
- $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
-Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic
- $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
- $ hg push -R r7 http://localhost:$HGPORT1
- pushing to http://localhost:$HGPORT1/
- searching for changes
- remote: adding changesets
- remote: adding manifests
- remote: adding file changes
- remote: added 2 changesets with 2 changes to 2 files
- $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
- server side corruption
- $ rm -rf empty
-
-Push a largefiles repository to a served empty repository
- $ hg init r8
- $ echo c3 > r8/f1
- $ hg add --large r8/f1 -R r8
- $ hg commit -m "m1" -R r8
- Invoking status precommit hook
- A f1
- $ hg init empty
- $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \
- > --config 'web.allow_push=*' --config web.push_ssl=False
- $ cat hg.pid >> $DAEMON_PIDS
- $ rm "${USERCACHE}"/*
- $ hg push -R r8 http://localhost:$HGPORT2/#default
- pushing to http://localhost:$HGPORT2/
- searching for changes
- remote: adding changesets
- remote: adding manifests
- remote: adding file changes
- remote: added 1 changesets with 1 changes to 1 files
- $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
- $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
-
-Clone over http, no largefiles pulled on clone.
-
- $ hg clone http://localhost:$HGPORT2/#default http-clone -U
- adding changesets
- adding manifests
- adding file changes
- added 1 changesets with 1 changes to 1 files
-
-test 'verify' with remotestore:
-
- $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90
- $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
- $ hg -R http-clone verify --large --lfa
- checking changesets
- checking manifests
- crosschecking files in changesets and manifests
- checking files
- 1 files, 1 changesets, 1 total revisions
- searching 1 changesets for largefiles
- changeset 0:cf03e5bb9936: f1 missing
- verified existence of 1 revisions of 1 largefiles
- [1]
- $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
- $ hg -R http-clone -q verify --large --lfa
-
-largefiles pulled on update - a largefile missing on the server:
- $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
- $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
- getting changed largefiles
- f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/
- 0 largefiles updated, 0 removed
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg -R http-clone st
- ! f1
- $ hg -R http-clone up -Cqr null
-
-largefiles pulled on update - a largefile corrupted on the server:
- $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90
- $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
- getting changed largefiles
- f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27)
- 0 largefiles updated, 0 removed
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg -R http-clone st
- ! f1
- $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
- $ [ ! -f http-clone/f1 ]
- $ [ ! -f http-clone-usercache ]
- $ hg -R http-clone verify --large --lfc
- checking changesets
- checking manifests
- crosschecking files in changesets and manifests
- checking files
- 1 files, 1 changesets, 1 total revisions
- searching 1 changesets for largefiles
- verified contents of 1 revisions of 1 largefiles
- $ hg -R http-clone up -Cqr null
-
-largefiles pulled on update - no server side problems:
- $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
- $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache
- resolving manifests
- branchmerge: False, force: False, partial: False
- ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936
- .hglf/f1: remote created -> g
- getting .hglf/f1
- updating: .hglf/f1 1/1 files (100.00%)
- getting changed largefiles
- using http://localhost:$HGPORT2/
- sending capabilities command
- sending batch command
- getting largefiles: 0/1 lfile (0.00%)
- getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90
- sending getlfile command
- found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store
- 1 largefiles updated, 0 removed
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
- $ ls http-clone-usercache/*
- http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90
-
- $ rm -rf empty http-clone*
-
-used all HGPORTs, kill all daemons
- $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
-
-#endif
-#if unix-permissions
-
-Clone a local repository owned by another user
-We have to simulate that here by setting $HOME and removing write permissions
- $ ORIGHOME="$HOME"
- $ mkdir alice
- $ HOME="`pwd`/alice"
- $ cd alice
- $ hg init pubrepo
- $ cd pubrepo
- $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null
- $ hg add --large a-large-file
- $ hg commit -m "Add a large file"
- Invoking status precommit hook
- A a-large-file
- $ cd ..
- $ chmod -R a-w pubrepo
- $ cd ..
- $ mkdir bob
- $ HOME="`pwd`/bob"
- $ cd bob
- $ hg clone --pull ../alice/pubrepo pubrepo
- requesting all changes
- adding changesets
- adding manifests
- adding file changes
- added 1 changesets with 1 changes to 1 files
- updating to branch default
- getting changed largefiles
- 1 largefiles updated, 0 removed
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ cd ..
- $ chmod -R u+w alice/pubrepo
- $ HOME="$ORIGHOME"
-
-#endif
-
-#if symlink
-
-Symlink to a large largefile should behave the same as a symlink to a normal file
- $ hg init largesymlink
- $ cd largesymlink
- $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null
- $ hg add --large largefile
- $ hg commit -m "commit a large file"
- Invoking status precommit hook
- A largefile
- $ ln -s largefile largelink
- $ hg add largelink
- $ hg commit -m "commit a large symlink"
- Invoking status precommit hook
- A largelink
- $ rm -f largelink
- $ hg up >/dev/null
- $ test -f largelink
- [1]
- $ test -L largelink
- [1]
- $ rm -f largelink # make next part of the test independent of the previous
- $ hg up -C >/dev/null
- $ test -f largelink
- $ test -L largelink
- $ cd ..
-
-#endif
-
-test for pattern matching on 'hg status':
-to boost performance, largefiles checks whether specified patterns are
-related to largefiles in working directory (NOT to STANDIN) or not.
-
- $ hg init statusmatch
- $ cd statusmatch
-
- $ mkdir -p a/b/c/d
- $ echo normal > a/b/c/d/e.normal.txt
- $ hg add a/b/c/d/e.normal.txt
- $ echo large > a/b/c/d/e.large.txt
- $ hg add --large a/b/c/d/e.large.txt
- $ mkdir -p a/b/c/x
- $ echo normal > a/b/c/x/y.normal.txt
- $ hg add a/b/c/x/y.normal.txt
- $ hg commit -m 'add files'
- Invoking status precommit hook
- A a/b/c/d/e.large.txt
- A a/b/c/d/e.normal.txt
- A a/b/c/x/y.normal.txt
-
-(1) no pattern: no performance boost
- $ hg status -A
- C a/b/c/d/e.large.txt
- C a/b/c/d/e.normal.txt
- C a/b/c/x/y.normal.txt
-
-(2) pattern not related to largefiles: performance boost
- $ hg status -A a/b/c/x
- C a/b/c/x/y.normal.txt
-
-(3) pattern related to largefiles: no performance boost
- $ hg status -A a/b/c/d
- C a/b/c/d/e.large.txt
- C a/b/c/d/e.normal.txt
-
-(4) pattern related to STANDIN (not to largefiles): performance boost
- $ hg status -A .hglf/a
- C .hglf/a/b/c/d/e.large.txt
-
-(5) mixed case: no performance boost
- $ hg status -A a/b/c/x a/b/c/d
- C a/b/c/d/e.large.txt
- C a/b/c/d/e.normal.txt
- C a/b/c/x/y.normal.txt
-
-verify that largefiles doesn't break filesets
-
- $ hg log --rev . --exclude "set:binary()"
- changeset: 0:41bd42f10efa
- tag: tip
- user: test
- date: Thu Jan 01 00:00:00 1970 +0000
- summary: add files
-
-verify that large files in subrepos handled properly
- $ hg init subrepo
- $ echo "subrepo = subrepo" > .hgsub
- $ hg add .hgsub
- $ hg ci -m "add subrepo"
- Invoking status precommit hook
- A .hgsub
- ? .hgsubstate
- $ echo "rev 1" > subrepo/large.txt
- $ hg -R subrepo add --large subrepo/large.txt
- $ hg sum
- parent: 1:8ee150ea2e9c tip
- add subrepo
- branch: default
- commit: 1 subrepos
- update: (current)
- $ hg st
- $ hg st -S
- A subrepo/large.txt
- $ hg ci -S -m "commit top repo"
- committing subrepository subrepo
- Invoking status precommit hook
- A large.txt
- Invoking status precommit hook
- M .hgsubstate
-# No differences
- $ hg st -S
- $ hg sum
- parent: 2:ce4cd0c527a6 tip
- commit top repo
- branch: default
- commit: (clean)
- update: (current)
- $ echo "rev 2" > subrepo/large.txt
- $ hg st -S
- M subrepo/large.txt
- $ hg sum
- parent: 2:ce4cd0c527a6 tip
- commit top repo
- branch: default
- commit: 1 subrepos
- update: (current)
- $ hg ci -m "this commit should fail without -S"
- abort: uncommitted changes in subrepo subrepo
- (use --subrepos for recursive commit)
- [255]
-
-Add a normal file to the subrepo, then test archiving
-
- $ echo 'normal file' > subrepo/normal.txt
- $ hg -R subrepo add subrepo/normal.txt
-
-Lock in subrepo, otherwise the change isn't archived
-
- $ hg ci -S -m "add normal file to top level"
- committing subrepository subrepo
- Invoking status precommit hook
- M large.txt
- A normal.txt
- Invoking status precommit hook
- M .hgsubstate
- $ hg archive -S ../lf_subrepo_archive
- $ find ../lf_subrepo_archive | sort
- ../lf_subrepo_archive
- ../lf_subrepo_archive/.hg_archival.txt
- ../lf_subrepo_archive/.hgsub
- ../lf_subrepo_archive/.hgsubstate
- ../lf_subrepo_archive/a
- ../lf_subrepo_archive/a/b
- ../lf_subrepo_archive/a/b/c
- ../lf_subrepo_archive/a/b/c/d
- ../lf_subrepo_archive/a/b/c/d/e.large.txt
- ../lf_subrepo_archive/a/b/c/d/e.normal.txt
- ../lf_subrepo_archive/a/b/c/x
- ../lf_subrepo_archive/a/b/c/x/y.normal.txt
- ../lf_subrepo_archive/subrepo
- ../lf_subrepo_archive/subrepo/large.txt
- ../lf_subrepo_archive/subrepo/normal.txt
-
-Test update with subrepos.
-
- $ hg update 0
- getting changed largefiles
- 0 largefiles updated, 1 removed
- 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
- $ hg status -S
- $ hg update tip
- getting changed largefiles
- 1 largefiles updated, 0 removed
- 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg status -S
-# modify a large file
- $ echo "modified" > subrepo/large.txt
- $ hg st -S
- M subrepo/large.txt
-# update -C should revert the change.
- $ hg update -C
- getting changed largefiles
- 1 largefiles updated, 0 removed
- getting changed largefiles
- 0 largefiles updated, 0 removed
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg status -S
-
-Test archiving a revision that references a subrepo that is not yet
-cloned (see test-subrepo-recursion.t):
-
- $ hg clone -U . ../empty
- $ cd ../empty
- $ hg archive --subrepos -r tip ../archive.tar.gz
- cloning subrepo subrepo from $TESTTMP/statusmatch/subrepo
- $ cd ..
-
-Test that addremove picks up largefiles prior to the initial commit (issue3541)
-
- $ hg init addrm2
- $ cd addrm2
- $ touch large.dat
- $ touch large2.dat
- $ touch normal
- $ hg add --large large.dat
- $ hg addremove -v
- adding large2.dat as a largefile
- adding normal
-
-Test that forgetting all largefiles reverts to islfilesrepo() == False
-(addremove will add *.dat as normal files now)
- $ hg forget large.dat
- $ hg forget large2.dat
- $ hg addremove -v
- adding large.dat
- adding large2.dat
-
-Test commit's addremove option prior to the first commit
- $ hg forget large.dat
- $ hg forget large2.dat
- $ hg add --large large.dat
- $ hg ci -Am "commit"
- adding large2.dat as a largefile
- Invoking status precommit hook
- A large.dat
- A large2.dat
- A normal
- $ find .hglf | sort
- .hglf
- .hglf/large.dat
- .hglf/large2.dat
-
-Test actions on largefiles using relative paths from subdir
-
- $ mkdir sub
- $ cd sub
- $ echo anotherlarge > anotherlarge
- $ hg add --large anotherlarge
- $ hg st
- A sub/anotherlarge
- $ hg st anotherlarge
- A anotherlarge
- $ hg commit -m anotherlarge anotherlarge
- Invoking status precommit hook
- A sub/anotherlarge
- $ hg log anotherlarge
- changeset: 1:9627a577c5e9
- tag: tip
- user: test
- date: Thu Jan 01 00:00:00 1970 +0000
- summary: anotherlarge
-
- $ hg log -G anotherlarge
- @ changeset: 1:9627a577c5e9
- | tag: tip
- | user: test
- | date: Thu Jan 01 00:00:00 1970 +0000
- | summary: anotherlarge
- |
- $ echo more >> anotherlarge
- $ hg st .
- M anotherlarge
- $ hg cat anotherlarge
- anotherlarge
- $ hg revert anotherlarge
- $ hg st
- ? sub/anotherlarge.orig
- $ cd ..
-
- $ cd ..
-
-issue3651: summary/outgoing with largefiles shows "no remote repo"
-unexpectedly
-
- $ mkdir issue3651
- $ cd issue3651
-
- $ hg init src
- $ echo a > src/a
- $ hg -R src add --large src/a
- $ hg -R src commit -m '#0'
- Invoking status precommit hook
- A a
-
-check messages when no remote repository is specified:
-"no remote repo" route for "hg outgoing --large" is not tested here,
-because it can't be reproduced easily.
-
- $ hg init clone1
- $ hg -R clone1 -q pull src
- $ hg -R clone1 -q update
- $ hg -R clone1 paths | grep default
- [1]
-
- $ hg -R clone1 summary --large
- parent: 0:fc0bd45326d3 tip
- #0
- branch: default
- commit: (clean)
- update: (current)
- largefiles: (no remote repo)
-
-check messages when there is no files to upload:
-
- $ hg -q clone src clone2
- $ hg -R clone2 paths | grep default
- default = $TESTTMP/issue3651/src (glob)
-
- $ hg -R clone2 summary --large
- parent: 0:fc0bd45326d3 tip
- #0
- branch: default
- commit: (clean)
- update: (current)
- largefiles: (no files to upload)
- $ hg -R clone2 outgoing --large
- comparing with $TESTTMP/issue3651/src (glob)
- searching for changes
- no changes found
- largefiles: no files to upload
- [1]
-
- $ hg -R clone2 outgoing --large --graph --template "{rev}"
- comparing with $TESTTMP/issue3651/src (glob)
- searching for changes
- no changes found
- largefiles: no files to upload
-
-check messages when there are files to upload:
-
- $ echo b > clone2/b
- $ hg -R clone2 add --large clone2/b
- $ hg -R clone2 commit -m '#1'
- Invoking status precommit hook
- A b
- $ hg -R clone2 summary --large
- parent: 1:1acbe71ce432 tip
- #1
- branch: default
- commit: (clean)
- update: (current)
- largefiles: 1 to upload
- $ hg -R clone2 outgoing --large
- comparing with $TESTTMP/issue3651/src (glob)
- searching for changes
- changeset: 1:1acbe71ce432
- tag: tip
- user: test
- date: Thu Jan 01 00:00:00 1970 +0000
- summary: #1
-
- largefiles to upload:
- b
-
- $ hg -R clone2 outgoing --large --graph --template "{rev}"
- comparing with $TESTTMP/issue3651/src
- searching for changes
- @ 1
-
- largefiles to upload:
- b
-
-
- $ cd ..
-
-merge action 'd' for 'local renamed directory to d2/g' which has no filename
-
- $ hg init merge-action
- $ cd merge-action
- $ touch l
- $ hg add --large l
- $ mkdir d1
- $ touch d1/f
- $ hg ci -Aqm0
- Invoking status precommit hook
- A d1/f
- A l
- $ echo > d1/f
- $ touch d1/g
- $ hg ci -Aqm1
- Invoking status precommit hook
- M d1/f
- A d1/g
- $ hg up -qr0
- $ hg mv d1 d2
- moving d1/f to d2/f (glob)
- $ hg ci -qm2
- Invoking status precommit hook
- A d2/f
- R d1/f
- $ hg merge
- merging d2/f and d1/f to d2/f
- 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
- (branch merge, don't forget to commit)
- getting changed largefiles
- 0 largefiles updated, 0 removed
- $ cd ..
-
-
-Merge conflicts:
-
- $ hg init merge
- $ cd merge
- $ echo 0 > f-different
- $ echo 0 > f-same
- $ echo 0 > f-unchanged-1
- $ echo 0 > f-unchanged-2
- $ hg add --large *
- $ hg ci -m0
- Invoking status precommit hook
- A f-different
- A f-same
- A f-unchanged-1
- A f-unchanged-2
- $ echo tmp1 > f-unchanged-1
- $ echo tmp1 > f-unchanged-2
- $ echo tmp1 > f-same
- $ hg ci -m1
- Invoking status precommit hook
- M f-same
- M f-unchanged-1
- M f-unchanged-2
- $ echo 2 > f-different
- $ echo 0 > f-unchanged-1
- $ echo 1 > f-unchanged-2
- $ echo 1 > f-same
- $ hg ci -m2
- Invoking status precommit hook
- M f-different
- M f-same
- M f-unchanged-1
- M f-unchanged-2
- $ hg up -qr0
- $ echo tmp2 > f-unchanged-1
- $ echo tmp2 > f-unchanged-2
- $ echo tmp2 > f-same
- $ hg ci -m3
- Invoking status precommit hook
- M f-same
- M f-unchanged-1
- M f-unchanged-2
- created new head
- $ echo 1 > f-different
- $ echo 1 > f-unchanged-1
- $ echo 0 > f-unchanged-2
- $ echo 1 > f-same
- $ hg ci -m4
- Invoking status precommit hook
- M f-different
- M f-same
- M f-unchanged-1
- M f-unchanged-2
- $ hg merge
- largefile f-different has a merge conflict
- ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7
- keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or
- take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l
- 0 files updated, 4 files merged, 0 files removed, 0 files unresolved
- (branch merge, don't forget to commit)
- getting changed largefiles
- 1 largefiles updated, 0 removed
- $ cat f-different
- 1
- $ cat f-same
- 1
- $ cat f-unchanged-1
- 1
- $ cat f-unchanged-2
- 1
- $ cd ..
-
-Check whether "largefiles" feature is supported only in repositories
-enabling largefiles extension.
-
- $ mkdir individualenabling
- $ cd individualenabling
-
- $ hg init enabledlocally
- $ echo large > enabledlocally/large
- $ hg -R enabledlocally add --large enabledlocally/large
- $ hg -R enabledlocally commit -m '#0'
- Invoking status precommit hook
- A large
-
- $ hg init notenabledlocally
- $ echo large > notenabledlocally/large
- $ hg -R notenabledlocally add --large notenabledlocally/large
- $ hg -R notenabledlocally commit -m '#0'
- Invoking status precommit hook
- A large
-
- $ cat >> $HGRCPATH <<EOF
- > [extensions]
- > # disable globally
- > largefiles=!
- > EOF
- $ cat >> enabledlocally/.hg/hgrc <<EOF
- > [extensions]
- > # enable locally
- > largefiles=
- > EOF
- $ hg -R enabledlocally root
- $TESTTMP/individualenabling/enabledlocally (glob)
- $ hg -R notenabledlocally root
- abort: repository requires features unknown to this Mercurial: largefiles!
- (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
- [255]
-
- $ hg init push-dst
- $ hg -R enabledlocally push push-dst
- pushing to push-dst
- abort: required features are not supported in the destination: largefiles
- [255]
-
- $ hg init pull-src
- $ hg -R pull-src pull enabledlocally
- pulling from enabledlocally
- abort: required features are not supported in the destination: largefiles
- [255]
-
- $ hg clone enabledlocally clone-dst
- abort: repository requires features unknown to this Mercurial: largefiles!
- (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
- [255]
- $ test -d clone-dst
- [1]
- $ hg clone --pull enabledlocally clone-pull-dst
- abort: required features are not supported in the destination: largefiles
- [255]
- $ test -d clone-pull-dst
- [1]
-
-#if serve
-
-Test largefiles specific peer setup, when largefiles is enabled
-locally (issue4109)
-
- $ hg showconfig extensions | grep largefiles
- extensions.largefiles=!
- $ mkdir -p $TESTTMP/individualenabling/usercache
-
- $ hg serve -R enabledlocally -d -p $HGPORT --pid-file hg.pid
- $ cat hg.pid >> $DAEMON_PIDS
-
- $ hg init pull-dst
- $ cat > pull-dst/.hg/hgrc <<EOF
- > [extensions]
- > # enable locally
- > largefiles=
- > [largefiles]
- > # ignore system cache to force largefiles specific wire proto access
- > usercache=$TESTTMP/individualenabling/usercache
- > EOF
- $ hg -R pull-dst -q pull -u http://localhost:$HGPORT
-
- $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
-#endif
-
- $ cd ..
--- a/tests/test-lfconvert.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-lfconvert.t Mon Jun 09 13:53:23 2014 -0500
@@ -132,6 +132,7 @@
[1]
$ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
$ hg resolve -m stuff/maybelarge.dat
+ no more unresolved files
$ hg commit -m"merge"
$ hg log -G --template "{rev}:{node|short} {desc|firstline}\n"
@ 5:4884f215abda merge
--- a/tests/test-log.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-log.t Mon Jun 09 13:53:23 2014 -0500
@@ -990,6 +990,7 @@
[1]
$ echo 'merge 1' > foo
$ hg resolve -m foo
+ no more unresolved files
$ hg ci -m "First merge, related"
$ hg merge 4
@@ -1001,6 +1002,7 @@
[1]
$ echo 'merge 2' > foo
$ hg resolve -m foo
+ no more unresolved files
$ hg ci -m "Last merge, related"
$ hg log --graph
--- a/tests/test-merge-commit.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-merge-commit.t Mon Jun 09 13:53:23 2014 -0500
@@ -71,8 +71,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28
+ preserving bar for resolve of bar
bar: versions differ -> m
- preserving bar for resolve of bar
updating: bar 1/1 files (100.00%)
picked tool 'internal:merge' for bar (binary False symlink False)
merging bar
@@ -158,8 +158,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0
+ preserving bar for resolve of bar
bar: versions differ -> m
- preserving bar for resolve of bar
updating: bar 1/1 files (100.00%)
picked tool 'internal:merge' for bar (binary False symlink False)
merging bar
--- a/tests/test-merge-criss-cross.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-merge-criss-cross.t Mon Jun 09 13:53:23 2014 -0500
@@ -81,11 +81,11 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
+ preserving f2 for resolve of f2
f1: remote is newer -> g
- f2: versions differ -> m
- preserving f2 for resolve of f2
getting f1
updating: f1 1/2 files (50.00%)
+ f2: versions differ -> m
updating: f2 2/2 files (100.00%)
picked tool 'internal:dump' for f2 (binary False symlink False)
merging f2
@@ -135,16 +135,16 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
- f1: g
- f2: m
+ f1: remote is newer -> g
+ f2: versions differ -> m
calculating bids for ancestor 40663881a6dd
searching for copies back to rev 3
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
- f1: m
- f2: k
+ f2: keep -> k
+ f1: versions differ -> m
auction for merging merge bids
f1: picking 'get' action
@@ -152,9 +152,9 @@
end of auction
f1: remote is newer -> g
- f2: keep -> k
getting f1
updating: f1 1/1 files (100.00%)
+ f2: keep -> k
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
@@ -180,26 +180,26 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5
- f1: k
- f2: m
+ f1: keep -> k
+ f2: versions differ -> m
calculating bids for ancestor 40663881a6dd
searching for copies back to rev 3
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5
- f1: m
- f2: g
+ f2: remote is newer -> g
+ f1: versions differ -> m
auction for merging merge bids
f1: picking 'keep' action
f2: picking 'get' action
end of auction
- f1: keep -> k
f2: remote is newer -> g
getting f2
updating: f2 1/1 files (100.00%)
+ f1: keep -> k
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
@@ -246,16 +246,16 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
- f1: g
- f2: m
+ f1: remote is newer -> g
+ f2: versions differ -> m
calculating bids for ancestor 40663881a6dd
searching for copies back to rev 3
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
- f1: m
- f2: k
+ f2: keep -> k
+ f1: versions differ -> m
auction for merging merge bids
f1: picking 'get' action
@@ -263,9 +263,9 @@
end of auction
f1: remote is newer -> g
- f2: keep -> k
getting f1
updating: f1 1/1 files (100.00%)
+ f2: keep -> k
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
--- a/tests/test-merge-revert2.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-merge-revert2.t Mon Jun 09 13:53:23 2014 -0500
@@ -57,11 +57,11 @@
@@ -1,3 +1,7 @@
added file1
another line of text
- +<<<<<<< local
+ +<<<<<<< working copy: c3fa057dd86f - test: added file1 and file2
+changed file1 different
+=======
changed file1
- +>>>>>>> other
+ +>>>>>>> destination: dfab7f3c2efb - test: changed file1
$ hg status
M file1
--- a/tests/test-merge-tools.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-merge-tools.t Mon Jun 09 13:53:23 2014 -0500
@@ -66,11 +66,11 @@
[1]
$ aftermerge
# cat f
- <<<<<<< local
+ <<<<<<< local: ef83787e2614 - test: revision 1
revision 1
=======
revision 2
- >>>>>>> other
+ >>>>>>> other: 0185f4e0cf02 - test: revision 2
space
# hg stat
M f
@@ -587,6 +587,54 @@
$ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests
+update is a merge ...
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ hg debugsetparent 0
+ $ hg update -r 2
+ merging f
+ revision 1
+ space
+ revision 0
+ space
+ revision 2
+ space
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ $ aftermerge
+ # cat f
+ revision 1
+ space
+ # hg stat
+ M f
+
+update should also have --tool
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ hg debugsetparent 0
+ $ hg update -r 2 --tool false
+ merging f
+ merging f failed!
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges
+ [1]
+ $ aftermerge
+ # cat f
+ revision 1
+ space
+ # hg stat
+ M f
+ ? f.orig
+
Default is silent simplemerge:
$ beforemerge
--- a/tests/test-merge-types.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-merge-types.t Mon Jun 09 13:53:23 2014 -0500
@@ -34,8 +34,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c
+ preserving a for resolve of a
a: versions differ -> m
- preserving a for resolve of a
updating: a 1/1 files (100.00%)
picked tool 'internal:merge' for a (binary False symlink True)
merging a
@@ -50,6 +50,7 @@
a is a symlink:
a -> symlink
$ hg resolve a --tool internal:other
+ no more unresolved files
$ tellmeabout a
a is an executable file with content:
a
@@ -67,8 +68,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
+ preserving a for resolve of a
a: versions differ -> m
- preserving a for resolve of a
updating: a 1/1 files (100.00%)
picked tool 'internal:merge' for a (binary False symlink True)
merging a
@@ -101,8 +102,8 @@
resolving manifests
branchmerge: False, force: False, partial: False
ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f
+ preserving a for resolve of a
a: versions differ -> m
- preserving a for resolve of a
updating: a 1/1 files (100.00%)
(couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
picked tool 'internal:prompt' for a (binary False symlink True)
@@ -289,18 +290,18 @@
U h
$ tellmeabout a
a is a plain file with content:
- <<<<<<< local
+ <<<<<<< local: 0139c5610547 - test: 2
2
=======
1
- >>>>>>> other
+ >>>>>>> other: 97e29675e796 - test: 1
$ tellmeabout b
b is a plain file with content:
- <<<<<<< local
+ <<<<<<< local: 0139c5610547 - test: 2
2
=======
1
- >>>>>>> other
+ >>>>>>> other: 97e29675e796 - test: 1
$ tellmeabout c
c is a plain file with content:
x
@@ -344,18 +345,18 @@
[1]
$ tellmeabout a
a is a plain file with content:
- <<<<<<< local
+ <<<<<<< local: 97e29675e796 - test: 1
1
=======
2
- >>>>>>> other
+ >>>>>>> other: 0139c5610547 - test: 2
$ tellmeabout b
b is an executable file with content:
- <<<<<<< local
+ <<<<<<< local: 97e29675e796 - test: 1
1
=======
2
- >>>>>>> other
+ >>>>>>> other: 0139c5610547 - test: 2
$ tellmeabout c
c is an executable file with content:
x
--- a/tests/test-merge7.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-merge7.t Mon Jun 09 13:53:23 2014 -0500
@@ -57,6 +57,7 @@
> EOF
$ rm -f *.orig
$ hg resolve -m test.txt
+ no more unresolved files
$ hg commit -m "Merge 1"
change test-a again
@@ -83,8 +84,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 96b70246a118, local: 50c3a7e29886+, remote: 40d11a4173a8
+ preserving test.txt for resolve of test.txt
test.txt: versions differ -> m
- preserving test.txt for resolve of test.txt
updating: test.txt 1/1 files (100.00%)
picked tool 'internal:merge' for test.txt (binary False symlink False)
merging test.txt
@@ -97,11 +98,11 @@
$ cat test.txt
one
- <<<<<<< local
+ <<<<<<< local: 50c3a7e29886 - test: Merge 1
two-point-five
=======
two-point-one
- >>>>>>> other
+ >>>>>>> other: 40d11a4173a8 - test: two -> two-point-one
three
$ hg debugindex test.txt
--- a/tests/test-mq-qfold.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-mq-qfold.t Mon Jun 09 13:53:23 2014 -0500
@@ -20,6 +20,8 @@
$ hg qnew -f p3
Fold in the middle of the queue:
+(this tests also that editor is not invoked if '--edit' is not
+specified)
$ hg qpop p1
popping p3
@@ -34,7 +36,7 @@
a
+a
- $ hg qfold p2
+ $ HGEDITOR=cat hg qfold p2
$ grep git .hg/patches/p1 && echo 'git patch found!'
[1]
@@ -153,8 +155,9 @@
> repo.__class__ = commitfailure
> EOF
- $ cat > .hg/hgrc <<EOF
+ $ cat >> .hg/hgrc <<EOF
> [extensions]
+ > # this failure occurs before editor invocation
> commitfailure = $TESTTMP/commitfailure.py
> EOF
@@ -165,16 +168,95 @@
> (echo; echo "test saving last-message.txt") >> \$1
> EOF
+ $ hg qapplied
+ p1
+ git
+ $ hg tip --template "{files}\n"
+ aa
+
+(test that editor is not invoked before transaction starting,
+and that combination of '--edit' and '--message' doesn't abort execution)
+
$ rm -f .hg/last-message.txt
- $ HGEDITOR="sh $TESTTMP/editor.sh" hg qfold -e p3
- ==== before editing
- original message====
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg qfold -e -m MESSAGE p3
refresh interrupted while patch was popped! (revert --all, qpush to recover)
abort: emulating unexpected abort
[255]
$ cat .hg/last-message.txt
+ cat: .hg/last-message.txt: No such file or directory
+ [1]
+
+(reset applied patches and directory status)
+
+ $ cat >> .hg/hgrc <<EOF
+ > [extensions]
+ > # this failure occurs after editor invocation
+ > commitfailure = !
+ > EOF
+
+ $ hg qapplied
+ p1
+ $ hg status -A aa
+ ? aa
+ $ rm aa
+ $ hg status -m
+ M a
+ $ hg revert --no-backup -q a
+ $ hg qpush -q git
+ now at: git
+
+(test that editor is invoked and commit message is saved into
+"last-message.txt")
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > # this failure occurs after editor invocation
+ > pretxncommit.unexpectedabort = false
+ > EOF
+
+ $ rm -f .hg/last-message.txt
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg qfold -e p3
+ ==== before editing
original message
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to use default message.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added aa
+ HG: changed a
+ ====
+ transaction abort!
+ rollback completed
+ note: commit message saved in .hg/last-message.txt
+ refresh interrupted while patch was popped! (revert --all, qpush to recover)
+ abort: pretxncommit.unexpectedabort hook exited with status 1
+ [255]
+ $ cat .hg/last-message.txt
+ original message
+
+
+
test saving last-message.txt
+(confirm whether files listed up in the commit message editing are correct)
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > pretxncommit.unexpectedabort =
+ > EOF
+ $ hg status -u | while read f; do rm ${f}; done
+ $ hg revert --no-backup -q --all
+ $ hg qpush -q git
+ now at: git
+ $ hg qpush -q --move p3
+ now at: p3
+
+ $ hg status --rev "git^1" --rev . -arm
+ M a
+ A aa
+
$ cd ..
--- a/tests/test-mq-qnew.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-mq-qnew.t Mon Jun 09 13:53:23 2014 -0500
@@ -158,6 +158,7 @@
merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+ no more unresolved files
abort: cannot manage merge changesets
$ rm -r sandbox
@@ -231,6 +232,7 @@
merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+ no more unresolved files
abort: cannot manage merge changesets
$ rm -r sandbox
@@ -247,8 +249,9 @@
> raise util.Abort('emulating unexpected abort')
> repo.__class__ = commitfailure
> EOF
- $ cat > .hg/hgrc <<EOF
+ $ cat >> .hg/hgrc <<EOF
> [extensions]
+ > # this failure occurs before editor invocation
> commitfailure = $TESTTMP/commitfailure.py
> EOF
@@ -259,13 +262,83 @@
> echo "test saving last-message.txt" >> \$1
> EOF
+(test that editor is not invoked before transaction starting)
+
$ rm -f .hg/last-message.txt
$ HGEDITOR="sh $TESTTMP/editor.sh" hg qnew -e patch
- ==== before editing
- ====
abort: emulating unexpected abort
[255]
$ cat .hg/last-message.txt
+ cat: .hg/last-message.txt: No such file or directory
+ [1]
+
+(test that editor is invoked and commit message is saved into
+"last-message.txt")
+
+ $ cat >> .hg/hgrc <<EOF
+ > [extensions]
+ > commitfailure = !
+ > [hooks]
+ > # this failure occurs after editor invocation
+ > pretxncommit.unexpectedabort = false
+ > EOF
+
+ $ rm -f .hg/last-message.txt
+ $ hg status
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg qnew -e patch
+ ==== before editing
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to use default message.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: no files changed
+ ====
+ transaction abort!
+ rollback completed
+ note: commit message saved in .hg/last-message.txt
+ abort: pretxncommit.unexpectedabort hook exited with status 1
+ [255]
+ $ cat .hg/last-message.txt
+
+
test saving last-message.txt
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > pretxncommit.unexpectedabort =
+ > EOF
+
+#if unix-permissions
+
+Test handling default message with the patch filename with tail whitespaces
+
+ $ cat > $TESTTMP/editor.sh << EOF
+ > echo "==== before editing"
+ > cat \$1
+ > echo "===="
+ > echo "[mq]: patch " > \$1
+ > EOF
+
+ $ rm -f .hg/last-message.txt
+ $ hg status
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg qnew -e "patch "
+ ==== before editing
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to use default message.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: no files changed
+ ====
+ $ cat ".hg/patches/patch "
+ # HG changeset patch
+ # Parent 0000000000000000000000000000000000000000
+
$ cd ..
+
+#endif
--- a/tests/test-mq-qrefresh-replace-log-message.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-mq-qrefresh-replace-log-message.t Mon Jun 09 13:53:23 2014 -0500
@@ -6,6 +6,8 @@
$ hg qinit
Should fail if no patches applied
+(this tests also that editor is not invoked if '--edit' is not
+specified)
$ hg qrefresh
no patches applied
@@ -16,7 +18,7 @@
$ hg qnew -m "First commit message" first-patch
$ echo aaaa > file
$ hg add file
- $ hg qrefresh
+ $ HGEDITOR=cat hg qrefresh
Should display 'First commit message'
@@ -24,9 +26,19 @@
First commit message
Testing changing message with -m
+(this tests also that '--edit' can be used with '--message')
$ echo bbbb > file
- $ hg qrefresh -m "Second commit message"
+ $ HGEDITOR=cat hg qrefresh -m "Second commit message" -e
+ Second commit message
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to use default message.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added file
Should display 'Second commit message'
@@ -59,3 +71,98 @@
$ hg log -l1 --template "{desc}\n"
Fifth commit message
This is the 5th log message
+
+Test saving last-message.txt:
+
+ $ cat > $TESTTMP/editor.sh << EOF
+ > echo "==== before editing"
+ > cat \$1
+ > echo "===="
+ > (echo; echo "test saving last-message.txt") >> \$1
+ > EOF
+
+ $ cat > $TESTTMP/commitfailure.py <<EOF
+ > from mercurial import util
+ > def reposetup(ui, repo):
+ > class commitfailure(repo.__class__):
+ > def commit(self, *args, **kwargs):
+ > raise util.Abort('emulating unexpected abort')
+ > repo.__class__ = commitfailure
+ > EOF
+
+ $ cat >> .hg/hgrc <<EOF
+ > [extensions]
+ > # this failure occurs before editor invocation
+ > commitfailure = $TESTTMP/commitfailure.py
+ > EOF
+
+ $ hg qapplied
+ first-patch
+ second-patch
+ $ hg tip --template "{files}\n"
+ file2
+
+(test that editor is not invoked before transaction starting)
+
+ $ rm -f .hg/last-message.txt
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
+ refresh interrupted while patch was popped! (revert --all, qpush to recover)
+ abort: emulating unexpected abort
+ [255]
+ $ cat .hg/last-message.txt
+ cat: .hg/last-message.txt: No such file or directory
+ [1]
+
+(reset applied patches and directory status)
+
+ $ cat >> .hg/hgrc <<EOF
+ > [extensions]
+ > commitfailure = !
+ > EOF
+
+ $ hg qapplied
+ first-patch
+ $ hg status -A file2
+ ? file2
+ $ rm file2
+ $ hg qpush -q second-patch
+ now at: second-patch
+
+(test that editor is invoked and commit message is saved into
+"last-message.txt")
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > # this failure occurs after editor invocation
+ > pretxncommit.unexpectedabort = false
+ > EOF
+
+ $ rm -f .hg/last-message.txt
+ $ hg status --rev "second-patch^1" -arm
+ A file2
+ $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
+ ==== before editing
+ Fifth commit message
+ This is the 5th log message
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to use default message.
+ HG: --
+ HG: user: test
+ HG: branch 'default'
+ HG: added file2
+ ====
+ transaction abort!
+ rollback completed
+ note: commit message saved in .hg/last-message.txt
+ refresh interrupted while patch was popped! (revert --all, qpush to recover)
+ abort: pretxncommit.unexpectedabort hook exited with status 1
+ [255]
+ $ cat .hg/last-message.txt
+ Fifth commit message
+ This is the 5th log message
+
+
+
+ test saving last-message.txt
--- a/tests/test-patchbomb.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-patchbomb.t Mon Jun 09 13:53:23 2014 -0500
@@ -17,6 +17,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -87,6 +89,8 @@
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)
In-Reply-To: <patchbomb.120@*> (glob)
References: <patchbomb.120@*> (glob)
@@ -116,6 +120,8 @@
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)
In-Reply-To: <patchbomb.120@*> (glob)
References: <patchbomb.120@*> (glob)
@@ -251,6 +257,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:04:00 +0000
@@ -294,6 +302,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:04:00 +0000
@@ -353,6 +363,8 @@
Content-Transfer-Encoding: quoted-printable
Subject: [PATCH] long line
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:04:00 +0000
@@ -404,6 +416,8 @@
Content-Transfer-Encoding: quoted-printable
Subject: [PATCH] long line
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:04:00 +0000
@@ -463,6 +477,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:05:00 +0000
@@ -508,6 +524,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -584,6 +602,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -617,6 +637,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -655,6 +677,8 @@
MIME-Version: 1.0
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -695,6 +719,8 @@
MIME-Version: 1.0
Subject: [PATCH] test
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -767,6 +793,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -802,6 +830,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -837,6 +867,8 @@
MIME-Version: 1.0
Subject: [PATCH 3 of 3] long line
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 3
+ X-Mercurial-Series-Total: 3
Message-Id: <a2ea8fc83dd8b93cfd86.63@*> (glob)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -894,6 +926,8 @@
MIME-Version: 1.0
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -942,6 +976,8 @@
MIME-Version: 1.0
Subject: [PATCH] test
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1006,6 +1042,8 @@
MIME-Version: 1.0
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1081,6 +1119,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1125,6 +1165,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1169,6 +1211,8 @@
MIME-Version: 1.0
Subject: [PATCH 3 of 3] long line
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 3
+ X-Mercurial-Series-Total: 3
Message-Id: <a2ea8fc83dd8b93cfd86.63@*> (glob)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1253,6 +1297,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH 1 of 1] c
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.61@*> (glob)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1304,6 +1350,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH 1 of 1] c
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.61@*> (glob)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1356,6 +1404,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1385,6 +1435,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1421,6 +1473,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1456,6 +1510,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1494,6 +1550,8 @@
MIME-Version: 1.0
Subject: [PATCH] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1549,6 +1607,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1584,6 +1644,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1628,6 +1690,8 @@
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)
In-Reply-To: <baz>
References: <baz>
@@ -1668,6 +1732,8 @@
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)
In-Reply-To: <baz>
References: <baz>
@@ -1697,6 +1763,8 @@
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)
In-Reply-To: <baz>
References: <baz>
@@ -1752,6 +1820,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1781,6 +1851,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1819,6 +1891,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH fooFlag] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1870,6 +1944,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1899,6 +1975,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -1937,6 +2015,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH fooFlag barFlag] test
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 1
Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Thu, 01 Jan 1970 00:01:00 +0000
@@ -1987,6 +2067,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -2016,6 +2098,8 @@
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)
In-Reply-To: <patchbomb.60@*> (glob)
References: <patchbomb.60@*> (glob)
@@ -2055,6 +2139,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Tue, 01 Jan 1980 00:01:00 +0000
@@ -2097,6 +2183,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Tue, 01 Jan 1980 00:01:00 +0000
@@ -2184,6 +2272,8 @@
Content-Transfer-Encoding: 7bit
Subject: [PATCH 1 of 6] c
X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
+ X-Mercurial-Series-Index: 1
+ X-Mercurial-Series-Total: 6
Message-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
In-Reply-To: <patchbomb.315532860@*> (glob)
References: <patchbomb.315532860@*> (glob)
@@ -2212,6 +2302,8 @@
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)
In-Reply-To: <patchbomb.315532860@*> (glob)
References: <patchbomb.315532860@*> (glob)
@@ -2247,6 +2339,8 @@
Content-Transfer-Encoding: quoted-printable
Subject: [PATCH 3 of 6] long line
X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
+ X-Mercurial-Series-Index: 3
+ X-Mercurial-Series-Total: 6
Message-Id: <a2ea8fc83dd8b93cfd86.315532863@*> (glob)
In-Reply-To: <patchbomb.315532860@*> (glob)
References: <patchbomb.315532860@*> (glob)
@@ -2291,6 +2385,8 @@
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)
In-Reply-To: <patchbomb.315532860@*> (glob)
References: <patchbomb.315532860@*> (glob)
@@ -2319,6 +2415,8 @@
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)
In-Reply-To: <patchbomb.315532860@*> (glob)
References: <patchbomb.315532860@*> (glob)
@@ -2348,6 +2446,8 @@
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)
In-Reply-To: <patchbomb.315532860@*> (glob)
References: <patchbomb.315532860@*> (glob)
@@ -2386,6 +2486,8 @@
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)
User-Agent: Mercurial-patchbomb/* (glob)
Date: Tue, 01 Jan 1980 00:01:00 +0000
--- a/tests/test-progress.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-progress.t Mon Jun 09 13:53:23 2014 -0500
@@ -1,7 +1,11 @@
$ cat > loop.py <<EOF
- > from mercurial import commands
+ > from mercurial import cmdutil, commands
> import time
+ >
+ > cmdtable = {}
+ > command = cmdutil.command(cmdtable)
+ >
> class incrementingtime(object):
> def __init__(self):
> self._time = 0.0
@@ -10,6 +14,11 @@
> return self._time
> time.time = incrementingtime()
>
+ > @command('loop',
+ > [('', 'total', '', 'override for total'),
+ > ('', 'nested', False, 'show nested results'),
+ > ('', 'parallel', False, 'show parallel sets of results')],
+ > 'hg loop LOOPS')
> def loop(ui, loops, **opts):
> loops = int(loops)
> total = None
@@ -38,14 +47,6 @@
> ui.progress('loop', None, 'loop.done', 'loopnum', total)
>
> commands.norepo += " loop"
- >
- > cmdtable = {
- > "loop": (loop, [('', 'total', '', 'override for total'),
- > ('', 'nested', False, 'show nested results'),
- > ('', 'parallel', False, 'show parallel sets of results'),
- > ],
- > 'hg loop LOOPS'),
- > }
> EOF
$ cp $HGRCPATH $HGRCPATH.orig
--- a/tests/test-rebase-bookmarks.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-bookmarks.t Mon Jun 09 13:53:23 2014 -0500
@@ -154,6 +154,7 @@
$ hg up 2
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark X)
$ echo 'C' > c
$ hg add c
$ hg ci -m 'other C'
@@ -168,6 +169,7 @@
[1]
$ echo 'c' > c
$ hg resolve --mark c
+ no more unresolved files
$ hg rebase --continue
saved backup bundle to $TESTTMP/a3/.hg/strip-backup/3d5fa227f4b5-backup.hg (glob)
$ hg tglog
--- a/tests/test-rebase-check-restore.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-check-restore.t Mon Jun 09 13:53:23 2014 -0500
@@ -76,6 +76,7 @@
$ echo 'conflict solved' > A
$ rm A.orig
$ hg resolve -m A
+ no more unresolved files
$ hg rebase --continue
$ hg tglog
@@ -129,6 +130,7 @@
$ echo 'conflict solved' > A
$ rm A.orig
$ hg resolve -m A
+ no more unresolved files
$ hg rebase --continue
saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-conflicts.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-conflicts.t Mon Jun 09 13:53:23 2014 -0500
@@ -77,6 +77,7 @@
$ echo 'resolved merge' >common
$ hg resolve -m common
+ no more unresolved files
$ hg rebase --continue
saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
@@ -219,9 +220,9 @@
branchmerge: False, force: True, partial: False
ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b
f2.txt: other deleted -> r
- f1.txt: remote created -> g
removing f2.txt
updating: f2.txt 1/2 files (50.00%)
+ f1.txt: remote created -> g
getting f1.txt
updating: f1.txt 2/2 files (100.00%)
merge against 9:e31216eec445
@@ -254,9 +255,9 @@
branchmerge: False, force: False, partial: False
ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
f1.txt: other deleted -> r
- f2.txt: remote created -> g
removing f1.txt
updating: f1.txt 1/2 files (50.00%)
+ f2.txt: remote created -> g
getting f2.txt
updating: f2.txt 2/2 files (100.00%)
3 changesets found
--- a/tests/test-rebase-detach.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-detach.t Mon Jun 09 13:53:23 2014 -0500
@@ -374,6 +374,7 @@
unresolved conflicts (see hg resolve, then hg rebase --continue)
[1]
$ hg resolve --all -t internal:local
+ no more unresolved files
$ hg rebase -c
saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6215fafa5447-backup.hg (glob)
$ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
--- a/tests/test-rebase-interruptions.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-interruptions.t Mon Jun 09 13:53:23 2014 -0500
@@ -104,6 +104,7 @@
$ echo 'conflict solved' > A
$ rm A.orig
$ hg resolve -m A
+ no more unresolved files
$ hg rebase --continue
warning: new changesets detected on source branch, not stripping
--- a/tests/test-rebase-mq-skip.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-mq-skip.t Mon Jun 09 13:53:23 2014 -0500
@@ -111,6 +111,7 @@
[1]
$ HGMERGE=internal:local hg resolve --all
+ no more unresolved files
$ hg rebase --continue
saved backup bundle to $TESTTMP/b/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-mq.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-mq.t Mon Jun 09 13:53:23 2014 -0500
@@ -69,6 +69,7 @@
$ echo mq1r1 > f
$ hg resolve -m f
+ no more unresolved files
$ hg rebase -c
merging f
warning: conflicts during merge.
@@ -80,6 +81,7 @@
$ echo mq1r1mq2 > f
$ hg resolve -m f
+ no more unresolved files
$ hg rebase -c
saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-parameters.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-parameters.t Mon Jun 09 13:53:23 2014 -0500
@@ -454,6 +454,7 @@
U c2
$ hg resolve -m c2
+ no more unresolved files
$ hg rebase -c --tool internal:fail
tool option will be ignored
saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-scenario-global.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rebase-scenario-global.t Mon Jun 09 13:53:23 2014 -0500
@@ -25,6 +25,7 @@
Rebasing
D onto H - simple rebase:
+(this also tests that editor is invoked if '--edit' is specified)
$ hg clone -q -u . a a1
$ cd a1
@@ -47,7 +48,18 @@
o 0: 'A'
- $ hg rebase -s 3 -d 7
+ $ hg status --rev "3^1" --rev 3
+ A D
+ $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit
+ D
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
+ HG: branch 'default'
+ HG: changed D
saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
$ hg tglog
@@ -71,11 +83,12 @@
D onto F - intermediate point:
+(this also tests that editor is not invoked if '--edit' is not specified)
$ hg clone -q -u . a a2
$ cd a2
- $ hg rebase -s 3 -d 5
+ $ HGEDITOR=cat hg rebase -s 3 -d 5
saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
$ hg tglog
--- a/tests/test-rename-dir-merge.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rename-dir-merge.t Mon Jun 09 13:53:23 2014 -0500
@@ -40,16 +40,16 @@
branchmerge: True, force: False, partial: False
ancestor: f9b20c0d4c51, local: ce36d17b18fb+, remote: 397f8b00a740
a/a: other deleted -> r
+ removing a/a
a/b: other deleted -> r
- b/a: remote created -> g
- b/b: remote created -> g
- b/c: remote directory rename - move from a/c -> dm
- removing a/a
removing a/b
updating: a/b 2/5 files (40.00%)
+ b/a: remote created -> g
getting b/a
+ b/b: remote created -> g
getting b/b
updating: b/b 4/5 files (80.00%)
+ b/c: remote directory rename - move from a/c -> dm
updating: b/c 5/5 files (100.00%)
moving a/c to b/c (glob)
3 files updated, 0 files merged, 2 files removed, 0 files unresolved
--- a/tests/test-rename-merge1.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rename-merge1.t Mon Jun 09 13:53:23 2014 -0500
@@ -36,22 +36,22 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: af1939970a1c, local: 044f8520aeeb+, remote: 85c198ef2f6c
- a2: divergent renames -> dr
- b: remote moved from a -> m
- preserving a for resolve of b
+ preserving a for resolve of b
+ removing a
b2: remote created -> g
- removing a
getting b2
updating: b2 1/3 files (33.33%)
- updating: a2 2/3 files (66.67%)
- note: possible conflict - a2 was renamed multiple times to:
- c2
- b2
- updating: b 3/3 files (100.00%)
+ b: remote moved from a -> m
+ updating: b 2/3 files (66.67%)
picked tool 'internal:merge' for b (binary False symlink False)
merging a and b to b
my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
premerge successful
+ a2: divergent renames -> dr
+ updating: a2 3/3 files (100.00%)
+ note: possible conflict - a2 was renamed multiple times to:
+ c2
+ b2
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
@@ -181,10 +181,10 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 19d7f95df299, local: 0084274f6b67+, remote: 5d32493049f0
- file: rename and delete -> rd
newfile: remote created -> g
getting newfile
updating: newfile 1/2 files (50.00%)
+ file: rename and delete -> rd
updating: file 2/2 files (100.00%)
note: possible conflict - file was deleted and renamed to:
newfile
--- a/tests/test-rename-merge2.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rename-merge2.t Mon Jun 09 13:53:23 2014 -0500
@@ -86,16 +86,16 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: e300d1c794ec+, remote: 4ce40f5aca24
+ preserving a for resolve of b
+ preserving rev for resolve of rev
a: keep -> k
b: remote copied from a -> m
- preserving a for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging a and b to b
my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337
premerge successful
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -122,18 +122,18 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 86a2aa42fc76+, remote: f4db7e329e71
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: remote is newer -> g
- b: local copied/moved from a -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
getting a
updating: a 1/3 files (33.33%)
+ b: local copied/moved from a -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b and a to b
my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337
premerge successful
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -160,16 +160,16 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: e300d1c794ec+, remote: bdb19105162a
+ preserving a for resolve of b
+ preserving rev for resolve of rev
+ removing a
b: remote moved from a -> m
- preserving a for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
- removing a
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging a and b to b
my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337
premerge successful
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -195,15 +195,15 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 02963e448370+, remote: f4db7e329e71
+ preserving b for resolve of b
+ preserving rev for resolve of rev
b: local copied/moved from a -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b and a to b
my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337
premerge successful
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -229,11 +229,11 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 94b33a1b7f2d+, remote: 4ce40f5aca24
+ preserving rev for resolve of rev
b: remote created -> g
- rev: versions differ -> m
- preserving rev for resolve of rev
getting b
updating: b 1/2 files (50.00%)
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -259,8 +259,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 97c705ade336
+ preserving rev for resolve of rev
rev: versions differ -> m
- preserving rev for resolve of rev
updating: rev 1/1 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -286,14 +286,14 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 94b33a1b7f2d+, remote: bdb19105162a
+ preserving rev for resolve of rev
a: other deleted -> r
- b: remote created -> g
- rev: versions differ -> m
- preserving rev for resolve of rev
removing a
updating: a 1/3 files (33.33%)
+ b: remote created -> g
getting b
updating: b 2/3 files (66.67%)
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -318,8 +318,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 02963e448370+, remote: 97c705ade336
+ preserving rev for resolve of rev
rev: versions differ -> m
- preserving rev for resolve of rev
updating: rev 1/1 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -341,14 +341,14 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 62e7bf090eba+, remote: 49b6d8032493
+ preserving b for resolve of b
+ preserving rev for resolve of rev
b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -379,20 +379,20 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 02963e448370+, remote: fe905ef2c33e
- a: divergent renames -> dr
+ preserving rev for resolve of rev
c: remote created -> g
- rev: versions differ -> m
- preserving rev for resolve of rev
getting c
updating: c 1/3 files (33.33%)
- updating: a 2/3 files (66.67%)
+ rev: versions differ -> m
+ updating: rev 2/3 files (66.67%)
+ picked tool 'python ../merge' for rev (binary False symlink False)
+ merging rev
+ my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337
+ a: divergent renames -> dr
+ updating: a 3/3 files (100.00%)
note: possible conflict - a was renamed multiple times to:
b
c
- updating: rev 3/3 files (100.00%)
- picked tool 'python ../merge' for rev (binary False symlink False)
- merging rev
- my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
--------------
@@ -411,14 +411,14 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 86a2aa42fc76+, remote: af30c7647fc7
+ preserving b for resolve of b
+ preserving rev for resolve of rev
b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -441,17 +441,17 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 59318016310c+, remote: bdb19105162a
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: other deleted -> r
- b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
removing a
updating: a 1/3 files (33.33%)
+ b: versions differ -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -473,17 +473,17 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 8dbce441892a
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: remote is newer -> g
- b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
getting a
updating: a 1/3 files (33.33%)
+ b: versions differ -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -506,17 +506,17 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 59318016310c+, remote: bdb19105162a
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: other deleted -> r
- b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
removing a
updating: a 1/3 files (33.33%)
+ b: versions differ -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -538,17 +538,17 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 8dbce441892a
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: remote is newer -> g
- b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
getting a
updating: a 1/3 files (33.33%)
+ b: versions differ -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -571,15 +571,15 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 0b76e65c8289+, remote: 4ce40f5aca24
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: keep -> k
b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -604,17 +604,17 @@
ancestor: 924404dff337, local: 02963e448370+, remote: 8dbce441892a
remote changed a which local deleted
use (c)hanged version or leave (d)eleted? c
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: prompt recreating -> g
- b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
getting a
updating: a 1/3 files (33.33%)
+ b: versions differ -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@02963e448370+ other b@8dbce441892a ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -639,16 +639,16 @@
ancestor: 924404dff337, local: 0b76e65c8289+, remote: bdb19105162a
local changed a which remote deleted
use (c)hanged version or (d)elete? c
+ preserving b for resolve of b
+ preserving rev for resolve of rev
a: prompt keep -> a
+ updating: a 1/3 files (33.33%)
b: versions differ -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
- updating: a 1/3 files (33.33%)
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b
my b@0b76e65c8289+ other b@bdb19105162a ancestor b@000000000000
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -674,15 +674,15 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: e300d1c794ec+, remote: 49b6d8032493
+ preserving a for resolve of b
+ preserving rev for resolve of rev
+ removing a
b: remote moved from a -> m
- preserving a for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
- removing a
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging a and b to b
my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -708,14 +708,14 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 62e7bf090eba+, remote: f4db7e329e71
+ preserving b for resolve of b
+ preserving rev for resolve of rev
b: local copied/moved from a -> m
- preserving b for resolve of b
- rev: versions differ -> m
- preserving rev for resolve of rev
updating: b 1/2 files (50.00%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b and a to b
my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337
+ rev: versions differ -> m
updating: rev 2/2 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -746,18 +746,18 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 924404dff337, local: 02963e448370+, remote: 2b958612230f
- b: local copied/moved from a -> m
- preserving b for resolve of b
+ preserving b for resolve of b
+ preserving rev for resolve of rev
c: remote created -> g
- rev: versions differ -> m
- preserving rev for resolve of rev
getting c
updating: c 1/3 files (33.33%)
+ b: local copied/moved from a -> m
updating: b 2/3 files (66.67%)
picked tool 'python ../merge' for b (binary False symlink False)
merging b and a to b
my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337
premerge successful
+ rev: versions differ -> m
updating: rev 3/3 files (100.00%)
picked tool 'python ../merge' for rev (binary False symlink False)
merging rev
@@ -836,28 +836,18 @@
ancestor: e6cb3cf11019, local: ec44bf929ab5+, remote: c62e34d0b898
remote changed 8/f which local deleted
use (c)hanged version or leave (d)eleted? c
- 0/f: versions differ -> m
- preserving 0/f for resolve of 0/f
- 1/g: versions differ -> m
- preserving 1/g for resolve of 1/g
- 2/f: versions differ -> m
- preserving 2/f for resolve of 2/f
- 3/f: versions differ -> m
- preserving 3/f for resolve of 3/f
- 3/g: remote copied from 3/f -> m
- preserving 3/f for resolve of 3/g
- 4/g: remote moved from 4/f -> m
- preserving 4/f for resolve of 4/g
- 5/f: versions differ -> m
- preserving 5/f for resolve of 5/f
- 5/g: local copied/moved from 5/f -> m
- preserving 5/g for resolve of 5/g
- 6/g: local copied/moved from 6/f -> m
- preserving 6/g for resolve of 6/g
- 7/f: remote differs from untracked local -> m
- preserving 7/f for resolve of 7/f
+ preserving 0/f for resolve of 0/f
+ preserving 1/g for resolve of 1/g
+ preserving 2/f for resolve of 2/f
+ preserving 3/f for resolve of 3/f
+ preserving 3/f for resolve of 3/g
+ preserving 4/f for resolve of 4/g
+ preserving 5/f for resolve of 5/f
+ preserving 5/g for resolve of 5/g
+ preserving 6/g for resolve of 6/g
+ preserving 7/f for resolve of 7/f
+ removing 4/f
8/f: prompt recreating -> g
- removing 4/f
getting 8/f
$ hg mani
0/f
--- a/tests/test-resolve.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-resolve.t Mon Jun 09 13:53:23 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 resolved
+
+resolve the failure
+
$ echo resolved > file
$ hg resolve -m file
+ no more unresolved files
$ hg commit -m 'resolved'
-resolve -l, should be empty
+resolve -l should be empty
$ hg resolve -l
+resolve -m should abort since no merge in progress
+ $ hg resolve -m
+ abort: resolve command not applicable when not merging
+ [255]
+
test crashed merge with empty mergestate
$ mkdir .hg/merge
--- a/tests/test-revert.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-revert.t Mon Jun 09 13:53:23 2014 -0500
@@ -299,4 +299,86 @@
removed
R ignored
- $ cd ..
+Test revert of a file added by one side of the merge
+
+(remove any pending change)
+
+ $ hg revert --all
+ forgetting allyour
+ forgetting base
+ undeleting ignored
+ $ hg purge --all --config extensions.purge=
+
+(Adds a new commit)
+
+ $ echo foo > newadd
+ $ hg add newadd
+ $ hg commit -m 'other adds'
+ created new head
+
+
+(merge it with the other head)
+
+ $ hg merge # merge 1 into 2
+ 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ hg summary
+ parent: 2:b8ec310b2d4e tip
+ other adds
+ parent: 1:f6180deb8fbe
+ rename
+ branch: default
+ commit: 2 modified, 1 removed (merge)
+ update: (current)
+
+(clarifies who added what)
+
+ $ hg status
+ M allyour
+ M base
+ R ignored
+ $ hg status --change 'p1()'
+ A newadd
+ $ hg status --change 'p2()'
+ A allyour
+ A base
+ R ignored
+
+(revert file added by p1() to p1() state)
+
+ $ hg revert -r 'p1()' 'glob:newad?'
+ $ hg status
+ M allyour
+ M base
+ R ignored
+
+(revert file added by p1() to p2() state)
+
+ $ hg revert -r 'p2()' 'glob:newad?'
+ removing newadd
+ $ hg status
+ M allyour
+ M base
+ R ignored
+ R newadd
+
+(revert file added by p2() to p2() state)
+
+ $ hg revert -r 'p2()' 'glob:allyou?'
+ $ hg status
+ M allyour
+ M base
+ R ignored
+ R newadd
+
+(revert file added by p2() to p1() state)
+
+ $ hg revert -r 'p1()' 'glob:allyou?'
+ removing allyour
+ $ hg status
+ M base
+ R allyour
+ R ignored
+ R newadd
+
+
--- a/tests/test-rollback.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-rollback.t Mon Jun 09 13:53:23 2014 -0500
@@ -82,6 +82,7 @@
0 default add a again
$ hg update default
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ (leaving bookmark foo)
$ hg bookmark bar
$ cat .hg/undo.branch ; echo
test
--- a/tests/test-run-tests.py Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-run-tests.py Mon Jun 09 13:53:23 2014 -0500
@@ -29,7 +29,7 @@
assert expected.endswith('\n') and output.endswith('\n'), 'missing newline'
assert not re.search(r'[^ \w\\/\r\n()*?]', expected + output), \
'single backslash or unknown char'
- match = run_tests.linematch(expected, output)
+ match = run_tests.TTest.linematch(expected, output)
if isinstance(match, str):
return 'special: ' + match
else:
--- a/tests/test-shelve.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-shelve.t Mon Jun 09 13:53:23 2014 -0500
@@ -90,6 +90,10 @@
a
+a
+ $ hg shelve --list --addremove
+ abort: options '--list' and '--addremove' may not be used together
+ [255]
+
delete our older shelved change
$ hg shelve -d default
@@ -210,11 +214,11 @@
+++ b/a/a
@@ -1,2 +1,6 @@
a
- +<<<<<<< local
+ +<<<<<<< dest: * - shelve: pending changes temporary commit (glob)
c
+=======
+a
- +>>>>>>> other
+ +>>>>>>> source: 4702e8911fe0 - shelve: changes to '[mq]: second.patch'
diff --git a/b.rename/b b/b.rename/b
new file mode 100644
--- /dev/null
@@ -292,6 +296,7 @@
$ hg revert -r . a/a
$ hg resolve -m a/a
+ no more unresolved files
$ hg commit -m 'commit while unshelve in progress'
abort: unshelve already in progress
@@ -394,6 +399,16 @@
$ hg shelve --cleanup
$ hg shelve --list
+ $ hg shelve --cleanup --delete
+ abort: options '--cleanup' and '--delete' may not be used together
+ [255]
+ $ hg shelve --cleanup --patch
+ abort: options '--cleanup' and '--patch' may not be used together
+ [255]
+ $ hg shelve --cleanup --message MESSAGE
+ abort: options '--cleanup' and '--message' may not be used together
+ [255]
+
test bookmarks
$ hg bookmark test
@@ -601,11 +616,11 @@
M f
? f.orig
$ cat f
- <<<<<<< local
+ <<<<<<< dest: 5f6b880e719b - shelve: pending changes temporary commit
g
=======
f
- >>>>>>> other
+ >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff'
$ cat f.orig
g
$ hg unshelve --abort
@@ -644,11 +659,11 @@
M f
? f.orig
$ cat f
- <<<<<<< local
+ <<<<<<< dest: * - test: intermediate other change (glob)
g
=======
f
- >>>>>>> other
+ >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff'
$ cat f.orig
g
$ hg unshelve --abort
@@ -663,4 +678,11 @@
g
$ hg shelve --delete default
+ $ hg shelve --delete --stat
+ abort: options '--delete' and '--stat' may not be used together
+ [255]
+ $ hg shelve --delete --name NAME
+ abort: options '--delete' and '--name' may not be used together
+ [255]
+
$ cd ..
--- a/tests/test-strip.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-strip.t Mon Jun 09 13:53:23 2014 -0500
@@ -490,6 +490,7 @@
$ hg bookmark -r 'c' 'delete'
$ hg up -C todelete
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark todelete)
$ hg strip -B nostrip
bookmark 'nostrip' deleted
abort: empty revision set
--- a/tests/test-subrepo-git.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-subrepo-git.t Mon Jun 09 13:53:23 2014 -0500
@@ -155,7 +155,7 @@
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg merge 2>/dev/null
- subrepository s diverged (local revision: 796959400868, remote revision: aa84837ccfbd)
+ subrepository s diverged (local revision: 7969594, remote revision: aa84837)
(M)erge, keep (l)ocal or keep (r)emote? m
pulling subrepo s from $TESTTMP/gitroot
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -464,7 +464,7 @@
da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
$ cd ..
$ hg update 4
- subrepository s diverged (local revision: da5f5b1d8ffc, remote revision: aa84837ccfbd)
+ subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
(M)erge, keep (l)ocal or keep (r)emote? m
subrepository sources for s differ
use (l)ocal source (da5f5b1) or (r)emote source (aa84837)?
@@ -491,7 +491,7 @@
HEAD is now at aa84837... f
$ cd ..
$ hg update 1
- subrepository s diverged (local revision: 32a343883b74, remote revision: da5f5b1d8ffc)
+ subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
(M)erge, keep (l)ocal or keep (r)emote? m
subrepository sources for s differ (in checked out version)
use (l)ocal source (32a3438) or (r)emote source (da5f5b1)?
@@ -514,7 +514,7 @@
$ hg id -n
1+
$ hg update 7
- subrepository s diverged (local revision: 32a343883b74, remote revision: 32a343883b74)
+ subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
(M)erge, keep (l)ocal or keep (r)emote? m
subrepository sources for s differ
use (l)ocal source (32a3438) or (r)emote source (32a3438)?
--- a/tests/test-subrepo-svn.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-subrepo-svn.t Mon Jun 09 13:53:23 2014 -0500
@@ -470,6 +470,7 @@
$ hg book other
$ hg co -r 'p1(tip)'
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark other)
$ echo "obstruct = [svn] $SVNREPOURL/src" >> .hgsub
$ svn co -r5 --quiet "$SVNREPOURL"/src obstruct
$ hg commit -m 'Other branch which will be obstructed'
@@ -481,6 +482,7 @@
A *obstruct/other (glob)
Checked out revision 1.
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark other)
This is surprising, but is also correct based on the current code:
$ echo "updating should (maybe) fail" > obstruct/other
@@ -543,6 +545,7 @@
A *recreated/somethingold (glob)
Checked out revision 10.
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark other)
$ test -f recreated/somethingold
Test archive
--- a/tests/test-subrepo.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-subrepo.t Mon Jun 09 13:53:23 2014 -0500
@@ -281,8 +281,8 @@
resolving manifests
branchmerge: True, force: False, partial: False
ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
+ preserving t for resolve of t
t: versions differ -> m
- preserving t for resolve of t
updating: t 1/1 files (100.00%)
picked tool 'internal:merge' for t (binary False symlink False)
merging t
@@ -298,11 +298,11 @@
should conflict
$ cat t/t
- <<<<<<< local
+ <<<<<<< local: 20a0db6fbf6c - test: 10
conflict
=======
t3
- >>>>>>> other
+ >>>>>>> other: 7af322bc1198 - test: 7
clone
--- a/tests/test-tag.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-tag.t Mon Jun 09 13:53:23 2014 -0500
@@ -16,7 +16,10 @@
abort: tag names cannot consist entirely of whitespace
[255]
- $ hg tag "bleah"
+(this tests also that editor is not invoked, if '--edit' is not
+specified)
+
+ $ HGEDITOR=cat hg tag "bleah"
$ hg history
changeset: 1:d4f0d2909abc
tag: tip
@@ -219,14 +222,20 @@
test custom commit messages
$ cat > editor.sh << '__EOF__'
+ > echo "==== before editing"
+ > cat "$1"
+ > echo "===="
> echo "custom tag message" > "$1"
> echo "second line" >> "$1"
> __EOF__
at first, test saving last-message.txt
+(test that editor is not invoked before transaction starting)
+
$ cat > .hg/hgrc << '__EOF__'
> [hooks]
+ > # this failure occurs before editor invocation
> pretag.test-saving-lastmessage = false
> __EOF__
$ rm -f .hg/last-message.txt
@@ -234,16 +243,66 @@
abort: pretag.test-saving-lastmessage hook exited with status 1
[255]
$ cat .hg/last-message.txt
+ cat: .hg/last-message.txt: No such file or directory
+ [1]
+
+(test that editor is invoked and commit message is saved into
+"last-message.txt")
+
+ $ cat >> .hg/hgrc << '__EOF__'
+ > [hooks]
+ > pretag.test-saving-lastmessage =
+ > # this failure occurs after editor invocation
+ > pretxncommit.unexpectedabort = false
+ > __EOF__
+
+(this tests also that editor is invoked, if '--edit' is specified,
+regardless of '--message')
+
+ $ rm -f .hg/last-message.txt
+ $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e -m "foo bar"
+ ==== before editing
+ foo bar
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'tag-and-branch-same-name'
+ HG: changed .hgtags
+ ====
+ transaction abort!
+ rollback completed
+ note: commit message saved in .hg/last-message.txt
+ abort: pretxncommit.unexpectedabort hook exited with status 1
+ [255]
+ $ cat .hg/last-message.txt
custom tag message
second line
- $ cat > .hg/hgrc << '__EOF__'
+
+ $ cat >> .hg/hgrc << '__EOF__'
> [hooks]
- > pretag.test-saving-lastmessage =
+ > pretxncommit.unexpectedabort =
> __EOF__
+ $ hg status .hgtags
+ M .hgtags
+ $ hg revert --no-backup -q .hgtags
then, test custom commit message itself
$ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
+ ==== before editing
+ Added tag custom-tag for changeset 75a534207be6
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: test
+ HG: branch 'tag-and-branch-same-name'
+ HG: changed .hgtags
+ ====
$ hg log -l1 --template "{desc}\n"
custom tag message
second line
--- a/tests/test-transplant.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-transplant.t Mon Jun 09 13:53:23 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
--- a/tests/test-up-local-change.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-up-local-change.t Mon Jun 09 13:53:23 2014 -0500
@@ -46,11 +46,11 @@
resolving manifests
branchmerge: False, force: False, partial: False
ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb
- a: versions differ -> m
- preserving a for resolve of a
+ preserving a for resolve of a
b: remote created -> g
getting b
updating: b 1/2 files (50.00%)
+ a: versions differ -> m
updating: a 2/2 files (100.00%)
picked tool 'true' for a (binary False symlink False)
merging a
@@ -67,11 +67,11 @@
resolving manifests
branchmerge: False, force: False, partial: False
ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
+ preserving a for resolve of a
b: other deleted -> r
- a: versions differ -> m
- preserving a for resolve of a
removing b
updating: b 1/2 files (50.00%)
+ a: versions differ -> m
updating: a 2/2 files (100.00%)
picked tool 'true' for a (binary False symlink False)
merging a
@@ -100,11 +100,11 @@
resolving manifests
branchmerge: False, force: False, partial: False
ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb
- a: versions differ -> m
- preserving a for resolve of a
+ preserving a for resolve of a
b: remote created -> g
getting b
updating: b 1/2 files (50.00%)
+ a: versions differ -> m
updating: a 2/2 files (100.00%)
picked tool 'true' for a (binary False symlink False)
merging a
@@ -181,14 +181,14 @@
resolving manifests
branchmerge: True, force: True, partial: False
ancestor: c19d34741b0a, local: 1e71731e6fbb+, remote: 83c51d0caff4
+ preserving a for resolve of a
+ preserving b for resolve of b
a: versions differ -> m
- preserving a for resolve of a
- b: versions differ -> m
- preserving b for resolve of b
updating: a 1/2 files (50.00%)
picked tool 'true' for a (binary False symlink False)
merging a
my a@1e71731e6fbb+ other a@83c51d0caff4 ancestor a@c19d34741b0a
+ b: versions differ -> m
updating: b 2/2 files (100.00%)
picked tool 'true' for b (binary False symlink False)
merging b
--- a/tests/test-update-reverse.t Mon May 26 19:02:11 2014 +0200
+++ b/tests/test-update-reverse.t Mon Jun 09 13:53:23 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