import errno import traceback import compathacks import svnexternals import util from mercurial import context, node, revlog, util as hgutil 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