Beispiel #1
0
def commit_message_filter(msg):
    # type: (bytes) -> Tuple[bytes, Dict[Text, Text]]
    metadata = {}
    m = commitparser.BUG_RE.match(msg)
    if m:
        bug_bytes, bug_number = m.groups()[:2]
        if msg.startswith(bug_bytes):
            prefix = re.compile(br"^%s[^\w\d\[\(]*" % bug_bytes)
            msg = prefix.sub(b"", msg)
        metadata[u"bugzilla-url"] = env.bz.bugzilla_url(int(bug_number))

    reviewers = u", ".join(
        item.decode("utf8", "replace")
        for item in commitparser.parse_reviewers(msg))
    if reviewers:
        metadata["gecko-reviewers"] = reviewers
    msg = commitparser.replace_reviewers(msg, "")
    msg = commitparser.strip_commit_metadata(msg)
    description = msg.splitlines()
    if description:
        summary = description.pop(0)
        summary = summary.rstrip(b"!#$%&(*+,-/:;<=>@[\\^_`{|~").rstrip()
        msg = summary + (b"\n" +
                         b"\n".join(description) if description else b"")

    return msg, metadata
def revset_reviewer(repo, subset, x):
    """``reviewer(REVIEWER)``
    Changesets reviewed by a specific person.
    """
    n = revset.getstring(x, _('reviewer() requires a string argument.'))

    return subset.filter(lambda x: n in parse_reviewers(repo[x].description()))
def revset_me(repo, subset, x):
    """``me()``
    Changesets that you are involved in.
    """
    if x:
        raise ParseError(_('me() does not take any arguments'))

    me = repo.ui.config('ui', 'username')
    if not me:
        raise util.Abort(_('"[ui] username" must be set to use me()'))

    ircnick = get_ircnick(repo.ui)

    n = encoding.lower(me)
    kind, pattern, matcher = revset._substringmatcher(n)

    revs = []

    for r in subset:
        ctx = repo[r]
        if matcher(encoding.lower(ctx.user())):
            revs.append(r)
            continue

        if ircnick in parse_reviewers(ctx.description()):
            revs.append(r)
            continue

    return revs
 def reviewersgen(_context):
     '''Generator for reviewers list'''
     for reviewer in commitparser.parse_reviewers(description):
         yield {
             b'name': reviewer,
             b'revset': b'reviewer(%s)' % reviewer,
         }
Beispiel #5
0
def revset_reviewed(repo, subset, x):
    """Changesets that were reviewed."""
    if x:
        raise ParseError(_('reviewed() does not take an argument'))

    return subset.filter(
        lambda x: list(parse_reviewers(repo[x].description())))
def revset_reviewer(repo, subset, x):
    """``reviewer(REVIEWER)``
    Changesets reviewed by a specific person.
    """
    n = revset.getstring(x, _('reviewer() requires a string argument.'))

    return [r for r in subset if n in parse_reviewers(repo[r].description())]
def revset_reviewed(repo, subset, x):
    """``reviewed()``
    Changesets that were reviewed.
    """
    if x:
        raise ParseError(_('reviewed() does not take an argument'))

    return [r for r in subset if list(parse_reviewers(repo[r].description()))]
Beispiel #8
0
def template_reviewer(repo, ctx, **args):
    """:reviewer: String. The first reviewer of this changeset."""
    reviewers = parse_reviewers(ctx.description())
    try:
        first_reviewer = next(reviewers)
        return first_reviewer
    except StopIteration:
        return None
def revset_reviewed(repo, subset, x):
    """``reviewed()``
    Changesets that were reviewed.
    """
    if x:
        raise ParseError(_('reviewed() does not take an argument'))

    return subset.filter(lambda x: list(parse_reviewers(repo[x].description())))
Beispiel #10
0
def template_reviewers(repo, ctx, **args):
    """:reviewers: List of strings. The reviewers associated with tis
    changeset."""
    # TRACKING hg47
    if templateutil:
        return templateutil.mappinggenerator(parse_reviewers,
                                             args=(ctx.description(), ))
    else:
        return parse_reviewers(ctx.description())
Beispiel #11
0
def commit_message_filter(msg):
    metadata = {}
    m = commitparser.BUG_RE.match(msg)
    if m:
        bug_str, bug_number = m.groups()[:2]
        if msg.startswith(bug_str):
            prefix = re.compile(r"^%s[^\w\d\[\(]*" % bug_str)
            msg = prefix.sub("", msg)
        metadata["bugzilla-url"] = env.bz.bugzilla_url(bug_number)

    reviewers = ", ".join(commitparser.parse_reviewers(msg))
    if reviewers:
        metadata["gecko-reviewers"] = reviewers
    msg = commitparser.replace_reviewers(msg, "")
    msg = commitparser.strip_commit_metadata(msg)
    description = msg.splitlines()
    if description:
        summary = description.pop(0)
        summary = summary.rstrip("!#$%&(*+,-/:;<=>@[\\^_`{|~").rstrip()
        description = "\n".join(description)
        msg = summary + ("\n" + description if description else "")

    return msg, metadata
 def test_first_reviewer_with_period_at_end_of_name():
     # TODO: this is not the current behaviour, but implementing this would
     # yield more expected results. We should probably also account for the
     # case of users having a period at the end of their username.
     self.assertEqual(list(parse_reviewers(b"stuff;r=a.,b")), [b"a", b"b"])
    def hasreviewer(r):
        for reviewer in commitparser.parse_reviewers(repo[r].description()):
            if encoding.lower(reviewer) == n:
                return True

        return False
Beispiel #14
0
    def test_reviewers(self):

        # first with r? reviewer request syntax
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff; r?romulus')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, r?remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus,r?remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus,remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r?romulus)')),['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r?romulus,remus)')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r?romulus]')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r?remus, r?romulus]')), ['remus', 'romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, a=test-only')), ['romulus', 'test-only'])

        # now with r= review granted syntax
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff; r=romulus')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus, r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus,r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus, remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus,remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r=romulus)')),['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r=romulus,remus)')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r=romulus]')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r=remus, r=romulus]')), ['remus', 'romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus, a=test-only')), ['romulus', 'test-only'])

        # try some other separators than ;
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff r=romulus')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff. r=romulus, r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff - r=romulus,r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff, r=romulus, remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff.. r=romulus,remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff | (r=romulus)')),['romulus'])

        # make sure things work with different spacing
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff;r=romulus,r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff.r=romulus, r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff,r=romulus, remus')), ['romulus', 'remus'])

        self.assertEqual(list(parse_reviewers(
            'Bug 1094764 - Implement AudioContext.suspend and friends.  r=roc,ehsan\n'
            '- Relevant spec text:\n'
            '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n'
            '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise\n')),
            ['roc', 'ehsan'])

        self.assertEqual(list(parse_reviewers(
            'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if '
            'string is null), patch by Mook <*****@*****.**>, '
            'r=bsmedberg/dbaron, sr=dbaron, a1.9=bz')),
            ['bsmedberg', 'dbaron', 'dbaron'])

        self.assertEqual(list(parse_reviewers(
            'Bug 123 - Blah blah; r=gps DONTBUILD (NPOTB)')),
            ['gps'])

        self.assertEqual(list(parse_reviewers(
            'Bug 123 - Blah blah; r=gps DONTBUILD')),
            ['gps'])

        self.assertEqual(list(parse_reviewers(
            'Bug 123 - Blah blah; r=gps (DONTBUILD)')),
            ['gps'])
    def fltr(x):
        ctx = repo[x]
        if matcher(encoding.lower(ctx.user())):
            return True

        return ircnick in parse_reviewers(ctx.description())
    def hasreviewer(r):
        for reviewer in commitparser.parse_reviewers(repo[r].description()):
            if encoding.lower(reviewer) == n:
                return True

        return False
def template_reviewer(repo, ctx, **args):
    """:reviewer: String. The first reviewer of this changeset."""
    reviewers = list(parse_reviewers(ctx.description()))
    return reviewers[0] if reviewers else None
def reviewboard(repo, proto, args=None):
    proto.redirect()

    o = parsepayload(proto, args)
    if isinstance(o, ServerError):
        return formatresponse(str(o))

    bzusername = o["bzusername"]
    bzapikey = o["bzapikey"]

    identifier, nodes, precursors = parseidentifier(o)
    if not identifier:
        return ["error %s" % _("no review identifier in request")]

    diffopts = mdiff.diffopts(context=8, showfunc=True, git=True)

    commits = {"individual": [], "squashed": {}}

    # We do multiple passes over the changesets requested for review because
    # some operations could be slow or may involve queries to external
    # resources. We want to run the fast checks first so we don't waste
    # resources before finding the error. The drawback here is the client
    # will not see the full set of errors. We may revisit this decision
    # later.

    for node in nodes:
        ctx = repo[node]
        # Reviewing merge commits doesn't make much sense and only makes
        # situations more complicated. So disallow the practice.
        if len(ctx.parents()) > 1:
            msg = "cannot review merge commits (%s)" % short(ctx.node())
            return formatresponse("error %s" % msg)

    # Invalid or confidental bugs will raise errors in the Review Board
    # interface later. Fail fast to minimize wasted time and resources.
    try:
        reviewid = ReviewID(identifier)
    except util.Abort as e:
        return formatresponse("error %s" % e)

    # We use xmlrpc here because the Bugsy REST client doesn't currently handle
    # errors in responses.

    # We don't use available Bugzilla credentials because that's the
    # easiest way to test for confidential bugs. If/when we support posting
    # reviews to confidential bugs, we'll need to change this.
    xmlrpc_url = repo.ui.config("bugzilla", "url").rstrip("/") + "/xmlrpc.cgi"
    proxy = xmlrpclib.ServerProxy(xmlrpc_url)
    try:
        proxy.Bug.get({"ids": [reviewid.bug]})
    except xmlrpclib.Fault as f:
        if f.faultCode == 101:
            return formatresponse(
                "error bug %s does not exist; " "please change the review id (%s)" % (reviewid.bug, reviewid.full)
            )
        elif f.faultCode == 102:
            return formatresponse(
                "error bug %s could not be accessed "
                "(we do not currently allow posting of reviews to "
                "confidential bugs)" % reviewid.bug
            )

        return formatresponse(
            "error server error verifying bug %s exists; " "please retry or report a bug" % reviewid.bug
        )

    # Find the first public node in the ancestry of this series. This is
    # used by MozReview to query the upstream repo for additional context.
    first_public_ancestor = None
    for node in repo[nodes[0]].ancestors():
        ctx = repo[node]
        if ctx.phase() == phases.public:
            first_public_ancestor = ctx.hex()
            break
    commits["squashed"]["first_public_ancestor"] = first_public_ancestor

    # Note patch.diff() appears to accept anything that can be fed into
    # repo[]. However, it blindly does a hex() on the argument as opposed
    # to the changectx, so we need to pass in the binary node.
    base_ctx = repo[nodes[0]].p1()
    base_parent_node = base_ctx.node()
    for i, node in enumerate(nodes):
        ctx = repo[node]
        p1 = ctx.p1().node()
        diff = None
        parent_diff = None

        diff = "".join(patch.diff(repo, node1=p1, node2=ctx.node(), opts=diffopts)) + "\n"

        if i:
            base_commit_id = nodes[i - 1]
        else:
            base_commit_id = base_ctx.hex()

        summary = encoding.fromlocal(ctx.description().splitlines()[0])
        commits["individual"].append(
            {
                "id": node,
                "precursors": precursors.get(node, []),
                "message": encoding.fromlocal(ctx.description()),
                "diff": diff,
                "bug": str(reviewid.bug),
                "base_commit_id": base_commit_id,
                "first_public_ancestor": first_public_ancestor,
                "reviewers": list(commitparser.parse_reviewers(summary)),
            }
        )

    commits["squashed"]["diff"] = (
        "".join(patch.diff(repo, node1=base_parent_node, node2=repo[nodes[-1]].node(), opts=diffopts)) + "\n"
    )
    commits["squashed"]["base_commit_id"] = base_ctx.hex()

    rburl = repo.ui.config("reviewboard", "url", None).rstrip("/")
    repoid = repo.ui.configint("reviewboard", "repoid", None)
    privleged_rb_username = repo.ui.config("reviewboard", "username", None)
    privleged_rb_password = repo.ui.config("reviewboard", "password", None)

    # We support pushing via HTTP and SSH. REMOTE_USER will be set via HTTP.
    # USER via SSH. But USER is a common variable and could also sneak into
    # the HTTP environment.
    #
    # REMOTE_USER values come from Bugzilla. USER values come from LDAP.
    # There is a potential privilege escalation vulnerability if someone
    # obtains a Bugzilla account overlapping with a LDAP user having
    # special privileges. So, we explicitly don't perform an LDAP lookup
    # if REMOTE_USER is present because we could be crossing the user
    # stores.
    ldap_username = os.environ.get("USER")
    remote_user = repo.ui.environ.get("REMOTE_USER", os.environ.get("REMOTE_USER"))

    if ldap_username and not remote_user:
        associate_ldap_username(
            rburl, ldap_username, privleged_rb_username, privleged_rb_password, username=bzusername, apikey=bzapikey
        )

    lines = ["rburl %s" % rburl, "reviewid %s" % identifier]

    try:
        parentrid, commitmap, reviews = post_reviews(
            rburl, repoid, identifier, commits, lines, username=bzusername, apikey=bzapikey
        )
        lines.extend(
            [
                "parentreview %s" % parentrid,
                "reviewdata %s status %s" % (parentrid, urllib.quote(reviews[parentrid]["status"].encode("utf-8"))),
                "reviewdata %s public %s" % (parentrid, reviews[parentrid]["public"]),
            ]
        )

        for node, rid in commitmap.items():
            rd = reviews[rid]
            lines.append("csetreview %s %s" % (node, rid))
            lines.append("reviewdata %s status %s" % (rid, urllib.quote(rd["status"].encode("utf-8"))))
            lines.append("reviewdata %s public %s" % (rid, rd["public"]))

            if rd["reviewers"]:
                parts = [urllib.quote(r.encode("utf-8")) for r in rd["reviewers"]]
                lines.append("reviewdata %s reviewers %s" % (rid, ",".join(parts)))

    except AuthorizationError as e:
        lines.append("error %s" % str(e))
    except BadRequestError as e:
        lines.append("error %s" % str(e))

    res = formatresponse(*lines)
    return res
    def test_reviewers(self):

        # first with r? reviewer request syntax
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - some stuff; r?romulus')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r?romulus, r?remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r?romulus,r?remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r?romulus, remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r?romulus,remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; (r?romulus)')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; (r?romulus,remus)')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; [r?romulus]')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; [r?remus, r?romulus]')),
            [b'remus', b'romulus'])
        self.assertEqual(
            list(parse_reviewers(
                b'Bug 1 - More stuff; r?romulus, a=test-only')),
            [b'romulus', b'test-only'])
        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1 - More stuff; r?romulus, ux-r=test-only')),
            [b'romulus'])

        # now with r= review granted syntax
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - some stuff; r=romulus')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r=romulus, r=remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r=romulus,r=remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r=romulus, remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; r=romulus,remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; (r=romulus)')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; (r=romulus,remus)')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; [r=romulus]')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff; [r=remus, r=romulus]')),
            [b'remus', b'romulus'])
        self.assertEqual(
            list(parse_reviewers(
                b'Bug 1 - More stuff; r=romulus, a=test-only')),
            [b'romulus', b'test-only'])

        # try some other separators than ;
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - some stuff r=romulus')),
            [b'romulus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff. r=romulus, r=remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff - r=romulus,r=remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff, r=romulus, remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff.. r=romulus,remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff | (r=romulus)')),
            [b'romulus'])

        # make sure things work with different spacing
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - some stuff;r=romulus,r=remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff.r=romulus, r=remus')),
            [b'romulus', b'remus'])
        self.assertEqual(
            list(parse_reviewers(b'Bug 1 - More stuff,r=romulus, remus')),
            [b'romulus', b'remus'])

        # test that periods in names are OK
        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1 - More stuff.r=jimmy.jones, r=bill.mcneal')),
            [b'jimmy.jones', b'bill.mcneal'])
        self.assertEqual(list(parse_reviewers(b'Bug 1 - More stuff,r=jimmy.')),
                         [b'jimmy'])

        # check some funky names too
        self.assertEqual(list(parse_reviewers(b"stuff;r=a")), [b"a"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=aa")), [b"aa"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=.a")), [b".a"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=..a")), [b"..a"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=...a")), [b"...a"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=a...a")), [b"a...a"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=a.b")), [b"a.b"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=a.b.c")), [b"a.b.c"])
        self.assertEqual(list(parse_reviewers(b"stuff;r=-.-.-")), [b"-.-.-"])

        # NOTE: a string such as "stuff;r=a.,b" will not be parsed as expected
        # and will yield ["a"]. TODO: fix this in the future in the regex, or
        # do some post processing in `parse_reviewers` if this is needed. The
        # following test is testing the current behaviour only.

        self.assertEqual(list(parse_reviewers(b"stuff;r=a.,b")), [b"a"])

        # altogether now with some spaces sprinkled here and there
        self.assertEqual(
            list(
                parse_reviewers(
                    b"hi;r=a,aa,.a,..a,...a, a...a,a.b, a.b.c, -.-.-")), [
                        b"a", b"aa", b".a", b"..a", b"...a", b"a...a", b"a.b",
                        b"a.b.c", b"-.-.-"
                    ])

        # bare r?
        self.assertEqual(list(parse_reviewers(b'Bug 123 - Blah blah; r?')), [])
        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1313324 - Cover the screensharing UI with browser chrome test, r='
                )), [])

        # oddball real-world examples
        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1094764 - Implement AudioContext.suspend and friends.  r=roc,ehsan\n'
                    b'- Relevant spec text:\n'
                    b'- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n'
                    b'- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise\n'
                )), [b'roc', b'ehsan'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if '
                    b'string is null), patch by Mook <*****@*****.**>, '
                    b'r=bsmedberg/dbaron, sr=dbaron, a1.9=bz')),
            [b'bsmedberg', b'dbaron', b'dbaron'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 123 - Blah blah; r=gps DONTBUILD (NPOTB)')),
            [b'gps'])

        self.assertEqual(
            list(parse_reviewers(b'Bug 123 - Blah blah; r=gps DONTBUILD')),
            [b'gps'])

        self.assertEqual(
            list(parse_reviewers(b'Bug 123 - Blah blah; r=gps (DONTBUILD)')),
            [b'gps'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1181382: move declaration into namespace to resolve conflict. r=hsinyi. try: -b d -p all -u none -t none'
                )), [b'hsinyi'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1024110 - Change Aurora\'s default profile behavior to use channel-specific profiles. r=bsmedberg f=gavin,markh'
                )), [b'bsmedberg'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1199050 - Round off the corners of browser-extension-panel\'s content. ui-r=maritz, r=gijs'
                )), [b'maritz', b'gijs'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1197422 - Part 2: [webext] Implement the pageAction API. r=billm ui-r=bwinton'
                )), [b'billm', b'bwinton'])

        # 'ui-reviewer=' isn't supported (less than 4% of ui-review commits use
        # it, 'ui-r=' being the preferred syntax)
        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1217369 - "Welcome to ..." has extra padding on Loop\'s standalone UI making it feel strange. r=mikedeboer,ui-review=sevaan'
                )), [b'mikedeboer'])

        self.assertEqual(
            list(
                parse_reviewers(
                    b'Bug 1182996 - Fix and add missing namespace comments. rs=ehsan\n'
                    b'run-clang-tidy.py \\\n'
                    b'-checks=\'-*,llvm-namespace-comment\' \\\n'
                    b'-header-filter=^/.../mozilla-central/.* \\\n'
                    b'-fix')), [b'ehsan'])
def addmetadata(repo, ctx, d, onlycheap=False):
    """Add changeset metadata for hgweb templates."""
    bugs = list(set(commitparser.parse_bugs(ctx.description())))
    d['bugs'] = []
    for bug in commitparser.parse_bugs(ctx.description()):
        d['bugs'].append({
            'no': str(bug),
            'url': 'https://bugzilla.mozilla.org/show_bug.cgi?id=%s' % bug,
        })

    d['reviewers'] = []
    for reviewer in commitparser.parse_reviewers(ctx.description()):
        d['reviewers'].append({
            'name': reviewer,
            'revset': 'reviewer(%s)' % reviewer,
        })

    d['backsoutnodes'] = []
    backouts = commitparser.parse_backouts(ctx.description())
    if backouts:
        for node in backouts[0]:
            try:
                bctx = repo[node]
                d['backsoutnodes'].append({'node': bctx.hex()})
            except error.LookupError:
                pass

    # Repositories can define which TreeHerder repository they are associated
    # with.
    treeherder = repo.ui.config('mozilla', 'treeherder_repo')
    if treeherder:
        d['treeherderrepourl'] = 'https://treeherder.mozilla.org/#/jobs?repo=%s' % treeherder

    if onlycheap:
        return

    # Obtain the Gecko/app version/milestone.
    #
    # We could probably only do this if the repo is a known app repo (by
    # looking at the initial changeset). But, path based lookup is relatively
    # fast, so just do it. However, we need this in the "onlycheap"
    # section because resolving manifests is relatively slow and resolving
    # several on changelist pages may add seconds to page load times.
    try:
        fctx = repo.filectx('config/milestone.txt', changeid=ctx.node())
        lines = fctx.data().splitlines()
        lines = [l for l in lines if not l.startswith('#') and l.strip()]

        if lines:
            d['milestone'] = lines[0].strip()
    except error.LookupError:
        pass

    # Look for changesets that back out this one.
    #
    # We limit the distance we search for backouts because an exhaustive
    # search could be very intensive. e.g. you load up the root commit
    # on a repository with 200,000 changesets and that commit is never
    # backed out. This finds most backouts because backouts typically happen
    # shortly after a bad commit is introduced.
    thisshort = short(ctx.node())
    count = 0
    searchlimit = repo.ui.configint('hgmo', 'backoutsearchlimit', 100)
    for bctx in repo.set('%ld::', [ctx.rev()]):
        count += 1
        if count >= searchlimit:
            break

        backouts = commitparser.parse_backouts(bctx.description())
        if backouts and thisshort in backouts[0]:
            d['backedoutbynode'] = bctx.hex()
            break
    def fltr(x):
        ctx = repo[x]
        if matcher(encoding.lower(ctx.user())):
            return True

        return ircnick in parse_reviewers(ctx.description())
def template_reviewer(repo, ctx, **args):
    """:reviewer: String. The first reviewer of this changeset."""
    reviewers = list(parse_reviewers(ctx.description()))
    return reviewers[0] if reviewers else None
    def test_reviewers(self):

        # first with r? reviewer request syntax
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff; r?romulus')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, r?remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus,r?remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus,remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r?romulus)')),['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r?romulus,remus)')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r?romulus]')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r?remus, r?romulus]')), ['remus', 'romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, a=test-only')), ['romulus', 'test-only'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r?romulus, ux-r=test-only')), ['romulus'])

        # now with r= review granted syntax
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff; r=romulus')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus, r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus,r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus, remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus,remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r=romulus)')),['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; (r=romulus,remus)')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r=romulus]')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; [r=remus, r=romulus]')), ['remus', 'romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff; r=romulus, a=test-only')), ['romulus', 'test-only'])

        # try some other separators than ;
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff r=romulus')), ['romulus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff. r=romulus, r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff - r=romulus,r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff, r=romulus, remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff.. r=romulus,remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff | (r=romulus)')),['romulus'])

        # make sure things work with different spacing
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff;r=romulus,r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff.r=romulus, r=remus')), ['romulus', 'remus'])
        self.assertEqual(list(parse_reviewers('Bug 1 - More stuff,r=romulus, remus')), ['romulus', 'remus'])

        # oddball real-world examples
        self.assertEqual(list(parse_reviewers(
            'Bug 1094764 - Implement AudioContext.suspend and friends.  r=roc,ehsan\n'
            '- Relevant spec text:\n'
            '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n'
            '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise\n')),
            ['roc', 'ehsan'])

        self.assertEqual(list(parse_reviewers(
            'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if '
            'string is null), patch by Mook <*****@*****.**>, '
            'r=bsmedberg/dbaron, sr=dbaron, a1.9=bz')),
            ['bsmedberg', 'dbaron', 'dbaron'])

        self.assertEqual(list(parse_reviewers(
            'Bug 123 - Blah blah; r=gps DONTBUILD (NPOTB)')),
            ['gps'])

        self.assertEqual(list(parse_reviewers(
            'Bug 123 - Blah blah; r=gps DONTBUILD')),
            ['gps'])

        self.assertEqual(list(parse_reviewers(
            'Bug 123 - Blah blah; r=gps (DONTBUILD)')),
            ['gps'])

        self.assertEqual(list(parse_reviewers(
             'Bug 1181382: move declaration into namespace to resolve conflict. r=hsinyi. try: -b d -p all -u none -t none')),
             ['hsinyi'])

        self.assertEqual(list(parse_reviewers(
            'Bug 1024110 - Change Aurora\'s default profile behavior to use channel-specific profiles. r=bsmedberg f=gavin,markh')),
            ['bsmedberg'])

        self.assertEqual(list(parse_reviewers(
            'Bug 1199050 - Round off the corners of browser-extension-panel\'s content. ui-r=maritz, r=gijs')),
            ['maritz', 'gijs'])

        self.assertEqual(list(parse_reviewers(
            'Bug 1197422 - Part 2: [webext] Implement the pageAction API. r=billm ui-r=bwinton')),
            ['billm', 'bwinton'])

        # 'ui-reviewer=' isn't supported (less than 4% of ui-review commits use
        # it, 'ui-r=' being the preferred syntax)
        self.assertEqual(list(parse_reviewers(
            'Bug 1217369 - "Welcome to ..." has extra padding on Loop''s standalone UI making it feel strange. r=mikedeboer,ui-review=sevaan')),
            ['mikedeboer'])

        self.assertEqual(list(parse_reviewers(
            'Bug 1182996 - Fix and add missing namespace comments. rs=ehsan\n'
            'run-clang-tidy.py \\\n'
            '-checks=\'-*,llvm-namespace-comment\' \\\n'
            '-header-filter=^/.../mozilla-central/.* \\\n'
            '-fix')),
            ['ehsan'])
def template_reviewers(repo, ctx, **args):
    """:reviewers: List of strings. The reviewers associated with tis
    changeset."""
    return parse_reviewers(ctx.description())
def template_reviewers(repo, ctx, **args):
    """:reviewers: List of strings. The reviewers associated with tis
    changeset."""
    return parse_reviewers(ctx.description())
    def test_reviewers(self):

        # first with r? reviewer request syntax
        self.assertEqual(
            list(parse_reviewers('Bug 1 - some stuff; r?romulus')),
            ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r?romulus, r?remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r?romulus,r?remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r?romulus, remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r?romulus,remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; (r?romulus)')),
            ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; (r?romulus,remus)')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; [r?romulus]')),
            ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; [r?remus, r?romulus]')),
            ['remus', 'romulus'])
        self.assertEqual(
            list(
                parse_reviewers('Bug 1 - More stuff; r?romulus, a=test-only')),
            ['romulus', 'test-only'])
        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1 - More stuff; r?romulus, ux-r=test-only')),
            ['romulus'])

        # now with r= review granted syntax
        self.assertEqual(
            list(parse_reviewers('Bug 1 - some stuff; r=romulus')),
            ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r=romulus, r=remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r=romulus,r=remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r=romulus, remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; r=romulus,remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; (r=romulus)')),
            ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; (r=romulus,remus)')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; [r=romulus]')),
            ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff; [r=remus, r=romulus]')),
            ['remus', 'romulus'])
        self.assertEqual(
            list(
                parse_reviewers('Bug 1 - More stuff; r=romulus, a=test-only')),
            ['romulus', 'test-only'])

        # try some other separators than ;
        self.assertEqual(list(parse_reviewers('Bug 1 - some stuff r=romulus')),
                         ['romulus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff. r=romulus, r=remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff - r=romulus,r=remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff, r=romulus, remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff.. r=romulus,remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff | (r=romulus)')),
            ['romulus'])

        # make sure things work with different spacing
        self.assertEqual(
            list(parse_reviewers('Bug 1 - some stuff;r=romulus,r=remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff.r=romulus, r=remus')),
            ['romulus', 'remus'])
        self.assertEqual(
            list(parse_reviewers('Bug 1 - More stuff,r=romulus, remus')),
            ['romulus', 'remus'])

        # bare r?
        self.assertEqual(list(parse_reviewers('Bug 123 - Blah blah; r?')), [])
        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1313324 - Cover the screensharing UI with browser chrome test, r='
                )), [])

        # oddball real-world examples
        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1094764 - Implement AudioContext.suspend and friends.  r=roc,ehsan\n'
                    '- Relevant spec text:\n'
                    '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n'
                    '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise\n'
                )), ['roc', 'ehsan'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if '
                    'string is null), patch by Mook <*****@*****.**>, '
                    'r=bsmedberg/dbaron, sr=dbaron, a1.9=bz')),
            ['bsmedberg', 'dbaron', 'dbaron'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 123 - Blah blah; r=gps DONTBUILD (NPOTB)')), ['gps'])

        self.assertEqual(
            list(parse_reviewers('Bug 123 - Blah blah; r=gps DONTBUILD')),
            ['gps'])

        self.assertEqual(
            list(parse_reviewers('Bug 123 - Blah blah; r=gps (DONTBUILD)')),
            ['gps'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1181382: move declaration into namespace to resolve conflict. r=hsinyi. try: -b d -p all -u none -t none'
                )), ['hsinyi'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1024110 - Change Aurora\'s default profile behavior to use channel-specific profiles. r=bsmedberg f=gavin,markh'
                )), ['bsmedberg'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1199050 - Round off the corners of browser-extension-panel\'s content. ui-r=maritz, r=gijs'
                )), ['maritz', 'gijs'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1197422 - Part 2: [webext] Implement the pageAction API. r=billm ui-r=bwinton'
                )), ['billm', 'bwinton'])

        # 'ui-reviewer=' isn't supported (less than 4% of ui-review commits use
        # it, 'ui-r=' being the preferred syntax)
        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1217369 - "Welcome to ..." has extra padding on Loop'
                    's standalone UI making it feel strange. r=mikedeboer,ui-review=sevaan'
                )), ['mikedeboer'])

        self.assertEqual(
            list(
                parse_reviewers(
                    'Bug 1182996 - Fix and add missing namespace comments. rs=ehsan\n'
                    'run-clang-tidy.py \\\n'
                    '-checks=\'-*,llvm-namespace-comment\' \\\n'
                    '-header-filter=^/.../mozilla-central/.* \\\n'
                    '-fix')), ['ehsan'])
def addmetadata(repo, ctx, d, onlycheap=False):
    """Add changeset metadata for hgweb templates."""
    description = encoding.fromlocal(ctx.description())

    d['bugs'] = []
    for bug in commitparser.parse_bugs(description):
        d['bugs'].append({
            'no':
            str(bug),
            'url':
            'https://bugzilla.mozilla.org/show_bug.cgi?id=%s' % bug,
        })

    d['reviewers'] = []
    for reviewer in commitparser.parse_reviewers(description):
        d['reviewers'].append({
            'name': reviewer,
            'revset': 'reviewer(%s)' % reviewer,
        })

    d['backsoutnodes'] = []
    backouts = commitparser.parse_backouts(description)
    if backouts:
        for node in backouts[0]:
            try:
                bctx = repo[node]
                d['backsoutnodes'].append({'node': bctx.hex()})
            except error.RepoLookupError:
                pass

    # Repositories can define which TreeHerder repository they are associated
    # with.
    treeherder = repo.ui.config('mozilla', 'treeherder_repo')
    if treeherder:
        d['treeherderrepourl'] = 'https://treeherder.mozilla.org/#/jobs?repo=%s' % treeherder
        d['treeherderrepo'] = treeherder

        push = repo.pushlog.pushfromchangeset(ctx)
        # Don't print Perfherder link on non-publishing repos (like Try)
        # because the previous push likely has nothing to do with this
        # push.
        if push and push.nodes and repo.ui.configbool('phases', 'publish',
                                                      True):
            lastpushhead = repo[push.nodes[0]].hex()
            d['perfherderurl'] = (
                'https://treeherder.mozilla.org/perf.html#/compare?'
                'originalProject=%s&'
                'originalRevision=%s&'
                'newProject=%s&'
                'newRevision=%s') % (treeherder, push.nodes[-1], treeherder,
                                     lastpushhead)

    # If this changeset was converted from another one and we know which repo
    # it came from, add that metadata.
    convertrevision = ctx.extra().get('convert_revision')
    if convertrevision:
        sourcerepo = repo.ui.config('hgmo', 'convertsource')
        if sourcerepo:
            d['convertsourcepath'] = sourcerepo
            d['convertsourcenode'] = convertrevision

    if onlycheap:
        return

    # Obtain the Gecko/app version/milestone.
    #
    # We could probably only do this if the repo is a known app repo (by
    # looking at the initial changeset). But, path based lookup is relatively
    # fast, so just do it. However, we need this in the "onlycheap"
    # section because resolving manifests is relatively slow and resolving
    # several on changelist pages may add seconds to page load times.
    try:
        fctx = repo.filectx('config/milestone.txt', changeid=ctx.node())
        lines = fctx.data().splitlines()
        lines = [l for l in lines if not l.startswith('#') and l.strip()]

        if lines:
            d['milestone'] = lines[0].strip()
    except error.LookupError:
        pass

    # Look for changesets that back out this one.
    #
    # We limit the distance we search for backouts because an exhaustive
    # search could be very intensive. e.g. you load up the root commit
    # on a repository with 200,000 changesets and that commit is never
    # backed out. This finds most backouts because backouts typically happen
    # shortly after a bad commit is introduced.
    thisshort = short(ctx.node())
    count = 0
    searchlimit = repo.ui.configint('hgmo', 'backoutsearchlimit', 100)
    for bctx in repo.set('%ld::', [ctx.rev()]):
        count += 1
        if count >= searchlimit:
            break

        backouts = commitparser.parse_backouts(
            encoding.fromlocal(bctx.description()))
        if backouts and thisshort in backouts[0]:
            d['backedoutbynode'] = bctx.hex()
            break
Beispiel #28
0
def phabsend(ui, repo, *revs, **opts):
    """upload changesets to Phabricator

    If there are multiple revisions specified, they will be send as a stack
    with a linear dependencies relationship using the order specified by the
    revset.

    For the first time uploading changesets, local tags will be created to
    maintain the association. After the first time, phabsend will check
    obsstore and tags information so it can figure out whether to update an
    existing Differential Revision, or create a new one.

    If --amend is set, update commit messages so they have the
    ``Differential Revision`` URL, remove related tags. This is similar to what
    arcanist will do, and is more desired in author-push workflows. Otherwise,
    use local tags to record the ``Differential Revision`` association.

    The --confirm option lets you confirm changesets before sending them. You
    can also add following to your configuration file to make it default
    behaviour::

        [phabsend]
        confirm = true

    phabsend will check obsstore and the above association to decide whether to
    update an existing Differential Revision, or create a new one.
    """
    revs = list(revs) + opts.get(b'rev', [])
    revs = scmutil.revrange(repo, revs)

    if not revs:
        raise error.Abort(_(b'phabsend requires at least one changeset'))
    if opts.get(b'amend'):
        cmdutil.checkunfinished(repo)

    # {newnode: (oldnode, olddiff, olddrev}
    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])

    confirm = ui.configbool(b'phabsend', b'confirm')
    confirm |= bool(opts.get(b'confirm'))
    if confirm:
        confirmed = _confirmbeforesend(repo, revs, oldmap)
        if not confirmed:
            raise error.Abort(_(b'phabsend cancelled'))

    actions = []
    reviewers = opts.get(b'reviewer', [])
    if reviewers:
        phids = user_group_phids(repo, reviewers)
        actions.append({b'type': b'reviewers.add', b'value': phids})

    drevids = [] # [int]
    diffmap = {} # {newnode: diff}

    # Send patches one by one so we know their Differential Revision IDs and
    # can provide dependency relationship
    lastrevid = None
    for rev in revs:
        ui.debug(b'sending rev %d\n' % rev)
        ctx = repo[rev]

        acts = list(actions)

        reviewers = list(commitparser.parse_reviewers(ctx.description()))
        if reviewers:
            phids = user_group_phids(repo, reviewers)
            acts.append({b'type': b'reviewers.add', b'value': phids})

        bugs = commitparser.parse_bugs(ctx.description())
        if bugs:
            acts.append({b'type': b'bugzilla.bug-id',
                         b'value': str(bugs[0]).encode('utf-8')})

        # Get Differential Revision ID
        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
        if oldnode != ctx.node() or opts.get(b'amend'):
            # Create or update Differential Revision
            revision, diff = createdifferentialrevision(
                ctx, revid, lastrevid, oldnode, olddiff, acts)
            diffmap[ctx.node()] = diff
            newrevid = int(revision[r'object'][r'id'])
            if revid:
                action = b'updated'
            else:
                action = b'created'

            # Create a local tag to note the association, if commit message
            # does not have it already
            m = _differentialrevisiondescre.search(ctx.description())
            if not m or int(m.group(b'id')) != newrevid:
                tagname = b'D%d' % newrevid
                tags.tag(repo, tagname, ctx.node(), message=None, user=None,
                         date=None, local=True)
        else:
            # Nothing changed. But still set "newrevid" so the next revision
            # could depend on this one.
            newrevid = revid
            action = b'skipped'

        actiondesc = ui.label(
            {b'created': _(b'created'),
             b'skipped': _(b'skipped'),
             b'updated': _(b'updated')}[action],
            b'phabricator.action.%s' % action)
        drevdesc = ui.label(b'D%s' % newrevid, b'phabricator.drev')
        nodedesc = ui.label(bytes(ctx), b'phabricator.node')
        desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
        ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
                                             desc))
        drevids.append(newrevid)
        lastrevid = newrevid

    # Update commit messages and remove tags
    if opts.get(b'amend'):
        unfi = repo.unfiltered()
        drevs = callconduit(repo, b'differential.query', {b'ids': drevids})
        with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
            wnode = unfi[b'.'].node()
            mapping = {} # {oldnode: [newnode]}
            for i, rev in enumerate(revs):
                old = unfi[rev]
                drevid = drevids[i]
                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
                newdesc = getdescfromdrev(drev)
                # Make sure commit message contain "Differential Revision"
                if old.description() != newdesc:
                    parents = [
                        mapping.get(old.p1().node(), (old.p1(),))[0],
                        mapping.get(old.p2().node(), (old.p2(),))[0],
                    ]
                    new = context.metadataonlyctx(
                        repo, old, parents=parents, text=newdesc,
                        user=old.user(), date=old.date(), extra=old.extra())

                    newnode = new.commit()

                    mapping[old.node()] = [newnode]
                    # Update diff property
                    writediffproperties(unfi[newnode], diffmap[old.node()])
                # Remove local tags since it's no longer necessary
                tagname = b'D%d' % drevid
                if tagname in repo.tags():
                    tags.tag(repo, tagname, nullid, message=None, user=None,
                             date=None, local=True)
            scmutil.cleanupnodes(repo, mapping, b'phabsend')
            if wnode in mapping:
                unfi.setparents(mapping[wnode][0])
Beispiel #29
0
def reviewboard(repo, proto, args=None):
    proto.redirect()

    o = parsepayload(proto, args)
    if isinstance(o, ServerError):
        return formatresponse(str(o))

    bzusername = o['bzusername']
    bzapikey = o['bzapikey']

    identifier, nodes, precursors = parseidentifier(o)
    if not identifier:
        return ['error %s' % _('no review identifier in request')]

    diffopts = mdiff.diffopts(context=8, showfunc=True, git=True)

    commits = {
        'individual': [],
        'squashed': {}
    }

    # We do multiple passes over the changesets requested for review because
    # some operations could be slow or may involve queries to external
    # resources. We want to run the fast checks first so we don't waste
    # resources before finding the error. The drawback here is the client
    # will not see the full set of errors. We may revisit this decision
    # later.

    for node in nodes:
        ctx = repo[node]
        # Reviewing merge commits doesn't make much sense and only makes
        # situations more complicated. So disallow the practice.
        if len(ctx.parents()) > 1:
            msg = 'cannot review merge commits (%s)' % short(ctx.node())
            return formatresponse('error %s' % msg)

    # Invalid or confidental bugs will raise errors in the Review Board
    # interface later. Fail fast to minimize wasted time and resources.
    try:
        reviewid = ReviewID(identifier)
    except util.Abort as e:
        return formatresponse('error %s' % e)

    if reviewid.bug:
        # We use xmlrpc here because the Bugsy REST client doesn't currently handle
        # errors in responses.

        # We don't use available Bugzilla credentials because that's the
        # easiest way to test for confidential bugs. If/when we support posting
        # reviews to confidential bugs, we'll need to change this.
        xmlrpc_url = repo.ui.config('bugzilla', 'url').rstrip('/') + '/xmlrpc.cgi'
        proxy = xmlrpclib.ServerProxy(xmlrpc_url)
        try:
            proxy.Bug.get({'ids': [reviewid.bug]})
        except xmlrpclib.Fault as f:
            if f.faultCode == 101:
                return formatresponse('error bug %s does not exist; '
                    'please change the review id (%s)' % (reviewid.bug,
                        reviewid.full))
            elif f.faultCode == 102:
                return formatresponse('error bug %s could not be accessed '
                    '(we do not currently allow posting of reviews to '
                    'confidential bugs)' % reviewid.bug)

            return formatresponse('error server error verifying bug %s exists; '
                'please retry or report a bug' % reviewid.bug)

    # Note patch.diff() appears to accept anything that can be fed into
    # repo[]. However, it blindly does a hex() on the argument as opposed
    # to the changectx, so we need to pass in the binary node.
    base_ctx = repo[nodes[0]].p1()
    base_parent_node = base_ctx.node()
    for i, node in enumerate(nodes):
        ctx = repo[node]
        p1 = ctx.p1().node()
        diff = None
        parent_diff = None

        diff = ''.join(patch.diff(repo, node1=p1, node2=ctx.node(), opts=diffopts)) + '\n'

        if i:
            base_commit_id = nodes[i-1]
        else:
            base_commit_id = base_ctx.hex()
        summary = ctx.description().splitlines()[0]
        commits['individual'].append({
            'id': node,
            'precursors': precursors.get(node, []),
            'message': ctx.description(),
            'diff': diff,
            'base_commit_id': base_commit_id,
            'reviewers': list(commitparser.parse_reviewers(summary))
        })

    commits['squashed']['diff'] = ''.join(patch.diff(repo, node1=base_parent_node,
        node2=repo[nodes[-1]].node(), opts=diffopts)) + '\n'
    commits['squashed']['base_commit_id'] = base_ctx.hex()

    rburl = repo.ui.config('reviewboard', 'url', None).rstrip('/')
    repoid = repo.ui.configint('reviewboard', 'repoid', None)
    privleged_rb_username = repo.ui.config('reviewboard', 'username', None)
    privleged_rb_password = repo.ui.config('reviewboard', 'password', None)

    ldap_username = os.environ.get('USER')

    if ldap_username:
        associate_ldap_username(rburl, ldap_username, privleged_rb_username,
                                privleged_rb_password, username=bzusername,
                                apikey=bzapikey)

    lines = [
        'rburl %s' % rburl,
        'reviewid %s' % identifier,
    ]

    try:
        parentrid, commitmap, reviews = post_reviews(rburl, repoid, identifier,
                                                     commits, lines,
                                                     username=bzusername,
                                                     apikey=bzapikey)
        lines.extend([
            'parentreview %s' % parentrid,
            'reviewdata %s status %s' % (
                parentrid,
                urllib.quote(reviews[parentrid]['status'].encode('utf-8'))),
            'reviewdata %s public %s' % (
                parentrid,
                reviews[parentrid]['public']),
        ])

        for node, rid in commitmap.items():
            rd = reviews[rid]
            lines.append('csetreview %s %s' % (node, rid))
            lines.append('reviewdata %s status %s' % (rid,
                urllib.quote(rd['status'].encode('utf-8'))))
            lines.append('reviewdata %s public %s' % (rid, rd['public']))

            if rd['reviewers']:
                parts = [urllib.quote(r.encode('utf-8'))
                         for r in rd['reviewers']]
                lines.append('reviewdata %s reviewers %s' %
                             (rid, ','.join(parts)))

    except AuthorizationError as e:
        lines.append('error %s' % str(e))
    except BadRequestError as e:
        lines.append('error %s' % str(e))

    res = formatresponse(*lines)
    return res