def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('url', help='URL of Mercurial repo to push to')
    parser.add_argument('commit', help='Git commit to push')
    parser.add_argument('--config-file', action='append',
                        help='Extra Mercurial config file to load')

    args = parser.parse_args(args)

    url = args.url
    commit = args.commit

    init_logging()

    if passwordmgr:
        repo = get_repo(Remote('hg::%s' % url, url))
    else:
        from mercurial import hg

        ui = get_ui()

        for p in args.config_file or []:
            ui.readconfig(p, trust=True)

        repo = hg.peer(ui, {}, url)

    heads = (hexlify(h) for h in repo.heads())
    store = PushStore()
    try:
        pushed = push(repo, store, {commit: (None, False)}, heads, ())
    except Exception as e:
        # This is a common error. So rather than display the entire stack to
        # the user, exit with a well-defined exit code so the caller can
        # display a nice error message.
        if any(arg.startswith('Pushing merges is not supported yet')
               for arg in getattr(e, 'args', ())[:1]):
            return 127
        raise

    commits = []
    if pushed:
        for commit in pushed.iternodes():
            changeset = store.hg_changeset(commit)
            ref = store.changeset_ref(changeset)
            new_data = type(ref) != str

            commits.append([commit, changeset, new_data])

    # By now, cinnabar or its subprocesses should not be writing anything to
    # either stdout or stderr. Ensure stderr is flushed for _this_ process,
    # since git-mozreview uses the same file descriptor for both stdout and
    # stderr, and we want to try to avoid mixed output.
    sys.stderr.flush()
    for commit, changeset, new_data in commits:
        print('>result>', commit, changeset, new_data)
    sys.stdout.flush()

    return 0
Ejemplo n.º 2
0
    def push(self, *refspecs):
        try:
            default = 'never' if self._graft else 'phase'
            values = {
                None: default,
                '': default,
                'never': 'never',
                'phase': 'phase',
                'always': 'always',
            }
            data = Git.config('cinnabar.data',
                              self._remote.name,
                              values=values)
        except InvalidConfig as e:
            logging.error(e.message)
            return 1

        pushes = {
            Git.resolve_ref(s.lstrip('+')): (d, s.startswith('+'))
            for s, d in (r.split(':', 1) for r in refspecs)
        }
        if not self._repo.capable('unbundle'):
            for source, (dest, force) in pushes.iteritems():
                self._helper.write(
                    'error %s Remote does not support the "unbundle" '
                    'capability\n' % dest)
            self._helper.write('\n')
            self._helper.flush()
        else:
            repo_heads = self._branchmap.heads()
            PushStore.adopt(self._store, self._graft)
            pushed = push(self._repo, self._store, pushes, repo_heads,
                          self._branchmap.names(), self._dry_run)

            status = {}
            for source, (dest, _) in pushes.iteritems():
                if dest.startswith('refs/tags/'):
                    if source:
                        status[dest] = 'Pushing tags is unsupported'
                    else:
                        status[dest] = \
                            'Deleting remote tags is unsupported'
                    continue
                if not dest.startswith('refs/heads/bookmarks/'):
                    if source:
                        status[dest] = bool(len(pushed))
                    else:
                        status[dest] = \
                            'Deleting remote branches is unsupported'
                    continue
                name = unquote(dest[21:])
                if source:
                    source = self._store.hg_changeset(source) or ''
                status[dest] = self._repo.pushkey(
                    'bookmarks', name, self._bookmarks.get(name, ''), source)

            for source, (dest, force) in pushes.iteritems():
                if status[dest] is True:
                    self._helper.write('ok %s\n' % dest)
                elif status[dest]:
                    self._helper.write('error %s %s\n' % (dest, status[dest]))
                else:
                    self._helper.write('error %s nothing changed on remote\n' %
                                       dest)
            self._helper.write('\n')
            self._helper.flush()

            if not pushed or self._dry_run:
                data = False
            elif data == 'always':
                data = True
            elif data == 'phase':
                phases = self._repo.listkeys('phases')
                drafts = {}
                if not phases.get('publishing', False):
                    drafts = set(p for p, is_draft in phases.iteritems()
                                 if int(is_draft))
                if not drafts:
                    data = True
                else:

                    def draft_commits():
                        for d in drafts:
                            c = self._store.changeset_ref(d)
                            if c:
                                yield '^%s^@' % c
                        for h in pushed.heads():
                            yield h

                    args = ['--ancestry-path', '--topo-order']
                    args.extend(draft_commits())

                    pushed_drafts = tuple(
                        c for c, t, p in GitHgHelper.rev_list(*args))

                    # Theoretically, we could have commits with no
                    # metadata that the remote declares are public, while
                    # the rest of our push is in a draft state. That is
                    # however so unlikely that it's not worth the effort
                    # to support partial metadata storage.
                    data = not bool(pushed_drafts)
            elif data == 'never':
                data = False

            self._store.close(rollback=not data)