Example #1
0
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)
Example #2
0
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)
Example #3
0
def fetch_externals(ui, svn, branchpath, r, parentctx):
    """Extract svn:externals for the current revision and branch

    Return an externalsfile instance or None if there are no externals
    to convert and never were.
    """
    externals = svnexternals.parse(ui, parentctx)
    # Detect property additions only, changes are handled by checking
    # existing entries individually. Projects are unlikely to store
    # externals on many different root directories, so we trade code
    # duplication and complexity for a constant lookup price at every
    # revision in the common case.
    dirs = set(externals)
    if parentctx.node() == revlog.nullid:
        dirs.update(
            [p for p, k in svn.list_files(branchpath, r.revnum) if k == 'd'])
        dirs.add('')
    else:
        branchprefix = (branchpath and branchpath + '/') or branchpath
        for path, e in r.paths.iteritems():
            if e.action == 'D':
                continue
            if not path.startswith(branchprefix) and path != branchpath:
                continue
            kind = svn.checkpath(path, r.revnum)
            if kind != 'd':
                continue
            path = path[len(branchprefix):]
            dirs.add(path)
            if e.action == 'M' or (e.action == 'A' and e.copyfrom_path):
                # Do not recurse in copied directories, changes are marked
                # as 'M', except for the copied one.
                continue
            for child, k in svn.list_files(branchprefix + path, r.revnum):
                if k == 'd':
                    dirs.add((path + '/' + child).strip('/'))

    # Retrieve new or updated values
    for dir in dirs:
        try:
            dpath = (branchpath and branchpath + '/' + dir) or dir
            values = svn.list_props(dpath, r.revnum)
            externals[dir] = values.get('svn:externals', '')
        except IOError:
            externals[dir] = ''
    return externals
Example #4
0
def fetch_externals(ui, svn, branchpath, r, parentctx):
    """Extract svn:externals for the current revision and branch

    Return an externalsfile instance or None if there are no externals
    to convert and never were.
    """
    externals = svnexternals.parse(ui, parentctx)
    # Detect property additions only, changes are handled by checking
    # existing entries individually. Projects are unlikely to store
    # externals on many different root directories, so we trade code
    # duplication and complexity for a constant lookup price at every
    # revision in the common case.
    dirs = set(externals)
    if parentctx.node() == revlog.nullid:
        dirs.update([p for p,k in svn.list_files(branchpath, r.revnum) if k == 'd'])
        dirs.add('')
    else:
        branchprefix = (branchpath and branchpath + '/') or branchpath
        for path, e in r.paths.iteritems():
            if e.action == 'D':
                continue
            if not path.startswith(branchprefix) and path != branchpath:
                continue
            kind = svn.checkpath(path, r.revnum)
            if kind != 'd':
                continue
            path = path[len(branchprefix):]
            dirs.add(path)
            if e.action == 'M' or (e.action == 'A' and e.copyfrom_path):
                # Do not recurse in copied directories, changes are marked
                # as 'M', except for the copied one.
                continue
            for child, k in svn.list_files(branchprefix + path, r.revnum):
                if k == 'd':
                    dirs.add((path + '/' + child).strip('/'))

    # Retrieve new or updated values
    for dir in dirs:
        try:
            dpath = (branchpath and branchpath + '/' + dir) or dir
            values = svn.list_props(dpath, r.revnum)
            externals[dir] = values.get('svn:externals', '')
        except IOError:
            externals[dir] = ''
    return externals
Example #5
0
    def add_directory(self, path, parent_baton, copyfrom_path,
                      copyfrom_revision, dir_pool=None):
        self._checkparentdir(parent_baton)
        baton = self._opendir(path)

        br_path, branch = self.meta.split_branch_path(path)[:2]
        if br_path is not None:
            if not copyfrom_path and not br_path:
                # This handles the case where a branch root is
                # replaced without copy info.  It will show up as a
                # deletion and then an add.
                self.meta.closebranches.discard(branch)
                self.current.emptybranches[branch] = True
            else:
                self.current.emptybranches[branch] = False
        if br_path is None or not copyfrom_path:
            return baton
        if self.meta.get_path_tag(path):
            del self.current.emptybranches[branch]
            return baton
        tag = self.meta.get_path_tag(copyfrom_path)
        if tag not in self.meta.tags:
            tag = None
            if not self.meta.is_path_valid(copyfrom_path, existing=False):
                # The source path only exists at copyfrom_revision, use
                # existing=False to guess a possible branch location and
                # test it against the filemap. The actual path and
                # revision will be resolved below if necessary.
                self.addmissing(path, isdir=True)
                return baton
        if tag:
            changeid = self.meta.tags[tag]
            source_rev, source_branch = self.meta.get_source_rev(changeid)[:2]
            frompath = ''
        else:
            source_rev = copyfrom_revision
            frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2]
        new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
        if frompath is None or new_hash == node.nullid:
            self.addmissing(path, isdir=True)
            return baton
        fromctx = self._getctx(new_hash)
        if frompath != '/' and frompath != '':
            frompath = '%s/' % frompath
        else:
            frompath = ''

        copyfromparent = False
        if frompath == '' and br_path == '':
            pnode = self.meta.get_parent_revision(
                    self.current.rev.revnum, branch)
            if pnode == new_hash:
                # Data parent is topological parent and relative paths
                # are the same, not need to do anything but restore
                # files marked as deleted.
                copyfromparent = True
            # Get the parent which would have been used for this branch
            # without the replace action.
            oldpnode = self.meta.get_parent_revision(
                    self.current.rev.revnum, branch, exact=True)
            if (oldpnode != revlog.nullid
                    and util.isancestor(self._getctx(oldpnode), fromctx)):
                # Branch-wide replacement, unmark the branch as deleted
                self.meta.closebranches.discard(branch)

        svncopies = {}
        copies = {}
        for f in fromctx:
            if not f.startswith(frompath):
                continue
            dest = path + '/' + f[len(frompath):]
            if not self.meta.is_path_valid(dest):
                continue
            if dest in self._deleted:
                self._deleted.remove(dest)
            if copyfromparent:
                continue
            svncopies[dest] = CopiedFile(new_hash, f, None)
            if branch == source_branch:
                copies[dest] = f
        if copies:
            # Preserve the directory copy records if no file was changed between
            # the source and destination revisions, or discard it completely.
            parentid = self.meta.get_parent_revision(
                    self.current.rev.revnum, branch)
            if parentid != revlog.nullid:
                parentctx = self._getctx(parentid)
                for k, v in copies.iteritems():
                    if util.issamefile(parentctx, fromctx, v):
                        svncopies[k].copypath = v
        self._svncopies.update(svncopies)

        # Copy the externals definitions of copied directories
        fromext = svnexternals.parse(self.ui, fromctx)
        for p, v in fromext.iteritems():
            pp = p and (p + '/') or ''
            if pp.startswith(frompath):
                dest = (path + '/' + pp[len(frompath):]).rstrip('/')
                self.current.externals[dest] = v
        return baton
Example #6
0
def commit(ui, repo, rev_ctx, meta, base_revision, svn):
    """Build and send a commit from Mercurial to Subversion.
    """
    file_data = {}
    parent = rev_ctx.parents()[0]
    parent_branch = rev_ctx.parents()[0].branch()
    branch_path = meta.layoutobj.remotename(parent_branch)

    extchanges = svnexternals.diff(svnexternals.parse(ui, parent),
                                   svnexternals.parse(ui, rev_ctx))
    addeddirs, deleteddirs = _getdirchanges(svn, branch_path, parent, rev_ctx,
                                            rev_ctx.files(), extchanges)
    deleteddirs = set(deleteddirs)

    props = {}
    copies = {}
    for file in rev_ctx.files():
        if file in util.ignoredfiles:
            continue
        new_data = base_data = ''
        action = ''
        if file in rev_ctx:
            fctx = rev_ctx.filectx(file)
            new_data = fctx.data()

            if 'x' in fctx.flags():
                props.setdefault(file, {})['svn:executable'] = '*'
            if 'l' in fctx.flags():
                props.setdefault(file, {})['svn:special'] = '*'
            isbinary = hgutil.binary(new_data)
            if isbinary:
                props.setdefault(
                    file, {})['svn:mime-type'] = 'application/octet-stream'

            if file not in parent:
                renamed = fctx.renamed()
                if renamed:
                    # TODO current model (and perhaps svn model) does not support
                    # this kind of renames: a -> b, b -> c
                    copies[file] = renamed[0]
                    base_data = parent[renamed[0]].data()
                    if 'l' in parent[renamed[0]].flags():
                        base_data = 'link ' + base_data
                else:
                    autoprops = svn.autoprops_config.properties(file)
                    if autoprops:
                        props.setdefault(file, {}).update(autoprops)

                action = 'add'
                dirname = '/'.join(file.split('/')[:-1] + [''])
            else:
                base_data = parent.filectx(file).data()
                if ('x' in parent.filectx(file).flags()
                        and 'x' not in rev_ctx.filectx(file).flags()):
                    props.setdefault(file, {})['svn:executable'] = None
                if 'l' in parent.filectx(file).flags():
                    base_data = 'link ' + base_data
                    if 'l' not in rev_ctx.filectx(file).flags():
                        props.setdefault(file, {})['svn:special'] = None
                if hgutil.binary(base_data) and not isbinary:
                    props.setdefault(file, {})['svn:mime-type'] = None
                action = 'modify'
        else:
            pos = file.rfind('/')
            if pos >= 0:
                if file[:pos] in deleteddirs:
                    # This file will be removed when its directory is removed
                    continue
            action = 'delete'
        file_data[file] = base_data, new_data, action

    def svnpath(p):
        return ('%s/%s' % (branch_path, p)).strip('/')

    changeddirs = []
    for d, v1, v2 in extchanges:
        props.setdefault(svnpath(d), {})['svn:externals'] = v2
        if d not in deleteddirs and d not in addeddirs:
            changeddirs.append(svnpath(d))

    # Now we are done with files, we can prune deleted directories
    # against themselves: ignore a/b if a/ is already removed
    deleteddirs2 = list(deleteddirs)
    deleteddirs2.sort(reverse=True)
    for d in deleteddirs2:
        pos = d.rfind('/')
        if pos >= 0 and d[:pos] in deleteddirs:
            deleteddirs.remove(d)

    newcopies = {}
    for source, dest in copies.iteritems():
        newcopies[svnpath(source)] = (svnpath(dest), base_revision)

    new_target_files = [svnpath(f) for f in file_data]
    for tf, ntf in zip(file_data, new_target_files):
        if tf in file_data and tf != ntf:
            file_data[ntf] = file_data[tf]
            if tf in props:
                props[ntf] = props.pop(tf)
            del file_data[tf]

    addeddirs = [svnpath(d) for d in addeddirs]
    deleteddirs = [svnpath(d) for d in deleteddirs]
    new_target_files += addeddirs + deleteddirs + changeddirs
    if not new_target_files:
        raise NoFilesException()
    try:
        return svn.commit(new_target_files, rev_ctx.description(),
                          file_data, base_revision, set(addeddirs),
                          set(deleteddirs), props, newcopies)
    except svnwrap.SubversionException, e:
        ui.traceback()

        if len(e.args) > 0 and e.args[1] in (svnwrap.ERR_FS_TXN_OUT_OF_DATE,
                                             svnwrap.ERR_FS_CONFLICT,
                                             svnwrap.ERR_FS_ALREADY_EXISTS):
            raise hgutil.Abort('Outgoing changesets parent is not at '
                               'subversion HEAD\n'
                               '(pull again and rebase on a newer revision)')
        elif len(e.args) > 0 and e.args[1] == svnwrap.ERR_REPOS_HOOK_FAILURE:
            # Special handling for svn hooks blocking error
            raise hgutil.Abort(e.args[0])
        else:
            raise
Example #7
0
def commit(ui, repo, rev_ctx, meta, base_revision, svn):
    """Build and send a commit from Mercurial to Subversion.
    """
    file_data = {}
    parent = rev_ctx.parents()[0]
    parent_branch = rev_ctx.parents()[0].branch()
    branch_path = 'trunk'

    if meta.layout == 'single':
        branch_path = ''
    elif parent_branch and parent_branch != 'default':
        branch_path = 'branches/%s' % parent_branch

    extchanges = svnexternals.diff(svnexternals.parse(ui, parent),
                                   svnexternals.parse(ui, rev_ctx))
    addeddirs, deleteddirs = _getdirchanges(svn, branch_path, parent, rev_ctx,
                                            rev_ctx.files(), extchanges)
    deleteddirs = set(deleteddirs)

    props = {}
    copies = {}
    for file in rev_ctx.files():
        if file in util.ignoredfiles:
            continue
        new_data = base_data = ''
        action = ''
        if file in rev_ctx:
            fctx = rev_ctx.filectx(file)
            new_data = fctx.data()

            if 'x' in fctx.flags():
                props.setdefault(file, {})['svn:executable'] = '*'
            if 'l' in fctx.flags():
                props.setdefault(file, {})['svn:special'] = '*'
            isbinary = hgutil.binary(new_data)
            if isbinary:
                props.setdefault(file, {})['svn:mime-type'] = 'application/octet-stream'

            if file not in parent:
                renamed = fctx.renamed()
                if renamed:
                    # TODO current model (and perhaps svn model) does not support
                    # this kind of renames: a -> b, b -> c
                    copies[file] = renamed[0]
                    base_data = parent[renamed[0]].data()

                action = 'add'
                dirname = '/'.join(file.split('/')[:-1] + [''])
            else:
                base_data = parent.filectx(file).data()
                if ('x' in parent.filectx(file).flags()
                    and 'x' not in rev_ctx.filectx(file).flags()):
                    props.setdefault(file, {})['svn:executable'] = None
                if ('l' in parent.filectx(file).flags()
                    and 'l' not in rev_ctx.filectx(file).flags()):
                    props.setdefault(file, {})['svn:special'] = None
                if hgutil.binary(base_data) and not isbinary:
                    props.setdefault(file, {})['svn:mime-type'] = None
                action = 'modify'
        else:
            pos = file.rfind('/')
            if pos >= 0:
                if file[:pos] in deleteddirs:
                    # This file will be removed when its directory is removed
                    continue
            action = 'delete'
        file_data[file] = base_data, new_data, action

    def svnpath(p):
        return ('%s/%s' % (branch_path, p)).strip('/')

    changeddirs = []
    for d, v1, v2 in extchanges:
        props.setdefault(svnpath(d), {})['svn:externals'] = v2
        if d not in deleteddirs and d not in addeddirs:
            changeddirs.append(svnpath(d))

    # Now we are done with files, we can prune deleted directories
    # against themselves: ignore a/b if a/ is already removed
    deleteddirs2 = list(deleteddirs)
    deleteddirs2.sort(reverse=True)
    for d in deleteddirs2:
        pos = d.rfind('/')
        if pos >= 0 and d[:pos] in deleteddirs:
            deleteddirs.remove(d)

    newcopies = {}
    for source, dest in copies.iteritems():
        newcopies[svnpath(source)] = (svnpath(dest), base_revision)

    new_target_files = [svnpath(f) for f in file_data]
    for tf, ntf in zip(file_data, new_target_files):
        if tf in file_data and tf != ntf:
            file_data[ntf] = file_data[tf]
            if tf in props:
                props[ntf] = props.pop(tf)
            del file_data[tf]

    addeddirs = [svnpath(d) for d in addeddirs]
    deleteddirs = [svnpath(d) for d in deleteddirs]
    new_target_files += addeddirs + deleteddirs + changeddirs
    if not new_target_files:
        raise NoFilesException()
    try:
        svn.commit(new_target_files, rev_ctx.description(), file_data,
                   base_revision, set(addeddirs), set(deleteddirs),
                   props, newcopies)
    except svnwrap.SubversionException, e:
        if len(e.args) > 0 and e.args[1] in (svnwrap.ERR_FS_TXN_OUT_OF_DATE,
                                             svnwrap.ERR_FS_CONFLICT):
            raise hgutil.Abort('Outgoing changesets parent is not at '
                               'subversion HEAD\n'
                               '(pull again and rebase on a newer revision)')
        else:
            raise
Example #8
0
 def add_directory(self, path, parent_baton, copyfrom_path,
                   copyfrom_revision, dir_pool=None):
     self.current.batons[path] = path
     br_path, branch = self.meta.split_branch_path(path)[:2]
     if br_path is not None:
         if not copyfrom_path and not br_path:
             self.current.emptybranches[branch] = True
         else:
             self.current.emptybranches[branch] = False
     if br_path is None or not copyfrom_path:
         return path
     if self.meta.get_path_tag(path):
         del self.current.emptybranches[branch]
         return path
     tag = self.meta.get_path_tag(copyfrom_path)
     if tag not in self.meta.tags:
         tag = None
         if not self.meta.is_path_valid(copyfrom_path):
             self.current.missing.add('%s/' % path)
             return path
     if tag:
         changeid = self.meta.tags[tag]
         source_rev, source_branch = self.meta.get_source_rev(changeid)[:2]
         frompath = ''
     else:
         source_rev = copyfrom_revision
         frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2]
         if frompath == '' and br_path == '':
             assert br_path is not None
             tmp = source_branch, source_rev, self.current.rev.revnum
             self.meta.branches[branch] = tmp
     new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
     if new_hash == node.nullid:
         self.current.missing.add('%s/' % path)
         return path
     fromctx = self.repo.changectx(new_hash)
     if frompath != '/' and frompath != '':
         frompath = '%s/' % frompath
     else:
         frompath = ''
     copies = {}
     for f in fromctx:
         if not f.startswith(frompath):
             continue
         fctx = fromctx.filectx(f)
         dest = path + '/' + f[len(frompath):]
         self.current.set(dest, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags())
         if dest in self.current.deleted:
             del self.current.deleted[dest]
         if branch == source_branch:
             copies[dest] = f
     if copies:
         # Preserve the directory copy records if no file was changed between
         # the source and destination revisions, or discard it completely.
         parentid = self.meta.get_parent_revision(self.current.rev.revnum, branch)
         if parentid != revlog.nullid:
             parentctx = self.repo.changectx(parentid)
             for k, v in copies.iteritems():
                 if util.issamefile(parentctx, fromctx, v):
                     self.current.copies[k] = v
     # Copy the externals definitions of copied directories
     fromext = svnexternals.parse(self.ui, fromctx)
     for p, v in fromext.iteritems():
         pp = p and (p + '/') or ''
         if pp.startswith(frompath):
             dest = (path + '/' + pp[len(frompath):]).rstrip('/')
             self.current.externals[dest] = v
     return path