replay.py 7.99 KB
Newer Older
Qijiang Fan's avatar
Qijiang Fan committed
1 2 3 4 5 6 7 8
import errno
import traceback

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

Qijiang Fan's avatar
Qijiang Fan committed
9
import compathacks
Qijiang Fan's avatar
Qijiang Fan committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
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):
Qijiang Fan's avatar
Qijiang Fan committed
56 57 58 59 60 61
    try:
        return _convert_rev(ui, meta, svn, r, tbdelta, firstrun)
    finally:
        meta.editor.current.close()

def _convert_rev(ui, meta, svn, r, tbdelta, firstrun):
Qijiang Fan's avatar
Qijiang Fan committed
62 63 64 65

    editor = meta.editor
    editor.current.clear()
    editor.current.rev = r
Qijiang Fan's avatar
Qijiang Fan committed
66
    editor.setsvn(svn)
Qijiang Fan's avatar
Qijiang Fan committed
67

68
    if firstrun and meta.firstpulled <= 0:
Qijiang Fan's avatar
Qijiang Fan committed
69 70 71 72 73
        # 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:
74
        svn.get_replay(r.revnum, editor, meta.firstpulled)
Qijiang Fan's avatar
Qijiang Fan committed
75
    editor.close()
Qijiang Fan's avatar
Qijiang Fan committed
76 77 78 79 80

    current = editor.current

    updateexternals(ui, meta, current)

Qijiang Fan's avatar
Qijiang Fan committed
81
    if current.exception is not None:  # pragma: no cover
Qijiang Fan's avatar
Qijiang Fan committed
82 83
        traceback.print_exception(*current.exception)
        raise ReplayException()
Qijiang Fan's avatar
Qijiang Fan committed
84 85

    files_to_commit = current.files()
Qijiang Fan's avatar
Qijiang Fan committed
86 87 88 89 90
    branch_batches = {}
    rev = current.rev
    date = meta.fixdate(rev.date)

    # build up the branches that have files on them
Qijiang Fan's avatar
Qijiang Fan committed
91 92
    failoninvalid = ui.configbool('hgsubversion',
            'failoninvalidreplayfile', False)
Qijiang Fan's avatar
Qijiang Fan committed
93 94
    for f in files_to_commit:
        if not meta.is_path_valid(f):
Qijiang Fan's avatar
Qijiang Fan committed
95 96
            if failoninvalid:
                raise hgutil.Abort('file %s should not be in commit list' % f)
Qijiang Fan's avatar
Qijiang Fan committed
97 98 99 100
            continue
        p, b = meta.split_branch_path(f)[:2]
        if b not in branch_batches:
            branch_batches[b] = []
Qijiang Fan's avatar
Qijiang Fan committed
101 102
        if p:
            branch_batches[b].append((p, f))
Qijiang Fan's avatar
Qijiang Fan committed
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

    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

    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]

        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
Qijiang Fan's avatar
Qijiang Fan committed
137
                and branch not in compathacks.branchset(meta.repo)
Qijiang Fan's avatar
Qijiang Fan committed
138 139 140 141 142 143 144 145 146 147 148 149
                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]
150 151 152 153
            try:
                data, isexec, islink, copied = current.pop(current_file)
            except IOError:
                return compathacks.filectxfn_deleted_reraise(memctx)
Qijiang Fan's avatar
Qijiang Fan committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167
            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)
Qijiang Fan's avatar
Qijiang Fan committed
168 169
            else:
                data = parentctx.filectx(path).data()
170 171 172 173 174 175
            return compathacks.makememfilectx(repo,
                                              path=path,
                                              data=data,
                                              islink=islink,
                                              isexec=isexec,
                                              copied=copied)
Qijiang Fan's avatar
Qijiang Fan committed
176 177 178 179

        meta.mapbranch(extra)
        current_ctx = context.memctx(meta.repo,
                                     parents,
180
                                     meta.getmessage(rev),
Qijiang Fan's avatar
Qijiang Fan committed
181 182 183 184 185 186
                                     files.keys(),
                                     filectxfn,
                                     meta.authors[rev.author],
                                     date,
                                     extra)

Qijiang Fan's avatar
Qijiang Fan committed
187
        new_hash = meta.repo.svn_commitctx(current_ctx)
Qijiang Fan's avatar
Qijiang Fan committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
        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:

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

        parent_ctx = meta.repo.changectx(ha)
Qijiang Fan's avatar
Qijiang Fan committed
203
        files = []
Qijiang Fan's avatar
Qijiang Fan committed
204 205 206
        def del_all_files(*args):
            raise IOError(errno.ENOENT, 'deleting all files')

Qijiang Fan's avatar
Qijiang Fan committed
207 208 209 210
        # 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()
Qijiang Fan's avatar
Qijiang Fan committed
211 212 213 214 215 216

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

        current_ctx = context.memctx(meta.repo,
                                     (ha, node.nullid),
217
                                     meta.getmessage(rev),
Qijiang Fan's avatar
Qijiang Fan committed
218
                                     files,
Qijiang Fan's avatar
Qijiang Fan committed
219 220 221 222
                                     del_all_files,
                                     meta.authors[rev.author],
                                     date,
                                     extra)
Qijiang Fan's avatar
Qijiang Fan committed
223
        new_hash = meta.repo.svn_commitctx(current_ctx)
Qijiang Fan's avatar
Qijiang Fan committed
224 225 226 227 228
        util.describe_commit(ui, new_hash, branch)
        if (rev.revnum, branch) not in meta.revmap:
            meta.revmap[rev.revnum, branch] = new_hash

    return closebranches