mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 08:18:15 +03:00
ba6e9cd752
Summary: Move's the extension code into hgext/ Test Plan: Tests are fixed in a later commit. Reviewers: quark, #mercurial Reviewed By: quark Differential Revision: https://phabricator.intern.facebook.com/D6698506 Signature: 6698506:1515628404:0006a07cef299eb3cd719f2fc0d5d381fdc2a3d5
243 lines
8.6 KiB
Python
243 lines
8.6 KiB
Python
import errno
|
|
import traceback
|
|
|
|
from mercurial import revlog
|
|
from mercurial import node
|
|
from mercurial import context
|
|
from mercurial import util as hgutil
|
|
|
|
import compathacks
|
|
import svnexternals
|
|
import util
|
|
|
|
|
|
class MissingPlainTextError(Exception):
|
|
"""Exception raised when the repo lacks a source file required for replaying
|
|
a txdelta.
|
|
"""
|
|
|
|
class ReplayException(Exception):
|
|
"""Exception raised when you try and commit but the replay encountered an
|
|
exception.
|
|
"""
|
|
|
|
def updateexternals(ui, meta, current):
|
|
# TODO fix and re-enable externals for single-directory clones
|
|
if not current.externals or meta.layout == 'single':
|
|
return
|
|
|
|
# accumulate externals records for all branches
|
|
revnum = current.rev.revnum
|
|
branches = {}
|
|
for path, entry in current.externals.iteritems():
|
|
if not meta.is_path_valid(path):
|
|
continue
|
|
|
|
p, b, bp = meta.split_branch_path(path)
|
|
if bp not in branches:
|
|
parent = meta.get_parent_revision(revnum, b)
|
|
pctx = meta.repo[parent]
|
|
branches[bp] = (svnexternals.parse(ui, pctx), pctx)
|
|
branches[bp][0][p] = entry
|
|
|
|
# register externals file changes
|
|
for bp, (external, pctx) in branches.iteritems():
|
|
if bp and bp[-1] != '/':
|
|
bp += '/'
|
|
updates = svnexternals.getchanges(ui, meta.repo, pctx, external)
|
|
for fn, data in updates.iteritems():
|
|
path = (bp and bp + fn) or fn
|
|
if data is not None:
|
|
current.set(path, data, False, False)
|
|
else:
|
|
current.delete(path)
|
|
|
|
def convert_rev(ui, meta, svn, r, tbdelta, firstrun):
|
|
try:
|
|
return _convert_rev(ui, meta, svn, r, tbdelta, firstrun)
|
|
finally:
|
|
meta.editor.current.close()
|
|
|
|
def _convert_rev(ui, meta, svn, r, tbdelta, firstrun):
|
|
|
|
editor = meta.editor
|
|
editor.current.clear()
|
|
editor.current.rev = r
|
|
editor.setsvn(svn)
|
|
|
|
if firstrun and meta.revmap.firstpulled <= 0:
|
|
# We know nothing about this project, so fetch everything before
|
|
# trying to apply deltas.
|
|
ui.debug('replay: fetching full revision\n')
|
|
svn.get_revision(r.revnum, editor)
|
|
else:
|
|
svn.get_replay(r.revnum, editor, meta.revmap.firstpulled)
|
|
editor.close()
|
|
|
|
current = editor.current
|
|
|
|
updateexternals(ui, meta, current)
|
|
|
|
if current.exception is not None: # pragma: no cover
|
|
traceback.print_exception(*current.exception)
|
|
raise ReplayException()
|
|
|
|
files_to_commit = current.files()
|
|
branch_batches = {}
|
|
rev = current.rev
|
|
date = meta.fixdate(rev.date)
|
|
|
|
# build up the branches that have files on them
|
|
failoninvalid = ui.configbool('hgsubversion',
|
|
'failoninvalidreplayfile', False)
|
|
for f in files_to_commit:
|
|
if not meta.is_path_valid(f):
|
|
if failoninvalid:
|
|
raise hgutil.Abort('file %s should not be in commit list' % f)
|
|
continue
|
|
p, b = meta.split_branch_path(f)[:2]
|
|
if b not in branch_batches:
|
|
branch_batches[b] = []
|
|
if p:
|
|
branch_batches[b].append((p, f))
|
|
|
|
closebranches = {}
|
|
for branch in tbdelta['branches'][1]:
|
|
branchedits = meta.revmap.branchedits(branch, rev.revnum)
|
|
if len(branchedits) < 1:
|
|
# can't close a branch that never existed
|
|
continue
|
|
ha = branchedits[0][1]
|
|
closebranches[branch] = ha
|
|
|
|
extraempty = (set(tbdelta['branches'][0]) -
|
|
(set(current.emptybranches) | set(branch_batches.keys())))
|
|
current.emptybranches.update([(x, False) for x in extraempty])
|
|
|
|
# 1. handle normal commits
|
|
closedrevs = closebranches.values()
|
|
for branch, files in branch_batches.iteritems():
|
|
|
|
if branch in current.emptybranches and files:
|
|
del current.emptybranches[branch]
|
|
|
|
if meta.skipbranch(branch):
|
|
# make sure we also get rid of it from emptybranches
|
|
if branch in current.emptybranches:
|
|
del current.emptybranches[branch]
|
|
continue
|
|
|
|
files = dict(files)
|
|
parents = meta.get_parent_revision(rev.revnum, branch), revlog.nullid
|
|
if parents[0] in closedrevs and branch in meta.closebranches:
|
|
continue
|
|
|
|
extra = meta.genextra(rev.revnum, branch)
|
|
tag = None
|
|
if branch is not None:
|
|
# New regular tag without modifications, it will be committed by
|
|
# svnmeta.committag(), we can skip the whole branch for now
|
|
tag = meta.get_path_tag(meta.remotename(branch))
|
|
if (tag and tag not in meta.tags
|
|
and branch not in meta.branches
|
|
and branch not in compathacks.branchset(meta.repo)
|
|
and not files):
|
|
continue
|
|
|
|
parentctx = meta.repo.changectx(parents[0])
|
|
if tag:
|
|
if parentctx.node() == node.nullid:
|
|
continue
|
|
extra.update({'branch': parentctx.extra().get('branch', None),
|
|
'close': 1})
|
|
|
|
def filectxfn(repo, memctx, path):
|
|
current_file = files[path]
|
|
try:
|
|
data, isexec, islink, copied = current.pop(current_file)
|
|
except IOError:
|
|
return compathacks.filectxfn_deleted_reraise(memctx)
|
|
if isexec is None or islink is None:
|
|
flags = parentctx.flags(path)
|
|
if isexec is None:
|
|
isexec = 'x' in flags
|
|
if islink is None:
|
|
islink = 'l' in flags
|
|
|
|
if data is not None:
|
|
if islink:
|
|
if data.startswith('link '):
|
|
data = data[len('link '):]
|
|
else:
|
|
ui.debug('file marked as link, but may contain data: '
|
|
'%s\n' % current_file)
|
|
else:
|
|
data = parentctx.filectx(path).data()
|
|
return compathacks.makememfilectx(repo,
|
|
memctx=memctx,
|
|
path=path,
|
|
data=data,
|
|
islink=islink,
|
|
isexec=isexec,
|
|
copied=copied)
|
|
|
|
meta.mapbranch(extra)
|
|
if 'branch' not in extra:
|
|
extra['branch'] = 'default'
|
|
current_ctx = context.memctx(meta.repo,
|
|
parents,
|
|
util.forceutf8(meta.getmessage(rev)),
|
|
[util.forceutf8(f) for f in files.keys()],
|
|
filectxfn,
|
|
util.forceutf8(meta.authors[rev.author]),
|
|
date,
|
|
extra)
|
|
|
|
new_hash = meta.repo.svn_commitctx(current_ctx)
|
|
util.describe_commit(ui, new_hash, branch)
|
|
if (rev.revnum, branch) not in meta.revmap and not tag:
|
|
meta.revmap[rev.revnum, branch] = new_hash
|
|
if tag:
|
|
meta.movetag(tag, new_hash, rev, date)
|
|
meta.addedtags.pop(tag, None)
|
|
|
|
# 2. handle branches that need to be committed without any files
|
|
for branch in current.emptybranches:
|
|
|
|
if meta.skipbranch(branch):
|
|
continue
|
|
|
|
ha = meta.get_parent_revision(rev.revnum, branch)
|
|
if ha == node.nullid:
|
|
continue
|
|
|
|
# Why are we constructing this? Follow up! (T24862348)
|
|
meta.repo.changectx(ha)
|
|
|
|
files = []
|
|
def del_all_files(*args):
|
|
raise IOError(errno.ENOENT, 'deleting all files')
|
|
|
|
# True here means nuke all files. This happens when you
|
|
# replace a branch root with an empty directory
|
|
if current.emptybranches[branch]:
|
|
files = meta.repo[ha].files()
|
|
|
|
extra = meta.genextra(rev.revnum, branch)
|
|
meta.mapbranch(extra)
|
|
|
|
current_ctx = context.memctx(meta.repo,
|
|
(ha, node.nullid),
|
|
util.forceutf8(meta.getmessage(rev)),
|
|
[util.forceutf8(f) for f in files],
|
|
del_all_files,
|
|
util.forceutf8(meta.authors[rev.author]),
|
|
date,
|
|
extra)
|
|
new_hash = meta.repo.svn_commitctx(current_ctx)
|
|
util.describe_commit(ui, new_hash, branch)
|
|
if (rev.revnum, branch) not in meta.revmap:
|
|
meta.revmap[rev.revnum, branch] = new_hash
|
|
|
|
return closebranches
|