mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 17:27:53 +03:00
a0a2518bf6
Summary: We're down to just one use of hgsubversion. In that use it's convienent to skip empty commits when pushing. Let's add logic to bypass empty commits. Differential Revision: D17452527 fbshipit-source-id: 5ef76df7d0a44f2d43f4ea0d8678e4174c0286ee
779 lines
28 KiB
Python
779 lines
28 KiB
Python
# no-check-code -- see T24862348
|
|
# isort:skip_file
|
|
|
|
from __future__ import absolute_import
|
|
import inspect
|
|
import os
|
|
|
|
from edenscm.hgext import rebase as hgrebase
|
|
|
|
from edenscm.mercurial import cmdutil
|
|
from edenscm.mercurial import discovery
|
|
|
|
try:
|
|
from edenscm.mercurial import exchange
|
|
|
|
exchange.push # existed in first iteration of this file
|
|
except ImportError:
|
|
# We only *use* the exchange module in hg 3.2+, so this is safe
|
|
pass
|
|
from edenscm.mercurial import patch
|
|
from edenscm.mercurial import progress
|
|
from edenscm.mercurial import hg
|
|
from edenscm.mercurial import util as hgutil
|
|
from edenscm.mercurial import node
|
|
from edenscm.mercurial import i18n
|
|
from edenscm.mercurial import extensions
|
|
from edenscm.mercurial import scmutil
|
|
|
|
from . import replay
|
|
from . import pushmod
|
|
from . import stupid as stupidmod
|
|
from . import svnwrap
|
|
from . import util
|
|
|
|
try:
|
|
from edenscm.mercurial import obsolete
|
|
except ImportError:
|
|
obsolete = None
|
|
|
|
pullfuns = {True: replay.convert_rev, False: stupidmod.convert_rev}
|
|
|
|
revmeta = [
|
|
("revision", "revnum"),
|
|
("user", "author"),
|
|
("date", "date"),
|
|
("message", "message"),
|
|
]
|
|
|
|
|
|
def version(orig, ui, *args, **opts):
|
|
svn = opts.pop("svn", None)
|
|
orig(ui, *args, **opts)
|
|
if svn:
|
|
svnversion, bindings = svnwrap.version()
|
|
ui.status("\n")
|
|
ui.status("hgsubversion: %s\n" % util.version(ui))
|
|
ui.status("Subversion: %s\n" % svnversion)
|
|
ui.status("bindings: %s\n" % bindings)
|
|
|
|
|
|
def parents(orig, ui, repo, *args, **opts):
|
|
"""show Mercurial & Subversion parents of the working dir or revision
|
|
"""
|
|
if not opts.get("svn", False):
|
|
return orig(ui, repo, *args, **opts)
|
|
meta = repo.svnmeta()
|
|
hashes = meta.revmap.hashes()
|
|
ha = util.parentrev(ui, repo, meta, hashes)
|
|
if ha.node() == node.nullid:
|
|
raise hgutil.Abort("No parent svn revision!")
|
|
displayer = cmdutil.show_changeset(ui, repo, opts, buffered=False)
|
|
displayer.show(ha)
|
|
return 0
|
|
|
|
|
|
def getpeer(ui, opts, source):
|
|
# Since 2.3 (1ac628cd7113)
|
|
peer = getattr(hg, "peer", None)
|
|
if peer:
|
|
return peer(ui, opts, source)
|
|
return hg.repository(ui, source)
|
|
|
|
|
|
def getlocalpeer(ui, opts, source):
|
|
peer = getpeer(ui, opts, source)
|
|
repo = getattr(peer, "local", lambda: peer)()
|
|
if isinstance(repo, bool):
|
|
repo = peer
|
|
return repo
|
|
|
|
|
|
def getcaps(other):
|
|
caps = getattr(other, "caps", None) or getattr(other, "capabilities", None) or set()
|
|
# 'capabilities' might be an instance method
|
|
if hgutil.safehasattr(caps, "__call__"):
|
|
caps = caps()
|
|
return caps
|
|
|
|
|
|
def incoming(orig, ui, repo, origsource="default", **opts):
|
|
"""show incoming revisions from Subversion
|
|
"""
|
|
|
|
source, revs, checkout = util.parseurl(ui.expandpath(origsource))
|
|
other = getpeer(ui, opts, source)
|
|
if "subversion" not in getcaps(other):
|
|
return orig(ui, repo, origsource, **opts)
|
|
|
|
svn = other.svn
|
|
meta = repo.svnmeta(svn.uuid, svn.subdir)
|
|
|
|
ui.status("incoming changes from %s\n" % other.svnurl)
|
|
svnrevisions = list(svn.revisions(start=meta.revmap.lastpulled))
|
|
if opts.get("newest_first"):
|
|
svnrevisions.reverse()
|
|
# Returns 0 if there are incoming changes, 1 otherwise.
|
|
if len(svnrevisions) > 0:
|
|
ret = 0
|
|
else:
|
|
ret = 1
|
|
for r in svnrevisions:
|
|
ui.status("\n")
|
|
for label, attr in revmeta:
|
|
l1 = label + ":"
|
|
val = str(getattr(r, attr)).strip()
|
|
if not ui.verbose:
|
|
val = val.split("\n")[0]
|
|
ui.status("%s%s\n" % (l1.ljust(13), val))
|
|
return ret
|
|
|
|
|
|
def findcommonoutgoing(
|
|
repo, other, onlyheads=None, force=False, commoninc=None, portable=False
|
|
):
|
|
assert other.capable("subversion")
|
|
# split off #rev; TODO implement --revision/#rev support
|
|
svn = other.svn
|
|
meta = repo.svnmeta(svn.uuid, svn.subdir)
|
|
parent = repo[None].parents()[0].node()
|
|
hashes = meta.revmap.hashes()
|
|
common, heads = util.outgoing_common_and_heads(repo, hashes, parent)
|
|
outobj = getattr(discovery, "outgoing", None)
|
|
if outobj is not None:
|
|
argspec = inspect.getargspec(outobj.__init__)
|
|
if "repo" in argspec[0]:
|
|
# Starting from Mercurial 3.9.1 outgoing.__init__ accepts
|
|
# `repo` object instead of a `changelog`
|
|
return outobj(repo, common, heads)
|
|
# Mercurial 2.1 through 3.9
|
|
return outobj(repo.changelog, common, heads)
|
|
# Mercurial 2.0 and earlier
|
|
return common, heads
|
|
|
|
|
|
def findoutgoing(repo, dest=None, heads=None, force=False):
|
|
"""show changesets not found in the Subversion repository
|
|
"""
|
|
assert dest.capable("subversion")
|
|
# split off #rev; TODO implement --revision/#rev support
|
|
# svnurl, revs, checkout = util.parseurl(dest.svnurl, heads)
|
|
svn = dest.svn
|
|
meta = repo.svnmeta(svn.uuid, svn.subdir)
|
|
parent = repo[None].parents()[0].node()
|
|
hashes = meta.revmap.hashes()
|
|
return util.outgoing_revisions(repo, hashes, parent)
|
|
|
|
|
|
def diff(orig, ui, repo, *args, **opts):
|
|
"""show a diff of the most recent revision against its parent from svn
|
|
"""
|
|
if not opts.get("svn", False) or opts.get("change", None):
|
|
return orig(ui, repo, *args, **opts)
|
|
meta = repo.svnmeta()
|
|
hashes = meta.revmap.hashes()
|
|
if not opts.get("rev", None):
|
|
parent = repo[None].parents()[0]
|
|
o_r = util.outgoing_revisions(repo, hashes, parent.node())
|
|
if o_r:
|
|
parent = repo[o_r[-1]].parents()[0]
|
|
opts["rev"] = ["%s:." % node.hex(parent.node())]
|
|
node1, node2 = scmutil.revpair(repo, opts["rev"])
|
|
baserev, _junk = hashes.get(node1, (-1, "junk"))
|
|
newrev, _junk = hashes.get(node2, (-1, "junk"))
|
|
it = patch.diff(
|
|
repo,
|
|
node1,
|
|
node2,
|
|
opts=patch.diffopts(
|
|
ui,
|
|
opts={
|
|
"git": True,
|
|
"show_function": False,
|
|
"ignore_all_space": False,
|
|
"ignore_space_change": False,
|
|
"ignore_blank_lines": False,
|
|
"unified": True,
|
|
"text": False,
|
|
},
|
|
),
|
|
)
|
|
ui.write(util.filterdiff("".join(it), baserev, newrev))
|
|
|
|
|
|
def push(repo, dest, force, revs):
|
|
"""push revisions starting at a specified head back to Subversion.
|
|
"""
|
|
assert not revs, "designated revisions for push remains unimplemented."
|
|
cmdutil.bailifchanged(repo)
|
|
checkpush = getattr(repo, "checkpush", None)
|
|
if checkpush:
|
|
try:
|
|
# The checkpush function changed as of e10000369b47 (first
|
|
# in 3.0) in mercurial
|
|
from edenscm.mercurial.exchange import pushoperation
|
|
|
|
pushop = pushoperation(repo, dest, force, revs)
|
|
checkpush(pushop)
|
|
except (ImportError, TypeError):
|
|
checkpush(force, revs)
|
|
|
|
ui = repo.ui
|
|
old_encoding = util.swap_out_encoding()
|
|
|
|
try:
|
|
hasobsolete = obsolete._enabled or obsolete.isenabled(
|
|
repo, obsolete.createmarkersopt
|
|
)
|
|
except:
|
|
hasobsolete = False
|
|
|
|
temporary_commits = []
|
|
obsmarkers = []
|
|
try:
|
|
# TODO: implement --rev/#rev support
|
|
# TODO: do credentials specified in the URL still work?
|
|
svn = dest.svn
|
|
meta = repo.svnmeta(svn.uuid, svn.subdir)
|
|
|
|
# Strategy:
|
|
# 1. Find all outgoing commits from this head
|
|
if len(repo[None].parents()) != 1:
|
|
ui.status("Cowardly refusing to push branch merge\n")
|
|
return 0 # results in nonzero exit status, see hg's commands.py
|
|
workingrev = repo[None].parents()[0]
|
|
workingbranch = "default"
|
|
ui.status("searching for changes\n")
|
|
hashes = meta.revmap.hashes()
|
|
outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
|
|
if not (outgoing and len(outgoing)):
|
|
ui.status("no changes found\n")
|
|
return 1 # so we get a sane exit status, see hg's commands.push
|
|
|
|
tip_ctx = repo[outgoing[-1]].p1()
|
|
svnbranch = tip_ctx.branch()
|
|
modified_files = {}
|
|
pushedrev = None
|
|
for i in range(len(outgoing) - 1, -1, -1):
|
|
# 2. Pick the oldest changeset that needs to be pushed
|
|
current_ctx = repo[outgoing[i]]
|
|
original_ctx = current_ctx
|
|
|
|
if len(current_ctx.parents()) != 1:
|
|
ui.status(
|
|
"Found a branch merge, this needs discussion and "
|
|
"implementation.\n"
|
|
)
|
|
# results in nonzero exit status, see hg's commands.py
|
|
return 0
|
|
|
|
if not current_ctx.files():
|
|
continue
|
|
|
|
if ui.configbool("hgsubversion", "skippostpushpulls"):
|
|
# We use the revmap for the first commit.
|
|
# After that, we use what we received from svn.
|
|
tip_hash = pushedrev.revnum if pushedrev else hashes[tip_ctx.node()][0]
|
|
else:
|
|
# 3. Move the changeset to the tip of the branch if necessary
|
|
conflicts = False
|
|
for file in current_ctx.files():
|
|
if file in modified_files:
|
|
conflicts = True
|
|
break
|
|
|
|
if conflicts or current_ctx.branch() != svnbranch:
|
|
util.swap_out_encoding(old_encoding)
|
|
try:
|
|
|
|
def extrafn(ctx, extra):
|
|
extra["branch"] = ctx.branch()
|
|
|
|
ui.note("rebasing %s onto %s \n" % (current_ctx, tip_ctx))
|
|
hgrebase.rebase(
|
|
ui,
|
|
repo,
|
|
dest=node.hex(tip_ctx.node()),
|
|
rev=[node.hex(current_ctx.node())],
|
|
extrafn=extrafn,
|
|
keep=True,
|
|
)
|
|
finally:
|
|
util.swap_out_encoding()
|
|
|
|
# Don't trust the pre-rebase repo and context.
|
|
repo = getlocalpeer(ui, {}, meta.path)
|
|
meta = repo.svnmeta(svn.uuid, svn.subdir)
|
|
hashes = meta.revmap.hashes()
|
|
tip_ctx = repo[tip_ctx.node()]
|
|
for c in tip_ctx.descendants():
|
|
rebasesrc = c.extra().get("rebase_source")
|
|
if rebasesrc and node.bin(rebasesrc) == current_ctx.node():
|
|
current_ctx = c
|
|
temporary_commits.append(c.node())
|
|
break
|
|
tip_hash = hashes[tip_ctx.node()][0]
|
|
|
|
# 4. Push the changeset to subversion
|
|
try:
|
|
ui.status("committing %s\n" % current_ctx)
|
|
pushedrev = pushmod.commit(
|
|
ui, repo, current_ctx, original_ctx, meta, tip_hash, svn
|
|
)
|
|
except pushmod.NoFilesException:
|
|
ui.warn(
|
|
"Could not push revision %s because it had no changes "
|
|
"in svn.\n" % current_ctx
|
|
)
|
|
return
|
|
|
|
# This hook is here purely for testing. It allows us to
|
|
# onsistently trigger hit the race condition between
|
|
# pushing and pulling here. In particular, we use it to
|
|
# trigger another revision landing between the time we
|
|
# push a revision and pull it back.
|
|
repo.hook("debug-hgsubversion-between-push-and-pull-for-tests")
|
|
|
|
if ui.configbool("hgsubversion", "skippostpushpulls"):
|
|
continue
|
|
|
|
# 5. Pull the latest changesets from subversion, which will
|
|
# include the one we just committed (and possibly others).
|
|
r = pull(repo, dest, force=force, meta=meta)
|
|
assert not r or r == 0
|
|
|
|
# 6. Move our tip to the latest pulled tip
|
|
for c in tip_ctx.descendants():
|
|
if c.node() in hashes and c.branch() == svnbranch:
|
|
if meta.get_source_rev(ctx=c)[0] == pushedrev.revnum:
|
|
# This is corresponds to the changeset we just pushed
|
|
if hasobsolete:
|
|
obsmarkers.append([(original_ctx, [c])])
|
|
|
|
tip_ctx = c
|
|
|
|
# Remember what files have been modified since the
|
|
# whole push started.
|
|
for file in c.files():
|
|
modified_files[file] = True
|
|
|
|
# 7. Rebase any children of the commit we just pushed
|
|
# that are not in the outgoing set
|
|
for c in original_ctx.children():
|
|
if not c.node() in hashes and not c.node() in outgoing:
|
|
util.swap_out_encoding(old_encoding)
|
|
try:
|
|
# Path changed as subdirectories were getting
|
|
# deleted during push.
|
|
saved_path = os.getcwd()
|
|
os.chdir(repo.root)
|
|
|
|
def extrafn(ctx, extra):
|
|
extra["branch"] = ctx.branch()
|
|
|
|
ui.status("rebasing non-outgoing %s onto %s\n" % (c, tip_ctx))
|
|
needs_rebase_set = "%s::" % node.hex(c.node())
|
|
hgrebase.rebase(
|
|
ui,
|
|
repo,
|
|
dest=node.hex(tip_ctx.node()),
|
|
rev=[needs_rebase_set],
|
|
extrafn=extrafn,
|
|
keep=not hasobsolete,
|
|
)
|
|
finally:
|
|
os.chdir(saved_path)
|
|
util.swap_out_encoding()
|
|
|
|
util.swap_out_encoding(old_encoding)
|
|
try:
|
|
hg.update(repo, repo.branchtip(workingbranch))
|
|
finally:
|
|
util.swap_out_encoding()
|
|
|
|
with repo.wlock():
|
|
with repo.lock():
|
|
if hasobsolete:
|
|
for marker in obsmarkers:
|
|
obsolete.createmarkers(repo, marker)
|
|
beforepush = marker[0][0]
|
|
afterpush = marker[0][1][0]
|
|
ui.note(
|
|
"marking %s as obsoleted by %s\n"
|
|
% (beforepush.hex(), afterpush.hex())
|
|
)
|
|
else:
|
|
# strip the original changesets since the push was
|
|
# successful and changeset obsolescence is unavailable
|
|
util.strip(ui, repo, outgoing, "all")
|
|
finally:
|
|
try:
|
|
# It's always safe to delete the temporary commits.
|
|
# The originals are not deleted unless the push
|
|
# completely succeeded.
|
|
if temporary_commits:
|
|
# If the repo is on a temporary commit, get off before
|
|
# the strip.
|
|
parent = repo[None].p1()
|
|
if parent.node() in temporary_commits:
|
|
hg.update(repo, parent.p1().node())
|
|
with repo.wlock():
|
|
with repo.lock():
|
|
if hasobsolete:
|
|
relations = [(repo[n], []) for n in temporary_commits]
|
|
obsolete.createmarkers(repo, relations)
|
|
else:
|
|
util.strip(ui, repo, temporary_commits, backup=None)
|
|
|
|
finally:
|
|
util.swap_out_encoding(old_encoding)
|
|
return 1 # so we get a sane exit status, see hg's commands.push
|
|
|
|
|
|
def exchangepush(orig, repo, remote, force=False, revs=None, bookmarks=(), **kwargs):
|
|
capable = getattr(remote, "capable", lambda x: False)
|
|
if capable("subversion"):
|
|
pushop = exchange.pushoperation(repo, remote, force, revs, bookmarks=bookmarks)
|
|
pushop.cgresult = push(repo, remote, force, revs)
|
|
return pushop
|
|
else:
|
|
return orig(repo, remote, force, revs, bookmarks=bookmarks, **kwargs)
|
|
|
|
|
|
def pull(repo, source, heads=None, force=False, meta=None):
|
|
"""pull new revisions from Subversion"""
|
|
assert source.capable("subversion")
|
|
if heads is None:
|
|
heads = []
|
|
svn_url = source.svnurl
|
|
|
|
# Split off #rev
|
|
svn_url, heads, checkout = util.parseurl(svn_url, heads)
|
|
old_encoding = util.swap_out_encoding()
|
|
total = None
|
|
try:
|
|
have_replay = not repo.ui.configbool("hgsubversion", "stupid")
|
|
if not have_replay:
|
|
repo.ui.note("fetching stupidly...\n")
|
|
|
|
svn = source.svn
|
|
if meta is None:
|
|
meta = repo.svnmeta(svn.uuid, svn.subdir)
|
|
|
|
stopat_rev = util.parse_revnum(svn, checkout)
|
|
|
|
if meta.layout == "auto":
|
|
meta.layout = meta.layout_from_subversion(svn, (stopat_rev or None))
|
|
repo.ui.note("using %s layout\n" % meta.layout)
|
|
|
|
if meta.branch:
|
|
if meta.layout != "single":
|
|
msg = (
|
|
"branch cannot be specified for Subversion clones using "
|
|
"standard directory layout"
|
|
)
|
|
raise hgutil.Abort(msg)
|
|
|
|
meta.branchmap["default"] = meta.branch
|
|
|
|
ui = repo.ui
|
|
start = meta.revmap.lastpulled
|
|
|
|
if start <= 0:
|
|
# we are initializing a new repository
|
|
start = util.parse_revnum(
|
|
svn, repo.ui.config("hgsubversion", "startrev", 0)
|
|
)
|
|
|
|
if start > 0:
|
|
if meta.layout == "standard":
|
|
raise hgutil.Abort(
|
|
"non-zero start revisions are only "
|
|
"supported for single-directory clones."
|
|
)
|
|
ui.note("starting at revision %d; any prior will be ignored\n" % start)
|
|
# fetch all revisions *including* the one specified...
|
|
start -= 1
|
|
|
|
# anything less than zero makes no sense
|
|
if start < 0:
|
|
start = 0
|
|
|
|
skiprevs = repo.ui.configlist("hgsubversion", "unsafeskip", "")
|
|
skiprevs = set(util.parse_revnum(svn, r) for r in skiprevs)
|
|
|
|
oldrevisions = len(meta.revmap)
|
|
if stopat_rev:
|
|
total = stopat_rev - start
|
|
else:
|
|
total = svn.HEAD - start
|
|
lastpulled = None
|
|
|
|
try:
|
|
# start converting revisions
|
|
firstrun = True
|
|
with repo.wlock(), repo.lock(), repo.transaction(
|
|
"svn"
|
|
), meta.revmap._transaction() if hgutil.safehasattr(
|
|
meta.revmap, "_transaction"
|
|
) else hgutil.nullcontextmanager(), progress.bar(
|
|
ui, "pull", total=total
|
|
) as prog:
|
|
for r in svn.revisions(start=start, stop=stopat_rev):
|
|
if r.revnum in skiprevs or (
|
|
r.author is None
|
|
and r.message == "This is an empty revision for padding."
|
|
):
|
|
lastpulled = r.revnum
|
|
continue
|
|
tbdelta = meta.update_branch_tag_map_for_rev(r)
|
|
# got a 502? Try more than once!
|
|
tries = 0
|
|
converted = False
|
|
while not converted:
|
|
try:
|
|
msg = meta.getmessage(r).strip()
|
|
if msg:
|
|
msg = [s.strip() for s in msg.splitlines() if s][0]
|
|
if getattr(ui, "termwidth", False):
|
|
w = ui.termwidth()
|
|
else:
|
|
w = hgutil.termwidth()
|
|
bits = (r.revnum, r.author, msg)
|
|
ui.status(("[r%d] %s: %s" % bits)[:w] + "\n")
|
|
prog.value = r.revnum - start
|
|
|
|
meta.save_tbdelta(tbdelta)
|
|
close = pullfuns[have_replay](
|
|
ui, meta, svn, r, tbdelta, firstrun
|
|
)
|
|
meta.committags(r, close)
|
|
for branch, parent in close.iteritems():
|
|
if parent in (None, node.nullid):
|
|
continue
|
|
meta.delbranch(branch, parent, r)
|
|
|
|
converted = True
|
|
firstrun = False
|
|
|
|
except svnwrap.SubversionRepoCanNotReplay as e:
|
|
ui.status("%s\n" % e.message)
|
|
stupidmod.print_your_svn_is_old_message(ui)
|
|
have_replay = False
|
|
except svnwrap.SubversionException as e:
|
|
if (
|
|
e.args[1] == svnwrap.ERR_RA_DAV_REQUEST_FAILED
|
|
and "502" in str(e)
|
|
and tries < 3
|
|
):
|
|
tries += 1
|
|
ui.status("Got a 502, retrying (%s)\n" % tries)
|
|
else:
|
|
ui.traceback()
|
|
raise hgutil.Abort(*e.args)
|
|
|
|
lastpulled = r.revnum
|
|
meta.save()
|
|
|
|
except KeyboardInterrupt:
|
|
ui.traceback()
|
|
finally:
|
|
util.swap_out_encoding(old_encoding)
|
|
|
|
if lastpulled is not None:
|
|
meta.revmap.lastpulled = lastpulled
|
|
revisions = len(meta.revmap) - oldrevisions
|
|
|
|
if revisions == 0:
|
|
ui.status(i18n._("no changes found\n"))
|
|
return 0
|
|
else:
|
|
ui.status("pulled %d revisions\n" % revisions)
|
|
|
|
|
|
def exchangepull(orig, repo, remote, heads=None, force=False, bookmarks=(), **kwargs):
|
|
capable = getattr(remote, "capable", lambda x: False)
|
|
if capable("subversion"):
|
|
# transaction manager is present in Mercurial >= 3.3
|
|
try:
|
|
trmanager = getattr(exchange, "transactionmanager")
|
|
except AttributeError:
|
|
trmanager = None
|
|
pullop = exchange.pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
|
|
if trmanager:
|
|
pullop.trmanager = trmanager(repo, "pull", remote.url())
|
|
try:
|
|
pullop.cgresult = pull(repo, remote, heads, force)
|
|
return pullop
|
|
finally:
|
|
if trmanager:
|
|
pullop.trmanager.release()
|
|
else:
|
|
pullop.releasetransaction()
|
|
else:
|
|
return orig(repo, remote, heads, force, bookmarks=bookmarks, **kwargs)
|
|
|
|
|
|
def rebase(orig, ui, repo, *pats, **opts):
|
|
"""rebase current unpushed revisions onto the Subversion head
|
|
|
|
This moves a line of development from making its own head to the top of
|
|
Subversion development, linearizing the changes. In order to make sure you
|
|
rebase on top of the current top of Subversion work, you should probably run
|
|
'hg svn pull' before running this.
|
|
|
|
Also looks for svnextrafn and svnsourcerev in **opts.
|
|
"""
|
|
if not opts.get("svn", False):
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
def extrafn2(ctx, extra):
|
|
"""defined here so we can add things easily.
|
|
"""
|
|
extra["branch"] = ctx.branch()
|
|
|
|
extrafn = opts.get("svnextrafn", extrafn2)
|
|
sourcerev = opts.get("svnsourcerev", repo[None].parents()[0].node())
|
|
meta = repo.svnmeta()
|
|
hashes = meta.revmap.hashes()
|
|
o_r = util.outgoing_revisions(repo, hashes, sourcerev=sourcerev)
|
|
if not o_r:
|
|
ui.note("nothing to rebase\n")
|
|
return 0
|
|
if len(repo[sourcerev].children()):
|
|
ui.status("refusing to rebase non-head commit like a coward\n")
|
|
return 0
|
|
parent_rev = repo[o_r[-1]].parents()[0]
|
|
target_rev = parent_rev
|
|
p_n = parent_rev.node()
|
|
exhausted_choices = False
|
|
while target_rev.children() and not exhausted_choices:
|
|
for c in target_rev.children():
|
|
exhausted_choices = True
|
|
n = c.node()
|
|
if n in hashes and hashes[n][1] == hashes[p_n][1]:
|
|
target_rev = c
|
|
exhausted_choices = False
|
|
break
|
|
if parent_rev == target_rev:
|
|
ui.status("already up to date!\n")
|
|
return 0
|
|
opts = {
|
|
"dest": node.hex(target_rev.node()),
|
|
"base": node.hex(sourcerev),
|
|
"extrafn": extrafn,
|
|
}
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
|
|
optionmap = {
|
|
"tagpaths": ("hgsubversion", "tagpaths"),
|
|
"authors": ("hgsubversion", "authormap"),
|
|
"mapauthorscmd": ("hgsubversion", "mapauthorscmd"),
|
|
"branchdir": ("hgsubversion", "branchdir"),
|
|
"trunkdir": ("hgsubversion", "trunkdir"),
|
|
"infix": ("hgsubversion", "infix"),
|
|
"filemap": ("hgsubversion", "filemap"),
|
|
"branchmap": ("hgsubversion", "branchmap"),
|
|
"tagmap": ("hgsubversion", "tagmap"),
|
|
"stupid": ("hgsubversion", "stupid"),
|
|
"defaulthost": ("hgsubversion", "defaulthost"),
|
|
"defaultauthors": ("hgsubversion", "defaultauthors"),
|
|
"usebranchnames": ("hgsubversion", "usebranchnames"),
|
|
"layout": ("hgsubversion", "layout"),
|
|
"startrev": ("hgsubversion", "startrev"),
|
|
}
|
|
|
|
extrasections = set(["hgsubversionbranch"])
|
|
|
|
dontretain = {
|
|
"hgsubversion": set(["authormap", "filemap", "layout"]),
|
|
"hgsubversionbranch": set(),
|
|
}
|
|
|
|
|
|
def clone(orig, ui, source, dest=None, **opts):
|
|
"""
|
|
Some of the options listed below only apply to Subversion
|
|
%(target)s. See 'hg help %(extension)s' for more information on
|
|
them as well as other ways of customising the conversion process.
|
|
"""
|
|
|
|
data = {}
|
|
|
|
def hgclonewrapper(orig, ui, *args, **opts):
|
|
origsource = args[1]
|
|
|
|
if isinstance(origsource, str):
|
|
source, branch, checkout = util.parseurl(
|
|
ui.expandpath(origsource), opts.get("branch")
|
|
)
|
|
srcrepo = getpeer(ui, opts, source)
|
|
else:
|
|
srcrepo = origsource
|
|
|
|
if srcrepo.capable("subversion"):
|
|
branches = opts.pop("branch", None)
|
|
if branches:
|
|
data["branches"] = branches
|
|
ui.setconfig("hgsubversion", "branch", branches[-1])
|
|
|
|
data["srcrepo"], data["dstrepo"] = orig(ui, *args, **opts)
|
|
|
|
return data["srcrepo"], data["dstrepo"]
|
|
|
|
for opt, (section, name) in optionmap.iteritems():
|
|
if opt in opts and opts[opt]:
|
|
ui.setconfig(section, name, str(opts.pop(opt)))
|
|
|
|
# calling hg.clone directoly to get the repository instances it returns,
|
|
# breaks in subtle ways, so we double-wrap
|
|
orighgclone = None
|
|
try:
|
|
orighgclone = extensions.wrapfunction(hg, "clone", hgclonewrapper)
|
|
orig(ui, source, dest, **opts)
|
|
finally:
|
|
if orighgclone:
|
|
hg.clone = orighgclone
|
|
|
|
# do this again; the ui instance isn't shared between the wrappers
|
|
if data.get("branches"):
|
|
ui.setconfig("hgsubversion", "branch", data["branches"][-1])
|
|
|
|
dstrepo = data.get("dstrepo")
|
|
srcrepo = data.get("srcrepo")
|
|
dst = dstrepo.local()
|
|
|
|
if dstrepo.local() and srcrepo.capable("subversion"):
|
|
dst = dstrepo.local()
|
|
fd = dst.vfs("hgrc", "a", text=True)
|
|
preservesections = set(s for s, v in optionmap.itervalues())
|
|
preservesections |= extrasections
|
|
for section in preservesections:
|
|
config = dict(ui.configitems(section))
|
|
for name in dontretain[section]:
|
|
config.pop(name, None)
|
|
|
|
if config:
|
|
fd.write("\n[%s]\n" % section)
|
|
map(fd.write, ("%s = %s\n" % p for p in config.iteritems()))
|
|
|
|
|
|
def generic(orig, ui, repo, *args, **opts):
|
|
"""
|
|
Subversion %(target)s can be used for %(command)s. See 'hg help
|
|
%(extension)s' for more on the conversion process.
|
|
"""
|
|
|
|
branch = opts.get("branch", None)
|
|
if branch:
|
|
ui.setconfig("hgsubversion", "branch", branch[-1])
|
|
|
|
for opt, (section, name) in optionmap.iteritems():
|
|
if opt in opts and opts[opt]:
|
|
if isinstance(repo, str):
|
|
ui.setconfig(section, name, opts.pop(opt))
|
|
else:
|
|
repo.ui.setconfig(section, name, opts.pop(opt))
|
|
return orig(ui, repo, *args, **opts)
|