Ejemplo n.º 1
0
def doreview(repo, ui, remote, nodes):
    """Do the work of submitting a review to a remote repo.

    :remote is a peerrepository.
    :nodes is a list of nodes to review.
    """
    assert nodes
    assert "pushreview" in getreviewcaps(remote)

    bzauth = getbugzillaauth(ui)
    if not bzauth:
        ui.warn(_("Bugzilla credentials not available. Not submitting review.\n"))
        return

    identifier = None

    # The review identifier can come from a number of places. In order of
    # priority:
    # 1. --reviewid argument passed to push command
    # 2. The active bookmark
    # 3. The active branch (if it isn't default)
    # 4. A bug number extracted from commit messages

    if repo.reviewid:
        identifier = repo.reviewid

    # TODO The server currently requires a bug number for the identifier.
    # Pull bookmark and branch names in once allowed.
    # elif repo._bookmarkcurrent:
    #    identifier = repo._bookmarkcurrent
    # elif repo.dirstate.branch() != 'default':
    #    identifier = repo.dirstate.branch()

    if not identifier:
        for node in nodes:
            ctx = repo[node]
            bugs = parse_bugs(ctx.description())
            if bugs:
                identifier = "bz://%s" % bugs[0]
                break

    identifier = ReviewID(identifier)

    if not identifier:
        ui.write(
            _(
                "Unable to determine review identifier. Review "
                "identifiers are extracted from commit messages automatically. "
                'Try to begin one of your commit messages with "Bug XXXXXX -"\n'
            )
        )
        return

    # Append irc nick to review identifier.
    # This is an ugly workaround to a limitation in ReviewBoard. RB doesn't
    # really support changing the owner of a review. It is doable, but no
    # history is stored and this leads to faulty attribution. More details
    # in bug 1034188.
    if not identifier.user:
        ircnick = ui.config("mozilla", "ircnick", None)
        identifier.user = ircnick

    if hasattr(repo, "mq"):
        for patch in repo.mq.applied:
            if patch.node in nodes:
                ui.warn(
                    _(
                        "(You are using mq to develop patches. For the best "
                        "code review experience, use bookmark-based development "
                        "with changeset evolution. Read more at "
                        "http://mozilla-version-control-tools.readthedocs.org/en/latest/mozreview-user.html)\n"
                    )
                )
                break

    lines = commonrequestlines(ui, bzauth)
    lines.append("reviewidentifier %s" % urllib.quote(identifier.full))

    reviews = repo.reviews
    oldparentid = reviews.findparentreview(identifier=identifier.full)

    # Include obsolescence data so server can make intelligent decisions.
    obsstore = repo.obsstore
    for node in nodes:
        lines.append("csetreview %s" % hex(node))
        precursors = [hex(n) for n in obsolete.allprecursors(obsstore, [node])]
        lines.append("precursors %s %s" % (hex(node), " ".join(precursors)))

    ui.write(_("submitting %d changesets for review\n") % len(nodes))

    res = remote._call("pushreview", data="\n".join(lines))
    lines = getpayload(res)

    newparentid = None
    nodereviews = {}
    reviewdata = {}

    for line in lines:
        t, d = line.split(" ", 1)

        if t == "display":
            ui.write("%s\n" % d)
        elif t == "error":
            raise util.Abort(d)
        elif t == "parentreview":
            newparentid = d
            reviews.addparentreview(identifier.full, newparentid)
            reviewdata[newparentid] = {}
        elif t == "csetreview":
            node, rid = d.split(" ", 1)
            node = bin(node)
            reviewdata[rid] = {}
            nodereviews[node] = rid
        elif t == "reviewdata":
            rid, field, value = d.split(" ", 2)
            reviewdata[rid][field] = decodepossiblelistvalue(value)
        elif t == "rburl":
            reviews.baseurl = d

    reviews.remoteurl = remote.url()

    for node, rid in nodereviews.items():
        reviews.addnodereview(node, rid, newparentid)

    reviews.write()
    for rid, data in reviewdata.iteritems():
        reviews.savereviewrequest(rid, data)

    havedraft = False

    ui.write("\n")
    for node in nodes:
        rid = nodereviews[node]
        ctx = repo[node]
        # Bug 1065024 use cmdutil.show_changeset() here.
        ui.write("changeset:  %s:%s\n" % (ctx.rev(), ctx.hex()[0:12]))
        ui.write("summary:    %s\n" % ctx.description().splitlines()[0])
        ui.write("review:     %s" % reviews.reviewurl(rid))
        if reviewdata[rid].get("public") == "False":
            havedraft = True
            ui.write(" (draft)")
        ui.write("\n\n")

    ui.write(_("review id:  %s\n") % identifier.full)
    ui.write(_("review url: %s") % reviews.parentreviewurl(identifier.full))
    if reviewdata[newparentid].get("public", None) == "False":
        havedraft = True
        ui.write(" (draft)")
    ui.write("\n")

    # Warn people that they have not assigned reviewers for at least some
    # of their commits.
    for node in nodes:
        rd = reviewdata[nodereviews[node]]
        if not rd.get("reviewers", None):
            ui.status(_("(review requests lack reviewers; visit review url " "to assign reviewers)\n"))
            break

    # Make it clear to the user that they need to take action in order for
    # others to see this review series.
    if havedraft:
        # At some point we may want an yes/no/prompt option for autopublish
        # but for safety reasons we only allow no/prompt for now.
        if ui.configbool("reviewboard", "autopublish", True):
            ui.write("\n")
            publish = ui.promptchoice(_("publish these review requests now (Yn)? $$ &Yes $$ &No"))
            if publish == 0:
                publishreviewrequests(ui, remote, bzauth, [newparentid])
            else:
                ui.status(_("(visit review url to publish these review " "requests so others can see them)\n"))
        else:
            ui.status(_("(visit review url to publish these review requests " "so others can see them)\n"))
Ejemplo n.º 2
0
def doreview(repo, ui, remote, nodes):
    """Do the work of submitting a review to a remote repo.

    :remote is a peerrepository.
    :nodes is a list of nodes to review.
    """
    assert nodes
    assert 'pushreview' in getreviewcaps(remote)

    bzauth = getbugzillaauth(ui)
    if not bzauth:
        ui.warn(_('Bugzilla credentials not available. Not submitting review.\n'))
        return

    identifier = None

    # The review identifier can come from a number of places. In order of
    # priority:
    # 1. --reviewid argument passed to push command
    # 2. The active bookmark
    # 3. The active branch (if it isn't default)
    # 4. A bug number extracted from commit messages

    if repo.reviewid:
        identifier = repo.reviewid

    # TODO The server currently requires a bug number for the identifier.
    # Pull bookmark and branch names in once allowed.
    #elif repo._bookmarkcurrent:
    #    identifier = repo._bookmarkcurrent
    #elif repo.dirstate.branch() != 'default':
    #    identifier = repo.dirstate.branch()

    if not identifier:
        identifiers = set()
        for node in nodes:
            ctx = repo[node]
            bugs = parse_bugs(ctx.description().split('\n')[0])
            if bugs:
                identifier = 'bz://%s' % bugs[0]
                identifiers.add(identifier)

        if len(identifiers) > 1:
            raise util.Abort('cannot submit reviews referencing multiple '
                             'bugs', hint='limit reviewed changesets '
                             'with "-c" or "-r" arguments')

    identifier = ReviewID(identifier)

    if not identifier:
        ui.write(_('Unable to determine review identifier. Review '
            'identifiers are extracted from commit messages automatically. '
            'Try to begin one of your commit messages with "Bug XXXXXX -"\n'))
        return

    # Append irc nick to review identifier.
    # This is an ugly workaround to a limitation in ReviewBoard. RB doesn't
    # really support changing the owner of a review. It is doable, but no
    # history is stored and this leads to faulty attribution. More details
    # in bug 1034188.
    if not identifier.user:
        ircnick = ui.config('mozilla', 'ircnick', None)
        identifier.user = ircnick

    if hasattr(repo, 'mq'):
        for patch in repo.mq.applied:
            if patch.node in nodes:
                ui.warn(_('(You are using mq to develop patches. For the best '
                    'code review experience, use bookmark-based development '
                    'with changeset evolution. Read more at '
                    'http://mozilla-version-control-tools.readthedocs.org/en/latest/mozreview-user.html)\n'))
                break

    req = commonrequestdict(ui, bzauth)
    req['identifier'] = identifier.full
    req['changesets'] = []
    req['obsolescence'] = obsolete.isenabled(repo, obsolete.createmarkersopt)

    reviews = repo.reviews
    oldparentid = reviews.findparentreview(identifier=identifier.full)

    # Include obsolescence data so server can make intelligent decisions.
    obsstore = repo.obsstore
    for node in nodes:
        precursors = [hex(n) for n in obsolete.allprecursors(obsstore, [node])]
        req['changesets'].append({
            'node': hex(node),
            'precursors': precursors,
        })

    ui.write(_('submitting %d changesets for review\n') % len(nodes))

    res = calljsoncommand(ui, remote, 'pushreview', data=req, httpcap='submithttp',
                          httpcommand='mozreviewsubmitseries')
    if 'error' in res:
        raise error.Abort(res['error'])

    for w in res['display']:
        ui.write('%s\n' % w)

    reviews.baseurl = res['rburl']
    newparentid = res['parentrrid']
    reviews.addparentreview(identifier.full, newparentid)

    nodereviews = {}
    reviewdata = {}

    for rid, info in sorted(res['reviewrequests'].iteritems()):
        if 'node' in info:
            node = bin(info['node'])
            nodereviews[node] = rid

        reviewdata[rid] = {
            'status': info['status'],
            'public': info['public'],
        }

        if 'reviewers' in info:
            reviewdata[rid]['reviewers'] = info['reviewers']

    reviews.remoteurl = remote.url()

    for node, rid in nodereviews.items():
        reviews.addnodereview(node, rid, newparentid)

    reviews.write()
    for rid, data in reviewdata.iteritems():
        reviews.savereviewrequest(rid, data)

    havedraft = False

    ui.write('\n')
    for node in nodes:
        rid = nodereviews[node]
        ctx = repo[node]
        # Bug 1065024 use cmdutil.show_changeset() here.
        ui.write('changeset:  %s:%s\n' % (ctx.rev(), ctx.hex()[0:12]))
        ui.write('summary:    %s\n' % ctx.description().splitlines()[0])
        ui.write('review:     %s' % reviews.reviewurl(rid))
        if not reviewdata[rid].get('public'):
            havedraft = True
            ui.write(' (draft)')
        ui.write('\n\n')

    ui.write(_('review id:  %s\n') % identifier.full)
    ui.write(_('review url: %s') % reviews.parentreviewurl(identifier.full))
    if not reviewdata[newparentid].get('public'):
        havedraft = True
        ui.write(' (draft)')
    ui.write('\n')

    # Warn people that they have not assigned reviewers for at least some
    # of their commits.
    for node in nodes:
        rd = reviewdata[nodereviews[node]]
        if not rd.get('reviewers', None):
            ui.status(_('(review requests lack reviewers; visit review url '
                        'to assign reviewers)\n'))
            break

    # Make it clear to the user that they need to take action in order for
    # others to see this review series.
    if havedraft:
        # At some point we may want an yes/no/prompt option for autopublish
        # but for safety reasons we only allow no/prompt for now.
        if ui.configbool('reviewboard', 'autopublish', True):
            ui.write('\n')
            publish = ui.promptchoice(
                _('publish these review requests now (Yn)? $$ &Yes $$ &No'))
            if publish == 0:
                publishreviewrequests(ui, remote, bzauth, [newparentid])
            else:
                ui.status(_('(visit review url to publish these review '
                            'requests so others can see them)\n'))
        else:
            ui.status(_('(visit review url to publish these review requests '
                        'so others can see them)\n'))
def doreview(repo, ui, remote, reviewnode, basenode=None):
    """Do the work of submitting a review to a remote repo.

    :remote is a peerrepository.
    :reviewnode is the node of the tip to review.
    :basenode is the bottom node to review. If not specified, we will review
    all non-public ancestors of :reviewnode.
    """
    assert remote.capable('reviewboard')

    bzauth = getbugzillaauth(ui)
    if not bzauth:
        ui.warn(_('Bugzilla credentials not available. Not submitting review.\n'))
        return

    # Given a tip node, we need to find all changesets to review.
    #
    # A solution that works most of the time is to find all non-public
    # ancestors of that node. This is our default.
    #
    # If basenode is specified, we stop the traversal when we encounter it.
    #
    # Note that we will still refuse to review a public changeset even with
    # basenode. This decision is somewhat arbitrary and can be revisited later
    # if there is an actual need to review public changesets.
    nodes = [reviewnode]
    for node in repo[reviewnode].ancestors():
        ctx = repo[node]

        if ctx.phase() == phases.public:
            break
        if basenode and ctx.node() == basenode:
            nodes.insert(0, ctx.node())
            break

        nodes.insert(0, ctx.node())

    identifier = None

    # The review identifier can come from a number of places. In order of
    # priority:
    # 1. --reviewid argument passed to push command
    # 2. The active bookmark
    # 3. The active branch (if it isn't default)
    # 4. A bug number extracted from commit messages

    if repo.reviewid:
        identifier = repo.reviewid

    # TODO The server currently requires a bug number for the identifier.
    # Pull bookmark and branch names in once allowed.
    #elif repo._bookmarkcurrent:
    #    identifier = repo._bookmarkcurrent
    #elif repo.dirstate.branch() != 'default':
    #    identifier = repo.dirstate.branch()

    if not identifier:
        for node in nodes:
            ctx = repo[node]
            bugs = parse_bugs(ctx.description())
            if bugs:
                identifier = 'bz://%s' % bugs[0]
                break

    identifier = ReviewID(identifier)

    if not identifier:
        ui.write(_('Unable to determine review identifier. Review '
            'identifiers are extracted from commit messages automatically. '
            'Try to begin one of your commit messages with "Bug XXXXXX -"\n'))
        return

    # Append irc nick to review identifier.
    # This is an ugly workaround to a limitation in ReviewBoard. RB doesn't
    # really support changing the owner of a review. It is doable, but no
    # history is stored and this leads to faulty attribution. More details
    # in bug 1034188.
    if not identifier.user:
        ircnick = ui.config('mozilla', 'ircnick', None)
        identifier.user = ircnick

    if hasattr(repo, 'mq'):
        for patch in repo.mq.applied:
            if patch.node in nodes:
                ui.warn(_('(You are using mq to develop patches. For the best '
                    'code review experience, use bookmark-based development '
                    'with changeset evolution. Read more at '
                    'http://mozilla-version-control-tools.readthedocs.org/en/latest/mozreview-user.html)\n'))
                break

    lines = [
        '1',
        'reviewidentifier %s' % urllib.quote(identifier.full),
    ]

    for p in ('username', 'password', 'userid', 'cookie'):
        if getattr(bzauth, p, None):
            lines.append('bz%s %s' % (p, urllib.quote(getattr(bzauth, p))))

    reviews = repo.reviews
    oldparentid = reviews.findparentreview(identifier=identifier.full)

    # Include obsolescence data so server can make intelligent decisions.
    obsstore = repo.obsstore
    for node in nodes:
        lines.append('csetreview %s' % hex(node))
        precursors = [hex(n) for n in obsolete.allprecursors(obsstore, [node])]
        lines.append('precursors %s %s' % (hex(node), ' '.join(precursors)))

    ui.write(_('submitting %d changesets for review\n') % len(nodes))

    res = remote._call('pushreview', data='\n'.join(lines))

    # All protocol versions begin with: <version>\n
    try:
        off = res.index('\n')
        version = int(res[0:off])

        if version != 1:
            raise util.Abort(_('do not know how to handle response from server.'))
    except ValueError:
        raise util.Abort(_('invalid response from server.'))

    assert version == 1
    lines = res.split('\n')[1:]

    newparentid = None
    nodereviews = {}
    reviewdata = {}

    for line in lines:
        t, d = line.split(' ', 1)

        if t == 'display':
            ui.write('%s\n' % d)
        elif t == 'error':
            raise util.Abort(d)
        elif t == 'parentreview':
            newparentid = d
            reviews.addparentreview(identifier.full, newparentid)
            reviewdata[newparentid] = {}
        elif t == 'csetreview':
            node, rid = d.split(' ', 1)
            node = bin(node)
            reviews.addnodereview(node, rid, newparentid)
            reviewdata[rid] = {}
            nodereviews[node] = rid
        elif t == 'reviewdata':
            rid, field, value = d.split(' ', 2)
            value = urllib.unquote(value)
            reviewdata[rid][field] = value
        elif t == 'rburl':
            reviews.baseurl = d

    reviews.remoteurl = remote.url()

    reviews.write()
    for rid, data in reviewdata.iteritems():
        reviews.savereviewrequest(rid, data)

    ui.write('\n')
    for node in nodes:
        rid = nodereviews[node]
        ctx = repo[node]
        # Bug 1065024 use cmdutil.show_changeset() here.
        ui.write('changeset:  %s:%s\n' % (ctx.rev(), ctx.hex()[0:12]))
        ui.write('summary:    %s\n' % ctx.description().splitlines()[0])
        ui.write('review:     %s' % reviews.reviewurl(rid))
        if reviewdata[rid].get('status') == 'pending':
            ui.write(' (pending)')
        ui.write('\n\n')

    ispending = reviewdata[newparentid].get('status', None) == 'pending'
    ui.write(_('review id:  %s\n') % identifier.full)
    ui.write(_('review url: %s') % reviews.parentreviewurl(identifier.full))
    if ispending:
        ui.write(' (pending)')
    ui.write('\n')

    # Make it clear to the user that they need to take action in order for
    # others to see this review series.
    if ispending:
        ui.status(_('(visit review url to publish this review request so others can see it)\n'))
Ejemplo n.º 4
0
def doreview(repo, ui, remote, nodes):
    """Do the work of submitting a review to a remote repo.

    :remote is a peerrepository.
    :nodes is a list of nodes to review.
    """
    assert nodes
    assert 'pushreview' in getreviewcaps(remote)

    # Ensure a color for ui.warning is defined.
    try:
        color = extensions.find('color')
        if 'ui.warning' not in color._styles:
            color._styles['ui.warning'] = 'red'
    except Exception:
        pass

    bzauth = getbugzillaauth(ui)
    if not bzauth:
        ui.warn(
            _('Bugzilla credentials not available. Not submitting review.\n'))
        return

    identifier = None

    # The review identifier can come from a number of places. In order of
    # priority:
    # 1. --reviewid argument passed to push command
    # 2. The active bookmark
    # 3. The active branch (if it isn't default)
    # 4. A bug number extracted from commit messages

    if repo.reviewid:
        identifier = repo.reviewid

    # TODO The server currently requires a bug number for the identifier.
    # Pull bookmark and branch names in once allowed.
    #elif repo._bookmarkcurrent:
    #    identifier = repo._bookmarkcurrent
    #elif repo.dirstate.branch() != 'default':
    #    identifier = repo.dirstate.branch()

    if not identifier:
        identifiers = set()
        for node in nodes:
            ctx = repo[node]
            bugs = parse_bugs(ctx.description().split('\n')[0])
            if bugs:
                identifier = 'bz://%s' % bugs[0]
                identifiers.add(identifier)

        if len(identifiers) > 1:
            raise util.Abort(
                'cannot submit reviews referencing multiple '
                'bugs',
                hint='limit reviewed changesets '
                'with "-c" or "-r" arguments')

    identifier = ReviewID(identifier)

    if not identifier:
        ui.write(
            _('Unable to determine review identifier. Review '
              'identifiers are extracted from commit messages automatically. '
              'Try to begin one of your commit messages with "Bug XXXXXX -"\n')
        )
        return

    # Append irc nick to review identifier.
    # This is an ugly workaround to a limitation in ReviewBoard. RB doesn't
    # really support changing the owner of a review. It is doable, but no
    # history is stored and this leads to faulty attribution. More details
    # in bug 1034188.
    if not identifier.user:
        ircnick = ui.config('mozilla', 'ircnick', None)
        identifier.user = ircnick

    if hasattr(repo, 'mq'):
        for patch in repo.mq.applied:
            if patch.node in nodes:
                ui.warn(
                    _('(You are using mq to develop patches. For the best '
                      'code review experience, use bookmark-based development '
                      'with changeset evolution. Read more at '
                      'https://mozilla-version-control-tools.readthedocs.io/en/latest/mozreview-user.html)\n'
                      ))
                break

    req = commonrequestdict(ui, bzauth)
    req['identifier'] = identifier.full
    req['changesets'] = []
    req['obsolescence'] = obsolete.isenabled(repo, obsolete.createmarkersopt)
    req['deduce-reviewers'] = ui.configbool('reviewboard', 'deduce-reviewers',
                                            True)

    reviews = repo.reviews
    oldparentid = reviews.findparentreview(identifier=identifier.full)

    # Include obsolescence data so server can make intelligent decisions.
    obsstore = repo.obsstore
    for node in nodes:
        precursors = [hex(n) for n in obsolete.allprecursors(obsstore, [node])]
        req['changesets'].append({
            'node': hex(node),
            'precursors': precursors,
        })

    ui.write(_('submitting %d changesets for review\n') % len(nodes))

    res = calljsoncommand(ui,
                          remote,
                          'pushreview',
                          data=req,
                          httpcap='submithttp',
                          httpcommand='mozreviewsubmitseries')

    # Re-encode all items in res from u'' to utf-8 byte str to avoid
    # exceptions during str operations.
    reencoderesponseinplace(res)

    if 'error' in res:
        raise error.Abort(res['error'])

    for w in res['display']:
        ui.write('%s\n' % w)

    reviews.baseurl = res['rburl']
    newparentid = res['parentrrid']
    reviews.addparentreview(identifier.full, newparentid)

    nodereviews = {}
    reviewdata = {}

    for rid, info in sorted(res['reviewrequests'].iteritems()):
        if 'node' in info:
            node = bin(info['node'])
            nodereviews[node] = rid

        reviewdata[rid] = {
            'status': info['status'],
            'public': info['public'],
        }

        if 'reviewers' in info:
            reviewdata[rid]['reviewers'] = info['reviewers']

    reviews.remoteurl = remote.url()

    for node, rid in nodereviews.items():
        reviews.addnodereview(node, rid, newparentid)

    reviews.write()
    for rid, data in reviewdata.iteritems():
        reviews.savereviewrequest(rid, data)

    havedraft = False

    ui.write('\n')
    for node in nodes:
        rid = nodereviews[node]
        ctx = repo[node]
        # Bug 1065024 use cmdutil.show_changeset() here.
        ui.write('changeset:  %s:%s\n' % (ctx.rev(), ctx.hex()[0:12]))
        ui.write('summary:    %s\n' % ctx.description().splitlines()[0])
        ui.write('review:     %s' % reviews.reviewurl(rid))
        if not reviewdata[rid].get('public'):
            havedraft = True
            ui.write(' (draft)')
        ui.write('\n\n')

    ui.write(_('review id:  %s\n') % identifier.full)
    ui.write(_('review url: %s') % reviews.parentreviewurl(identifier.full))
    if not reviewdata[newparentid].get('public'):
        havedraft = True
        ui.write(' (draft)')
    ui.write('\n')

    # Warn people that they have not assigned reviewers for at least some
    # of their commits.
    for node in nodes:
        rd = reviewdata[nodereviews[node]]
        if not rd.get('reviewers', None):
            ui.write('\n')
            ui.warn(
                _('(review requests lack reviewers; visit review url '
                  'to assign reviewers)\n'))
            break

    # Make it clear to the user that they need to take action in order for
    # others to see this review series.
    if havedraft:
        # If there is no configuration value specified for
        # reviewboard.autopublish, prompt the user. Otherwise, publish
        # automatically or not based on this value.
        if ui.config('reviewboard', 'autopublish', None) is None:
            ui.write('\n')
            publish = ui.promptchoice(
                _('publish these review '
                  'requests now (Yn)? '
                  '$$ &Yes $$ &No')) == 0
        else:
            publish = ui.configbool('reviewboard', 'autopublish')

        if publish:
            publishreviewrequests(ui, remote, bzauth, [newparentid])
        else:
            ui.status(
                _('(visit review url to publish these review '
                  'requests so others can see them)\n'))
Ejemplo n.º 5
0
def doreview(repo, ui, remote, nodes):
    """Do the work of submitting a review to a remote repo.

    :remote is a peerrepository.
    :nodes is a list of nodes to review.
    """
    assert nodes
    assert 'pushreview' in getreviewcaps(remote)

    bzauth = getbugzillaauth(ui)
    if not bzauth:
        ui.warn(_('Bugzilla credentials not available. Not submitting review.\n'))
        return

    identifier = None

    # The review identifier can come from a number of places. In order of
    # priority:
    # 1. --reviewid argument passed to push command
    # 2. The active bookmark
    # 3. The active branch (if it isn't default)
    # 4. A bug number extracted from commit messages

    if repo.reviewid:
        identifier = repo.reviewid

    # TODO The server currently requires a bug number for the identifier.
    # Pull bookmark and branch names in once allowed.
    #elif repo._bookmarkcurrent:
    #    identifier = repo._bookmarkcurrent
    #elif repo.dirstate.branch() != 'default':
    #    identifier = repo.dirstate.branch()

    if not identifier:
        for node in nodes:
            ctx = repo[node]
            bugs = parse_bugs(ctx.description())
            if bugs:
                identifier = 'bz://%s' % bugs[0]
                break

    identifier = ReviewID(identifier)

    if not identifier:
        ui.write(_('Unable to determine review identifier. Review '
            'identifiers are extracted from commit messages automatically. '
            'Try to begin one of your commit messages with "Bug XXXXXX -"\n'))
        return

    # Append irc nick to review identifier.
    # This is an ugly workaround to a limitation in ReviewBoard. RB doesn't
    # really support changing the owner of a review. It is doable, but no
    # history is stored and this leads to faulty attribution. More details
    # in bug 1034188.
    if not identifier.user:
        ircnick = ui.config('mozilla', 'ircnick', None)
        identifier.user = ircnick

    if hasattr(repo, 'mq'):
        for patch in repo.mq.applied:
            if patch.node in nodes:
                ui.warn(_('(You are using mq to develop patches. For the best '
                    'code review experience, use bookmark-based development '
                    'with changeset evolution. Read more at '
                    'http://mozilla-version-control-tools.readthedocs.org/en/latest/mozreview-user.html)\n'))
                break

    lines = commonrequestlines(ui, bzauth)
    lines.append('reviewidentifier %s' % urllib.quote(identifier.full))

    reviews = repo.reviews
    oldparentid = reviews.findparentreview(identifier=identifier.full)

    # Include obsolescence data so server can make intelligent decisions.
    obsstore = repo.obsstore
    for node in nodes:
        lines.append('csetreview %s' % hex(node))
        precursors = [hex(n) for n in obsolete.allprecursors(obsstore, [node])]
        lines.append('precursors %s %s' % (hex(node), ' '.join(precursors)))

    ui.write(_('submitting %d changesets for review\n') % len(nodes))

    res = remote._call('pushreview', data='\n'.join(lines))
    lines = getpayload(res)

    newparentid = None
    nodereviews = {}
    reviewdata = {}

    for line in lines:
        t, d = line.split(' ', 1)

        if t == 'display':
            ui.write('%s\n' % d)
        elif t == 'error':
            raise util.Abort(d)
        elif t == 'parentreview':
            newparentid = d
            reviews.addparentreview(identifier.full, newparentid)
            reviewdata[newparentid] = {}
        elif t == 'csetreview':
            node, rid = d.split(' ', 1)
            node = bin(node)
            reviewdata[rid] = {}
            nodereviews[node] = rid
        elif t == 'reviewdata':
            rid, field, value = d.split(' ', 2)
            reviewdata[rid][field] = decodepossiblelistvalue(value)
        elif t == 'rburl':
            reviews.baseurl = d

    reviews.remoteurl = remote.url()

    for node, rid in nodereviews.items():
        reviews.addnodereview(node, rid, newparentid)

    reviews.write()
    for rid, data in reviewdata.iteritems():
        reviews.savereviewrequest(rid, data)

    havedraft = False

    ui.write('\n')
    for node in nodes:
        rid = nodereviews[node]
        ctx = repo[node]
        # Bug 1065024 use cmdutil.show_changeset() here.
        ui.write('changeset:  %s:%s\n' % (ctx.rev(), ctx.hex()[0:12]))
        ui.write('summary:    %s\n' % ctx.description().splitlines()[0])
        # We want to encourage people to use r? when asking for a review rather
        # than r=.
        if list(parse_requal_reviewers(ctx.description())):
            ui.warn(_('(It appears you are using r= to specify reviewers for a'
                ' patch under review. Please use r? to avoid ambiguity as to'
                ' whether or not review has been granted.)\n'))
        ui.write('review:     %s' % reviews.reviewurl(rid))
        if reviewdata[rid].get('public') == 'False':
            havedraft = True
            ui.write(' (draft)')
        ui.write('\n\n')

    ui.write(_('review id:  %s\n') % identifier.full)
    ui.write(_('review url: %s') % reviews.parentreviewurl(identifier.full))
    if reviewdata[newparentid].get('public', None) == 'False':
        havedraft = True
        ui.write(' (draft)')
    ui.write('\n')

    havereviewers = bool(nodes)
    for node in nodes:
        rd = reviewdata[nodereviews[node]]
        if not rd.get('reviewers', None):
            havereviewers = False
            break

    # Make it clear to the user that they need to take action in order for
    # others to see this review series.
    if havedraft:
        # If the series is ready for publishing, prompt the user to perform the
        # publishing.
        if havereviewers:
            caps = getreviewcaps(remote)
            if 'publish' in caps:
                ui.write('\n')
                publish = ui.promptchoice(
                    _('publish these review requests now (Yn)? $$ &Yes $$ &No'))
                if publish == 0:
                    publishreviewrequests(ui, remote, bzauth, [newparentid])
                else:
                    ui.status(_('(visit review url to publish these review '
                                'requests so others can see them)\n'))
            else:
                ui.status(_('(visit review url to publish these review requests'
                            'so others can see them)\n'))
        else:
            ui.status(_('(review requests lack reviewers; visit review url '
                        'to assign reviewers and publish these review '
                        'requests)\n'))