31 Commits

Author SHA1 Message Date
Mark Nauwelaerts
88b2756e43 t: post-merge adjust of test-lib.sh 2025-08-30 20:31:47 +02:00
Mark Nauwelaerts
d000a0b5e6 t: post-merge align of main-push test 2025-08-23 18:12:41 +02:00
Mark Nauwelaerts
4e119d9ea6 t: post-merge align of helper test 2025-08-23 18:12:41 +02:00
Mark Nauwelaerts
e31f83cbc5 Merge branch 'felipec' 2025-08-23 15:39:32 +02:00
Mark Nauwelaerts
5e1aa3a8e0 Merge commit 'bad0a3e5e5dd8352b3ea67d6efa8584ebde5311e' into felipec 2025-08-23 15:30:30 +02:00
Mark Nauwelaerts
ce7ddae09a Merge commit 'f4cdd20cb106c9e0ba57bc63b4231f9da6e72513' into felipec 2025-08-23 15:27:12 +02:00
Felipe Contreras
bad0a3e5e5 check-versions: test hg 7.0 2025-08-02 03:54:13 -06:00
Felipe Contreras
705f9ecaec github: enable windows test 2025-08-02 03:49:15 -06:00
Felipe Contreras
7c3eccdb93 t: cleanup win check 2025-08-02 03:44:52 -06:00
Felipe Contreras
44d09e7d37 t: check for MinGW32 2025-08-02 03:44:52 -06:00
Felipe Contreras
f87d0bc1a1 github: update python version 2025-08-02 03:44:28 -06:00
Felipe Contreras
54804bc402 github: update actions 2025-08-02 03:44:28 -06:00
Felipe Contreras
99c02f49df github: cleanup 2025-08-02 03:44:28 -06:00
Felipe Contreras
39960e1ae8 github: use prove 2025-08-02 03:44:28 -06:00
Felipe Contreras
5353a87001 t: allow execution from other dirs 2025-08-02 03:44:28 -06:00
Felipe Contreras
9a80e68234 t: update to sharness 1.2.1 2025-08-02 03:44:28 -06:00
Felipe Contreras
f4cdd20cb1 t: rename directory 2025-08-01 19:05:51 -06:00
Mark Nauwelaerts
16b33919e4 Release v1.0.5 2025-06-29 21:34:12 +02:00
Mark Nauwelaerts
9813797360 Transform invalid reserved pathname components
Fixes mnauw/git-remote-hg#58
2025-05-04 12:45:41 +02:00
Mark Nauwelaerts
e1a9c3e91b Update loading source file as module 2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
a8f6d92613 helper: add mapfile subcommand
Fixes mnauw/git-remote-hg#55
2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
4b8a307400 helper: use proper variable in error message 2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
6d75435eab helper: align update of reference to shared hg repo
... to use a relative path where possible
2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
d47a4abdae Always ensure reference to shared hg repo is up to date 2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
afdb8943ea ci: disable trigger on push 2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
dc1be060d1 README: drop mention of supported version
... as the intention is always to support the latest anyway
2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
13781788eb README: add a note about broken hg-git compatibility mode 2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
5061e6a322 README: minor note on notes 2025-05-04 12:19:35 +02:00
Mark Nauwelaerts
587099b968 Disable a hg-git mimic attempt
This essentially mostly reverts b029ac0500,
as we have no way to properly decide on those extra pieces.

hg-git uses commit extra metadata, which is not supported by fast-import
or fast-export, and until/unless that changes there is no way to match
hg-git.  Trying to do so in contorted ways only adds confusion.
2025-05-04 12:17:27 +02:00
Mark Nauwelaerts
b5f104364f test: ensure original behaviour in hg-git test 2025-05-03 12:18:25 +02:00
Mark Nauwelaerts
a48d4fd7fb test: post-merge align of main-push test 2025-05-03 12:18:25 +02:00
39 changed files with 546 additions and 476 deletions

View File

@@ -1,20 +1,22 @@
name: CI name: CI
# on: [push]
on: on:
push: # save cycles; disable on push, enable manual trigger
workflow_dispatch:
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
hg: [ '6.0', '6.1', '6.2', '6.3', '6.4', '6.5', '6.6', '6.7', '6.8', '6.9' ] hg: [ '6.0', '6.1', '6.2', '6.3', '6.4', '6.5', '6.6', '6.7', '6.8', '6.9', '7.0' ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.11'
- uses: actions/cache@v3 - uses: actions/cache@v4
id: cache-pip id: cache-pip
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -22,4 +24,21 @@ jobs:
- name: Install hg - name: Install hg
run: run:
pip install mercurial==${{ matrix.hg }} pip install mercurial==${{ matrix.hg }}
- run: make test - run: prove -j4
test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- uses: actions/cache@v4
id: cache-pip
with:
path: ~/appdata/local/pip/cache
key: pip-windows
- name: Install hg
run:
pip install mercurial
- name: Run all tests
run: prove -j4 --exec bash.exe

View File

@@ -29,7 +29,7 @@ build:
doc: doc/git-remote-hg.1 doc: doc/git-remote-hg.1
test: test:
$(MAKE) -C test $(MAKE) -C t
doc/git-remote-hg.1: doc/git-remote-hg.txt doc/git-remote-hg.1: doc/git-remote-hg.txt
asciidoctor -d manpage -b manpage $< asciidoctor -d manpage -b manpage $<

View File

@@ -71,13 +71,31 @@ the same commits:
% git config --global remote-hg.hg-git-compat true % git config --global remote-hg.hg-git-compat true
-------------------------------------- --------------------------------------
****
mnauw's note; The above is not quite the case, it only ever has been (somewhat)
if an undocumented debug mode (`debugextrainmessage` setting) was enabled
in (likely somewhat patched) `hg-git`. And as of `hg-git` v1.2.0 the latter is
no longer considered. In fact, `hg-git` creates git commits with additional hg
metadata stored in so-called "extra commit headers". The latter might be seen by
`git log --format=raw` or `git cat-file -p <commitid>`, but are otherwise mostly
only used internally by the git suite (for signatures). While they are supported
by `dulwich`'s API (which is a python git implementation), there is, however,
limited to no support for those in git "porcelain or plumbing" commands. In
particular, `git fast-export` and `git fast-import` do not consider these, so a
`gitremote-helpers` tool is then also out of luck. Incidentally, it also
follows that a `git fast-export | git fast-import` "clone" approach would also
lose such extra metadata, and likewise so for e.g. `git filter-repo`.
All in all, this mode is not quite recommended.
If the concern here is not so much `hg-git` compatibility but rather "hg-git-hg
round-trip fidelity", then see the discussion below on `check-hg-commits` setting.
****
== Notes == == Notes ==
Remember to run `git gc --aggressive` after cloning a repository, especially if Remember to run `git gc --aggressive` after cloning a repository, especially if
it's a big one. Otherwise lots of space will be wasted. it's a big one. Otherwise lots of space will be wasted.
The newest supported version of Mercurial is 6.2, the oldest one is 2.4.
=== Pushing branches === === Pushing branches ===
To push a branch, you need to use the 'branches/' prefix: To push a branch, you need to use the 'branches/' prefix:
@@ -369,7 +387,8 @@ up elsewhere as expected (regardless of conversion mapping or ABI).
Note that identifying and re-using the hg changeset relies on metadata Note that identifying and re-using the hg changeset relies on metadata
(`refs/notes/hg` and marks files) that is not managed or maintained by any (`refs/notes/hg` and marks files) that is not managed or maintained by any
git-to-git fetch (or clone). git-to-git fetch (or clone) (as that is only automatically so for `refs/heads/`,
though it could be pushed manually).
As such (and as said), this approach aims for plain-and-simple safety, but only As such (and as said), this approach aims for plain-and-simple safety, but only
within a local scope (git repo). within a local scope (git repo).

View File

@@ -104,6 +104,20 @@ the invalid '~'
% git config --global remote-hg.ignore-name ~ % git config --global remote-hg.ignore-name ~
-------------------------------------- --------------------------------------
Even though the "gitdir" is configurable (using `GIT_DIR`), git does not accept
certain pathname components, e.g. `.git` or `.gitmodules` (case-insensitive).
Problems arise if the hg repo contains such pathnames, and recent git versions
will reject this in a very hard way. So these pathnames are now mapped
from "hg space" to "git space" in a one-to-one way, where (e.g.)
`.git[0 or more suffix]` is mapped to `.git[1 or more suffix]` (obviously by
appending or removing a suffix). The "suffix" in question defaults to `_`,
but can be configured using
--------------------------------------
% git config --global remote-hg.dotfile-suffix _
--------------------------------------
NOTES NOTES
----- -----

View File

@@ -97,11 +97,27 @@ def debug(msg, *args):
def log(msg, *args): def log(msg, *args):
logger.log(logging.LOG, msg, *args) logger.log(logging.LOG, msg, *args)
# new style way to import a source file
def _imp_load_source(module_name, file_path):
import importlib.util
loader = importlib.machinery.SourceFileLoader(module_name, file_path)
spec = importlib.util.spec_from_loader(module_name, loader)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def import_sibling(mod, filename): def import_sibling(mod, filename):
import imp
mydir = os.path.dirname(__file__) mydir = os.path.dirname(__file__)
sys.dont_write_bytecode = True sys.dont_write_bytecode = True
return imp.load_source(mod, os.path.join(mydir, filename)) vi = sys.version_info
ff = os.path.join(mydir, filename)
if vi.major >= 3 and vi.minor >= 5:
return _imp_load_source(mod, ff)
else:
import imp
return imp.load_source(mod, ff)
class GitHgRepo: class GitHgRepo:
@@ -146,7 +162,7 @@ class GitHgRepo:
process = self.start_cmd(args, **kwargs) process = self.start_cmd(args, **kwargs)
output = process.communicate()[0] output = process.communicate()[0]
if check and process.returncode != 0: if check and process.returncode != 0:
die(b'command failed: %s' % b' '.join([compat.to_b(a) for a in cmd])) die(b'git command failed: %s' % b' '.join([compat.to_b(a) for a in args]))
return output return output
def get_config(self, config, getall=False): def get_config(self, config, getall=False):
@@ -227,9 +243,12 @@ class GitHgRepo:
warn(b'failed to find local hg for remote %s' % (r)) warn(b'failed to find local hg for remote %s' % (r))
continue continue
else: else:
npath = os.path.abspath(hg_path)
# use relative path if possible
if check_version(4, 2):
npath = os.path.join(b'..', b'..', b'..', b'.hg')
# make sure the shared path is always up-to-date # make sure the shared path is always up-to-date
util.writefile(os.path.join(local_hg, b'sharedpath'), util.writefile(os.path.join(local_hg, b'sharedpath'), npath)
os.path.abspath(hg_path))
self.hg_repos[r] = os.path.join(local_path) self.hg_repos[r] = os.path.join(local_path)
log('%s determined hg_repos %s', self.identity(), self.hg_repos) log('%s determined hg_repos %s', self.identity(), self.hg_repos)
@@ -555,6 +574,43 @@ class GcCommand(SubCommand):
gm.store() gm.store()
class MapFileCommand(SubCommand):
def argumentparser(self):
usage = '%%(prog)s %s [options] <remote>' % (self.subcommand)
p = argparse.ArgumentParser(usage=usage)
p.add_argument('--output', required=True,
help='mapfile to write')
p.epilog = textwrap.dedent("""\
Writes a so-called git-mapfile, as used internally by hg-git.
This files consists of lines of format `<githexsha> <hghexsha>`.
As such, the result could be used to coax hg-git in some manner.
However, as git-remote-hg and hg-git may (likely) produce different
commits (either git or hg), mixed use of both tools is not recommended.
""")
return p
def do(self, options, args):
remotehg = import_sibling('remotehg', 'git-remote-hg')
if not args or len(args) != 1:
self.usage('expect 1 remote')
remote = args[0]
hgpath = remotehg.select_marks_dir(remote, self.githgrepo.gitdir, False)
puts(b"Loading hg marks ...")
hgm = remotehg.Marks(os.path.join(hgpath, b'marks-hg'), None)
puts(b"Loading git marks ...")
gm = GitMarks(os.path.join(hgpath, b'marks-git'))
puts(b"Writing mapfile ...")
with open(options.output, 'wb') as f:
for c, m in gm.marks.items():
hgc = hgm.rev_marks.get(m, None)
if hgc:
f.write(b'%s %s\n' % (c, hgc))
class SubRepoCommand(SubCommand): class SubRepoCommand(SubCommand):
def writestate(repo, state): def writestate(repo, state):
@@ -921,6 +977,7 @@ def get_subcommands():
b'repo': RepoCommand, b'repo': RepoCommand,
b'gc': GcCommand, b'gc': GcCommand,
b'sub': SubRepoCommand, b'sub': SubRepoCommand,
b'mapfile': MapFileCommand,
b'help' : HelpCommand b'help' : HelpCommand
} }
# add remote named subcommands # add remote named subcommands
@@ -943,6 +1000,7 @@ def do_usage():
gc \t perform maintenance and consistency cleanup on repo tracking marks gc \t perform maintenance and consistency cleanup on repo tracking marks
sub \t manage subrepos sub \t manage subrepos
repo \t show local hg repo backing a remote repo \t show local hg repo backing a remote
mapfile \t dump a hg-git git-mapfile
If the subcommand is the name of a remote hg repo, then any remaining arguments If the subcommand is the name of a remote hg repo, then any remaining arguments
are considered a "hg command", e.g. hg heads, or thg, and it is then executed are considered a "hg command", e.g. hg heads, or thg, and it is then executed

View File

@@ -122,6 +122,27 @@ else:
urlparse = staticmethod(_urlparse) urlparse = staticmethod(_urlparse)
urljoin = staticmethod(_urljoin) urljoin = staticmethod(_urljoin)
# new style way to import a source file
def _imp_load_source(module_name, file_path):
import importlib.util
loader = importlib.machinery.SourceFileLoader(module_name, file_path)
spec = importlib.util.spec_from_loader(module_name, loader)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def import_sibling(mod, filename):
mydir = os.path.dirname(__file__)
sys.dont_write_bytecode = True
vi = sys.version_info
ff = os.path.join(mydir, filename)
if vi.major >= 3 and vi.minor >= 5:
return _imp_load_source(mod, ff)
else:
import imp
return imp.load_source(mod, ff)
# #
# If you want to see Mercurial revisions as Git commit notes: # If you want to see Mercurial revisions as Git commit notes:
# git config core.notesRef refs/notes/hg # git config core.notesRef refs/notes/hg
@@ -417,7 +438,7 @@ def export_file(ctx, fname):
puts(b"data %d" % len(d)) puts(b"data %d" % len(d))
puts(f.data()) puts(f.data())
path = fix_file_path(f.path()) path = fixup_path_to_git(fix_file_path(f.path()))
return (gitmode(f.flags()), mark, path) return (gitmode(f.flags()), mark, path)
def get_filechanges(repo, ctx, parent): def get_filechanges(repo, ctx, parent):
@@ -497,6 +518,51 @@ def fixup_user(user):
return b'%s <%s>' % (name, mail) return b'%s <%s>' % (name, mail)
# (recent) git fast-import does not accept .git or .gitmodule component names
# (anywhere, case-insensitive)
# in any case, surprising things may happen, so add some front-end replacement magic;
# transform of (hg) .git(0 or more suffix) to (git) .git(1 or more suffix)
# (likewise so for any invalid git keyword)
def fixup_dotfile_path(path, suffix, add):
def subst(part):
if (not part) or part[0] != ord(b'.'):
return part
for prefix in (b'.git', b'.gitmodules'):
pl = len(prefix)
tail = len(part) - pl
if tail < 0:
continue
if part[0:pl].lower() == prefix and part[pl:] == suffix * tail:
if add:
return part + suffix
elif tail == 0:
# .git should not occur in git space
# so complain
if pl == 3:
die('invalid path component %s' % part)
else:
# but .gitmodules might
# leave as-is, it is handled/ignored elsewhere
return part
else:
return part[0:-1]
return part
# quick optimization check;
if (not path) or (path[0] != ord(b'.') and path.find(b'/.') < 0):
return path
sep = b'/'
return sep.join((subst(part) for part in path.split(sep)))
def fixup_path_to_git(path):
if not dotfile_suffix:
return path
return fixup_dotfile_path(path, dotfile_suffix, True)
def fixup_path_from_git(path):
if not dotfile_suffix:
return path
return fixup_dotfile_path(path, dotfile_suffix, False)
def updatebookmarks(repo, peer): def updatebookmarks(repo, peer):
remotemarks = peer.listkeys(b'bookmarks') remotemarks = peer.listkeys(b'bookmarks')
@@ -578,16 +644,16 @@ def get_repo(url, alias):
os.makedirs(dirname) os.makedirs(dirname)
local_path = os.path.join(dirname, b'clone') local_path = os.path.join(dirname, b'clone')
kwargs = {}
hg_path = os.path.join(shared_path, b'.hg')
if check_version(4, 2): if check_version(4, 2):
if not os.path.exists(local_path): kwargs = {'relative': True}
hg.share(myui, shared_path, local_path, update=False, relative=True) hg_path = os.path.join(b'..', b'..', b'..', b'.hg')
if not os.path.exists(local_path):
hg.share(myui, shared_path, local_path, update=False, **kwargs)
else: else:
if not os.path.exists(local_path): # make sure the shared path is always up-to-date
hg.share(myui, shared_path, local_path, update=False) util.writefile(os.path.join(local_path, b'.hg', b'sharedpath'), hg_path)
else:
# make sure the shared path is always up-to-date
hg_path = os.path.join(shared_path, b'.hg')
util.writefile(os.path.join(local_path, b'.hg', b'sharedpath'), hg_path)
repo = hg.repository(myui, local_path) repo = hg.repository(myui, local_path)
try: try:
@@ -693,6 +759,7 @@ def export_ref(repo, name, kind, head):
if rename: if rename:
renames.append((rename[0], f)) renames.append((rename[0], f))
# NOTE no longer used in hg-git, a HG:rename extra header is used
for e in renames: for e in renames:
extra_msg += b"rename : %s => %s\n" % e extra_msg += b"rename : %s => %s\n" % e
@@ -727,7 +794,7 @@ def export_ref(repo, name, kind, head):
puts(b"merge :%u" % (rev_to_mark(parents[1]))) puts(b"merge :%u" % (rev_to_mark(parents[1])))
for f in removed: for f in removed:
puts(b"D %s" % (fix_file_path(f))) puts(b"D %s" % fixup_path_to_git(fix_file_path(f)))
for f in modified_final: for f in modified_final:
puts(b"M %s :%u %s" % f) puts(b"M %s :%u %s" % f)
puts() puts()
@@ -1020,6 +1087,7 @@ def parse_commit(parser):
else: else:
die(b'Unknown file command: %s' % line) die(b'Unknown file command: %s' % line)
path = c_style_unescape(path) path = c_style_unescape(path)
path = fixup_path_from_git(path)
files[path] = files.get(path, {}) files[path] = files.get(path, {})
files[path].update(f) files[path].update(f)
@@ -1127,11 +1195,17 @@ def parse_commit(parser):
# add some extra that hg-git adds (almost) unconditionally # add some extra that hg-git adds (almost) unconditionally
# see also https://foss.heptapod.net/mercurial/hg-git/-/merge_requests/211 # see also https://foss.heptapod.net/mercurial/hg-git/-/merge_requests/211
# NOTE it could be changed to another value below # NOTE it could be changed to another value below
extra[b'hg-git-rename-source'] = b'git' # actually, it is *almost* unconditionally, and only done if the commit
# is deduced to originate in git. However, the latter is based on
# presence/absence of HG markers in commit "extra headers".
# The latter can not be handled here, and so this can not be correctly
# reproduced.
# extra[b'hg-git-rename-source'] = b'git'
i = data.find(b'\n--HG--\n') i = data.find(b'\n--HG--\n')
if i >= 0: if i >= 0:
tmp = data[i + len(b'\n--HG--\n'):].strip() tmp = data[i + len(b'\n--HG--\n'):].strip()
for k, v in [e.split(b' : ', 1) for e in tmp.split(b'\n')]: for k, v in [e.split(b' : ', 1) for e in tmp.split(b'\n')]:
# NOTE no longer used in hg-git, a HG:rename extra header is used
if k == b'rename': if k == b'rename':
old, new = v.split(b' => ', 1) old, new = v.split(b' => ', 1)
files[new]['rename'] = old files[new]['rename'] = old
@@ -1602,10 +1676,7 @@ def do_push_refspec(parser, refspec, revs):
tmpfastexport = open(os.path.join(marksdir, b'git-fast-export-%d' % (os.getpid())), 'w+b') tmpfastexport = open(os.path.join(marksdir, b'git-fast-export-%d' % (os.getpid())), 'w+b')
subprocess.check_call(cmd, stdin=None, stdout=tmpfastexport) subprocess.check_call(cmd, stdin=None, stdout=tmpfastexport)
try: try:
import imp ctx.hghelper = import_sibling('hghelper', 'git-hg-helper')
sys.dont_write_bytecode = True
ctx.hghelper = imp.load_source('hghelper', \
os.path.join(os.path.dirname(__file__), 'git-hg-helper'))
ctx.hghelper.init_git(gitdir) ctx.hghelper.init_git(gitdir)
ctx.gitmarks = ctx.hghelper.GitMarks(tmpmarks) ctx.gitmarks = ctx.hghelper.GitMarks(tmpmarks)
# let processing know it should not bother pushing if not requested # let processing know it should not bother pushing if not requested
@@ -1842,6 +1913,7 @@ def main(args):
global capability_push global capability_push
global remove_username_quotes global remove_username_quotes
global marksdir global marksdir
global dotfile_suffix
marks = None marks = None
is_tmp = False is_tmp = False
@@ -1861,6 +1933,7 @@ def main(args):
track_branches = get_config_bool('remote-hg.track-branches', True) track_branches = get_config_bool('remote-hg.track-branches', True)
capability_push = get_config_bool('remote-hg.capability-push', True) capability_push = get_config_bool('remote-hg.capability-push', True)
remove_username_quotes = get_config_bool('remote-hg.remove-username-quotes', True) remove_username_quotes = get_config_bool('remote-hg.remove-username-quotes', True)
dotfile_suffix = get_config('remote-hg.dotfile-suffix').strip() or b'_'
force_push = False force_push = False
if hg_git_compat: if hg_git_compat:

View File

@@ -3,7 +3,7 @@
import setuptools import setuptools
# strip leading v # strip leading v
version = 'v1.0.4'[1:] version = 'v1.0.5'[1:]
# check for released version # check for released version
assert (len(version) > 0) assert (len(version) > 0)

View File

View File

@@ -8,7 +8,7 @@
test_description='Test bidirectionality of remote-hg' test_description='Test bidirectionality of remote-hg'
. ./test-lib.sh . "$(dirname "$0")"/test-lib.sh
# clone to a git repo # clone to a git repo
git_clone () { git_clone () {

View File

@@ -8,7 +8,7 @@
test_description='Test git-hg-helper' test_description='Test git-hg-helper'
. ./test-lib.sh . "$(dirname "$0")"/test-lib.sh
if ! test_have_prereq PYTHON if ! test_have_prereq PYTHON
then then
@@ -99,7 +99,7 @@ test_expect_success 'subcommand repo - with local proxy' '
test_cmp expected actual test_cmp expected actual
' '
test_expect_success 'subcommands hg-rev and git-rev' ' test_expect_success 'subcommands hg-rev and git-rev and mapfile' '
test_when_finished "rm -rf gitrepo* hgrepo*" && test_when_finished "rm -rf gitrepo* hgrepo*" &&
setup_repos && setup_repos &&
@@ -110,7 +110,9 @@ test_expect_success 'subcommands hg-rev and git-rev' '
test -s rev-HEAD && test -s rev-HEAD &&
git-hg-helper hg-rev `cat rev-HEAD` > hg-HEAD && git-hg-helper hg-rev `cat rev-HEAD` > hg-HEAD &&
git-hg-helper git-rev `cat hg-HEAD` > git-HEAD && git-hg-helper git-rev `cat hg-HEAD` > git-HEAD &&
test_cmp rev-HEAD git-HEAD git-hg-helper mapfile --output mapfile origin &&
test_cmp rev-HEAD git-HEAD &&
grep "`cat rev-HEAD` `cat hg-HEAD`" mapfile
) )
' '

View File

@@ -10,7 +10,7 @@
test_description='Test remote-hg output compared to hg-git' test_description='Test remote-hg output compared to hg-git'
. ./test-lib.sh . "$(dirname "$0")"/test-lib.sh
export EXPECTED_DIR="$SHARNESS_TEST_DIRECTORY/expected" export EXPECTED_DIR="$SHARNESS_TEST_DIRECTORY/expected"
@@ -101,6 +101,8 @@ setup () {
[remote-hg] [remote-hg]
hg-git-compat = true hg-git-compat = true
track-branches = false track-branches = false
# directly use local repo to avoid push (and hence phase issues)
shared-marks = false
EOF EOF
export HGEDITOR=true export HGEDITOR=true

View File

@@ -1,8 +1,8 @@
#!/bin/bash
CAPABILITY_PUSH=t CAPABILITY_PUSH=t
test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=$(dirname $0)/ . "$(dirname "$0")"/main.t
. "$TEST_DIRECTORY"/main.t
# .. and some push mode only specific tests # .. and some push mode only specific tests

View File

@@ -8,7 +8,7 @@
test_description='Test remote-hg' test_description='Test remote-hg'
. ./test-lib.sh . "$(dirname "$0")"/test-lib.sh
if test "$CAPABILITY_PUSH" = "t" if test "$CAPABILITY_PUSH" = "t"
then then
@@ -268,6 +268,41 @@ test_expect_success 'strip' '
test_cmp actual expected test_cmp actual expected
' '
test_expect_success 'dotfiles' '
test_when_finished "rm -rf hgrepo gitrepo" &&
(
hg init hgrepo &&
cd hgrepo &&
echo one >.git &&
echo ONE >.GIT &&
mkdir a && echo two > a/.gitmodules &&
hg add .git .GIT a/.gitmodules &&
hg commit -m zero
) &&
git clone "hg::hgrepo" gitrepo &&
test_cmp gitrepo/.git_ hgrepo/.git &&
test_cmp gitrepo/.GIT_ hgrepo/.GIT &&
test_cmp gitrepo/a/.gitmodules_ hgrepo/a/.gitmodules &&
(
cd gitrepo &&
echo three >.git_ &&
echo THREE >.GIT &&
echo four >a/.gitmodules_ &&
git add .git_ .GIT_ a/.gitmodules_ &&
git commit -m one &&
git push
) &&
hg -R hgrepo update &&
test_cmp gitrepo/.git_ hgrepo/.git &&
test_cmp gitrepo/.GIT_ hgrepo/.GIT &&
test_cmp gitrepo/a/.gitmodules_ hgrepo/a/.gitmodules
'
test_expect_success 'remote push with master bookmark' ' test_expect_success 'remote push with master bookmark' '
test_when_finished "rm -rf hgrepo gitrepo*" && test_when_finished "rm -rf hgrepo gitrepo*" &&

View File

@@ -1,8 +1,9 @@
#!/bin/sh # Sharness test framework.
# #
# Copyright (c) 2011-2012 Mathias Lafeldt # Copyright (c) 2011-2012 Mathias Lafeldt
# Copyright (c) 2005-2012 Git project # Copyright (c) 2005-2012 Git project
# Copyright (c) 2005-2012 Junio C Hamano # Copyright (c) 2005-2012 Junio C Hamano
# Copyright (c) 2019-2023 Felipe Contreras
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@@ -17,42 +18,52 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/ . # along with this program. If not, see http://www.gnu.org/licenses/ .
if test -n "${ZSH_VERSION-}"
then
# shellcheck disable=SC2296
SHARNESS_SOURCE=${(%):-%x}
emulate sh -o POSIX_ARGZERO
else
# shellcheck disable=SC3028
SHARNESS_SOURCE=${BASH_SOURCE-$0}
fi
# Public: Current version of Sharness. # Public: Current version of Sharness.
SHARNESS_VERSION="1.1.0" SHARNESS_VERSION="1.2.1"
export SHARNESS_VERSION export SHARNESS_VERSION
# Public: The file extension for tests. By default, it is set to "t".
: "${SHARNESS_TEST_EXTENSION:=t}" : "${SHARNESS_TEST_EXTENSION:=t}"
# Public: The file extension for tests. By default, it is set to "t".
export SHARNESS_TEST_EXTENSION export SHARNESS_TEST_EXTENSION
: "${SHARNESS_TEST_DIRECTORY:=$(dirname "$0")}"
# ensure that SHARNESS_TEST_DIRECTORY is an absolute path so that it
# is valid even if the current working directory is changed
SHARNESS_TEST_DIRECTORY=$(cd "$SHARNESS_TEST_DIRECTORY" && pwd) || exit 1
# Public: Root directory containing tests. Tests can override this variable, # Public: Root directory containing tests. Tests can override this variable,
# e.g. for testing Sharness itself. # e.g. for testing Sharness itself.
if test -z "$SHARNESS_TEST_DIRECTORY"
then
SHARNESS_TEST_DIRECTORY=$(pwd)
else
# ensure that SHARNESS_TEST_DIRECTORY is an absolute path so that it
# is valid even if the current working directory is changed
SHARNESS_TEST_DIRECTORY=$(cd "$SHARNESS_TEST_DIRECTORY" && pwd) || exit 1
fi
export SHARNESS_TEST_DIRECTORY export SHARNESS_TEST_DIRECTORY
if test -z "$SHARNESS_TEST_OUTPUT_DIRECTORY" : "${SHARNESS_TEST_SRCDIR:=$(cd "$(dirname "$SHARNESS_SOURCE")" && pwd)}"
then # Public: Source directory of test code and sharness library.
# Similarly, override this to store the test-results subdir # This directory may be different from the directory in which tests are
# elsewhere # being run.
SHARNESS_TEST_OUTPUT_DIRECTORY=$SHARNESS_TEST_DIRECTORY export SHARNESS_TEST_SRCDIR
fi
: "${SHARNESS_TEST_OUTDIR:=$SHARNESS_TEST_DIRECTORY}"
# Public: Directory where the output of the tests should be stored (i.e.
# trash directories).
export SHARNESS_TEST_OUTDIR
# Reset TERM to original terminal if found, otherwise save original TERM # Reset TERM to original terminal if found, otherwise save original TERM
[ "x" = "x$SHARNESS_ORIG_TERM" ] && [ -z "$SHARNESS_ORIG_TERM" ] &&
SHARNESS_ORIG_TERM="$TERM" || SHARNESS_ORIG_TERM="$TERM" ||
TERM="$SHARNESS_ORIG_TERM" TERM="$SHARNESS_ORIG_TERM"
# Public: The unsanitized TERM under which sharness is originally run # Public: The unsanitized TERM under which sharness is originally run
export SHARNESS_ORIG_TERM export SHARNESS_ORIG_TERM
# Export SHELL_PATH # Export SHELL_PATH
: "${SHELL_PATH:=$SHELL}" : "${SHELL_PATH:=/bin/sh}"
export SHELL_PATH export SHELL_PATH
# if --tee was passed, write the output not only to the terminal, but # if --tee was passed, write the output not only to the terminal, but
@@ -62,8 +73,8 @@ done,*)
# do not redirect again # do not redirect again
;; ;;
*' --tee '*|*' --verbose-log '*) *' --tee '*|*' --verbose-log '*)
mkdir -p "$SHARNESS_TEST_OUTPUT_DIRECTORY/test-results" mkdir -p "$SHARNESS_TEST_OUTDIR/test-results"
BASE="$SHARNESS_TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" ".$SHARNESS_TEST_EXTENSION")" BASE="$SHARNESS_TEST_OUTDIR/test-results/$(basename "$0" ".$SHARNESS_TEST_EXTENSION")"
# Make this filename available to the sub-process in case it is using # Make this filename available to the sub-process in case it is using
# --verbose-log. # --verbose-log.
@@ -128,6 +139,9 @@ while test "$#" -ne 0; do
--root=*) --root=*)
root=$(expr "z$1" : 'z[^=]*=\(.*\)') root=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;; shift ;;
-x)
trace=t
shift ;;
--verbose-log) --verbose-log)
verbose_log=t verbose_log=t
shift ;; shift ;;
@@ -177,6 +191,40 @@ else
} }
fi fi
: "${test_untraceable:=}"
# Public: When set to a non-empty value, the current test will not be
# traced, unless it's run with a Bash version supporting
# BASH_XTRACEFD, i.e. v4.1 or later.
export test_untraceable
if test -n "$trace" && test -n "$test_untraceable"
then
# '-x' tracing requested, but this test script can't be reliably
# traced, unless it is run with a Bash version supporting
# BASH_XTRACEFD (introduced in Bash v4.1).
#
# Perform this version check _after_ the test script was
# potentially re-executed with $TEST_SHELL_PATH for '--tee' or
# '--verbose-log', so the right shell is checked and the
# warning is issued only once.
if test -n "$BASH_VERSION" && eval '
test ${BASH_VERSINFO[0]} -gt 4 || {
test ${BASH_VERSINFO[0]} -eq 4 &&
test ${BASH_VERSINFO[1]} -ge 1
}
'
then
: Executed by a Bash version supporting BASH_XTRACEFD. Good.
else
echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD"
trace=
fi
fi
if test -n "$trace" && test -z "$verbose_log"
then
verbose=t
fi
TERM=dumb TERM=dumb
export TERM export TERM
@@ -209,11 +257,22 @@ else
exec 4>/dev/null 3>/dev/null exec 4>/dev/null 3>/dev/null
fi fi
test_failure=0 # Send any "-x" output directly to stderr to avoid polluting tests
test_count=0 # which capture stderr. We can do this unconditionally since it
test_fixed=0 # has no effect if tracing isn't turned on.
test_broken=0 #
test_success=0 # Note that this sets up the trace fd as soon as we assign the variable, so it
# must come after the creation of descriptor 4 above. Likewise, we must never
# unset this, as it has the side effect of closing descriptor 4, which we
# use to show verbose tests to the user.
#
# Note also that we don't need or want to export it. The tracing is local to
# this shell, and we would not want to influence any shells we exec.
BASH_XTRACEFD=4
# Public: The current test number, starting at 0.
SHARNESS_TEST_NB=0
export SHARNESS_TEST_NB
die() { die() {
code=$? code=$?
@@ -228,105 +287,30 @@ die() {
EXIT_OK= EXIT_OK=
trap 'die' EXIT trap 'die' EXIT
# Public: Define that a test prerequisite is available. test_prereq=
# missing_prereq=
# The prerequisite can later be checked explicitly using test_have_prereq or
# implicitly by specifying the prerequisite name in calls to test_expect_success
# or test_expect_failure.
#
# $1 - Name of prerequisite (a simple word, in all capital letters by convention)
#
# Examples
#
# # Set PYTHON prerequisite if interpreter is available.
# command -v python >/dev/null && test_set_prereq PYTHON
#
# # Set prerequisite depending on some variable.
# test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
#
# Returns nothing.
test_set_prereq() {
satisfied_prereq="$satisfied_prereq$1 "
}
satisfied_prereq=" "
# Public: Check if one or more test prerequisites are defined. test_failure=0
# test_fixed=0
# The prerequisites must have previously been set with test_set_prereq. test_broken=0
# The most common use of this is to skip all the tests if some essential test_success=0
# prerequisite is missing.
#
# $1 - Comma-separated list of test prerequisites.
#
# Examples
#
# # Skip all remaining tests if prerequisite is not set.
# if ! test_have_prereq PERL; then
# skip_all='skipping perl interface tests, perl not available'
# test_done
# fi
#
# Returns 0 if all prerequisites are defined or 1 otherwise.
test_have_prereq() {
# prerequisites can be concatenated with ','
save_IFS=$IFS
IFS=,
set -- $@
IFS=$save_IFS
total_prereq=0 if test -e "$SHARNESS_TEST_SRCDIR/lib-sharness/functions.sh"
ok_prereq=0 then
missing_prereq= . "$SHARNESS_TEST_SRCDIR/lib-sharness/functions.sh"
fi
for prerequisite; do
case "$prerequisite" in
!*)
negative_prereq=t
prerequisite=${prerequisite#!}
;;
*)
negative_prereq=
esac
total_prereq=$((total_prereq + 1))
case "$satisfied_prereq" in
*" $prerequisite "*)
satisfied_this_prereq=t
;;
*)
satisfied_this_prereq=
esac
case "$satisfied_this_prereq,$negative_prereq" in
t,|,t)
ok_prereq=$((ok_prereq + 1))
;;
*)
# Keep a list of missing prerequisites; restore
# the negative marker if necessary.
prerequisite=${negative_prereq:+!}$prerequisite
if test -z "$missing_prereq"; then
missing_prereq=$prerequisite
else
missing_prereq="$prerequisite,$missing_prereq"
fi
esac
done
test $total_prereq = $ok_prereq
}
# You are not expected to call test_ok_ and test_failure_ directly, use # You are not expected to call test_ok_ and test_failure_ directly, use
# the text_expect_* functions instead. # the text_expect_* functions instead.
test_ok_() { test_ok_() {
test_success=$((test_success + 1)) test_success=$((test_success + 1))
say_color "" "ok $test_count - $*" say_color "" "ok $SHARNESS_TEST_NB - $*"
} }
test_failure_() { test_failure_() {
test_failure=$((test_failure + 1)) test_failure=$((test_failure + 1))
say_color error "not ok $test_count - $1" say_color error "not ok $SHARNESS_TEST_NB - $1"
shift shift
echo "$@" | sed -e 's/^/# /' echo "$@" | sed -e 's/^/# /'
test "$immediate" = "" || { EXIT_OK=t; exit 1; } test "$immediate" = "" || { EXIT_OK=t; exit 1; }
@@ -334,53 +318,76 @@ test_failure_() {
test_known_broken_ok_() { test_known_broken_ok_() {
test_fixed=$((test_fixed + 1)) test_fixed=$((test_fixed + 1))
say_color error "ok $test_count - $* # TODO known breakage vanished" say_color error "ok $SHARNESS_TEST_NB - $* # TODO known breakage vanished"
} }
test_known_broken_failure_() { test_known_broken_failure_() {
test_broken=$((test_broken + 1)) test_broken=$((test_broken + 1))
say_color warn "not ok $test_count - $* # TODO known breakage" say_color warn "not ok $SHARNESS_TEST_NB - $* # TODO known breakage"
} }
# Public: Execute commands in debug mode. want_trace () {
# test "$trace" = t && {
# Takes a single argument and evaluates it only when the test script is started test "$verbose" = t || test "$verbose_log" = t
# with --debug. This is primarily meant for use during the development of test }
# scripts.
#
# $1 - Commands to be executed.
#
# Examples
#
# test_debug "cat some_log_file"
#
# Returns the exit code of the last command executed in debug mode or 0
# otherwise.
test_debug() {
test "$debug" = "" || eval "$1"
} }
# Public: Stop execution and start a shell. # This is a separate function because some tests use
# # "return" to end a test_expect_success block early
# This is useful for debugging tests and only makes sense together with "-v". # (and we want to make sure we run any cleanup like
# Be sure to remove all invocations of this command before submitting. # "set +x").
test_pause() { test_eval_inner_ () {
if test "$verbose" = t; then # Do not add anything extra (including LF) after '$*'
"$SHELL_PATH" <&6 >&3 2>&4 eval "
else want_trace && set -x
error >&5 "test_pause requires --verbose" $*"
}
test_eval_x_ () {
# If "-x" tracing is in effect, then we want to avoid polluting stderr
# with non-test commands. But once in "set -x" mode, we cannot prevent
# the shell from printing the "set +x" to turn it off (nor the saving
# of $? before that). But we can make sure that the output goes to
# /dev/null.
#
# There are a few subtleties here:
#
# - we have to redirect descriptor 4 in addition to 2, to cover
# BASH_XTRACEFD
#
# - the actual eval has to come before the redirection block (since
# it needs to see descriptor 4 to set up its stderr)
#
# - likewise, any error message we print must be outside the block to
# access descriptor 4
#
# - checking $? has to come immediately after the eval, but it must
# be _inside_ the block to avoid polluting the "set -x" output
#
test_eval_inner_ "$@" </dev/null >&3 2>&4
{
test_eval_ret_=$?
if want_trace
then
set +x
fi
} 2>/dev/null 4>&2
if test "$test_eval_ret_" != 0 && want_trace
then
say_color error >&4 "error: last command exited with \$?=$test_eval_ret_"
fi fi
return $test_eval_ret_
} }
test_eval_() { test_eval_() {
# This is a separate function because some tests use
# "return" to end a test_expect_success block early.
case ",$test_prereq," in case ",$test_prereq," in
*,INTERACTIVE,*) *,INTERACTIVE,*)
eval "$*" eval "$*"
;; ;;
*) *)
eval </dev/null >&3 2>&4 "$*" test_eval_x_ "$@"
;; ;;
esac esac
} }
@@ -392,13 +399,22 @@ test_run_() {
eval_ret=$? eval_ret=$?
if test "$chain_lint" = "t"; then if test "$chain_lint" = "t"; then
# turn off tracing for this test-eval, as it simply creates
# confusing noise in the "-x" output
trace_tmp=$trace
trace=
# 117 is magic because it is unlikely to match the exit
# code of other programs
test_eval_ "(exit 117) && $1" test_eval_ "(exit 117) && $1"
if test "$?" != 117; then if test "$?" != 117; then
error "bug in the test script: broken &&-chain: $1" error "bug in the test script: broken &&-chain: $1"
fi fi
trace=$trace_tmp
fi fi
if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"; then if test -z "$immediate" || test $eval_ret = 0 ||
test -n "$expecting_failure" && test "$test_cleanup" != ":"
then
test_eval_ "$test_cleanup" test_eval_ "$test_cleanup"
fi fi
if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
@@ -408,10 +424,11 @@ test_run_() {
} }
test_skip_() { test_skip_() {
test_count=$((test_count + 1)) SHARNESS_TEST_NB=$((SHARNESS_TEST_NB + 1))
to_skip= to_skip=
for skp in $SKIP_TESTS; do for skp in $SKIP_TESTS; do
case $this_test.$test_count in # shellcheck disable=SC2254
case $this_test.$SHARNESS_TEST_NB in
$skp) $skp)
to_skip=t to_skip=t
break break
@@ -428,7 +445,7 @@ test_skip_() {
fi fi
say_color skip >&3 "skipping test: $*" say_color skip >&3 "skipping test: $*"
say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" say_color skip "ok $SHARNESS_TEST_NB # skip $1 (missing $missing_prereq${of_prereq})"
: true : true
;; ;;
*) *)
@@ -437,6 +454,13 @@ test_skip_() {
esac esac
} }
remove_trash_() {
test -d "$remove_trash" && (
cd "$(dirname "$remove_trash")" &&
rm -rf "$(basename "$remove_trash")"
)
}
# Public: Run test commands and expect them to succeed. # Public: Run test commands and expect them to succeed.
# #
# When the test passed, an "ok" message is printed and the number of successful # When the test passed, an "ok" message is printed and the number of successful
@@ -563,246 +587,6 @@ test_expect_unstable() {
echo >&3 "" echo >&3 ""
} }
# Public: Run command and ensure that it fails in a controlled way.
#
# Use it instead of "! <command>". For example, when <command> dies due to a
# segfault, test_must_fail diagnoses it as an error, while "! <command>" would
# mistakenly be treated as just another expected failure.
#
# This is one of the prefix functions to be used inside test_expect_success or
# test_expect_failure.
#
# $1.. - Command to be executed.
#
# Examples
#
# test_expect_success 'complain and die' '
# do something &&
# do something else &&
# test_must_fail git checkout ../outerspace
# '
#
# Returns 1 if the command succeeded (exit code 0).
# Returns 1 if the command died by signal (exit codes 130-192)
# Returns 1 if the command could not be found (exit code 127).
# Returns 0 otherwise.
test_must_fail() {
"$@"
exit_code=$?
if test $exit_code = 0; then
echo >&2 "test_must_fail: command succeeded: $*"
return 1
elif test $exit_code -gt 129 -a $exit_code -le 192; then
echo >&2 "test_must_fail: died by signal: $*"
return 1
elif test $exit_code = 127; then
echo >&2 "test_must_fail: command not found: $*"
return 1
fi
return 0
}
# Public: Run command and ensure that it succeeds or fails in a controlled way.
#
# Similar to test_must_fail, but tolerates success too. Use it instead of
# "<command> || :" to catch failures caused by a segfault, for instance.
#
# This is one of the prefix functions to be used inside test_expect_success or
# test_expect_failure.
#
# $1.. - Command to be executed.
#
# Examples
#
# test_expect_success 'some command works without configuration' '
# test_might_fail git config --unset all.configuration &&
# do something
# '
#
# Returns 1 if the command died by signal (exit codes 130-192)
# Returns 1 if the command could not be found (exit code 127).
# Returns 0 otherwise.
test_might_fail() {
"$@"
exit_code=$?
if test $exit_code -gt 129 -a $exit_code -le 192; then
echo >&2 "test_might_fail: died by signal: $*"
return 1
elif test $exit_code = 127; then
echo >&2 "test_might_fail: command not found: $*"
return 1
fi
return 0
}
# Public: Run command and ensure it exits with a given exit code.
#
# This is one of the prefix functions to be used inside test_expect_success or
# test_expect_failure.
#
# $1 - Expected exit code.
# $2.. - Command to be executed.
#
# Examples
#
# test_expect_success 'Merge with d/f conflicts' '
# test_expect_code 1 git merge "merge msg" B master
# '
#
# Returns 0 if the expected exit code is returned or 1 otherwise.
test_expect_code() {
want_code=$1
shift
"$@"
exit_code=$?
if test "$exit_code" = "$want_code"; then
return 0
fi
echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
return 1
}
# Public: Compare two files to see if expected output matches actual output.
#
# The TEST_CMP variable defines the command used for the comparison; it
# defaults to "diff -u". Only when the test script was started with --verbose,
# will the command's output, the diff, be printed to the standard output.
#
# This is one of the prefix functions to be used inside test_expect_success or
# test_expect_failure.
#
# $1 - Path to file with expected output.
# $2 - Path to file with actual output.
#
# Examples
#
# test_expect_success 'foo works' '
# echo expected >expected &&
# foo >actual &&
# test_cmp expected actual
# '
#
# Returns the exit code of the command set by TEST_CMP.
test_cmp() {
${TEST_CMP:-diff -u} "$@"
}
# Public: portably print a sequence of numbers.
#
# seq is not in POSIX and GNU seq might not be available everywhere,
# so it is nice to have a seq implementation, even a very simple one.
#
# $1 - Starting number.
# $2 - Ending number.
#
# Examples
#
# test_expect_success 'foo works 10 times' '
# for i in $(test_seq 1 10)
# do
# foo || return
# done
# '
#
# Returns 0 if all the specified numbers can be displayed.
test_seq() {
i="$1"
j="$2"
while test "$i" -le "$j"
do
echo "$i" || return
i=$(("$i" + 1))
done
}
# Public: Check if the file expected to be empty is indeed empty, and barfs
# otherwise.
#
# $1 - File to check for emptiness.
#
# Returns 0 if file is empty, 1 otherwise.
test_must_be_empty() {
if test -s "$1"
then
echo "'$1' is not empty, it contains:"
cat "$1"
return 1
fi
}
# debugging-friendly alternatives to "test [-f|-d|-e]"
# The commands test the existence or non-existence of $1. $2 can be
# given to provide a more precise diagnosis.
test_path_is_file () {
if ! test -f "$1"
then
echo "File $1 doesn't exist. $2"
false
fi
}
test_path_is_dir () {
if ! test -d "$1"
then
echo "Directory $1 doesn't exist. $2"
false
fi
}
# Check if the directory exists and is empty as expected, barf otherwise.
test_dir_is_empty () {
test_path_is_dir "$1" &&
if test -n "$(find "$1" -mindepth 1 -maxdepth 1)"
then
echo "Directory '$1' is not empty, it contains:"
ls -la "$1"
return 1
fi
}
# Public: Schedule cleanup commands to be run unconditionally at the end of a
# test.
#
# If some cleanup command fails, the test will not pass. With --immediate, no
# cleanup is done to help diagnose what went wrong.
#
# This is one of the prefix functions to be used inside test_expect_success or
# test_expect_failure.
#
# $1.. - Commands to prepend to the list of cleanup commands.
#
# Examples
#
# test_expect_success 'test core.capslock' '
# git config core.capslock true &&
# test_when_finished "git config --unset core.capslock" &&
# do_something
# '
#
# Returns the exit code of the last cleanup command executed.
test_when_finished() {
test_cleanup="{ $*
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
}
# Public: Schedule cleanup commands to be run unconditionally when all tests
# have run.
#
# This can be used to clean up things like test databases. It is not needed to
# clean up temporary files, as test_done already does that.
#
# Examples:
#
# cleanup mysql -e "DROP DATABASE mytest"
#
# Returns the exit code of the last cleanup command executed.
final_cleanup=
cleanup() {
final_cleanup="{ $*
} && (exit \"\$eval_ret\"); eval_ret=\$?; $final_cleanup"
}
# Public: Summarize test results and exit with an appropriate error code. # Public: Summarize test results and exit with an appropriate error code.
# #
# Must be called at the end of each test script. # Must be called at the end of each test script.
@@ -823,16 +607,17 @@ cleanup() {
# fi # fi
# #
# Returns 0 if all tests passed or 1 if there was a failure. # Returns 0 if all tests passed or 1 if there was a failure.
# shellcheck disable=SC2154,SC2034
test_done() { test_done() {
EXIT_OK=t EXIT_OK=t
if test -z "$HARNESS_ACTIVE"; then if test -z "$HARNESS_ACTIVE"; then
test_results_dir="$SHARNESS_TEST_OUTPUT_DIRECTORY/test-results" test_results_dir="$SHARNESS_TEST_OUTDIR/test-results"
mkdir -p "$test_results_dir" mkdir -p "$test_results_dir"
test_results_path="$test_results_dir/$this_test.$$.counts" test_results_path="$test_results_dir/$this_test.$$.counts"
cat >>"$test_results_path" <<-EOF cat >>"$test_results_path" <<-EOF
total $test_count total $SHARNESS_TEST_NB
success $test_success success $test_success
fixed $test_fixed fixed $test_fixed
broken $test_broken broken $test_broken
@@ -848,54 +633,43 @@ test_done() {
say_color warn "# still have $test_broken known breakage(s)" say_color warn "# still have $test_broken known breakage(s)"
fi fi
if test "$test_broken" != 0 || test "$test_fixed" != 0; then if test "$test_broken" != 0 || test "$test_fixed" != 0; then
test_remaining=$((test_count - test_broken - test_fixed)) test_remaining=$((SHARNESS_TEST_NB - test_broken - test_fixed))
msg="remaining $test_remaining test(s)" msg="remaining $test_remaining test(s)"
else else
test_remaining=$test_count test_remaining=$SHARNESS_TEST_NB
msg="$test_count test(s)" msg="$SHARNESS_TEST_NB test(s)"
fi fi
case "$test_failure" in case "$test_failure" in
0) 0)
# Maybe print SKIP message # Maybe print SKIP message
if test -n "$skip_all" && test $test_count -gt 0; then check_skip_all_
error "Can't use skip_all after running some tests" if test "$test_remaining" -gt 0; then
fi
[ -z "$skip_all" ] || skip_all=" # SKIP $skip_all"
if test $test_remaining -gt 0; then
say_color pass "# passed all $msg" say_color pass "# passed all $msg"
fi fi
say "1..$test_count$skip_all" say "1..$SHARNESS_TEST_NB$skip_all"
test_eval_ "$final_cleanup" test_eval_ "$final_cleanup"
test -d "$remove_trash" && remove_trash_
cd "$(dirname "$remove_trash")" &&
rm -rf "$(basename "$remove_trash")"
exit 0 ;; exit 0 ;;
*) *)
say_color error "# failed $test_failure among $msg" say_color error "# failed $test_failure among $msg"
say "1..$test_count" say "1..$SHARNESS_TEST_NB"
exit 1 ;; exit 1 ;;
esac esac
} }
# Public: Source directory of test code and sharness library. : "${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."}"
# This directory may be different from the directory in which tests are
# being run.
: "${SHARNESS_TEST_SRCDIR:=$(cd "$(dirname "$0")" && pwd)}"
export SHARNESS_TEST_SRCDIR
# Public: Build directory that will be added to PATH. By default, it is set to # Public: Build directory that will be added to PATH. By default, it is set to
# the parent directory of SHARNESS_TEST_DIRECTORY. # the parent directory of SHARNESS_TEST_DIRECTORY.
: "${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."}" export SHARNESS_BUILD_DIRECTORY
PATH="$SHARNESS_BUILD_DIRECTORY:$PATH" PATH="$SHARNESS_BUILD_DIRECTORY:$PATH"
export PATH SHARNESS_BUILD_DIRECTORY export PATH
# Public: Path to test script currently executed. # Public: Path to test script currently executed.
SHARNESS_TEST_FILE="$0" SHARNESS_TEST_FILE="$0"
@@ -906,7 +680,7 @@ SHARNESS_TRASH_DIRECTORY="trash directory.$(basename "$SHARNESS_TEST_FILE" ".$SH
test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY" test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY"
case "$SHARNESS_TRASH_DIRECTORY" in case "$SHARNESS_TRASH_DIRECTORY" in
/*) ;; # absolute path is good /*) ;; # absolute path is good
*) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_OUTPUT_DIRECTORY/$SHARNESS_TRASH_DIRECTORY" ;; *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_OUTDIR/$SHARNESS_TRASH_DIRECTORY" ;;
esac esac
test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY" test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY"
rm -rf "$SHARNESS_TRASH_DIRECTORY" || { rm -rf "$SHARNESS_TRASH_DIRECTORY" || {
@@ -917,11 +691,11 @@ rm -rf "$SHARNESS_TRASH_DIRECTORY" || {
# #
# Load any extensions in $srcdir/sharness.d/*.sh # Load any extensions in $testdir/sharness.d/*.sh
# #
if test -d "${SHARNESS_TEST_SRCDIR}/sharness.d" if test -d "${SHARNESS_TEST_DIRECTORY}/sharness.d"
then then
for file in "${SHARNESS_TEST_SRCDIR}"/sharness.d/*.sh for file in "${SHARNESS_TEST_DIRECTORY}"/sharness.d/*.sh
do do
# Ensure glob was not an empty match: # Ensure glob was not an empty match:
test -e "${file}" || break test -e "${file}" || break
@@ -930,6 +704,7 @@ then
then then
echo >&5 "sharness: loading extensions from ${file}" echo >&5 "sharness: loading extensions from ${file}"
fi fi
# shellcheck disable=SC1090
. "${file}" . "${file}"
if test $? != 0 if test $? != 0
then then
@@ -946,14 +721,28 @@ export SHARNESS_TRASH_DIRECTORY
HOME="$SHARNESS_TRASH_DIRECTORY" HOME="$SHARNESS_TRASH_DIRECTORY"
export HOME export HOME
# shellcheck disable=SC3028
if [ "$OSTYPE" = msys ]; then
USERPROFILE="$SHARNESS_TRASH_DIRECTORY"
export USERPROFILE
fi
mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1 mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1
# Use -P to resolve symlinks in our working directory so that the cwd # Use -P to resolve symlinks in our working directory so that the cwd
# in subprocesses like git equals our $PWD (for pathname comparisons). # in subprocesses like git equals our $PWD (for pathname comparisons).
cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1 cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1
check_skip_all_() {
if test -n "$skip_all" && test $SHARNESS_TEST_NB -gt 0; then
error "Can't use skip_all after running some tests"
fi
[ -z "$skip_all" ] || skip_all=" # SKIP $skip_all"
}
this_test=${SHARNESS_TEST_FILE##*/} this_test=${SHARNESS_TEST_FILE##*/}
this_test=${this_test%.$SHARNESS_TEST_EXTENSION} this_test=${this_test%".$SHARNESS_TEST_EXTENSION"}
for skp in $SKIP_TESTS; do for skp in $SKIP_TESTS; do
# shellcheck disable=SC2254
case "$this_test" in case "$this_test" in
$skp) $skp)
say_color info >&3 "skipping test $this_test altogether" say_color info >&3 "skipping test $this_test altogether"

View File

@@ -29,6 +29,83 @@ SHARNESS_BUILD_DIRECTORY="$(mktemp -d)"
export PATH="${PATH#*:}" export PATH="${PATH#*:}"
rmdir "$SHARNESS_BUILD_DIRECTORY" rmdir "$SHARNESS_BUILD_DIRECTORY"
GIT_AUTHOR_EMAIL=author@example.com
GIT_AUTHOR_NAME='A U Thor'
GIT_COMMITTER_EMAIL=committer@example.com
GIT_COMMITTER_NAME='C O Mitter'
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
# maintain backwards compatible default
# (as used in remote helper)
git config --global init.defaultBranch master
git config --global protocol.file.allow always
unset XDG_CONFIG_HOME
test_set_prereq() {
satisfied_prereq="$satisfied_prereq$1 "
}
satisfied_prereq=" "
case "$(uname -s)" in
MSYS*|MINGW*)
test_set_prereq WIN
export TEST_CMP='diff --strip-trailing-cr -u'
;;
esac
test_cmp() {
${TEST_CMP:-diff -u} "$@"
}
test_when_finished() {
test_cleanup="{ $*
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
}
test_expect_code() {
want_code=$1
shift
"$@"
exit_code=$?
if test "$exit_code" = "$want_code"; then
return 0
fi
echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
return 1
}
test_have_prereq() {
prerequisite=$1
case "$prerequisite" in
!*)
negative_prereq=t
prerequisite=${prerequisite#!}
;;
*)
negative_prereq=
esac
case "$satisfied_prereq" in
*" $prerequisite "*)
satisfied_this_prereq=t
;;
*)
satisfied_this_prereq=
esac
case "$satisfied_this_prereq,$negative_prereq" in
t,|,t)
return 0
;;
esac
return 1
}
if [ -z "$TEST_INSTALLED_SCRIPTS" ] ; then if [ -z "$TEST_INSTALLED_SCRIPTS" ] ; then
if [ -n "$PYTHON" ] && "$PYTHON" -c 'import mercurial' 2> /dev/null ; then if [ -n "$PYTHON" ] && "$PYTHON" -c 'import mercurial' 2> /dev/null ; then
: Use chosen Python version : Use chosen Python version
@@ -57,22 +134,3 @@ else
# The build/install process ensures Python is available # The build/install process ensures Python is available
test_set_prereq PYTHON test_set_prereq PYTHON
fi fi
GIT_AUTHOR_EMAIL=author@example.com
GIT_AUTHOR_NAME='A U Thor'
GIT_COMMITTER_EMAIL=committer@example.com
GIT_COMMITTER_NAME='C O Mitter'
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
# maintain backwards compatible default
# (as used in remote helper)
git config --global init.defaultBranch master
git config --global protocol.file.allow always
unset XDG_CONFIG_HOME
if [[ $(uname -s) = MSYS* ]]; then
test_set_prereq WIN
export TEST_CMP='diff --strip-trailing-cr -u'
fi

View File

@@ -44,3 +44,4 @@
6.7 6.7
6.8 6.8
6.9 6.9
7.0