view hgsubversion/replay.py @ 547:9e6499c415a9

tags: fix files edited during tag creation This was broken because file edits were skipped if they were in tags, but committags in svnmeta didn't check to see if any files were changed during initial tag creation.
author Augie Fackler <durin42@gmail.com>
date Sat, 06 Feb 2010 10:36:21 -0600
parents cf4fe45bf8fd
children d17cec76e769
line wrap: on
line source

import errno
import traceback

from mercurial import revlog
from mercurial import node
from mercurial import context
from mercurial import util as hgutil

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 convert_rev(ui, meta, svn, r, tbdelta):

    editor = meta.editor
    editor.current.clear()
    editor.current.rev = r
    svn.get_replay(r.revnum, editor)
    current = editor.current
    current.findmissing(svn)

    # update externals
    # TODO fix and re-enable externals for single-directory clones
    if current.externals and not meta.layout == 'single':

        # 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):
                ui.warn('WARNING: Invalid path %s in externals\n' % path)
                continue

            p, b, bp = meta.split_branch_path(path)
            if bp not in branches:
                external = svnexternals.externalsfile()
                parent = meta.get_parent_revision(revnum, b)
                pctx = meta.repo[parent]
                if '.hgsvnexternals' in pctx:
                    external.read(pctx['.hgsvnexternals'].data())
                branches[bp] = external
            else:
                external = branches[bp]

            external[p] = entry

        # register externals file changes
        for bp, external in branches.iteritems():
            if bp and bp[-1] != '/':
                bp += '/'
            path = (bp and bp + '.hgsvnexternals') or '.hgsvnexternals'
            if external:
                current.set(path, external.write(), False, False)
            else:
                current.delete(path)

    if current.exception is not None:  #pragma: no cover
        traceback.print_exception(*current.exception)
        raise ReplayException()
    if current.missing:
        raise MissingPlainTextError()

    # paranoidly generate the list of files to commit
    files_to_commit = set(current.files.keys())
    files_to_commit.update(current.symlinks.keys())
    files_to_commit.update(current.execfiles.keys())
    files_to_commit.update(current.deleted.keys())
    # back to a list and sort so we get sane behavior
    files_to_commit = list(files_to_commit)
    files_to_commit.sort()
    branch_batches = {}
    rev = current.rev
    date = meta.fixdate(rev.date)

    # build up the branches that have files on them
    for f in files_to_commit:
        if not meta.is_path_valid(f):
            continue
        p, b = meta.split_branch_path(f)[:2]
        if b not in branch_batches:
            branch_batches[b] = []
        branch_batches[b].append((p, f))

    closebranches = {}
    for branch in tbdelta['branches'][1]:
        branchedits = meta.revmap.branchedits(branch, rev)
        if len(branchedits) < 1:
            # can't close a branch that never existed
            continue
        ha = branchedits[0][1]
        closebranches[branch] = ha

    # 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]

        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.branchtags()
                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})

        if '.hgsvnexternals' not in parentctx and '.hgsvnexternals' in files:
            # Do not register empty externals files
            if (files['.hgsvnexternals'] in current.files
                and not current.files[files['.hgsvnexternals']]):
                del files['.hgsvnexternals']

        def filectxfn(repo, memctx, path):
            current_file = files[path]
            if current_file in current.deleted:
                raise IOError(errno.EBADF,
                              'Operation on deleted file attempted')
            copied = current.copies.get(current_file)
            flags = parentctx.flags(path)
            is_exec = current.execfiles.get(current_file, 'x' in flags)
            is_link = current.symlinks.get(current_file, 'l' in flags)
            if current_file in current.files:
                data = current.files[current_file]
                if is_link and data.startswith('link '):
                    data = data[len('link '):]
                elif is_link:
                    ui.warn('file marked as link, but contains data: '
                            '%s (%r)\n' % (current_file, flags))
            else:
                data = parentctx.filectx(path).data()
            return context.memfilectx(path=path,
                                      data=data,
                                      islink=is_link, isexec=is_exec,
                                      copied=copied)

        if not meta.usebranchnames or extra.get('branch', None) == 'default':
            extra.pop('branch', None)
        current_ctx = context.memctx(meta.repo,
                                     parents,
                                     rev.message or '...',
                                     files.keys(),
                                     filectxfn,
                                     meta.authors[rev.author],
                                     date,
                                     extra)

        new_hash = meta.repo.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, parentctx.extra().get('branch', None), rev, date)
            meta.addedtags.pop(tag, None)

    # 2. handle branches that need to be committed without any files
    for branch in current.emptybranches:

        ha = meta.get_parent_revision(rev.revnum, branch)
        if ha == node.nullid:
            continue

        parent_ctx = meta.repo.changectx(ha)
        def del_all_files(*args):
            raise IOError

        # True here meant nuke all files, shouldn't happen with branch closing
        if current.emptybranches[branch]: #pragma: no cover
            raise hgutil.Abort('Empty commit to an open branch attempted. '
                               'Please report this issue.')

        extra = meta.genextra(rev.revnum, branch)

        if not meta.usebranchnames:
            extra.pop('branch', None)

        current_ctx = context.memctx(meta.repo,
                                     (ha, node.nullid),
                                     rev.message or ' ',
                                     [],
                                     del_all_files,
                                     meta.authors[rev.author],
                                     date,
                                     extra)
        new_hash = meta.repo.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