2018-01-03 22:51:20 +03:00
|
|
|
# no-check-code -- see T24862348
|
2018-02-10 04:31:46 +03:00
|
|
|
# flake8: noqa
|
2018-01-03 22:51:20 +03:00
|
|
|
|
2010-02-06 19:57:06 +03:00
|
|
|
import difflib
|
2008-11-10 03:08:35 +03:00
|
|
|
import errno
|
2018-01-17 14:23:44 +03:00
|
|
|
import imp
|
2009-09-16 06:34:53 +04:00
|
|
|
import gettext
|
2008-10-08 03:42:43 +04:00
|
|
|
import os
|
2008-11-10 03:08:35 +03:00
|
|
|
import shutil
|
|
|
|
import stat
|
2009-09-16 06:34:53 +04:00
|
|
|
import subprocess
|
|
|
|
import sys
|
2012-05-18 01:15:14 +04:00
|
|
|
import tarfile
|
2008-11-15 01:18:24 +03:00
|
|
|
import tempfile
|
|
|
|
import unittest
|
2008-10-08 03:42:43 +04:00
|
|
|
|
2010-09-29 20:04:26 +04:00
|
|
|
from mercurial import cmdutil
|
2009-05-15 21:18:43 +04:00
|
|
|
from mercurial import commands
|
2009-09-16 06:34:53 +04:00
|
|
|
from mercurial import context
|
2011-06-15 16:44:14 +04:00
|
|
|
from mercurial import dispatch as dispatchmod
|
2018-01-11 03:21:03 +03:00
|
|
|
from mercurial import encoding
|
2008-10-09 05:09:28 +04:00
|
|
|
from mercurial import hg
|
2009-09-16 06:34:53 +04:00
|
|
|
from mercurial import i18n
|
2008-11-15 01:18:24 +03:00
|
|
|
from mercurial import node
|
2013-04-13 02:41:51 +04:00
|
|
|
from mercurial import scmutil
|
2008-11-15 01:18:24 +03:00
|
|
|
from mercurial import ui
|
2014-10-15 02:12:24 +04:00
|
|
|
from mercurial import util as hgutil
|
2010-01-30 01:36:14 +03:00
|
|
|
from mercurial import extensions
|
2008-10-09 05:09:28 +04:00
|
|
|
|
2018-01-11 03:20:54 +03:00
|
|
|
from hgext.hgsubversion import compathacks
|
|
|
|
from hgext.hgsubversion import svnrepo
|
|
|
|
from hgext.hgsubversion import svnwrap
|
2018-02-10 04:31:41 +03:00
|
|
|
from hgext.hgsubversion import util
|
2014-06-03 04:54:02 +04:00
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
try:
|
|
|
|
from mercurial import obsolete
|
2014-04-22 17:53:11 +04:00
|
|
|
obsolete._enabled
|
2013-08-08 11:25:24 +04:00
|
|
|
except ImportError:
|
|
|
|
obsolete = None
|
|
|
|
|
2010-10-03 01:44:37 +04:00
|
|
|
try:
|
2010-10-10 01:20:52 +04:00
|
|
|
SkipTest = unittest.SkipTest
|
|
|
|
except AttributeError:
|
2016-06-16 06:54:51 +03:00
|
|
|
if 'nose' in sys.modules:
|
|
|
|
SkipTest = sys.modules['nose'].SkipTest
|
|
|
|
else:
|
|
|
|
SkipTest = None
|
2010-10-03 01:44:37 +04:00
|
|
|
|
2009-07-29 18:26:29 +04:00
|
|
|
# Documentation for Subprocess.Popen() says:
|
|
|
|
# "Note that on Windows, you cannot set close_fds to true and
|
|
|
|
# also redirect the standard handles by setting stdin, stdout or
|
|
|
|
# stderr."
|
2011-10-12 11:35:25 +04:00
|
|
|
canCloseFds = 'win32' not in sys.platform
|
2009-07-29 18:26:29 +04:00
|
|
|
|
2009-07-29 20:01:13 +04:00
|
|
|
if not 'win32' in sys.platform:
|
|
|
|
def kill_process(popen_obj):
|
|
|
|
os.kill(popen_obj.pid, 9)
|
|
|
|
else:
|
|
|
|
import ctypes
|
2009-08-13 00:54:21 +04:00
|
|
|
from ctypes.wintypes import BOOL, DWORD, HANDLE, UINT
|
|
|
|
|
|
|
|
def win_status_check(result, func, args):
|
|
|
|
if result == 0:
|
|
|
|
raise ctypes.WinError()
|
|
|
|
return args
|
|
|
|
|
|
|
|
def WINAPI(returns, func, *params):
|
|
|
|
assert len(params) % 2 == 0
|
|
|
|
|
|
|
|
func.argtypes = tuple(params[0::2])
|
|
|
|
func.resvalue = returns
|
|
|
|
func.errcheck = win_status_check
|
|
|
|
|
|
|
|
return func
|
2009-07-29 20:01:13 +04:00
|
|
|
|
|
|
|
# dwDesiredAccess
|
|
|
|
PROCESS_TERMINATE = 0x0001
|
|
|
|
|
2009-08-13 00:54:21 +04:00
|
|
|
OpenProcess = WINAPI(HANDLE, ctypes.windll.kernel32.OpenProcess,
|
|
|
|
DWORD, 'dwDesiredAccess',
|
|
|
|
BOOL, 'bInheritHandle',
|
|
|
|
DWORD, 'dwProcessId',
|
|
|
|
)
|
2009-07-29 20:01:13 +04:00
|
|
|
|
2011-10-12 11:35:25 +04:00
|
|
|
CloseHandle = WINAPI(BOOL, ctypes.windll.kernel32.CloseHandle,
|
2009-08-13 00:54:21 +04:00
|
|
|
HANDLE, 'hObject'
|
|
|
|
)
|
2009-07-29 20:01:13 +04:00
|
|
|
|
2009-08-13 00:54:21 +04:00
|
|
|
TerminateProcess = WINAPI(BOOL, ctypes.windll.kernel32.TerminateProcess,
|
|
|
|
HANDLE, 'hProcess',
|
|
|
|
UINT, 'uExitCode'
|
|
|
|
)
|
2009-07-29 20:01:13 +04:00
|
|
|
|
|
|
|
def kill_process(popen_obj):
|
|
|
|
phnd = OpenProcess(PROCESS_TERMINATE, False, popen_obj.pid)
|
|
|
|
TerminateProcess(phnd, 1)
|
|
|
|
CloseHandle(phnd)
|
|
|
|
|
2009-01-22 05:27:51 +03:00
|
|
|
# Fixtures that need to be pulled at a subdirectory of the repo path
|
|
|
|
subdir = {'truncatedhistory.svndump': '/project2',
|
|
|
|
'fetch_missing_files_subdir.svndump': '/foo',
|
2009-04-22 00:25:08 +04:00
|
|
|
'empty_dir_in_trunk_not_repo_root.svndump': '/project',
|
2009-05-23 08:37:33 +04:00
|
|
|
'project_root_not_repo_root.svndump': '/dummyproj',
|
2009-12-24 22:38:06 +03:00
|
|
|
'project_name_with_space.svndump': '/project name',
|
svnwrap: fix handling of quotable URLs (fixes #197, refs #132)
The way hgsubversion handles URLs that may or may not be quoted is
somewhat fragile. As part of fixing issue 132 in 06d89c2063a2, the
path component of URLs was always quoted. The URL has been attempted
encoded since the initial check-in.
The fix from 06d89c2063a2 was incomplete; reverting it allows us to
clone a URL with a '~' in it.[1] Encoding the URL as UTF-8 seldom
works as expected, as the default string encoding is ASCII, causing
Python to be unable to decode any URL containing an 8-bit
character.
The core problem here is that we don't know whether the URL specified
by the user is quoted or not. Rather than trying to deal with this
ourselves, we pass the problem on to Subversion. Then, we obtain the
URL from the RA instance, where it is always quoted. (It's worth
noting that the editor interface, on the other hand, always deals with
unquoted paths...)
Thus, the following invariants should apply to SubversionRepo
attributes:
- svn_url and root will always be quoted.
- subdir will always be unquoted.
Tests are added that verify that it won't affect the conversion
whether a URL is specified in quoted or unquoted form. Furthermore, a
test fixture for this is added *twice*, so that we can thoroughly test
both quoted and unquoted URLs. I'm not adding a test dedicated to
tildes in URLs; it doesn't seem necessary.
[1] Such as <https://svn.kenai.com/svn/winsw~subversion>.
2010-10-05 06:00:36 +04:00
|
|
|
'non_ascii_path_1.svndump': '/b\xC3\xB8b',
|
|
|
|
'non_ascii_path_2.svndump': '/b%C3%B8b',
|
2011-12-14 03:07:57 +04:00
|
|
|
'subdir_is_file_prefix.svndump': '/flaf',
|
2014-04-08 05:28:35 +04:00
|
|
|
'renames_with_prefix.svndump': '/prefix',
|
2009-01-22 05:27:51 +03:00
|
|
|
}
|
2013-08-27 03:40:31 +04:00
|
|
|
# map defining the layouts of the fixtures we can use with custom layout
|
|
|
|
# these are really popular layouts, so I gave them names
|
|
|
|
trunk_only = {
|
|
|
|
'default': 'trunk',
|
|
|
|
}
|
|
|
|
trunk_dev_branch = {
|
|
|
|
'default': 'trunk',
|
|
|
|
'dev_branch': 'branches/dev_branch',
|
|
|
|
}
|
|
|
|
custom = {
|
|
|
|
'addspecial.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'foo': 'branches/foo',
|
|
|
|
},
|
|
|
|
'binaryfiles.svndump': trunk_only,
|
|
|
|
'branch_create_with_dir_delete.svndump': trunk_dev_branch,
|
|
|
|
'branch_delete_parent_dir.svndump': trunk_dev_branch,
|
|
|
|
'branchmap.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'badname': 'branches/badname',
|
|
|
|
'feature': 'branches/feature',
|
|
|
|
},
|
|
|
|
'branch_prop_edit.svndump': trunk_dev_branch,
|
|
|
|
'branch_rename_to_trunk.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'dev_branch': 'branches/dev_branch',
|
|
|
|
'old_trunk': 'branches/old_trunk',
|
|
|
|
},
|
|
|
|
'copies.svndump': trunk_only,
|
2013-11-17 21:57:00 +04:00
|
|
|
'copyafterclose.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'test': 'branches/test'
|
|
|
|
},
|
2013-08-27 03:40:31 +04:00
|
|
|
'copybeforeclose.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'test': 'branches/test'
|
|
|
|
},
|
|
|
|
'delentries.svndump': trunk_only,
|
|
|
|
'delete_restore_trunk.svndump': trunk_only,
|
|
|
|
'empty_dir_in_trunk_not_repo_root.svndump': trunk_only,
|
|
|
|
'executebit.svndump': trunk_only,
|
|
|
|
'filecase.svndump': trunk_only,
|
|
|
|
'file_not_in_trunk_root.svndump': trunk_only,
|
|
|
|
'project_name_with_space.svndump': trunk_dev_branch,
|
|
|
|
'pushrenames.svndump': trunk_only,
|
|
|
|
'rename_branch_parent_dir.svndump': trunk_dev_branch,
|
|
|
|
'renamedproject.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'branch': 'branches/branch',
|
|
|
|
},
|
|
|
|
'renames.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'branch1': 'branches/branch1',
|
|
|
|
},
|
2014-04-08 05:28:35 +04:00
|
|
|
'renames_with_prefix.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'branch1': 'branches/branch1',
|
|
|
|
},
|
2013-08-27 03:40:31 +04:00
|
|
|
'replace_branch_with_branch.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'branch1': 'branches/branch1',
|
|
|
|
'branch2': 'branches/branch2',
|
|
|
|
},
|
|
|
|
'replace_trunk_with_branch.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'test': 'branches/test',
|
|
|
|
},
|
|
|
|
'revert.svndump': trunk_only,
|
|
|
|
'siblingbranchfix.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'wrongbranch': 'branches/wrongbranch',
|
|
|
|
},
|
|
|
|
'simple_branch.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'the_branch': 'branches/the_branch',
|
|
|
|
},
|
|
|
|
'spaces-in-path.svndump': trunk_dev_branch,
|
|
|
|
'symlinks.svndump': trunk_only,
|
|
|
|
'truncatedhistory.svndump': trunk_only,
|
|
|
|
'unorderedbranch.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'branch': 'branches/branch',
|
|
|
|
},
|
|
|
|
'unrelatedbranch.svndump': {
|
|
|
|
'default': 'trunk',
|
|
|
|
'branch1': 'branches/branch1',
|
|
|
|
'branch2': 'branches/branch2',
|
|
|
|
},
|
|
|
|
}
|
2009-01-22 05:27:51 +03:00
|
|
|
|
2008-10-08 03:42:43 +04:00
|
|
|
FIXTURES = os.path.join(os.path.abspath(os.path.dirname(__file__)),
|
|
|
|
'fixtures')
|
|
|
|
|
2012-09-11 00:42:49 +04:00
|
|
|
def getlocalpeer(repo):
|
|
|
|
localrepo = getattr(repo, 'local', lambda: repo)()
|
|
|
|
if isinstance(localrepo, bool):
|
|
|
|
localrepo = repo
|
|
|
|
return localrepo
|
2010-10-05 08:02:15 +04:00
|
|
|
|
2013-08-10 09:46:21 +04:00
|
|
|
def repolen(repo, svnonly=False):
|
2013-08-09 19:22:50 +04:00
|
|
|
"""Naively calculate the amount of available revisions in a repository.
|
|
|
|
|
|
|
|
this is usually equal to len(repo) -- except in the face of
|
|
|
|
obsolete revisions.
|
2013-08-10 09:46:21 +04:00
|
|
|
|
|
|
|
if svnonly is true, only count revisions converted from Subversion.
|
2013-08-09 19:22:50 +04:00
|
|
|
"""
|
|
|
|
# kind of nasty way of calculating the length, but fortunately,
|
|
|
|
# our test repositories tend to be rather small
|
2013-08-10 09:46:21 +04:00
|
|
|
revs = set(repo)
|
|
|
|
|
|
|
|
if obsolete:
|
|
|
|
revs -= obsolete.getrevs(repo, 'obsolete')
|
|
|
|
|
|
|
|
if svnonly:
|
|
|
|
revs = set(r for r in revs if util.getsvnrev(repo[r]))
|
|
|
|
|
|
|
|
return len(revs)
|
2013-08-09 19:22:50 +04:00
|
|
|
|
2010-10-05 08:02:15 +04:00
|
|
|
def _makeskip(name, message):
|
2010-10-08 22:58:26 +04:00
|
|
|
if SkipTest:
|
|
|
|
def skip(*args, **kwargs):
|
|
|
|
raise SkipTest(message)
|
|
|
|
skip.__name__ = name
|
|
|
|
return skip
|
2010-10-05 08:02:15 +04:00
|
|
|
|
2010-10-05 08:03:12 +04:00
|
|
|
def requiresmodule(mod):
|
|
|
|
"""Skip a test if the specified module is not None."""
|
|
|
|
def decorator(fn):
|
2010-10-08 22:58:26 +04:00
|
|
|
if fn is None:
|
|
|
|
return
|
2010-10-05 08:03:12 +04:00
|
|
|
if mod is not None:
|
|
|
|
return fn
|
|
|
|
return _makeskip(fn.__name__, 'missing required feature')
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
2010-09-29 20:04:26 +04:00
|
|
|
def requiresoption(option):
|
2010-10-03 01:44:17 +04:00
|
|
|
'''Skip a test if commands.clone does not take the specified option.'''
|
2010-09-29 20:04:26 +04:00
|
|
|
def decorator(fn):
|
|
|
|
for entry in cmdutil.findcmd('clone', commands.table)[1][1]:
|
|
|
|
if entry[1] == option:
|
|
|
|
return fn
|
2010-10-03 01:44:37 +04:00
|
|
|
# no match found, so skip
|
|
|
|
if SkipTest:
|
2010-10-05 08:02:15 +04:00
|
|
|
return _makeskip(fn.__name__,
|
|
|
|
'test requires clone to accept %s' % option)
|
2010-10-03 01:44:37 +04:00
|
|
|
# no skipping support, so erase decorated method
|
|
|
|
return
|
2010-09-29 20:04:26 +04:00
|
|
|
if not isinstance(option, str):
|
|
|
|
raise TypeError('requiresoption takes a string argument')
|
|
|
|
return decorator
|
|
|
|
|
2013-08-10 01:34:16 +04:00
|
|
|
def requiresreplay(method):
|
|
|
|
'''Skip a test in stupid mode.'''
|
|
|
|
def test(self, *args, **kwargs):
|
|
|
|
if self.stupid:
|
|
|
|
if SkipTest:
|
2013-08-10 04:41:25 +04:00
|
|
|
raise SkipTest("test requires replay mode")
|
2013-08-10 01:34:16 +04:00
|
|
|
else:
|
|
|
|
return method(self, *args, **kwargs)
|
|
|
|
|
|
|
|
test.__name__ = method.__name__
|
|
|
|
return test
|
|
|
|
|
2009-10-17 07:33:41 +04:00
|
|
|
def filtermanifest(manifest):
|
2010-11-30 04:54:11 +03:00
|
|
|
return [f for f in manifest if f not in util.ignoredfiles]
|
2009-10-17 07:33:41 +04:00
|
|
|
|
2008-11-15 01:52:30 +03:00
|
|
|
def fileurl(path):
|
2010-01-20 22:50:59 +03:00
|
|
|
path = os.path.abspath(path).replace(os.sep, '/')
|
2008-11-10 03:08:35 +03:00
|
|
|
drive, path = os.path.splitdrive(path)
|
|
|
|
if drive:
|
2012-08-26 19:49:58 +04:00
|
|
|
# In svn 1.7, the swig svn wrapper returns local svn URLs
|
|
|
|
# with an uppercase drive letter, try to match that to
|
|
|
|
# simplify svn info tests.
|
|
|
|
drive = '/' + drive.upper()
|
2009-05-20 20:38:01 +04:00
|
|
|
url = 'file://%s%s' % (drive, path)
|
2008-11-10 03:08:35 +03:00
|
|
|
return url
|
|
|
|
|
2017-10-29 16:07:12 +03:00
|
|
|
class _testui(ui.ui):
|
|
|
|
def develwarn(self, msg, stacklevel=1, *args, **kwargs):
|
2018-01-11 03:20:54 +03:00
|
|
|
from hgext.hgsubversion import util
|
2017-10-29 16:07:12 +03:00
|
|
|
if util.smartset is not None:
|
|
|
|
config = args[0] if args else kwargs['config']
|
|
|
|
raise Exception('flunked develwarn: %r (%r)' % (msg, config))
|
|
|
|
return ui.ui.develwarn(self, msg, stacklevel=stacklevel,
|
|
|
|
*args, **kwargs)
|
|
|
|
|
2010-07-20 13:55:07 +04:00
|
|
|
def testui(stupid=False, layout='auto', startrev=0):
|
2018-01-11 03:21:03 +03:00
|
|
|
encoding.environ['HGPLAIN'] = 'True'
|
2017-10-29 16:07:12 +03:00
|
|
|
u = _testui()
|
2010-02-26 16:50:22 +03:00
|
|
|
bools = {True: 'true', False: 'false'}
|
|
|
|
u.setconfig('ui', 'quiet', bools[True])
|
2017-02-12 23:16:16 +03:00
|
|
|
u.setconfig('ui', 'username', 'automated tests')
|
2010-09-20 19:08:18 +04:00
|
|
|
u.setconfig('extensions', 'hgsubversion', '')
|
2010-02-26 16:50:22 +03:00
|
|
|
u.setconfig('hgsubversion', 'stupid', bools[stupid])
|
|
|
|
u.setconfig('hgsubversion', 'layout', layout)
|
2010-07-20 13:55:07 +04:00
|
|
|
u.setconfig('hgsubversion', 'startrev', startrev)
|
2017-08-08 04:11:56 +03:00
|
|
|
u.setconfig('devel', 'all-warnings', True)
|
2017-12-05 05:13:00 +03:00
|
|
|
u.setconfig('subrepos', 'hgsubversion:allowed', True)
|
2010-02-26 16:50:22 +03:00
|
|
|
return u
|
|
|
|
|
2011-06-15 16:44:14 +04:00
|
|
|
def dispatch(cmd):
|
2015-08-11 23:52:38 +03:00
|
|
|
assert '--quiet' in cmd
|
2012-07-20 03:01:17 +04:00
|
|
|
cmd = getattr(dispatchmod, 'request', lambda x: x)(cmd)
|
2013-08-02 18:39:11 +04:00
|
|
|
return dispatchmod.dispatch(cmd)
|
2011-06-15 16:44:14 +04:00
|
|
|
|
2008-11-10 03:08:35 +03:00
|
|
|
def rmtree(path):
|
|
|
|
# Read-only files cannot be removed under Windows
|
|
|
|
for root, dirs, files in os.walk(path):
|
|
|
|
for f in files:
|
|
|
|
f = os.path.join(root, f)
|
|
|
|
try:
|
|
|
|
s = os.stat(f)
|
|
|
|
except OSError, e:
|
|
|
|
if e.errno == errno.ENOENT:
|
|
|
|
continue
|
|
|
|
raise
|
|
|
|
if (s.st_mode & stat.S_IWRITE) == 0:
|
|
|
|
os.chmod(f, s.st_mode | stat.S_IWRITE)
|
|
|
|
shutil.rmtree(path)
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2012-05-12 18:28:23 +04:00
|
|
|
def hgclone(ui, source, dest, update=True, rev=None):
|
2011-06-15 16:44:14 +04:00
|
|
|
if getattr(hg, 'peer', None):
|
|
|
|
# Since 1.9 (d976542986d2)
|
2012-05-12 18:28:23 +04:00
|
|
|
src, dest = hg.clone(ui, {}, source, dest, update=update, rev=rev)
|
2011-06-15 16:44:14 +04:00
|
|
|
else:
|
2012-05-12 18:28:23 +04:00
|
|
|
src, dest = hg.clone(ui, source, dest, update=update, rev=rev)
|
2011-06-15 16:44:14 +04:00
|
|
|
return src, dest
|
|
|
|
|
2012-04-19 20:29:30 +04:00
|
|
|
def svnls(repo_path, path, rev='HEAD'):
|
|
|
|
path = repo_path + '/' + path
|
|
|
|
path = util.normalize_url(fileurl(path))
|
|
|
|
args = ['svn', 'ls', '-r', rev, '-R', path]
|
|
|
|
p = subprocess.Popen(args,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT)
|
|
|
|
stdout, stderr = p.communicate()
|
|
|
|
if p.returncode:
|
|
|
|
raise Exception('svn ls failed on %s: %r' % (path, stderr))
|
|
|
|
entries = [e.strip('/') for e in stdout.splitlines()]
|
|
|
|
entries.sort()
|
|
|
|
return entries
|
|
|
|
|
2012-04-19 20:29:31 +04:00
|
|
|
def svnpropget(repo_path, path, prop, rev='HEAD'):
|
|
|
|
path = repo_path + '/' + path
|
|
|
|
path = util.normalize_url(fileurl(path))
|
|
|
|
args = ['svn', 'propget', '-r', str(rev), prop, path]
|
|
|
|
p = subprocess.Popen(args,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT)
|
|
|
|
stdout, stderr = p.communicate()
|
2015-10-30 03:18:41 +03:00
|
|
|
if p.returncode and stderr:
|
2012-04-19 20:29:31 +04:00
|
|
|
raise Exception('svn ls failed on %s: %r' % (path, stderr))
|
2015-10-30 03:18:41 +03:00
|
|
|
if 'W200017' in stdout:
|
|
|
|
# subversion >= 1.9 changed 'no properties' to be an error, so let's
|
|
|
|
# avoid that
|
|
|
|
return ''
|
2012-04-19 20:29:31 +04:00
|
|
|
return stdout.strip()
|
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
|
|
|
|
def _obsolete_wrap(cls, name):
|
|
|
|
origfunc = getattr(cls, name)
|
|
|
|
|
|
|
|
if not name.startswith('test_') or not origfunc:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not obsolete:
|
|
|
|
wrapper = _makeskip(name, 'obsolete not available')
|
|
|
|
else:
|
|
|
|
def wrapper(self, *args, **opts):
|
|
|
|
self.assertFalse(obsolete._enabled, 'obsolete was already active')
|
|
|
|
|
|
|
|
obsolete._enabled = True
|
|
|
|
|
|
|
|
try:
|
|
|
|
origfunc(self, *args, **opts)
|
|
|
|
self.assertTrue(obsolete._enabled, 'obsolete remains active')
|
|
|
|
finally:
|
|
|
|
obsolete._enabled = False
|
|
|
|
|
|
|
|
if not wrapper:
|
|
|
|
return
|
|
|
|
|
|
|
|
wrapper.__name__ = name + ' obsolete'
|
|
|
|
wrapper.__module__ = origfunc.__module__
|
|
|
|
|
|
|
|
if origfunc.__doc__:
|
|
|
|
firstline = origfunc.__doc__.strip().splitlines()[0]
|
|
|
|
wrapper.__doc__ = firstline + ' (obsolete)'
|
|
|
|
|
|
|
|
assert getattr(cls, wrapper.__name__, None) is None
|
|
|
|
|
|
|
|
setattr(cls, wrapper.__name__, wrapper)
|
|
|
|
|
2013-08-10 01:34:16 +04:00
|
|
|
|
|
|
|
def _stupid_wrap(cls, name):
|
|
|
|
origfunc = getattr(cls, name)
|
|
|
|
|
|
|
|
if not name.startswith('test_') or not origfunc:
|
|
|
|
return
|
|
|
|
|
|
|
|
def wrapper(self, *args, **opts):
|
|
|
|
self.assertFalse(self.stupid, 'stupid mode was already active')
|
|
|
|
|
|
|
|
self.stupid = True
|
|
|
|
|
|
|
|
try:
|
|
|
|
origfunc(self, *args, **opts)
|
|
|
|
finally:
|
|
|
|
self.stupid = False
|
|
|
|
|
|
|
|
wrapper.__name__ = name + ' stupid'
|
|
|
|
wrapper.__module__ = origfunc.__module__
|
|
|
|
|
|
|
|
if origfunc.__doc__:
|
|
|
|
firstline = origfunc.__doc__.strip().splitlines()[0]
|
|
|
|
wrapper.__doc__ = firstline + ' (stupid)'
|
|
|
|
|
|
|
|
assert getattr(cls, wrapper.__name__, None) is None
|
|
|
|
|
|
|
|
setattr(cls, wrapper.__name__, wrapper)
|
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
class TestMeta(type):
|
|
|
|
def __init__(cls, *args, **opts):
|
|
|
|
if cls.obsolete_mode_tests:
|
|
|
|
for origname in dir(cls):
|
|
|
|
_obsolete_wrap(cls, origname)
|
|
|
|
|
2015-12-31 20:06:58 +03:00
|
|
|
if cls.stupid_mode_tests and svnwrap.subversion_version < (1, 9, 0):
|
2013-08-10 01:34:16 +04:00
|
|
|
for origname in dir(cls):
|
|
|
|
_stupid_wrap(cls, origname)
|
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
return super(TestMeta, cls).__init__(*args, **opts)
|
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
class TestBase(unittest.TestCase):
|
2013-08-08 11:25:24 +04:00
|
|
|
__metaclass__ = TestMeta
|
|
|
|
|
|
|
|
obsolete_mode_tests = False
|
2013-08-10 01:34:16 +04:00
|
|
|
stupid_mode_tests = False
|
|
|
|
|
|
|
|
stupid = False
|
2013-08-08 11:25:24 +04:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
def setUp(self):
|
layouts: fix crash when importing hgsubversion itself (issue #402)
Mercurial extensions are a bit weird: they aren't normally in
sys.path, so you can't assume that "import hgsubversion" works.
Luckily, Mercurial sneaks a little treat into sys.modules so that
"import hgext_hgsubversion" does work. In fact, to get things working
*as a Mercurial extension*, all that's needed is that trivial change
to two import lines, in layouts/detect.py and layouts/standard.py.
Unfortunately, hgsubversion is also imported as a Python module, in
its own test suite. In that context, there is no "hgext_" trick --
unless we do it in ourselves, which I've done in TestBase.setUp().
That would work fine ... except that test_util imports from
hgsubversion, which ends up importing hgsubversion.layouts.{detect,standard},
which want the "hgext_" trick to work. But it hasn't been done yet
when we're still importing; it doesn't happen until setUp() runs.
So make those two imports happen late, in the functions that need them.
Incidentally, this is only necessary to support Mercurial <= 2.7.
Mercurial got a bit smarter in 2.8:
http://selenic.com/repo/hg/rev/284a000c67bf
2014-01-03 06:53:39 +04:00
|
|
|
if 'hgsubversion' in sys.modules:
|
|
|
|
sys.modules['hgext_hgsubversion'] = sys.modules['hgsubversion']
|
2010-07-14 17:39:24 +04:00
|
|
|
|
2012-05-12 18:19:59 +04:00
|
|
|
# the Python 2.7 default of 640 is obnoxiously low
|
|
|
|
self.maxDiff = 4096
|
|
|
|
|
2011-10-12 11:35:25 +04:00
|
|
|
self.oldenv = dict([(k, os.environ.get(k, None),) for k in
|
|
|
|
('LANG', 'LC_ALL', 'HGRCPATH',)])
|
2014-10-15 02:12:24 +04:00
|
|
|
try:
|
|
|
|
self.oldugettext = i18n._ugettext # Mercurial >= 3.2
|
|
|
|
except AttributeError:
|
|
|
|
self.oldt = i18n.t
|
|
|
|
os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
|
|
|
|
i18n.t = gettext.translation('hg', i18n.localedir, fallback=True)
|
|
|
|
else:
|
|
|
|
os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
|
|
|
|
i18n.setdatapath(hgutil.datapath)
|
2009-09-16 06:33:41 +04:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
self.oldwd = os.getcwd()
|
2008-12-21 04:13:46 +03:00
|
|
|
self.tmpdir = tempfile.mkdtemp(
|
|
|
|
'svnwrap_test', dir=os.environ.get('HGSUBVERSION_TEST_TEMP', None))
|
2013-08-15 04:33:40 +04:00
|
|
|
os.chdir(self.tmpdir)
|
2009-03-31 19:50:21 +04:00
|
|
|
self.hgrc = os.path.join(self.tmpdir, '.hgrc')
|
|
|
|
os.environ['HGRCPATH'] = self.hgrc
|
2013-04-13 02:41:51 +04:00
|
|
|
scmutil._rcpath = None
|
2009-03-31 19:50:21 +04:00
|
|
|
rc = open(self.hgrc, 'w')
|
2013-04-13 00:23:46 +04:00
|
|
|
rc.write('[ui]\nusername=test-user\n')
|
2009-05-12 22:14:15 +04:00
|
|
|
for l in '[extensions]', 'hgsubversion=':
|
|
|
|
print >> rc, l
|
2008-12-21 04:13:46 +03:00
|
|
|
|
2012-04-19 20:29:32 +04:00
|
|
|
self.repocount = 0
|
2008-11-15 01:18:24 +03:00
|
|
|
self.wc_path = '%s/testrepo_wc' % self.tmpdir
|
2010-09-08 11:57:06 +04:00
|
|
|
self.svn_wc = None
|
2009-05-15 21:18:43 +04:00
|
|
|
|
2012-01-01 18:59:15 +04:00
|
|
|
self.config_dir = self.tmpdir
|
|
|
|
svnwrap.common._svn_config_dir = self.config_dir
|
|
|
|
self.setup_svn_config('')
|
|
|
|
|
2009-05-15 21:18:43 +04:00
|
|
|
# Previously, we had a MockUI class that wrapped ui, and giving access
|
|
|
|
# to the stream. The ui.pushbuffer() and ui.popbuffer() can be used
|
|
|
|
# instead. Using the regular UI class, with all stderr redirected to
|
|
|
|
# stdout ensures that the test setup is much more similar to usage
|
|
|
|
# setups.
|
2009-05-12 22:14:15 +04:00
|
|
|
self.patch = (ui.ui.write_err, ui.ui.write)
|
|
|
|
setattr(ui.ui, self.patch[0].func_name, self.patch[1])
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2012-01-01 18:59:15 +04:00
|
|
|
def setup_svn_config(self, config):
|
2013-08-02 19:34:05 +04:00
|
|
|
c = open(self.config_dir + '/config', 'w')
|
|
|
|
try:
|
2012-01-01 18:59:15 +04:00
|
|
|
c.write(config)
|
2013-08-02 19:34:05 +04:00
|
|
|
finally:
|
|
|
|
c.close()
|
2012-01-01 18:59:15 +04:00
|
|
|
|
2012-04-19 20:29:32 +04:00
|
|
|
def _makerepopath(self):
|
|
|
|
self.repocount += 1
|
|
|
|
return '%s/testrepo-%d' % (self.tmpdir, self.repocount)
|
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
def tearDown(self):
|
2009-09-16 06:33:41 +04:00
|
|
|
for var, val in self.oldenv.iteritems():
|
|
|
|
if val is None:
|
|
|
|
del os.environ[var]
|
|
|
|
else:
|
|
|
|
os.environ[var] = val
|
2014-10-15 02:12:24 +04:00
|
|
|
try:
|
|
|
|
i18n._ugettext = self.oldugettext # Mercurial >= 3.2
|
|
|
|
except AttributeError:
|
|
|
|
i18n.t = self.oldt
|
2008-11-15 01:18:24 +03:00
|
|
|
os.chdir(self.oldwd)
|
2015-05-14 07:49:52 +03:00
|
|
|
rmtree(self.tmpdir)
|
2009-05-12 22:14:15 +04:00
|
|
|
setattr(ui.ui, self.patch[0].func_name, self.patch[0])
|
2008-11-15 01:52:30 +03:00
|
|
|
|
2013-08-08 01:16:39 +04:00
|
|
|
def ui(self, layout='auto'):
|
|
|
|
return testui(self.stupid, layout)
|
2010-02-26 16:50:22 +03:00
|
|
|
|
2012-04-19 20:29:28 +04:00
|
|
|
def load_svndump(self, fixture_name):
|
|
|
|
'''Loads an svnadmin dump into a fresh repo. Return the svn repo
|
|
|
|
path.
|
|
|
|
'''
|
2012-04-19 20:29:32 +04:00
|
|
|
path = self._makerepopath()
|
|
|
|
assert not os.path.exists(path)
|
2016-06-07 10:15:53 +03:00
|
|
|
with open(os.path.join(FIXTURES, fixture_name)) as inp:
|
|
|
|
svnwrap.create_and_load(path, inp)
|
2012-04-19 20:29:28 +04:00
|
|
|
return path
|
|
|
|
|
2012-05-18 01:15:14 +04:00
|
|
|
def load_repo_tarball(self, fixture_name):
|
|
|
|
'''Extracts a tarball of an svn repo and returns the svn repo path.'''
|
|
|
|
path = self._makerepopath()
|
|
|
|
assert not os.path.exists(path)
|
|
|
|
os.mkdir(path)
|
|
|
|
tarball = tarfile.open(os.path.join(FIXTURES, fixture_name))
|
|
|
|
# This is probably somewhat fragile, but I'm not sure how to
|
|
|
|
# do better in particular, I think it assumes that the tar
|
|
|
|
# entries are in the right order and that directories appear
|
|
|
|
# before their contents. This is a valid assummption for sane
|
|
|
|
# tarballs, from what I can tell. In particular, for a simple
|
|
|
|
# tarball of a svn repo with paths relative to the repo root,
|
|
|
|
# it seems to work
|
|
|
|
for entry in tarball:
|
|
|
|
tarball.extract(entry, path)
|
|
|
|
return path
|
|
|
|
|
2013-08-08 01:16:39 +04:00
|
|
|
def fetch(self, repo_path, subdir=None, layout='auto',
|
2012-09-28 23:43:50 +04:00
|
|
|
startrev=0, externals=None, noupdate=True, dest=None, rev=None,
|
|
|
|
config=None):
|
2009-10-17 07:33:41 +04:00
|
|
|
if layout == 'single':
|
|
|
|
if subdir is None:
|
|
|
|
subdir = 'trunk'
|
|
|
|
elif subdir is None:
|
|
|
|
subdir = ''
|
2012-04-19 20:29:28 +04:00
|
|
|
projectpath = repo_path
|
2012-04-19 20:29:25 +04:00
|
|
|
if subdir:
|
|
|
|
projectpath += '/' + subdir
|
|
|
|
|
|
|
|
cmd = [
|
|
|
|
'clone',
|
2015-08-11 23:56:37 +03:00
|
|
|
'--quiet',
|
2012-04-19 20:29:25 +04:00
|
|
|
'--layout=%s' % layout,
|
|
|
|
'--startrev=%s' % startrev,
|
|
|
|
fileurl(projectpath),
|
|
|
|
self.wc_path,
|
|
|
|
]
|
2013-08-08 01:16:39 +04:00
|
|
|
if self.stupid:
|
2012-04-19 20:29:25 +04:00
|
|
|
cmd.append('--stupid')
|
|
|
|
if noupdate:
|
|
|
|
cmd.append('--noupdate')
|
2012-05-17 03:52:25 +04:00
|
|
|
if rev is not None:
|
|
|
|
cmd.append('--rev=%s' % rev)
|
2012-09-28 23:43:50 +04:00
|
|
|
config = dict(config or {})
|
2012-04-19 20:29:25 +04:00
|
|
|
if externals:
|
2012-09-28 23:43:50 +04:00
|
|
|
config['hgsubversion.externals'] = str(externals)
|
|
|
|
for k,v in reversed(sorted(config.iteritems())):
|
|
|
|
cmd[:0] = ['--config', '%s=%s' % (k, v)]
|
2012-04-19 20:29:25 +04:00
|
|
|
|
2013-08-02 18:39:11 +04:00
|
|
|
r = dispatch(cmd)
|
|
|
|
assert not r, 'fetch of %s failed' % projectpath
|
2012-04-19 20:29:25 +04:00
|
|
|
|
2012-05-12 13:12:57 +04:00
|
|
|
return hg.repository(testui(), self.wc_path)
|
|
|
|
|
2016-06-07 10:15:53 +03:00
|
|
|
def load(self, fixture_name):
|
2012-05-18 01:15:14 +04:00
|
|
|
if fixture_name.endswith('.svndump'):
|
|
|
|
repo_path = self.load_svndump(fixture_name)
|
|
|
|
elif fixture_name.endswith('tar.gz'):
|
|
|
|
repo_path = self.load_repo_tarball(fixture_name)
|
|
|
|
else:
|
|
|
|
assert False, 'Unknown fixture type'
|
2012-05-12 13:12:57 +04:00
|
|
|
|
2016-06-07 10:15:53 +03:00
|
|
|
return repo_path
|
|
|
|
|
|
|
|
def load_and_fetch(self, fixture_name, *args, **opts):
|
|
|
|
repo_path = self.load(fixture_name)
|
2012-05-12 13:12:57 +04:00
|
|
|
return self.fetch(repo_path, *args, **opts), repo_path
|
2012-04-19 20:29:28 +04:00
|
|
|
|
|
|
|
def _load_fixture_and_fetch(self, *args, **kwargs):
|
|
|
|
repo, repo_path = self.load_and_fetch(*args, **kwargs)
|
|
|
|
return repo
|
2008-12-12 04:49:36 +03:00
|
|
|
|
2012-04-19 20:29:29 +04:00
|
|
|
def add_svn_rev(self, repo_path, changes):
|
2010-09-08 11:57:06 +04:00
|
|
|
'''changes is a dict of filename -> contents'''
|
|
|
|
if self.svn_wc is None:
|
|
|
|
self.svn_wc = os.path.join(self.tmpdir, 'testsvn_wc')
|
|
|
|
subprocess.call([
|
2012-04-19 20:29:29 +04:00
|
|
|
'svn', 'co', '-q', fileurl(repo_path),
|
2010-09-08 11:57:06 +04:00
|
|
|
self.svn_wc
|
|
|
|
],
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
|
|
|
|
for filename, contents in changes.iteritems():
|
|
|
|
# filenames are / separated
|
|
|
|
filename = filename.replace('/', os.path.sep)
|
|
|
|
filename = os.path.join(self.svn_wc, filename)
|
|
|
|
open(filename, 'w').write(contents)
|
|
|
|
# may be redundant
|
|
|
|
subprocess.call(['svn', 'add', '-q', filename],
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
subprocess.call([
|
|
|
|
'svn', 'commit', '-q', self.svn_wc, '-m', 'test changes'],
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
# define this as a property so that it reloads anytime we need it
|
|
|
|
@property
|
|
|
|
def repo(self):
|
2010-02-26 16:50:22 +03:00
|
|
|
return hg.repository(testui(), self.wc_path)
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2013-08-08 01:16:39 +04:00
|
|
|
def pushrevisions(self, expected_extra_back=0):
|
2013-08-09 19:22:50 +04:00
|
|
|
before = repolen(self.repo)
|
2013-08-08 01:16:39 +04:00
|
|
|
self.repo.ui.setconfig('hgsubversion', 'stupid', str(self.stupid))
|
2010-06-28 06:18:47 +04:00
|
|
|
res = commands.push(self.repo.ui, self.repo)
|
2013-08-09 19:22:50 +04:00
|
|
|
after = repolen(self.repo)
|
2009-05-28 00:51:04 +04:00
|
|
|
self.assertEqual(expected_extra_back, after - before)
|
2010-06-28 06:18:47 +04:00
|
|
|
return res
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2012-04-19 20:29:30 +04:00
|
|
|
def svnco(self, repo_path, svnpath, rev, path):
|
2010-11-25 23:55:21 +03:00
|
|
|
path = os.path.join(self.wc_path, path)
|
|
|
|
subpath = os.path.dirname(path)
|
|
|
|
if not os.path.isdir(subpath):
|
|
|
|
os.makedirs(subpath)
|
2012-04-19 20:29:30 +04:00
|
|
|
svnpath = fileurl(repo_path + '/' + svnpath)
|
2010-11-25 23:55:21 +03:00
|
|
|
args = ['svn', 'co', '-r', rev, svnpath, path]
|
|
|
|
p = subprocess.Popen(args,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT)
|
|
|
|
stdout, stderr = p.communicate()
|
|
|
|
if p.returncode:
|
|
|
|
raise Exception('svn co failed on %s: %r' % (svnpath, stderr))
|
|
|
|
|
2009-05-28 00:51:04 +04:00
|
|
|
def commitchanges(self, changes, parent='tip', message='automated test'):
|
2008-11-15 01:18:24 +03:00
|
|
|
"""Commit changes to mercurial directory
|
|
|
|
|
|
|
|
'changes' is a sequence of tuples (source, dest, data). It can look
|
|
|
|
like:
|
|
|
|
- (source, source, data) to set source content to data
|
2008-11-15 01:52:30 +03:00
|
|
|
- (source, dest, None) to set dest content to source one, and mark it as
|
2008-11-15 01:18:24 +03:00
|
|
|
copied from source.
|
|
|
|
- (source, dest, data) to set dest content to data, and mark it as copied
|
|
|
|
from source.
|
|
|
|
- (source, None, None) to remove source.
|
|
|
|
"""
|
|
|
|
repo = self.repo
|
2009-04-25 05:31:17 +04:00
|
|
|
parentctx = repo[parent]
|
2008-11-15 01:18:24 +03:00
|
|
|
|
|
|
|
changed, removed = [], []
|
|
|
|
for source, dest, newdata in changes:
|
|
|
|
if dest is None:
|
|
|
|
removed.append(source)
|
|
|
|
else:
|
|
|
|
changed.append(dest)
|
|
|
|
|
|
|
|
def filectxfn(repo, memctx, path):
|
|
|
|
if path in removed:
|
2014-09-17 03:02:44 +04:00
|
|
|
return compathacks.filectxfn_deleted(memctx, path)
|
2008-11-15 01:18:24 +03:00
|
|
|
entry = [e for e in changes if path == e[1]][0]
|
|
|
|
source, dest, newdata = entry
|
|
|
|
if newdata is None:
|
|
|
|
newdata = parentctx[source].data()
|
|
|
|
copied = None
|
|
|
|
if source != dest:
|
|
|
|
copied = source
|
2014-06-03 04:54:02 +04:00
|
|
|
return compathacks.makememfilectx(repo,
|
2017-12-20 01:29:55 +03:00
|
|
|
memctx=memctx,
|
2014-06-03 04:54:02 +04:00
|
|
|
path=dest,
|
|
|
|
data=newdata,
|
|
|
|
islink=False,
|
|
|
|
isexec=False,
|
|
|
|
copied=copied)
|
2008-11-15 01:52:30 +03:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
ctx = context.memctx(repo,
|
|
|
|
(parentctx.node(), node.nullid),
|
2009-05-28 00:51:04 +04:00
|
|
|
message,
|
2008-11-15 01:18:24 +03:00
|
|
|
changed + removed,
|
|
|
|
filectxfn,
|
|
|
|
'an_author',
|
2016-06-15 00:08:43 +03:00
|
|
|
'2008-10-07 20:59:48 -0500',
|
|
|
|
{'branch': parentctx.branch()})
|
2008-11-15 01:18:24 +03:00
|
|
|
nodeid = repo.commitctx(ctx)
|
|
|
|
repo = self.repo
|
2009-04-25 05:31:17 +04:00
|
|
|
hg.clean(repo, nodeid)
|
2008-11-15 01:18:24 +03:00
|
|
|
return nodeid
|
2008-11-21 07:41:16 +03:00
|
|
|
|
|
|
|
def assertchanges(self, changes, ctx):
|
|
|
|
"""Assert that all 'changes' (as in defined in commitchanged())
|
|
|
|
went into ctx.
|
|
|
|
"""
|
|
|
|
for source, dest, data in changes:
|
|
|
|
if dest is None:
|
|
|
|
self.assertTrue(source not in ctx)
|
|
|
|
continue
|
|
|
|
self.assertTrue(dest in ctx)
|
|
|
|
if data is None:
|
|
|
|
data = ctx.parents()[0][source].data()
|
|
|
|
self.assertEqual(ctx[dest].data(), data)
|
|
|
|
if dest != source:
|
|
|
|
copy = ctx[dest].renamed()
|
|
|
|
self.assertEqual(copy[0], source)
|
2010-01-30 01:36:14 +03:00
|
|
|
|
2010-06-22 23:58:24 +04:00
|
|
|
def assertMultiLineEqual(self, first, second, msg=None):
|
|
|
|
"""Assert that two multi-line strings are equal. (Based on Py3k code.)
|
|
|
|
"""
|
2010-11-11 23:30:31 +03:00
|
|
|
try:
|
|
|
|
return super(TestBase, self).assertMultiLineEqual(first, second,
|
|
|
|
msg)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
2010-06-22 23:58:24 +04:00
|
|
|
self.assert_(isinstance(first, str),
|
|
|
|
('First argument is not a string'))
|
|
|
|
self.assert_(isinstance(second, str),
|
|
|
|
('Second argument is not a string'))
|
|
|
|
|
|
|
|
if first != second:
|
|
|
|
diff = ''.join(difflib.unified_diff(first.splitlines(True),
|
|
|
|
second.splitlines(True),
|
|
|
|
fromfile='a',
|
|
|
|
tofile='b'))
|
|
|
|
msg = '%s\n%s' % (msg or '', diff)
|
|
|
|
raise self.failureException, msg
|
|
|
|
|
2012-10-16 23:17:55 +04:00
|
|
|
def getgraph(self, repo):
|
2010-01-30 01:36:14 +03:00
|
|
|
"""Helper function displaying a repository graph, especially
|
|
|
|
useful when debugging comprehensive tests.
|
|
|
|
"""
|
|
|
|
# Could be more elegant, but it works with stock hg
|
2015-07-09 02:45:04 +03:00
|
|
|
_ui = testui()
|
2010-01-30 01:36:14 +03:00
|
|
|
_ui.setconfig('extensions', 'graphlog', '')
|
|
|
|
extensions.loadall(_ui)
|
|
|
|
graphlog = extensions.find('graphlog')
|
|
|
|
templ = """\
|
2013-08-12 00:20:59 +04:00
|
|
|
changeset: {rev}:{node|short} (r{svnrev})
|
2010-01-30 01:36:14 +03:00
|
|
|
branch: {branches}
|
2010-02-06 19:36:12 +03:00
|
|
|
tags: {tags}
|
2010-01-30 01:36:14 +03:00
|
|
|
summary: {desc|firstline}
|
|
|
|
files: {files}
|
|
|
|
|
|
|
|
"""
|
2012-10-16 23:17:55 +04:00
|
|
|
_ui.pushbuffer()
|
2015-12-31 20:06:58 +03:00
|
|
|
try:
|
|
|
|
graphlog.graphlog(_ui, repo, rev=None, template=templ)
|
|
|
|
except AttributeError:
|
|
|
|
from mercurial import commands
|
|
|
|
commands.log(_ui, repo, rev=None, template=templ, graph=True)
|
2012-10-16 23:17:55 +04:00
|
|
|
return _ui.popbuffer()
|
|
|
|
|
2016-06-14 22:31:37 +03:00
|
|
|
def svnlog(self, repo=None):
|
|
|
|
'''log of the remote Subversion repository corresponding to repo
|
|
|
|
|
|
|
|
In order to make the format suitable for direct comparison in
|
|
|
|
tests, we exclude dates and convert the path operations into
|
|
|
|
a tuple.
|
|
|
|
'''
|
|
|
|
|
|
|
|
if repo is None:
|
|
|
|
repo = self.repo
|
|
|
|
|
|
|
|
return [(r.revnum, r.message,
|
|
|
|
dict((p, (op.action, op.copyfrom_path, int(op.copyfrom_rev)))
|
|
|
|
for (p, op) in r.paths.items()))
|
|
|
|
for r in svnrepo.svnremoterepo(repo.ui).svn.revisions()]
|
|
|
|
|
2012-10-16 23:17:55 +04:00
|
|
|
def draw(self, repo):
|
|
|
|
sys.stdout.write(self.getgraph(repo))
|
2018-01-17 14:23:44 +03:00
|
|
|
|
|
|
|
def import_test(name):
|
|
|
|
components = name.split('_')
|
|
|
|
components.insert(1, 'hgsubversion')
|
|
|
|
testname = '-'.join(components) + '.py'
|
|
|
|
dot = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
candidates = [os.path.join(dot, testname),
|
|
|
|
os.path.join(dot, 'comprehensive', testname)]
|
|
|
|
for candidate in candidates:
|
|
|
|
if os.path.exists(candidate):
|
|
|
|
return imp.load_source(name, candidate)
|
|
|
|
|
|
|
|
raise ImportError('Could not import module %s from files like %s' %
|
|
|
|
(name, testname))
|
|
|
|
|