def get_auth(ui, bugzilla, profile): auth = getbugzillaauth(ui, require=True, profile=profile) if auth.apikey: return bzAuth(bugzilla, apikey=auth.apikey, username=auth.username) if auth.userid: return bzAuth(bugzilla, userid=auth.userid, cookie=auth.cookie) return bzAuth(bugzilla, username=auth.username, password=auth.password)
def bzauth(ui, require=False, fakegetpass=None): if fakegetpass: def newgetpass(arg): return fakegetpass getpass.getpass = newgetpass a = getbugzillaauth(ui, require=require) if a: ui.write('userid: %s\n' % a.userid) ui.write('cookie: %s\n' % a.cookie) ui.write('username: %s\n' % a.username) ui.write('password: %s\n' % a.password) else: ui.write('no auth\n')
def bzauth(ui, require=False, fakegetpass=None, ffprofile=None): if fakegetpass: def newgetpass(arg): return fakegetpass getpass.getpass = newgetpass a = getbugzillaauth(ui, require=require, profile=ffprofile) if a: ui.write('userid: %s\n' % a.userid) ui.write('cookie: %s\n' % a.cookie) ui.write('username: %s\n' % a.username) ui.write('password: %s\n' % a.password) ui.write('apikey: %s\n' % a.apikey) else: ui.write('no auth\n')
def bzauth(ui, require=False, fakegetpass=None, ffprofile=None): if fakegetpass: def newgetpass(arg): return fakegetpass getpass.getpass = newgetpass a = getbugzillaauth(ui, require=require, profile=ffprofile) if a: ui.write("userid: %s\n" % a.userid) ui.write("cookie: %s\n" % a.cookie) ui.write("username: %s\n" % a.username) ui.write("password: %s\n" % a.password) ui.write("apikey: %s\n" % a.apikey) else: ui.write("no auth\n")
def wrappedpushbookmark(orig, pushop): result = orig(pushop) # pushop.ret was renamed to pushop.cgresult in Mercurial 3.2. We can drop # this branch once we drop <3.2 support. if hasattr(pushop, 'cgresult'): origresult = pushop.cgresult else: origresult = pushop.ret # Don't do anything if error from push. if not origresult: return result remoteurl = pushop.remote.url() tree = repository.resolve_uri_to_tree(remoteurl) # We don't support release trees (yet) because they have special flags # that need to get updated. if tree and tree in repository.RELEASE_TREES: return result ui = pushop.ui if tree and tree in ui.configlist('bzpost', 'excludetrees', default=[]): return result if tree: baseuri = repository.resolve_trees_to_uris([tree])[0][1].encode('utf-8') assert baseuri else: # This isn't a known Firefox tree. Fall back to resolving URLs by # hostname. # Only attend Mozilla's server. if not updateunknown(remoteurl, repository.BASE_WRITE_URI, ui): return result baseuri = remoteurl.replace(repository.BASE_WRITE_URI, repository.BASE_READ_URI).rstrip('/') bugsmap = {} lastbug = None lastnode = None for node in pushop.outgoing.missing: ctx = pushop.repo[node] # Don't do merge commits. if len(ctx.parents()) > 1: continue # Our bug parser is buggy for Gaia bump commit messages. if '<*****@*****.**>' in ctx.user(): continue # Pushing to Try (and possibly other repos) could push unrelated # changesets that have been pushed to an official tree but aren't yet # on this specific remote. We use the phase information as a proxy # for "already pushed" and prune public changesets from consideration. if tree == 'try' and ctx.phase() == phases.public: continue bugs = parse_bugs(ctx.description()) if not bugs: continue bugsmap.setdefault(bugs[0], []).append(ctx.hex()[0:12]) lastbug = bugs[0] lastnode = ctx.hex()[0:12] if not bugsmap: return result bzauth = getbugzillaauth(ui) if not bzauth: return result bzurl = ui.config('bugzilla', 'url', 'https://bugzilla.mozilla.org/rest') bugsy = Bugsy(username=bzauth.username, password=bzauth.password, userid=bzauth.userid, cookie=bzauth.cookie, api_key=bzauth.apikey, bugzilla_url=bzurl) def public_url_for_bug(bug): '''Turn 123 into "https://bugzilla.mozilla.org/show_bug.cgi?id=123".''' public_baseurl = bzurl.replace('rest', '').rstrip('/') return '%s/show_bug.cgi?id=%s' % (public_baseurl, bug) # If this is a try push, we paste the Treeherder link for the tip commit, because # the per-commit URLs don't have much value. # TODO roll this into normal pushing so we get a Treeherder link in bugs as well. if tree == 'try' and lastbug: treeherderurl = repository.treeherder_url(tree, lastnode) bug = bugsy.get(lastbug) comments = bug.get_comments() for comment in comments: if treeherderurl in comment.text: return result ui.write(_('recording Treeherder push at %s\n') % public_url_for_bug(lastbug)) bug.add_comment(treeherderurl) return result for bugnumber, nodes in bugsmap.items(): bug = bugsy.get(bugnumber) comments = bug.get_comments() missing_nodes = [] # When testing whether this changeset URL is referenced in a # comment, we only need to test for the node fragment. The # important side-effect is that each unique node for a changeset # is recorded in the bug. for node in nodes: if not any(node in comment.text for comment in comments): missing_nodes.append(node) if not missing_nodes: ui.write(_('bug %s already knows about pushed changesets\n') % bugnumber) continue lines = [] for node in missing_nodes: ctx = pushop.repo[node] lines.append('%s/rev/%s' % (baseuri, ctx.hex())) # description is using local encodings. Depending on the # configured encoding, replacement characters could be involved. We # use encoding.fromlocal() to get the raw bytes, which should be # valid UTF-8. lines.append(encoding.fromlocal(ctx.description()).splitlines()[0]) lines.append('') comment = '\n'.join(lines) ui.write(_('recording push at %s\n') % public_url_for_bug(bugnumber)) bug.add_comment(comment) return result
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"))
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 wrappedpushbookmark(orig, pushop): result = orig(pushop) # pushop.ret was renamed to pushop.cgresult in Mercurial 3.2. We can drop # this branch once we drop <3.2 support. if hasattr(pushop, 'cgresult'): origresult = pushop.cgresult else: origresult = pushop.ret # Don't do anything if error from push. if not origresult: return result remoteurl = pushop.remote.url() tree = repository.resolve_uri_to_tree(remoteurl) # We don't support release trees (yet) because they have special flags # that need to get updated. if tree and tree in repository.RELEASE_TREES: return result ui = pushop.ui if tree and tree in ui.configlist('bzpost', 'excludetrees', default=[]): return result if tree: baseuri = repository.resolve_trees_to_uris([tree ])[0][1].encode('utf-8') assert baseuri else: # This isn't a known Firefox tree. Fall back to resolving URLs by # hostname. # Only attend Mozilla's server. if not updateunknown(remoteurl, repository.BASE_WRITE_URI, ui): return result baseuri = remoteurl.replace(repository.BASE_WRITE_URI, repository.BASE_READ_URI).rstrip('/') bugsmap = {} lastbug = None lastnode = None for node in pushop.outgoing.missing: ctx = pushop.repo[node] # Don't do merge commits. if len(ctx.parents()) > 1: continue # Our bug parser is buggy for Gaia bump commit messages. if '<*****@*****.**>' in ctx.user(): continue # Pushing to Try (and possibly other repos) could push unrelated # changesets that have been pushed to an official tree but aren't yet # on this specific remote. We use the phase information as a proxy # for "already pushed" and prune public changesets from consideration. if tree == 'try' and ctx.phase() == phases.public: continue bugs = parse_bugs(ctx.description()) if not bugs: continue bugsmap.setdefault(bugs[0], []).append(ctx.hex()) lastbug = bugs[0] lastnode = ctx.hex() if not bugsmap: return result bzauth = getbugzillaauth(ui) if not bzauth: return result bzurl = ui.config('bugzilla', 'url', 'https://bugzilla.mozilla.org/rest') bugsy = Bugsy(username=bzauth.username, password=bzauth.password, userid=bzauth.userid, cookie=bzauth.cookie, api_key=bzauth.apikey, bugzilla_url=bzurl) def public_url_for_bug(bug): '''Turn 123 into "https://bugzilla.mozilla.org/show_bug.cgi?id=123".''' public_baseurl = bzurl.replace('rest', '').rstrip('/') return '%s/show_bug.cgi?id=%s' % (public_baseurl, bug) # If this is a try push, we paste the Treeherder link for the tip commit, because # the per-commit URLs don't have much value. # TODO roll this into normal pushing so we get a Treeherder link in bugs as well. if tree == 'try' and lastbug: treeherderurl = repository.treeherder_url(tree, lastnode) bug = bugsy.get(lastbug) comments = bug.get_comments() for comment in comments: if treeherderurl in comment.text: return result ui.write( _('recording Treeherder push at %s\n') % public_url_for_bug(lastbug)) bug.add_comment(treeherderurl) return result for bugnumber, nodes in bugsmap.items(): bug = bugsy.get(bugnumber) comments = bug.get_comments() missing_nodes = [] # When testing whether this changeset URL is referenced in a # comment, we only need to test for the node fragment. The # important side-effect is that each unique node for a changeset # is recorded in the bug. for node in nodes: if not any(node in comment.text for comment in comments): missing_nodes.append(node) if not missing_nodes: ui.write( _('bug %s already knows about pushed changesets\n') % bugnumber) continue lines = [] for node in missing_nodes: ctx = pushop.repo[node] lines.append('%s/rev/%s' % (baseuri, ctx.hex())) # description is using local encodings. Depending on the # configured encoding, replacement characters could be involved. We # use encoding.fromlocal() to get the raw bytes, which should be # valid UTF-8. lines.append(encoding.fromlocal(ctx.description()).splitlines()[0]) lines.append('') comment = '\n'.join(lines) ui.write(_('recording push at %s\n') % public_url_for_bug(bugnumber)) bug.add_comment(comment) return result
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'))
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'))
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'))