view hgsubversion/replay.py @ 1599:7bb6562feb85

compathacks: drop branchset() which is compat code for hg<2.9
author Pulkit Goyal <pulkit@yandex-team.ru>
date Tue, 27 Nov 2018 14:08:11 +0300
parents 8a66277136ab
children
line wrap: on
line source

import errno
import traceback

from mercurial import error as hgerror
from mercurial import revlog
from mercurial import node
from mercurial import context

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 hgerror.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 meta.repo.branchmap()
                and not files):
                continue

        parentctx = meta.repo[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

        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