mirror of
https://github.com/mnauw/git-remote-hg.git
synced 2025-10-29 15:46:07 +01:00
git-hg-helper: add support for subrepo management
See felipec/git-remote-hg#1
This commit is contained in:
397
git-hg-helper
397
git-hg-helper
@@ -4,6 +4,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from mercurial import hg, ui, commands, util
|
from mercurial import hg, ui, commands, util
|
||||||
|
from mercurial import context, subrepo
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -175,6 +176,84 @@ class GitHgRepo:
|
|||||||
if srepo and rev in srepo and srepo[rev]:
|
if srepo and rev in srepo and srepo[rev]:
|
||||||
return srepo
|
return srepo
|
||||||
|
|
||||||
|
def rev_describe(self, rev):
|
||||||
|
result = self.run_cmd(['describe', rev]) or \
|
||||||
|
self.run_cmd(['describe', '--tags', rev]) or \
|
||||||
|
self.run_cmd(['describe', '--contains', rev]) or \
|
||||||
|
self.run_cmd(['describe', '--all', '--always', rev])
|
||||||
|
return result.strip()
|
||||||
|
|
||||||
|
class dictmemctx(context.memctx):
|
||||||
|
|
||||||
|
def __init__(self, repo, files):
|
||||||
|
p1, p2, data = repo[None], '0' * 40, ''
|
||||||
|
context.memctx.__init__(self, repo, (p1, p2),
|
||||||
|
data, files.keys(), self.getfilectx)
|
||||||
|
self.files = files
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self.files
|
||||||
|
|
||||||
|
def getfilectx(self, repo, memctx, path):
|
||||||
|
is_exec = is_link = rename = False
|
||||||
|
if check_version(3, 1):
|
||||||
|
return context.memfilectx(repo, path, self.files[path],
|
||||||
|
is_link, is_exec, rename)
|
||||||
|
else:
|
||||||
|
return context.memfilectx(path, self.files[path],
|
||||||
|
is_link, is_exec, rename)
|
||||||
|
|
||||||
|
def read(self, relpath, rev=None):
|
||||||
|
rev = rev if rev else ':0'
|
||||||
|
obj = '%s:%s' % (rev, relpath)
|
||||||
|
# might complain bitterly to stderr if no subrepos so let's not show that
|
||||||
|
return self.run_cmd(['show', obj])
|
||||||
|
|
||||||
|
# see also subrepo.state
|
||||||
|
def state(self, remote='origin', rev=None):
|
||||||
|
"""return a state dict, mapping subrepo paths configured in .hgsub
|
||||||
|
to tuple: (source from .hgsub, revision from .hgsubstate, kind
|
||||||
|
(key in types dict))
|
||||||
|
"""
|
||||||
|
|
||||||
|
# obtain relevant files' content from specified revision
|
||||||
|
files = { }
|
||||||
|
for f in ('.hgsub', '.hgsubstate'):
|
||||||
|
files[f] = self.read(f)
|
||||||
|
log('state files for %s in revision %s:\n%s', remote, rev, files)
|
||||||
|
|
||||||
|
# wrap in a context and delegate to subrepo
|
||||||
|
# (rather than duplicating the admittedly simple parsing here)
|
||||||
|
repo = self.get_hg_repo(remote)
|
||||||
|
if not repo:
|
||||||
|
die('no hg repo for alias %s' % remote)
|
||||||
|
ctx = self.dictmemctx(repo, files)
|
||||||
|
state = subrepo.state(ctx, ui.ui())
|
||||||
|
log('obtained state %s', state)
|
||||||
|
|
||||||
|
# now filter on type and resolve relative urls
|
||||||
|
import posixpath
|
||||||
|
resolved = {}
|
||||||
|
for s in state:
|
||||||
|
src, rev, kind = state[s]
|
||||||
|
if not kind in ('hg', 'git'):
|
||||||
|
warn('skipping unsupported subrepo type %s' % kind)
|
||||||
|
continue
|
||||||
|
if not util.url(src).isabs():
|
||||||
|
parent = self.get_hg_repo_url(remote)
|
||||||
|
if not parent:
|
||||||
|
die('could not determine repo url of %s' % remote)
|
||||||
|
parent = util.url(parent)
|
||||||
|
parent.path = posixpath.join(parent.path or '', src)
|
||||||
|
parent.path = posixpath.normpath(parent.path)
|
||||||
|
src = str(parent)
|
||||||
|
# translate to git view url
|
||||||
|
if kind == 'hg':
|
||||||
|
src = 'hg::' + src
|
||||||
|
resolved[s] = (src.strip(), rev or '', kind)
|
||||||
|
log('resolved state %s', resolved)
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
class SubCommand:
|
class SubCommand:
|
||||||
|
|
||||||
@@ -405,6 +484,304 @@ class MarksCommand(SubCommand):
|
|||||||
gm.store()
|
gm.store()
|
||||||
|
|
||||||
|
|
||||||
|
class SubRepoCommand(SubCommand):
|
||||||
|
|
||||||
|
def writestate(repo, state):
|
||||||
|
"""rewrite .hgsubstate in (outer) repo with these subrepo states"""
|
||||||
|
lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
|
||||||
|
if state[s][1] != nullstate[1]]
|
||||||
|
repo.wwrite('.hgsubstate', ''.join(lines), '')
|
||||||
|
|
||||||
|
def argumentparser(self):
|
||||||
|
#usage = '%%(prog)s %s [options] <remote>...' % (self.subcommand)
|
||||||
|
# argparse.ArgumentParser(parents=[common])
|
||||||
|
# top-level
|
||||||
|
p = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
p.add_argument('--quiet', '-q', action='store_true')
|
||||||
|
p.add_argument('--recursive', '-r', action='store_true',
|
||||||
|
help='recursive execution in each submodule')
|
||||||
|
p.add_argument('--remote', metavar='ALIAS', default='origin',
|
||||||
|
help='remote alias to use as base for relative url')
|
||||||
|
p.epilog = textwrap.dedent("""\
|
||||||
|
A group of (sub-)subcommands that aid in handling Mercurial subrepos
|
||||||
|
as managed by .hgsub and .hgsubstate. The commands are (where applicable)
|
||||||
|
loosely modeled after the corresponding and similar git-submodule command,
|
||||||
|
though otherwise there is no relation whatsoever with the git-submodule system.
|
||||||
|
Each command supports recursive execution into subrepos of subrepos,
|
||||||
|
and the considered (top-level) repos can be restricted by a list of subrepo
|
||||||
|
paths as arguments. For most commands, the state files are examined
|
||||||
|
in the parent repo's index.
|
||||||
|
|
||||||
|
Though depending on your workflow, the \'update\' command will likely
|
||||||
|
suffice, along with possibly an occasional \'upstate\'.
|
||||||
|
""")
|
||||||
|
# subparsers
|
||||||
|
sub = p.add_subparsers()
|
||||||
|
# common
|
||||||
|
common = argparse.ArgumentParser(add_help=False)
|
||||||
|
common.add_argument('paths', nargs=argparse.REMAINDER)
|
||||||
|
# update
|
||||||
|
p_update = sub.add_parser('update', parents=[common],
|
||||||
|
help='update subrepos to state as specified in parent repo')
|
||||||
|
p_update.set_defaults(func=self.cmd_update)
|
||||||
|
p_update.add_argument('--rebase', action='store_true',
|
||||||
|
help='rebase the subrepo current branch onto recorded superproject commit')
|
||||||
|
p_update.add_argument('--merge', action='store_true',
|
||||||
|
help='merge recorded superproject commit into the current branch of subrepo')
|
||||||
|
p_update.add_argument('--force', action='store_true',
|
||||||
|
help='throw away local changes when switching to a different commit')
|
||||||
|
p_update.epilog = textwrap.dedent("""\
|
||||||
|
Brings all subrepos in the state as specified by the parent repo.
|
||||||
|
If not yet present in the subpath, the subrepo is cloned from the URL
|
||||||
|
specified by the parent repo (resolving relative path wrt a parent repo URL).
|
||||||
|
If already present, and target state revision is ancestor of current HEAD,
|
||||||
|
no update is done. Otherwise, a checkout to the required revision is done
|
||||||
|
(optionally forcibly so). Alternatively, a rebase or merge is performed if
|
||||||
|
so requested by the corresponding option.
|
||||||
|
""")
|
||||||
|
# foreach
|
||||||
|
p_foreach = sub.add_parser('foreach',
|
||||||
|
help='evaluate shell command in each checked out subrepo')
|
||||||
|
p_foreach.set_defaults(func=self.cmd_foreach)
|
||||||
|
p_foreach.add_argument('command', help='shell command')
|
||||||
|
p_foreach.epilog = textwrap.dedent("""\
|
||||||
|
The command is executed in the subrepo directory and has access to
|
||||||
|
the variables $path, $toplevel, $sha1, $rev and $kind.
|
||||||
|
$path is the subrepo directory relative to the parent project.
|
||||||
|
$toplevel is the absolute path to the top-level of the parent project.
|
||||||
|
$rev is the state revision info as set in .hgsubstate whereas
|
||||||
|
$sha1 is the git commit corresponding to it (which may be identical
|
||||||
|
if subrepo is a git repo). $kind is the type of subrepo, e.g. git or hg.
|
||||||
|
Unless given --quiet, the (recursively collected) submodule path is printed
|
||||||
|
before evaluating the command. A non-zero return from the command in
|
||||||
|
any submodule causes the processing to terminate.
|
||||||
|
""")
|
||||||
|
# add state
|
||||||
|
p_upstate = sub.add_parser('upstate', parents=[common],
|
||||||
|
help='update .hgsubstate to current HEAD of subrepo')
|
||||||
|
p_upstate.set_defaults(func=self.cmd_upstate)
|
||||||
|
p_upstate.epilog = textwrap.dedent("""\
|
||||||
|
Rather than dealing with the index the .hgsubstate file is simply and only
|
||||||
|
edited to reflect the current HEAD of (all or selected) subrepos
|
||||||
|
(and any adding to index and committing is left to user's discretion).
|
||||||
|
""")
|
||||||
|
# status
|
||||||
|
p_status = sub.add_parser('status', parents=[common],
|
||||||
|
help='show the status of the subrepos')
|
||||||
|
p_status.add_argument('--cached', action='store_true',
|
||||||
|
help='show index .hgsubstate revision if != subrepo HEAD')
|
||||||
|
p_status.set_defaults(func=self.cmd_status)
|
||||||
|
p_status.epilog = textwrap.dedent("""\
|
||||||
|
This will print the SHA-1 of the currently checked out commit for each
|
||||||
|
subrepo, along with the subrepo path and the output of git describe for
|
||||||
|
the SHA-1. Each SHA-1 will be prefixed with - if the submodule is not
|
||||||
|
yet set up (i.e. cloned) and + if the currently checked out submodule commit
|
||||||
|
does not match the SHA-1 found in the parent repo index state.
|
||||||
|
""")
|
||||||
|
# sync
|
||||||
|
p_sync = sub.add_parser('sync', parents=[common],
|
||||||
|
help='synchronize subrepo\'s remote URL configuration with parent configuration')
|
||||||
|
p_sync.set_defaults(func=self.cmd_sync)
|
||||||
|
p_sync.epilog = textwrap.dedent("""\
|
||||||
|
The subrepo's .git/config URL configuration is set to the value specified in
|
||||||
|
the parent's .hgstate (with relative path suitable resolved wrt parent project).
|
||||||
|
""")
|
||||||
|
return p
|
||||||
|
|
||||||
|
def git_hg_repo_try(self, path):
|
||||||
|
try:
|
||||||
|
return GitHgRepo(topdir=path)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def run_cmd(self, options, repo, *args, **kwargs):
|
||||||
|
# no output if quiet, otherwise show output by default
|
||||||
|
# unless the caller specified something explicitly
|
||||||
|
if hasattr(options, 'quiet') and options.quiet:
|
||||||
|
kwargs['stdout'] = os.devnull
|
||||||
|
elif 'stdout' not in kwargs:
|
||||||
|
kwargs['stdout'] = None
|
||||||
|
# show errors unless caller decide some way
|
||||||
|
if 'stderr' not in kwargs:
|
||||||
|
kwargs['stderr'] = None
|
||||||
|
repo.run_cmd(*args, **kwargs)
|
||||||
|
|
||||||
|
class subcontext(dict):
|
||||||
|
__getattr__= dict.__getitem__
|
||||||
|
__setattr__= dict.__setitem__
|
||||||
|
__delattr__= dict.__delitem__
|
||||||
|
def __init__(self, repo):
|
||||||
|
# recursion level
|
||||||
|
self.level = 0
|
||||||
|
# super repo object
|
||||||
|
self.repo = repo
|
||||||
|
# sub repo object (if any)
|
||||||
|
self.subrepo = None
|
||||||
|
# (recursively) collected path of subrepo
|
||||||
|
self.subpath = None
|
||||||
|
# relative path of subrepo wrt super repo
|
||||||
|
self.relpath = None
|
||||||
|
|
||||||
|
def do(self, options, args):
|
||||||
|
if args:
|
||||||
|
# all arguments are registered, so should not have leftover
|
||||||
|
# could be that main arguments were given to subcommands
|
||||||
|
warn('unparsed arguments: %s' % ' '.join(args))
|
||||||
|
log('running subcmd options %s, args %s', options, args)
|
||||||
|
# establish initial operation ctx
|
||||||
|
ctx = self.subcontext(self.githgrepo)
|
||||||
|
self.do_operation(options, args, ctx)
|
||||||
|
|
||||||
|
def do_operation(self, options, args, ctx):
|
||||||
|
log('running %s with options %s in context %s', \
|
||||||
|
options.func, options, ctx)
|
||||||
|
subrepos = ctx.repo.state(options.remote)
|
||||||
|
paths = subrepos.keys()
|
||||||
|
selabspaths = None
|
||||||
|
if ctx.level == 0 and hasattr(options, 'paths') and options.paths:
|
||||||
|
selabspaths = [ os.path.abspath(p) for p in options.paths ]
|
||||||
|
log('level %s selected paths %s', ctx.level, selabspaths)
|
||||||
|
for p in paths:
|
||||||
|
# prep context
|
||||||
|
ctx.relpath = p
|
||||||
|
ctx.subpath = os.path.join(ctx.repo.topdir, p)
|
||||||
|
# check if selected
|
||||||
|
abspath = os.path.abspath(ctx.subpath)
|
||||||
|
if selabspaths != None and abspath not in selabspaths:
|
||||||
|
log('skipping subrepo abspath %s' % abspath)
|
||||||
|
continue
|
||||||
|
ctx.subrepo = self.git_hg_repo_try(ctx.subpath)
|
||||||
|
ctx.state = subrepos[p]
|
||||||
|
# perform operation
|
||||||
|
log('exec for context %s', ctx)
|
||||||
|
options.func(options, args, ctx)
|
||||||
|
if not ctx.subrepo:
|
||||||
|
ctx.subrepo = self.git_hg_repo_try(ctx.subpath)
|
||||||
|
# prep recursion (only into git-hg subrepos)
|
||||||
|
if ctx.subrepo and options.recursive and ctx.state[2] == 'hg':
|
||||||
|
newctx = self.subcontext(ctx.subrepo)
|
||||||
|
newctx.level = ctx.level + 1
|
||||||
|
self.do_operation(options, args, newctx)
|
||||||
|
|
||||||
|
def get_git_commit(self, ctx):
|
||||||
|
src, rev, kind = ctx.state
|
||||||
|
if kind == 'hg':
|
||||||
|
gitcommit = ctx.subrepo.get_git_commit(rev)
|
||||||
|
if not gitcommit:
|
||||||
|
die('could not determine git commit for %s; a fetch may update notes' % rev)
|
||||||
|
else:
|
||||||
|
gitcommit = rev
|
||||||
|
return gitcommit
|
||||||
|
|
||||||
|
def cmd_upstate(self, options, args, ctx):
|
||||||
|
if not ctx.subrepo:
|
||||||
|
return
|
||||||
|
src, orig, kind = ctx.state
|
||||||
|
gitcommit = ctx.subrepo.rev_parse('HEAD')
|
||||||
|
if not gitcommit:
|
||||||
|
die('could not determine current HEAD state in %s' % ctx.subrepo.topdir)
|
||||||
|
rev = gitcommit
|
||||||
|
if kind == 'hg':
|
||||||
|
rev = ctx.subrepo.get_hg_rev(gitcommit)
|
||||||
|
if not rev:
|
||||||
|
die('could not determine hg changeset for commit %s' % gitcommit)
|
||||||
|
else:
|
||||||
|
rev = gitcommit
|
||||||
|
# obtain state from index
|
||||||
|
state_path = os.path.join(ctx.repo.topdir, '.hgsubstate')
|
||||||
|
# should have this, since we have subrepo (state) in the first place ...
|
||||||
|
if not os.path.exists(state_path):
|
||||||
|
die('no .hgsubstate found in repo %s' % ctx.repo.topdir)
|
||||||
|
if orig != rev:
|
||||||
|
short = ctx.subrepo.rev_parse(['--short', gitcommit])
|
||||||
|
print "Updating %s to %s [git %s]" % (ctx.subpath, rev, short)
|
||||||
|
# replace and update index
|
||||||
|
with open(state_path, 'r') as f:
|
||||||
|
state = f.read()
|
||||||
|
state = re.sub('.{40} %s' % (ctx.relpath), '%s %s' % (rev, ctx.relpath), state)
|
||||||
|
with open(state_path, 'wb') as f:
|
||||||
|
f.write(state)
|
||||||
|
|
||||||
|
def cmd_foreach(self, options, args, ctx):
|
||||||
|
if not ctx.subrepo:
|
||||||
|
return
|
||||||
|
if not options.quiet:
|
||||||
|
print 'Entering %s' % ctx.subpath
|
||||||
|
sys.stdout.flush()
|
||||||
|
newenv = os.environ.copy()
|
||||||
|
newenv['path'] = ctx.relpath
|
||||||
|
newenv['sha1'] = self.get_git_commit(ctx)
|
||||||
|
newenv['toplevel'] = os.path.abspath(ctx.repo.topdir)
|
||||||
|
newenv['rev'] = ctx.state[1]
|
||||||
|
newenv['kind'] = ctx.state[2]
|
||||||
|
proc = subprocess.Popen(options.command, shell=True, cwd=ctx.subpath, env=newenv)
|
||||||
|
proc.wait()
|
||||||
|
if proc.returncode != 0:
|
||||||
|
die('stopping at %s; script returned non-zero status' % ctx.subpath)
|
||||||
|
|
||||||
|
def cmd_update(self, options, args, ctx):
|
||||||
|
if not ctx.subrepo:
|
||||||
|
src, _, _ = ctx.state
|
||||||
|
self.run_cmd(options, ctx.repo, ['clone', src, ctx.subpath], cwd=None)
|
||||||
|
ctx.subrepo = self.git_hg_repo_try(ctx.subpath)
|
||||||
|
if not ctx.subrepo:
|
||||||
|
die('subrepo %s setup clone failed', ctx.subpath)
|
||||||
|
# force (detached) checkout of target commit following clone
|
||||||
|
cmd = [ 'checkout', '-q' ]
|
||||||
|
else:
|
||||||
|
self.run_cmd(options, ctx.subrepo, ['fetch', 'origin'], check=True)
|
||||||
|
cmd = []
|
||||||
|
# check if subrepo is up-to-date,
|
||||||
|
# i.e. if target commit is ancestor of HEAD
|
||||||
|
# (output never to be shown)
|
||||||
|
gitcommit = self.get_git_commit(ctx)
|
||||||
|
newrev = ctx.subrepo.run_cmd(['rev-list', gitcommit, '^HEAD']).strip()
|
||||||
|
if newrev and not cmd:
|
||||||
|
if options.force:
|
||||||
|
self.run_cmd(options, ctx.subrepo, ['reset', '--hard', 'HEAD'],
|
||||||
|
check=True)
|
||||||
|
if options.rebase:
|
||||||
|
cmd = [ 'rebase' ]
|
||||||
|
elif options.merge:
|
||||||
|
cmd = [ 'merge' ]
|
||||||
|
else:
|
||||||
|
# we know about the detached consequences ... keep it a bit quiet
|
||||||
|
cmd = [ 'checkout', '-q' ]
|
||||||
|
if cmd:
|
||||||
|
cmd.append(gitcommit)
|
||||||
|
self.run_cmd(options, ctx.subrepo, cmd, check=True)
|
||||||
|
|
||||||
|
def cmd_status(self, options, args, ctx):
|
||||||
|
if not ctx.subrepo:
|
||||||
|
state = '-'
|
||||||
|
revname = ''
|
||||||
|
_, gitcommit, kind = ctx.state
|
||||||
|
if kind != 'git':
|
||||||
|
gitcommit += '[hg] '
|
||||||
|
else:
|
||||||
|
gitcommit = self.get_git_commit(ctx)
|
||||||
|
head = ctx.subrepo.rev_parse('HEAD')
|
||||||
|
if head == gitcommit:
|
||||||
|
state = ' '
|
||||||
|
else:
|
||||||
|
state = '+'
|
||||||
|
# option determines what to print
|
||||||
|
if not options.cached:
|
||||||
|
gitcommit = head
|
||||||
|
revname = ctx.subrepo.rev_describe(gitcommit)
|
||||||
|
if revname:
|
||||||
|
revname = ' (%s)' % revname
|
||||||
|
print "%s%s %s%s" % (state, gitcommit, ctx.subpath, revname)
|
||||||
|
|
||||||
|
def cmd_sync(self, options, args, ctx):
|
||||||
|
if not ctx.subrepo:
|
||||||
|
return
|
||||||
|
src, _, _ = ctx.state
|
||||||
|
self.run_cmd(options, ctx.subrepo, \
|
||||||
|
['config', 'remote.%s.url' % (options.remote), src])
|
||||||
|
|
||||||
|
|
||||||
class RepoCommand(SubCommand):
|
class RepoCommand(SubCommand):
|
||||||
|
|
||||||
def argumentparser(self):
|
def argumentparser(self):
|
||||||
@@ -471,6 +848,7 @@ def get_subcommands():
|
|||||||
'git-rev': GitRevCommand,
|
'git-rev': GitRevCommand,
|
||||||
'repo': RepoCommand,
|
'repo': RepoCommand,
|
||||||
'marks': MarksCommand,
|
'marks': MarksCommand,
|
||||||
|
'sub': SubRepoCommand,
|
||||||
'help' : HelpCommand
|
'help' : HelpCommand
|
||||||
}
|
}
|
||||||
# add remote named subcommands
|
# add remote named subcommands
|
||||||
@@ -491,6 +869,7 @@ def do_usage():
|
|||||||
hg-rev \t show hg revision corresponding to a git revision
|
hg-rev \t show hg revision corresponding to a git revision
|
||||||
git-rev \t find git revision corresponding to a hg revision
|
git-rev \t find git revision corresponding to a hg revision
|
||||||
marks \t perform maintenance on repo tracking marks
|
marks \t perform maintenance on repo tracking marks
|
||||||
|
sub \t manage subrepos
|
||||||
repo \t show local hg repo backing a remote
|
repo \t show local hg repo backing a remote
|
||||||
|
|
||||||
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
|
||||||
@@ -530,6 +909,23 @@ def init_logger():
|
|||||||
format='%(asctime)-15s %(levelname)s %(message)s')
|
format='%(asctime)-15s %(levelname)s %(message)s')
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
def init_version():
|
||||||
|
global hg_version
|
||||||
|
|
||||||
|
try:
|
||||||
|
version, _, extra = util.version().partition('+')
|
||||||
|
version = list(int(e) for e in version.split('.'))
|
||||||
|
if extra:
|
||||||
|
version[1] += 1
|
||||||
|
hg_version = tuple(version)
|
||||||
|
except:
|
||||||
|
hg_version = None
|
||||||
|
|
||||||
|
def check_version(*check):
|
||||||
|
if not hg_version:
|
||||||
|
return True
|
||||||
|
return hg_version >= check
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
global subcommands
|
global subcommands
|
||||||
|
|
||||||
@@ -555,5 +951,6 @@ def main(argv):
|
|||||||
do_usage()
|
do_usage()
|
||||||
|
|
||||||
init_logger()
|
init_logger()
|
||||||
|
init_version()
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main(sys.argv))
|
sys.exit(main(sys.argv))
|
||||||
|
|||||||
362
test/helper.t
362
test/helper.t
@@ -176,4 +176,366 @@ test_expect_success 'subcommand [some-repo]' '
|
|||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
setup_repo () {
|
||||||
|
kind=$1 &&
|
||||||
|
repo=$2 &&
|
||||||
|
$kind init $repo &&
|
||||||
|
(
|
||||||
|
cd $repo &&
|
||||||
|
echo zero > content_$repo &&
|
||||||
|
$kind add content_$repo &&
|
||||||
|
$kind commit -m zero_$repo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
check () {
|
||||||
|
echo $3 > expected &&
|
||||||
|
git --git-dir=$1/.git log --format='%s' -1 $2 > actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
}
|
||||||
|
|
||||||
|
check_branch () {
|
||||||
|
if test -n "$3"
|
||||||
|
then
|
||||||
|
echo $3 > expected &&
|
||||||
|
hg -R $1 log -r $2 --template '{desc}\n' > actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
else
|
||||||
|
hg -R $1 branches > out &&
|
||||||
|
! grep $2 out
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub initial update (hg and git subrepos)' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
setup_repo hg hgrepo &&
|
||||||
|
(
|
||||||
|
cd hgrepo &&
|
||||||
|
setup_repo hg sub_hg_a &&
|
||||||
|
setup_repo hg sub_hg_b &&
|
||||||
|
setup_repo git sub_git &&
|
||||||
|
echo "sub_hg_a = sub_hg_a" > .hgsub &&
|
||||||
|
echo "sub_hg_b = sub_hg_b" >> .hgsub &&
|
||||||
|
echo "sub_git = [git]sub_git" >> .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate
|
||||||
|
)
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub update --force &&
|
||||||
|
test -f content_hgrepo &&
|
||||||
|
test -f sub_hg_a/content_sub_hg_a &&
|
||||||
|
test -f sub_hg_b/content_sub_hg_b &&
|
||||||
|
test -f sub_git/content_sub_git
|
||||||
|
) &&
|
||||||
|
|
||||||
|
check gitrepo HEAD substate &&
|
||||||
|
check gitrepo/sub_hg_a HEAD zero_sub_hg_a &&
|
||||||
|
check gitrepo/sub_hg_b HEAD zero_sub_hg_b &&
|
||||||
|
check gitrepo/sub_git HEAD zero_sub_git
|
||||||
|
'
|
||||||
|
|
||||||
|
setup_subrepos () {
|
||||||
|
setup_repo hg hgrepo &&
|
||||||
|
(
|
||||||
|
cd hgrepo &&
|
||||||
|
setup_repo hg sub_hg_a &&
|
||||||
|
(
|
||||||
|
cd sub_hg_a &&
|
||||||
|
setup_repo hg sub_hg_a_x &&
|
||||||
|
echo "sub_hg_a_x = sub_hg_a_x" > .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate_hg_a
|
||||||
|
) &&
|
||||||
|
setup_repo hg sub_hg_b &&
|
||||||
|
(
|
||||||
|
cd sub_hg_b &&
|
||||||
|
setup_repo git sub_git &&
|
||||||
|
echo "sub_git = [git]sub_git" > .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate_hg_b
|
||||||
|
) &&
|
||||||
|
echo "sub_hg_a = sub_hg_a" > .hgsub &&
|
||||||
|
echo "sub_hg_b = sub_hg_b" >> .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub initial recursive update' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
setup_subrepos &&
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub --recursive update --force &&
|
||||||
|
test -f content_hgrepo &&
|
||||||
|
test -f sub_hg_a/content_sub_hg_a &&
|
||||||
|
test -f sub_hg_a/sub_hg_a_x/content_sub_hg_a_x &&
|
||||||
|
test -f sub_hg_b/content_sub_hg_b &&
|
||||||
|
test -f sub_hg_b/sub_git/content_sub_git
|
||||||
|
) &&
|
||||||
|
|
||||||
|
check gitrepo HEAD substate &&
|
||||||
|
check gitrepo/sub_hg_a HEAD substate_hg_a &&
|
||||||
|
check gitrepo/sub_hg_b HEAD substate_hg_b &&
|
||||||
|
check gitrepo/sub_hg_a/sub_hg_a_x HEAD zero_sub_hg_a_x &&
|
||||||
|
check gitrepo/sub_hg_b/sub_git HEAD zero_sub_git
|
||||||
|
'
|
||||||
|
|
||||||
|
test_sub_update () {
|
||||||
|
export option=$1
|
||||||
|
|
||||||
|
setup_subrepos &&
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub --recursive update --force
|
||||||
|
) &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd hgrepo &&
|
||||||
|
(
|
||||||
|
cd sub_hg_a &&
|
||||||
|
(
|
||||||
|
cd sub_hg_a_x &&
|
||||||
|
echo one > content_sub_hg_a_x &&
|
||||||
|
hg commit -m one_sub_hg_a_x
|
||||||
|
) &&
|
||||||
|
hg commit -m substate_updated_hg_a
|
||||||
|
) &&
|
||||||
|
hg commit -m substate_updated
|
||||||
|
) &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git fetch origin &&
|
||||||
|
git merge origin/master &&
|
||||||
|
git-hg-helper sub --recursive update --force $option &&
|
||||||
|
test -f content_hgrepo &&
|
||||||
|
test -f sub_hg_a/content_sub_hg_a &&
|
||||||
|
test -f sub_hg_a/sub_hg_a_x/content_sub_hg_a_x &&
|
||||||
|
test -f sub_hg_b/content_sub_hg_b &&
|
||||||
|
test -f sub_hg_b/sub_git/content_sub_git
|
||||||
|
) &&
|
||||||
|
|
||||||
|
check gitrepo HEAD substate_updated &&
|
||||||
|
check gitrepo/sub_hg_a HEAD substate_updated_hg_a &&
|
||||||
|
check gitrepo/sub_hg_b HEAD substate_hg_b &&
|
||||||
|
check gitrepo/sub_hg_a/sub_hg_a_x HEAD one_sub_hg_a_x &&
|
||||||
|
check gitrepo/sub_hg_b/sub_git HEAD zero_sub_git
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub subsequent recursive update' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
test_sub_update
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub subsequent recursive update -- rebase' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
test_sub_update --rebase
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub subsequent recursive update -- merge' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
test_sub_update --merge
|
||||||
|
'
|
||||||
|
|
||||||
|
check_foreach_vars () {
|
||||||
|
cat $1 | while read kind sha1 rev path remainder
|
||||||
|
do
|
||||||
|
ok=0
|
||||||
|
if test "$kind" = "hg" ; then
|
||||||
|
if test "$sha1" != "$rev" ; then
|
||||||
|
ok=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if test "$sha1" = "$rev" ; then
|
||||||
|
ok=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
test $ok -eq 1 || echo "invalid $kind $sha1 $rev $path"
|
||||||
|
test $ok -eq 1 || return 1
|
||||||
|
done &&
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test_sub_foreach () {
|
||||||
|
setup_subrepos &&
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub --recursive update --force &&
|
||||||
|
git-hg-helper sub --recursive --quiet foreach 'echo $kind $sha1 $rev $path $toplevel' > output &&
|
||||||
|
cat output &&
|
||||||
|
echo 1 > expected_git &&
|
||||||
|
grep -c ^git output > actual_git &&
|
||||||
|
test_cmp expected_git actual_git &&
|
||||||
|
echo 3 > expected_hg &&
|
||||||
|
grep -c ^hg output > actual_hg &&
|
||||||
|
test_cmp expected_hg actual_hg &&
|
||||||
|
grep '\(hg\|git\) [0-9a-f]* [0-9a-f]* sub[^ ]* /.*' output > actual &&
|
||||||
|
test_cmp output actual &&
|
||||||
|
check_foreach_vars output
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub foreach' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
test_sub_foreach
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub sync' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
setup_repo hg hgrepo &&
|
||||||
|
(
|
||||||
|
cd hgrepo &&
|
||||||
|
setup_repo hg sub_hg &&
|
||||||
|
echo "sub_hg = sub_hg" > .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate
|
||||||
|
)
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub update --force &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd sub_hg &&
|
||||||
|
grep url .git/config > ../expected &&
|
||||||
|
git config remote.origin.url foobar &&
|
||||||
|
grep foobar .git/config
|
||||||
|
) &&
|
||||||
|
|
||||||
|
git-hg-helper sub sync &&
|
||||||
|
grep url sub_hg/.git/config > actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub addstate' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
setup_repo hg hgrepo &&
|
||||||
|
(
|
||||||
|
cd hgrepo &&
|
||||||
|
setup_repo hg sub_hg &&
|
||||||
|
setup_repo git sub_git &&
|
||||||
|
echo "sub_hg = sub_hg" > .hgsub &&
|
||||||
|
echo "sub_git = [git]sub_git" >> .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate
|
||||||
|
)
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub update --force &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd sub_hg &&
|
||||||
|
echo one > content_sub_hg &&
|
||||||
|
git add content_sub_hg &&
|
||||||
|
git commit -m one_sub_hg &&
|
||||||
|
# detached HEAD
|
||||||
|
git push origin HEAD:master &&
|
||||||
|
# also fetch to ensure notes are updated
|
||||||
|
git fetch origin
|
||||||
|
) &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd sub_git &&
|
||||||
|
echo one > content_sub_git &&
|
||||||
|
git add content_sub_git &&
|
||||||
|
git commit -m one_sub_git &&
|
||||||
|
# detached HEAD; push revision to other side ... anywhere
|
||||||
|
git push origin HEAD:refs/heads/new
|
||||||
|
)
|
||||||
|
) &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub upstate &&
|
||||||
|
git diff &&
|
||||||
|
git status --porcelain | grep .hgsubstate &&
|
||||||
|
git add .hgsubstate &&
|
||||||
|
git commit -m update_sub &&
|
||||||
|
git push origin master
|
||||||
|
) &&
|
||||||
|
|
||||||
|
hg clone hgrepo hgclone &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd hgclone &&
|
||||||
|
hg update
|
||||||
|
) &&
|
||||||
|
|
||||||
|
check_branch hgclone default update_sub &&
|
||||||
|
check_branch hgclone/sub_hg default one_sub_hg &&
|
||||||
|
check hgclone/sub_git HEAD one_sub_git
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'subcommand sub status' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
setup_repo hg hgrepo &&
|
||||||
|
(
|
||||||
|
cd hgrepo &&
|
||||||
|
setup_repo hg sub_hg_a &&
|
||||||
|
setup_repo hg sub_hg_b &&
|
||||||
|
setup_repo git sub_git &&
|
||||||
|
echo "sub_hg_a = sub_hg_a" > .hgsub &&
|
||||||
|
echo "sub_hg_b = sub_hg_b" >> .hgsub &&
|
||||||
|
echo "sub_git = [git]sub_git" >> .hgsub &&
|
||||||
|
hg add .hgsub &&
|
||||||
|
hg commit -m substate
|
||||||
|
)
|
||||||
|
|
||||||
|
git clone hg::hgrepo gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git-hg-helper sub update sub_hg_a --force &&
|
||||||
|
git-hg-helper sub update sub_git --force &&
|
||||||
|
(
|
||||||
|
# advance and add a tag to the git repo
|
||||||
|
cd sub_git &&
|
||||||
|
echo one > content_sub_git &&
|
||||||
|
git add content_sub_git &&
|
||||||
|
git commit -m one_sub_git &&
|
||||||
|
git tag feature-a
|
||||||
|
) &&
|
||||||
|
|
||||||
|
git-hg-helper sub status --cached > output &&
|
||||||
|
cat output &&
|
||||||
|
grep "^ .*sub_hg_a (.*master.*)$" output &&
|
||||||
|
grep "^-.*sub_hg_b$" output &&
|
||||||
|
grep "^+.*sub_git (feature-a~1)$" output &&
|
||||||
|
git-hg-helper sub status sub_git > output &&
|
||||||
|
cat output &&
|
||||||
|
grep "^+.*sub_git (feature-a)$" output > actual &&
|
||||||
|
test_cmp output actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
Reference in New Issue
Block a user