Example #1
0
def diff(orig, ui, repo, *args, **opts):
    """show a diff of the most recent revision against its parent from svn
    """
    if not opts.get('svn', False) or opts.get('change', None):
        return orig(ui, repo, *args, **opts)
    meta = repo.svnmeta()
    hashes = meta.revmap.hashes()
    if not opts.get('rev', None):
        parent = repo[None].parents()[0]
        o_r = util.outgoing_revisions(repo, hashes, parent.node())
        if o_r:
            parent = repo[o_r[-1]].parents()[0]
        opts['rev'] = ['%s:.' % node.hex(parent.node()), ]
    node1, node2 = scmutil.revpair(repo, opts['rev'])
    if not isinstance(node1, bytes):
        # hg 4.6 and later return contexts, so convert to bytestr
        node1, node2 = node1.node(), node2.node()
    baserev, _junk = hashes.get(node1, (-1, 'junk'))
    newrev, _junk = hashes.get(node2, (-1, 'junk'))
    it = patch.diff(repo, node1, node2,
                    opts=patch.diffopts(ui, opts={'git': True,
                                                  'show_function': False,
                                                  'ignore_all_space': False,
                                                  'ignore_space_change': False,
                                                  'ignore_blank_lines': False,
                                                  'unified': True,
                                                  'text': False,
                                                  }))
    ui.write(util.filterdiff(''.join(it), baserev, newrev))
Example #2
0
def diff(orig, ui, repo, *args, **opts):
    """show a diff of the most recent revision against its parent from svn
    """
    if not opts.get('svn', False) or opts.get('change', None):
        return orig(ui, repo, *args, **opts)
    meta = repo.svnmeta()
    hashes = meta.revmap.hashes()
    if not opts.get('rev', None):
        parent = repo.parents()[0]
        o_r = util.outgoing_revisions(repo, hashes, parent.node())
        if o_r:
            parent = repo[o_r[-1]].parents()[0]
        opts['rev'] = ['%s:.' % node.hex(parent.node()), ]
    node1, node2 = cmdutil.revpair(repo, opts['rev'])
    baserev, _junk = hashes.get(node1, (-1, 'junk'))
    newrev, _junk = hashes.get(node2, (-1, 'junk'))
    it = patch.diff(repo, node1, node2,
                    opts=patch.diffopts(ui, opts={'git': True,
                                                  'show_function': False,
                                                  'ignore_all_space': False,
                                                  'ignore_space_change': False,
                                                  'ignore_blank_lines': False,
                                                  'unified': True,
                                                  'text': False,
                                                  }))
    ui.write(util.filterdiff(''.join(it), baserev, newrev))
Example #3
0
def findoutgoing(repo, dest=None, heads=None, force=False):
    """show changesets not found in the Subversion repository
    """
    assert dest.capable('subversion')
    # split off #rev; TODO implement --revision/#rev support
    # svnurl, revs, checkout = util.parseurl(dest.svnurl, heads)
    svn = dest.svn
    meta = repo.svnmeta(svn.uuid, svn.subdir)
    parent = repo[None].parents()[0].node()
    hashes = meta.revmap.hashes()
    return util.outgoing_revisions(repo, hashes, parent)
Example #4
0
def outgoing(repo, dest=None, heads=None, force=False):
    """show changesets not found in the Subversion repository
    """
    assert dest.capable('subversion')

    # split off #rev; TODO implement --revision/#rev support
    svnurl, revs, checkout = util.parseurl(dest.svnurl, heads)
    meta = repo.svnmeta()
    parent = repo.parents()[0].node()
    hashes = meta.revmap.hashes()
    return util.outgoing_revisions(repo, hashes, parent)
Example #5
0
def rebase(orig, ui, repo, **opts):
    """rebase current unpushed revisions onto the Subversion head

    This moves a line of development from making its own head to the top of
    Subversion development, linearizing the changes. In order to make sure you
    rebase on top of the current top of Subversion work, you should probably run
    'hg svn pull' before running this.

    Also looks for svnextrafn and svnsourcerev in **opts.
    """
    if not opts.get('svn', False):
        return orig(ui, repo, **opts)

    def extrafn2(ctx, extra):
        """defined here so we can add things easily.
        """
        extra['branch'] = ctx.branch()

    extrafn = opts.get('svnextrafn', extrafn2)
    sourcerev = opts.get('svnsourcerev', repo.parents()[0].node())
    meta = repo.svnmeta()
    hashes = meta.revmap.hashes()
    o_r = util.outgoing_revisions(repo, hashes, sourcerev=sourcerev)
    if not o_r:
        ui.status('Nothing to rebase!\n')
        return 0
    if len(repo[sourcerev].children()):
        ui.status('Refusing to rebase non-head commit like a coward\n')
        return 0
    parent_rev = repo[o_r[-1]].parents()[0]
    target_rev = parent_rev
    p_n = parent_rev.node()
    exhausted_choices = False
    while target_rev.children() and not exhausted_choices:
        for c in target_rev.children():
            exhausted_choices = True
            n = c.node()
            if (n in hashes and hashes[n][1] == hashes[p_n][1]):
                target_rev = c
                exhausted_choices = False
                break
    if parent_rev == target_rev:
        ui.status('Already up to date!\n')
        return 0
    return orig(ui,
                repo,
                dest=node.hex(target_rev.node()),
                base=node.hex(sourcerev),
                extrafn=extrafn)
Example #6
0
def rebase(orig, ui, repo, **opts):
    """rebase current unpushed revisions onto the Subversion head

    This moves a line of development from making its own head to the top of
    Subversion development, linearizing the changes. In order to make sure you
    rebase on top of the current top of Subversion work, you should probably run
    'hg svn pull' before running this.

    Also looks for svnextrafn and svnsourcerev in **opts.
    """
    if not opts.get('svn', False):
        return orig(ui, repo, **opts)
    def extrafn2(ctx, extra):
        """defined here so we can add things easily.
        """
        extra['branch'] = ctx.branch()
    extrafn = opts.get('svnextrafn', extrafn2)
    sourcerev = opts.get('svnsourcerev', repo.parents()[0].node())
    meta = repo.svnmeta()
    hashes = meta.revmap.hashes()
    o_r = util.outgoing_revisions(repo, hashes, sourcerev=sourcerev)
    if not o_r:
        ui.status('Nothing to rebase!\n')
        return 0
    if len(repo[sourcerev].children()):
        ui.status('Refusing to rebase non-head commit like a coward\n')
        return 0
    parent_rev = repo[o_r[-1]].parents()[0]
    target_rev = parent_rev
    p_n = parent_rev.node()
    exhausted_choices = False
    while target_rev.children() and not exhausted_choices:
        for c in target_rev.children():
            exhausted_choices = True
            n = c.node()
            if (n in hashes and hashes[n][1] == hashes[p_n][1]):
                target_rev = c
                exhausted_choices = False
                break
    if parent_rev == target_rev:
        ui.status('Already up to date!\n')
        return 0
    return orig(ui, repo, dest=node.hex(target_rev.node()),
                base=node.hex(sourcerev),
                extrafn=extrafn)
Example #7
0
def push(repo, dest, force, revs):
    """push revisions starting at a specified head back to Subversion.
    """
    assert not revs, 'designated revisions for push remains unimplemented.'
    cmdutil.bailifchanged(repo)
    checkpush = getattr(repo, 'checkpush', None)
    if checkpush:
        try:
            # The checkpush function changed as of e10000369b47 (first
            # in 3.0) in mercurial
            from mercurial.exchange import pushoperation
            pushop = pushoperation(repo, dest, force, revs, False)
            checkpush(pushop)
        except (ImportError, TypeError):
            checkpush(force, revs)

    ui = repo.ui
    old_encoding = util.swap_out_encoding()

    try:
        hasobsolete = (obsolete._enabled or
                       obsolete.isenabled(repo, obsolete.createmarkersopt))
    except:
        hasobsolete = False

    temporary_commits = []
    obsmarkers = []
    try:
        # TODO: implement --rev/#rev support
        # TODO: do credentials specified in the URL still work?
        svn = dest.svn
        meta = repo.svnmeta(svn.uuid, svn.subdir)

        # Strategy:
        # 1. Find all outgoing commits from this head
        if len(repo[None].parents()) != 1:
            ui.status('Cowardly refusing to push branch merge\n')
            return 0 # results in nonzero exit status, see hg's commands.py
        workingrev = repo[None].parents()[0]
        workingbranch = workingrev.branch()
        ui.status('searching for changes\n')
        hashes = meta.revmap.hashes()
        outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
        if not (outgoing and len(outgoing)):
            ui.status('no changes found\n')
            return 1 # so we get a sane exit status, see hg's commands.push

        tip_ctx = repo[outgoing[-1]].p1()
        svnbranch = tip_ctx.branch()
        modified_files = {}
        for i in range(len(outgoing) - 1, -1, -1):
            # 2. Pick the oldest changeset that needs to be pushed
            current_ctx = repo[outgoing[i]]
            original_ctx = current_ctx

            if len(current_ctx.parents()) != 1:
                ui.status('Found a branch merge, this needs discussion and '
                          'implementation.\n')
                # results in nonzero exit status, see hg's commands.py
                return 0

            # 3. Move the changeset to the tip of the branch if necessary
            conflicts = False
            for file in current_ctx.files():
                if file in modified_files:
                    conflicts = True
                    break

            if conflicts or current_ctx.branch() != svnbranch:
                util.swap_out_encoding(old_encoding)
                try:
                    def extrafn(ctx, extra):
                        extra['branch'] = ctx.branch()

                    ui.note('rebasing %s onto %s \n' % (current_ctx, tip_ctx))
                    hgrebase.rebase(ui, repo,
                                    dest=node.hex(tip_ctx.node()),
                                    rev=[node.hex(current_ctx.node())],
                                    extrafn=extrafn, keep=True)
                finally:
                    util.swap_out_encoding()

                # Don't trust the pre-rebase repo and context.
                repo = getlocalpeer(ui, {}, meta.path)
                meta = repo.svnmeta(svn.uuid, svn.subdir)
                hashes = meta.revmap.hashes()
                tip_ctx = repo[tip_ctx.node()]
                for c in tip_ctx.descendants():
                    rebasesrc = c.extra().get('rebase_source')
                    if rebasesrc and node.bin(rebasesrc) == current_ctx.node():
                        current_ctx = c
                        temporary_commits.append(c.node())
                        break

            # 4. Push the changeset to subversion
            tip_hash = hashes[tip_ctx.node()][0]
            try:
                ui.status('committing %s\n' % current_ctx)
                pushedrev = pushmod.commit(ui, repo, current_ctx, meta,
                                           tip_hash, svn)
            except pushmod.NoFilesException:
                ui.warn("Could not push revision %s because it had no changes "
                        "in svn.\n" % current_ctx)
                return

            # This hook is here purely for testing.  It allows us to
            # onsistently trigger hit the race condition between
            # pushing and pulling here.  In particular, we use it to
            # trigger another revision landing between the time we
            # push a revision and pull it back.
            repo.hook('debug-hgsubversion-between-push-and-pull-for-tests')

            # 5. Pull the latest changesets from subversion, which will
            # include the one we just committed (and possibly others).
            r = pull(repo, dest, force=force, meta=meta)
            assert not r or r == 0

            # 6. Move our tip to the latest pulled tip
            for c in tip_ctx.descendants():
                if c.node() in hashes and c.branch() == svnbranch:
                    if meta.get_source_rev(ctx=c)[0] == pushedrev.revnum:
                        # This is corresponds to the changeset we just pushed
                        if hasobsolete:
                            obsmarkers.append([(original_ctx, [c])])

                    tip_ctx = c

                    # Remember what files have been modified since the
                    # whole push started.
                    for file in c.files():
                        modified_files[file] = True

            # 7. Rebase any children of the commit we just pushed
            # that are not in the outgoing set
            for c in original_ctx.children():
                if not c.node() in hashes and not c.node() in outgoing:
                    util.swap_out_encoding(old_encoding)
                    try:
                        # Path changed as subdirectories were getting
                        # deleted during push.
                        saved_path = os.getcwd()
                        os.chdir(repo.root)

                        def extrafn(ctx, extra):
                            extra['branch'] = ctx.branch()

                        ui.status('rebasing non-outgoing %s onto %s\n' % (c, tip_ctx))
                        needs_rebase_set = "%s::" % node.hex(c.node())
                        hgrebase.rebase(ui, repo,
                                        dest=node.hex(tip_ctx.node()),
                                        rev=[needs_rebase_set],
                                        extrafn=extrafn,
                                        keep=not hasobsolete)
                    finally:
                        os.chdir(saved_path)
                        util.swap_out_encoding()


        util.swap_out_encoding(old_encoding)
        try:
            hg.update(repo, repo.branchtip(workingbranch))
        finally:
            util.swap_out_encoding()

        with repo.wlock():
            with repo.lock():
                if hasobsolete:
                    for marker in obsmarkers:
                        obsolete.createmarkers(repo, marker)
                        beforepush = marker[0][0]
                        afterpush = marker[0][1][0]
                        ui.note('marking %s as obsoleted by %s\n' %
                                (beforepush.hex(), afterpush.hex()))
                else:
                    # strip the original changesets since the push was
                    # successful and changeset obsolescence is unavailable
                    util.strip(ui, repo, outgoing, "all")
    finally:
        try:
            # It's always safe to delete the temporary commits.
            # The originals are not deleted unless the push
            # completely succeeded.
            if temporary_commits:
                # If the repo is on a temporary commit, get off before
                # the strip.
                parent = repo[None].p1()
                if parent.node() in temporary_commits:
                    hg.update(repo, parent.p1().node())
                with repo.wlock():
                    with repo.lock():
                        if hasobsolete:
                            relations = (
                                (repo[n], ()) for n in temporary_commits)
                            obsolete.createmarkers(repo, relations)
                        else:
                            util.strip(
                                ui, repo, temporary_commits, backup=None)

        finally:
            util.swap_out_encoding(old_encoding)
    return 1 # so we get a sane exit status, see hg's commands.push
Example #8
0
def push(repo, dest, force, revs):
    """push revisions starting at a specified head back to Subversion.
    """
    assert not revs, 'designated revisions for push remains unimplemented.'
    cmdutil.bail_if_changed(repo)
    ui = repo.ui
    old_encoding = util.swap_out_encoding()
    # TODO: implement --rev/#rev support
    # TODO: do credentials specified in the URL still work?
    svnurl = repo.ui.expandpath(dest.svnurl)
    svn = svnrepo.svnremoterepo(repo.ui, svnurl).svn
    meta = repo.svnmeta(svn.uuid)

    # Strategy:
    # 1. Find all outgoing commits from this head
    if len(repo.parents()) != 1:
        ui.status('Cowardly refusing to push branch merge\n')
        return 1
    workingrev = repo.parents()[0]
    ui.status('searching for changes\n')
    hashes = meta.revmap.hashes()
    outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
    if not (outgoing and len(outgoing)):
        ui.status('no changes found\n')
        return 0
    while outgoing:
        oldest = outgoing.pop(-1)
        old_ctx = repo[oldest]
        if len(old_ctx.parents()) != 1:
            ui.status('Found a branch merge, this needs discussion and '
                      'implementation.\n')
            return 1
        base_n = old_ctx.parents()[0].node()
        old_children = repo[base_n].children()
        svnbranch = repo[base_n].branch()
        oldtip = base_n
        samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch
                              and c.node() in hashes]
        while samebranchchildren:
            oldtip = samebranchchildren[0].node()
            samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch
                                  and c.node() in hashes]
        # 2. Commit oldest revision that needs to be pushed
        base_revision = hashes[base_n][0]
        try:
            pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn)
        except pushmod.NoFilesException:
            ui.warn("Could not push revision %s because it had no changes in svn.\n" %
                     old_ctx)
            return 1
        # 3. Fetch revisions from svn
        # TODO: this probably should pass in the source explicitly - rev too?
        r = repo.pull(dest, force=force)
        assert not r or r == 0
        # 4. Find the new head of the target branch
        oldtipctx = repo[oldtip]
        replacement = [c for c in oldtipctx.children() if c not in old_children
                       and c.branch() == oldtipctx.branch()]
        assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement
        replacement = replacement[0]
        # 5. Rebase all children of the currently-pushing rev to the new branch
        heads = repo.heads(old_ctx.node())
        for needs_transplant in heads:
            def extrafn(ctx, extra):
                if ctx.node() == oldest:
                    return
                extra['branch'] = ctx.branch()
            # TODO: can we avoid calling our own rebase wrapper here?
            rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn,
                   svnsourcerev=needs_transplant)
            repo = hg.repository(ui, meta.path)
            for child in repo[replacement.node()].children():
                rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid)))
                if rebasesrc in outgoing:
                    while rebasesrc in outgoing:
                        rebsrcindex = outgoing.index(rebasesrc)
                        outgoing = (outgoing[0:rebsrcindex] +
                                    [child.node(), ] + outgoing[rebsrcindex+1:])
                        children = [c for c in child.children() if c.branch() == child.branch()]
                        if children:
                            child = children[0]
                        rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid)))
        # TODO: stop constantly creating the SVNMeta instances.
        meta = repo.svnmeta(svn.uuid)
        hashes = meta.revmap.hashes()
    util.swap_out_encoding(old_encoding)
    return 0
Example #9
0
def push(repo, dest, force, revs):
    """push revisions starting at a specified head back to Subversion.
    """
    assert not revs, 'designated revisions for push remains unimplemented.'
    cmdutil.bail_if_changed(repo)
    checkpush = getattr(repo, 'checkpush', None)
    if checkpush:
        checkpush(force, revs)
    ui = repo.ui
    old_encoding = util.swap_out_encoding()
    # TODO: implement --rev/#rev support
    # TODO: do credentials specified in the URL still work?
    svnurl = repo.ui.expandpath(dest.svnurl)
    svn = dest.svn
    meta = repo.svnmeta(svn.uuid, svn.subdir)

    # Strategy:
    # 1. Find all outgoing commits from this head
    if len(repo.parents()) != 1:
        ui.status('Cowardly refusing to push branch merge\n')
        return 0 # results in nonzero exit status, see hg's commands.py
    workingrev = repo.parents()[0]
    ui.status('searching for changes\n')
    hashes = meta.revmap.hashes()
    outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
    if not (outgoing and len(outgoing)):
        ui.status('no changes found\n')
        return 1 # so we get a sane exit status, see hg's commands.push
    while outgoing:

        # 2. Commit oldest revision that needs to be pushed
        oldest = outgoing.pop(-1)
        old_ctx = repo[oldest]
        old_pars = old_ctx.parents()
        if len(old_pars) != 1:
            ui.status('Found a branch merge, this needs discussion and '
                      'implementation.\n')
            return 0 # results in nonzero exit status, see hg's commands.py
        # We will commit to svn against this node's parent rev. Any file-level
        # conflicts here will result in an error reported by svn.
        base_ctx = old_pars[0]
        base_revision = hashes[base_ctx.node()][0]
        svnbranch = base_ctx.branch()
        # Find most recent svn commit we have on this branch.
        # This node will become the nearest known ancestor of the pushed rev.
        oldtipctx = base_ctx
        old_children = oldtipctx.descendants()
        seen = set(c.node() for c in old_children)
        samebranchchildren = [c for c in old_children if c.branch() == svnbranch
                              and c.node() in hashes]
        if samebranchchildren:
            # The following relies on descendants being sorted by rev.
            oldtipctx = samebranchchildren[-1]
        # All set, so commit now.
        try:
            pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn)
        except pushmod.NoFilesException:
            ui.warn("Could not push revision %s because it had no changes in svn.\n" %
                     old_ctx)
            return 1

        # 3. Fetch revisions from svn
        # TODO: this probably should pass in the source explicitly - rev too?
        r = repo.pull(dest, force=force)
        assert not r or r == 0

        # 4. Find the new head of the target branch
        # We expect to get our own new commit back, but we might also get other
        # commits that happened since our last pull, or even right after our own
        # commit (race).
        for c in oldtipctx.descendants():
            if c.node() not in seen and c.branch() == svnbranch:
                newtipctx = c

        # 5. Rebase all children of the currently-pushing rev to the new head
        heads = repo.heads(old_ctx.node())
        for needs_transplant in heads:
            def extrafn(ctx, extra):
                if ctx.node() == oldest:
                    return
                extra['branch'] = ctx.branch()
            # TODO: can we avoid calling our own rebase wrapper here?
            rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn,
                   svnsourcerev=needs_transplant)
            # Reload the repo after the rebase. Do not reuse contexts across this.
            newtip = newtipctx.node()
            repo = hg.repository(ui, meta.path)
            newtipctx = repo[newtip]
            # Rewrite the node ids in outgoing to their rebased versions.
            rebasemap = dict()
            for child in newtipctx.descendants():
                rebasesrc = child.extra().get('rebase_source')
                if rebasesrc:
                    rebasemap[node.bin(rebasesrc)] = child.node()
            outgoing = [rebasemap.get(n) or n for n in outgoing]
        # TODO: stop constantly creating the SVNMeta instances.
        meta = repo.svnmeta(svn.uuid, svn.subdir)
        hashes = meta.revmap.hashes()
    util.swap_out_encoding(old_encoding)
    return 1 # so we get a sane exit status, see hg's commands.push
Example #10
0
def push(repo, dest, force, revs):
    """push revisions starting at a specified head back to Subversion.
    """
    assert not revs, 'designated revisions for push remains unimplemented.'
    cmdutil.bailifchanged(repo)
    checkpush = getattr(repo, 'checkpush', None)
    if checkpush:
        try:
            # The checkpush function changed as of e10000369b47 (first
            # in 3.0) in mercurial
            from mercurial.exchange import pushoperation
            pushop = pushoperation(repo, dest, force, revs, False)
            checkpush(pushop)
        except (ImportError, TypeError):
            checkpush(force, revs)

    ui = repo.ui
    old_encoding = util.swap_out_encoding()

    try:
        hasobsolete = obsolete._enabled
    except:
        hasobsolete = False

    temporary_commits = []
    obsmarkers = []
    try:
        # TODO: implement --rev/#rev support
        # TODO: do credentials specified in the URL still work?
        svn = dest.svn
        meta = repo.svnmeta(svn.uuid, svn.subdir)

        # Strategy:
        # 1. Find all outgoing commits from this head
        if len(repo[None].parents()) != 1:
            ui.status('Cowardly refusing to push branch merge\n')
            return 0 # results in nonzero exit status, see hg's commands.py
        workingrev = repo[None].parents()[0]
        workingbranch = workingrev.branch()
        ui.status('searching for changes\n')
        hashes = meta.revmap.hashes()
        outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
        if not (outgoing and len(outgoing)):
            ui.status('no changes found\n')
            return 1 # so we get a sane exit status, see hg's commands.push

        tip_ctx = repo[outgoing[-1]].p1()
        svnbranch = tip_ctx.branch()
        modified_files = {}
        for i in range(len(outgoing) - 1, -1, -1):
            # 2. Pick the oldest changeset that needs to be pushed
            current_ctx = repo[outgoing[i]]
            original_ctx = current_ctx

            if len(current_ctx.parents()) != 1:
                ui.status('Found a branch merge, this needs discussion and '
                          'implementation.\n')
                # results in nonzero exit status, see hg's commands.py
                return 0

            # 3. Move the changeset to the tip of the branch if necessary
            conflicts = False
            for file in current_ctx.files():
                if file in modified_files:
                    conflicts = True
                    break

            if conflicts or current_ctx.branch() != svnbranch:
                util.swap_out_encoding(old_encoding)
                try:
                    def extrafn(ctx, extra):
                        extra['branch'] = ctx.branch()

                    ui.note('rebasing %s onto %s \n' % (current_ctx, tip_ctx))
                    hgrebase.rebase(ui, repo,
                                    dest=node.hex(tip_ctx.node()),
                                    rev=[node.hex(current_ctx.node())],
                                    extrafn=extrafn, keep=True)
                finally:
                    util.swap_out_encoding()

                # Don't trust the pre-rebase repo and context.
                repo = getlocalpeer(ui, {}, meta.path)
                meta = repo.svnmeta(svn.uuid, svn.subdir)
                hashes = meta.revmap.hashes()
                tip_ctx = repo[tip_ctx.node()]
                for c in tip_ctx.descendants():
                    rebasesrc = c.extra().get('rebase_source')
                    if rebasesrc and node.bin(rebasesrc) == current_ctx.node():
                        current_ctx = c
                        temporary_commits.append(c.node())
                        break

            # 4. Push the changeset to subversion
            tip_hash = hashes[tip_ctx.node()][0]
            try:
                ui.status('committing %s\n' % current_ctx)
                pushedrev = pushmod.commit(ui, repo, current_ctx, meta,
                                           tip_hash, svn)
            except pushmod.NoFilesException:
                ui.warn("Could not push revision %s because it had no changes "
                        "in svn.\n" % current_ctx)
                return

            # This hook is here purely for testing.  It allows us to
            # onsistently trigger hit the race condition between
            # pushing and pulling here.  In particular, we use it to
            # trigger another revision landing between the time we
            # push a revision and pull it back.
            repo.hook('debug-hgsubversion-between-push-and-pull-for-tests')

            # 5. Pull the latest changesets from subversion, which will
            # include the one we just committed (and possibly others).
            r = pull(repo, dest, force=force, meta=meta)
            assert not r or r == 0

            # 6. Move our tip to the latest pulled tip
            for c in tip_ctx.descendants():
                if c.node() in hashes and c.branch() == svnbranch:
                    if meta.get_source_rev(ctx=c)[0] == pushedrev.revnum:
                        # This is corresponds to the changeset we just pushed
                        if hasobsolete:
                            obsmarkers.append([(original_ctx, [c])])

                    tip_ctx = c

                    # Remember what files have been modified since the
                    # whole push started.
                    for file in c.files():
                        modified_files[file] = True

            # 7. Rebase any children of the commit we just pushed
            # that are not in the outgoing set
            for c in original_ctx.children():
                if not c.node() in hashes and not c.node() in outgoing:
                    util.swap_out_encoding(old_encoding)
                    try:
                        # Path changed as subdirectories were getting
                        # deleted during push.
                        saved_path = os.getcwd()
                        os.chdir(repo.root)

                        def extrafn(ctx, extra):
                            extra['branch'] = ctx.branch()

                        ui.status('rebasing non-outgoing %s onto %s\n' % (c, tip_ctx))
                        needs_rebase_set = "%s::" % node.hex(c.node())
                        hgrebase.rebase(ui, repo,
                                        dest=node.hex(tip_ctx.node()),
                                        rev=[needs_rebase_set],
                                        extrafn=extrafn,
                                        keep=not hasobsolete)
                    finally:
                        os.chdir(saved_path)
                        util.swap_out_encoding()


        util.swap_out_encoding(old_encoding)
        try:
            hg.update(repo, repo.branchtip(workingbranch))
        finally:
            util.swap_out_encoding()

        if hasobsolete:
            for marker in obsmarkers:
                obsolete.createmarkers(repo, marker)
                beforepush = marker[0][0]
                afterpush = marker[0][1][0]
                ui.note('marking %s as obsoleted by %s\n' %
                        (beforepush.hex(), afterpush.hex()))
        else:
            # strip the original changesets since the push was
            # successful and changeset obsolescence is unavailable
            util.strip(ui, repo, outgoing, "all")
    finally:
        try:
            # It's always safe to delete the temporary commits.
            # The originals are not deleted unless the push
            # completely succeeded.
            if temporary_commits:
                # If the repo is on a temporary commit, get off before
                # the strip.
                parent = repo[None].p1()
                if parent.node() in temporary_commits:
                    hg.update(repo, parent.p1().node())
                if hasobsolete:
                    relations = ((repo[n], ()) for n in temporary_commits)
                    obsolete.createmarkers(repo, relations)
                else:
                    util.strip(ui, repo, temporary_commits, backup=None)

        finally:
            util.swap_out_encoding(old_encoding)
    return 1 # so we get a sane exit status, see hg's commands.push
Example #11
0
def push(repo, dest, force, revs):
    """push revisions starting at a specified head back to Subversion.
    """
    assert not revs, 'designated revisions for push remains unimplemented.'
    cmdutil.bail_if_changed(repo)
    checkpush = getattr(repo, 'checkpush', None)
    if checkpush:
        checkpush(force, revs)
    ui = repo.ui
    old_encoding = util.swap_out_encoding()
    # TODO: implement --rev/#rev support
    # TODO: do credentials specified in the URL still work?
    svnurl = repo.ui.expandpath(dest.svnurl)
    svn = dest.svn
    meta = repo.svnmeta(svn.uuid, svn.subdir)

    # Strategy:
    # 1. Find all outgoing commits from this head
    if len(repo.parents()) != 1:
        ui.status('Cowardly refusing to push branch merge\n')
        return 0  # results in nonzero exit status, see hg's commands.py
    workingrev = repo.parents()[0]
    ui.status('searching for changes\n')
    hashes = meta.revmap.hashes()
    outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
    if not (outgoing and len(outgoing)):
        ui.status('no changes found\n')
        return 1  # so we get a sane exit status, see hg's commands.push
    while outgoing:

        # 2. Commit oldest revision that needs to be pushed
        oldest = outgoing.pop(-1)
        old_ctx = repo[oldest]
        old_pars = old_ctx.parents()
        if len(old_pars) != 1:
            ui.status('Found a branch merge, this needs discussion and '
                      'implementation.\n')
            return 0  # results in nonzero exit status, see hg's commands.py
        # We will commit to svn against this node's parent rev. Any file-level
        # conflicts here will result in an error reported by svn.
        base_ctx = old_pars[0]
        base_revision = hashes[base_ctx.node()][0]
        svnbranch = base_ctx.branch()
        # Find most recent svn commit we have on this branch.
        # This node will become the nearest known ancestor of the pushed rev.
        oldtipctx = base_ctx
        old_children = oldtipctx.descendants()
        seen = set(c.node() for c in old_children)
        samebranchchildren = [
            c for c in old_children
            if c.branch() == svnbranch and c.node() in hashes
        ]
        if samebranchchildren:
            # The following relies on descendants being sorted by rev.
            oldtipctx = samebranchchildren[-1]
        # All set, so commit now.
        try:
            pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn)
        except pushmod.NoFilesException:
            ui.warn(
                "Could not push revision %s because it had no changes in svn.\n"
                % old_ctx)
            return 1

        # 3. Fetch revisions from svn
        # TODO: this probably should pass in the source explicitly - rev too?
        r = repo.pull(dest, force=force)
        assert not r or r == 0

        # 4. Find the new head of the target branch
        # We expect to get our own new commit back, but we might also get other
        # commits that happened since our last pull, or even right after our own
        # commit (race).
        for c in oldtipctx.descendants():
            if c.node() not in seen and c.branch() == svnbranch:
                newtipctx = c

        # 5. Rebase all children of the currently-pushing rev to the new head
        heads = repo.heads(old_ctx.node())
        for needs_transplant in heads:

            def extrafn(ctx, extra):
                if ctx.node() == oldest:
                    return
                extra['branch'] = ctx.branch()

            # TODO: can we avoid calling our own rebase wrapper here?
            rebase(hgrebase.rebase,
                   ui,
                   repo,
                   svn=True,
                   svnextrafn=extrafn,
                   svnsourcerev=needs_transplant)
            # Reload the repo after the rebase. Do not reuse contexts across this.
            newtip = newtipctx.node()
            repo = hg.repository(ui, meta.path)
            newtipctx = repo[newtip]
            # Rewrite the node ids in outgoing to their rebased versions.
            rebasemap = dict()
            for child in newtipctx.descendants():
                rebasesrc = child.extra().get('rebase_source')
                if rebasesrc:
                    rebasemap[node.bin(rebasesrc)] = child.node()
            outgoing = [rebasemap.get(n) or n for n in outgoing]
        # TODO: stop constantly creating the SVNMeta instances.
        meta = repo.svnmeta(svn.uuid, svn.subdir)
        hashes = meta.revmap.hashes()
    util.swap_out_encoding(old_encoding)
    return 1  # so we get a sane exit status, see hg's commands.push