Example #1
0
def processcommits(req, db, user):
    review_id = req.getParameter("review", filter=int)
    commit_ids = map(int, req.getParameter("commits").split(","))

    review = dbutils.Review.fromId(db, review_id)
    all_commits = [
        gitutils.Commit.fromId(db, review.repository, commit_id)
        for commit_id in commit_ids
    ]
    commitset = log_commitset.CommitSet(all_commits)

    heads = commitset.getHeads()
    tails = commitset.getTails()

    if len(heads) != 1:
        return "invalid commit-set; multiple heads"
    if len(tails) != 1:
        return "invalid commit-set; multiple tails"

    old_head = gitutils.Commit.fromSHA1(db, review.repository, tails.pop())
    new_head = heads.pop()

    output = cStringIO.StringIO()

    extensions.role.processcommits.execute(db, user, review, all_commits,
                                           old_head, new_head, output)

    return output.getvalue()
Example #2
0
def createReview(db,
                 user,
                 repository,
                 commits,
                 branch_name,
                 summary,
                 description,
                 from_branch_name=None,
                 via_push=False,
                 reviewfilters=None,
                 applyfilters=True,
                 applyparentfilters=False,
                 recipientfilters=None):
    cursor = db.cursor()

    if via_push:
        applyparentfilters = bool(
            user.getPreference(db, 'review.applyUpstreamFilters'))

    branch = dbutils.Branch.fromName(db, repository, branch_name)

    if branch is not None:
        raise OperationFailure(
            code="branchexists",
            title="Invalid review branch name",
            message="""\
<p>There is already a branch named <code>%s</code> in the repository.  You have
to select a different name.</p>

<p>If you believe the existing branch was created during an earlier (failed)
attempt to create this review, you can try to delete it from the repository
using the command<p>

<pre>  git push &lt;remote&gt; :%s</pre>

<p>and then press the "Submit Review" button on this page again.""" %
            (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)),
            is_html=True)

    if not commits:
        raise OperationFailure(
            code="nocommits",
            title="No commits specified",
            message="You need at least one commit to create a review.")

    commitset = log_commitset.CommitSet(commits)
    heads = commitset.getHeads()

    if len(heads) != 1:
        # There is really no plausible way for this error to occur.
        raise OperationFailure(
            code="disconnectedtree",
            title="Disconnected tree",
            message=("The specified commits do do not form a single connected "
                     "tree.  Creating a review of them is not supported."))

    head = heads.pop()

    if len(commitset.getTails()) != 1:
        tail_id = None
    else:
        tail_id = gitutils.Commit.fromSHA1(
            db, repository,
            commitset.getTails().pop()).getId(db)

    if not via_push:
        try:
            repository.createBranch(branch_name, head.sha1)
        except gitutils.GitCommandError as error:
            raise OperationFailure(
                code="branchfailed",
                title="Failed to create review branch",
                message=("<p><b>Output from git:</b></p>"
                         "<code style='padding-left: 1em'>%s</code>" %
                         htmlutils.htmlify(error.output)),
                is_html=True)

    createChangesetsForCommits(db, commits)

    try:
        cursor.execute(
            "INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id",
            [repository.id, branch_name,
             head.getId(db), tail_id])

        branch_id = cursor.fetchone()[0]
        reachable_values = [(branch_id, commit.getId(db))
                            for commit in commits]

        cursor.executemany(
            "INSERT INTO reachable (branch, commit) VALUES (%s, %s)",
            reachable_values)

        cursor.execute(
            "INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id",
            (branch_id, summary, description, applyfilters,
             applyparentfilters))

        review = dbutils.Review.fromId(db, cursor.fetchone()[0])

        cursor.execute(
            "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)",
            (review.id, user.id))

        if reviewfilters is not None:
            cursor.executemany(
                """INSERT INTO reviewfilters (review, uid, path, type, creator)
                                       VALUES (%s, %s, %s, %s, %s)""",
                [(review.id, filter_user_id, filter_path, filter_type, user.id)
                 for filter_user_id, filter_path, filter_type, filter_delegate
                 in reviewfilters])

        is_opt_in = False

        if recipientfilters is not None:
            cursor.executemany(
                "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)",
                [(review.id, filter_user_id, filter_include)
                 for filter_user_id, filter_include in recipientfilters])

            for filter_user_id, filter_include in recipientfilters:
                if filter_user_id is None and not filter_include:
                    is_opt_in = True

        addCommitsToReview(db, user, review, commits, new_review=True)

        if from_branch_name is not None:
            cursor.execute(
                "UPDATE branches SET review=%s WHERE repository=%s AND name=%s",
                (review.id, repository.id, from_branch_name))

        # Reload to get list of changesets added by addCommitsToReview().
        review = dbutils.Review.fromId(db, review.id)

        pending_mails = []
        recipients = review.getRecipients(db)
        for to_user in recipients:
            pending_mails.extend(
                mail.sendReviewCreated(db, user, to_user, recipients, review))

        if not is_opt_in:
            recipient_by_id = dict(
                (to_user.id, to_user) for to_user in recipients)

            cursor.execute(
                """SELECT userpreferences.uid, userpreferences.repository,
                                     userpreferences.filter, userpreferences.integer
                                FROM userpreferences
                     LEFT OUTER JOIN filters ON (filters.id=userpreferences.filter)
                               WHERE userpreferences.item='review.defaultOptOut'
                                 AND userpreferences.uid=ANY (%s)
                                 AND (userpreferences.filter IS NULL
                                   OR filters.repository=%s)
                                 AND (userpreferences.repository IS NULL
                                   OR userpreferences.repository=%s)""",
                (recipient_by_id.keys(), repository.id, repository.id))

            user_settings = {}
            has_filter_settings = False

            for user_id, repository_id, filter_id, integer in cursor:
                settings = user_settings.setdefault(user_id, [None, None, {}])
                value = bool(integer)

                if repository_id is None and filter_id is None:
                    settings[0] = value
                elif repository_id is not None:
                    settings[1] = value
                else:
                    settings[2][filter_id] = value
                    has_filter_settings = True

            if has_filter_settings:
                filters = Filters()
                filters.setFiles(db, review=review)

            for user_id, (global_default, repository_default,
                          filter_settings) in user_settings.items():
                to_user = recipient_by_id[user_id]
                opt_out = None

                if repository_default is not None:
                    opt_out = repository_default
                elif global_default is not None:
                    opt_out = global_default

                if filter_settings:
                    # Policy:
                    #
                    # If all of the user's filters that matched files in the
                    # review have review.defaultOptOut enabled, then opt out.
                    # When determining this, any review filters of the user's
                    # that match files in the review count as filters that don't
                    # have the review.defaultOptOut enabled.
                    #
                    # If any of the user's filters that matched files in the
                    # review have review.defaultOptOut disabled, then don't opt
                    # out.  When determining this, review filters are ignored.
                    #
                    # Otherwise, ignore the filter settings, and go with either
                    # the user's per-repository or global setting (as set
                    # above.)

                    filters.load(db, review=review, user=to_user)

                    # A set of filter ids.  If None is in the set, the user has
                    # one or more review filters in the review.  (These do not
                    # have ids.)
                    active_filters = filters.getActiveFilters(to_user)

                    for filter_id in active_filters:
                        if filter_id is None:
                            continue
                        elif filter_id in filter_settings:
                            if not filter_settings[filter_id]:
                                opt_out = False
                                break
                        else:
                            break
                    else:
                        if None not in active_filters:
                            opt_out = True

                if opt_out:
                    cursor.execute(
                        """INSERT INTO reviewrecipientfilters (review, uid, include)
                                           VALUES (%s, %s, FALSE)""",
                        (review.id, to_user.id))

        db.commit()

        mail.sendPendingMails(pending_mails)

        return review
    except:
        if not via_push:
            repository.run("branch", "-D", branch_name)
        raise
Example #3
0
def addCommitsToReview(db,
                       user,
                       review,
                       commits,
                       new_review=False,
                       commitset=None,
                       pending_mails=None,
                       silent_if_empty=set(),
                       full_merges=set(),
                       replayed_rebases={},
                       tracked_branch=False):
    cursor = db.cursor()

    if not new_review:
        import index

        new_commits = log_commitset.CommitSet(commits)
        old_commits = log_commitset.CommitSet(review.branch.commits)
        merges = new_commits.getMerges()

        for merge in merges:
            # We might have stripped it in a previous pass.
            if not merge in new_commits: continue

            tails = filter(
                lambda sha1: sha1 not in old_commits and sha1 not in merge.
                parents, new_commits.getTailsFrom(merge))

            if tails:
                if tracked_branch:
                    raise index.IndexException("""\
Merge %s adds merged-in commits.  Please push the merge manually
and follow the instructions.""" % merge.sha1[:8])

                cursor.execute(
                    "SELECT id, confirmed, tail FROM reviewmergeconfirmations WHERE review=%s AND uid=%s AND merge=%s",
                    (review.id, user.id, merge.getId(db)))

                row = cursor.fetchone()

                if not row or not row[1]:
                    if not row:
                        cursor.execute(
                            "INSERT INTO reviewmergeconfirmations (review, uid, merge) VALUES (%s, %s, %s) RETURNING id",
                            (review.id, user.id, merge.getId(db)))
                        confirmation_id = cursor.fetchone()[0]

                        merged = set()

                        for tail_sha1 in tails:
                            children = new_commits.getChildren(tail_sha1)

                            while children:
                                child = children.pop()
                                if child not in merged and new_commits.isAncestorOf(
                                        child, merge):
                                    merged.add(child)
                                    children.update(
                                        new_commits.getChildren(child) -
                                        merged)

                        merged_values = [(confirmation_id, commit.getId(db))
                                         for commit in merged]
                        cursor.executemany(
                            "INSERT INTO reviewmergecontributions (id, merged) VALUES (%s, %s)",
                            merged_values)
                        db.commit()
                    else:
                        confirmation_id = row[0]

                    message = "Merge %s adds merged-in commits:" % merge.sha1[:
                                                                              8]

                    for tail_sha1 in tails:
                        for parent_sha1 in merge.parents:
                            if parent_sha1 in new_commits:
                                parent = new_commits.get(parent_sha1)
                                if tail_sha1 in new_commits.getTailsFrom(
                                        parent):
                                    message += "\n  %s..%s" % (tail_sha1[:8],
                                                               parent_sha1[:8])

                    message += """
Please confirm that this is intended by loading:
  %s/confirmmerge?id=%d""" % (dbutils.getURLPrefix(db, user), confirmation_id)

                    raise index.IndexException(message)
                elif row[2] is not None:
                    if row[2] == merge.getId(db):
                        cursor.execute(
                            "SELECT merged FROM reviewmergecontributions WHERE id=%s",
                            (row[0], ))

                        for (merged_id, ) in cursor:
                            merged = gitutils.Commit.fromId(
                                db, review.repository, merged_id)
                            if merged.sha1 in merge.parents:
                                new_commits = new_commits.without([merged])
                                break
                    else:
                        tail = gitutils.Commit.fromId(db, review.repository,
                                                      row[2])
                        cut = [
                            gitutils.Commit.fromSHA1(db, review.repository,
                                                     sha1)
                            for sha1 in tail.parents if sha1 in new_commits
                        ]
                        new_commits = new_commits.without(cut)

        if commitset:
            commitset &= set(new_commits)
            commits = [commit for commit in commits if commit in commitset]

    changesets, silent_commits, silent_changesets = \
        createChangesetsForCommits(db, commits, silent_if_empty, full_merges, replayed_rebases)

    if not new_review:
        print "Adding %d commit%s to the review at:\n  %s" % (
            len(commits), len(commits) > 1 and "s" or "", review.getURL(db))

    reviewchangesets_values = [(review.id, changeset.id)
                               for changeset in changesets]

    cursor.executemany(
        """INSERT INTO reviewchangesets (review, changeset) VALUES (%s, %s)""",
        reviewchangesets_values)
    cursor.executemany(
        """INSERT INTO reviewfiles (review, changeset, file, deleted, inserted)
                               SELECT reviewchangesets.review, reviewchangesets.changeset, fileversions.file,
                                      COALESCE(SUM(chunks.deleteCount), 0), COALESCE(SUM(chunks.insertCount), 0)
                                 FROM reviewchangesets
                                 JOIN fileversions USING (changeset)
                      LEFT OUTER JOIN chunks USING (changeset, file)
                                WHERE reviewchangesets.review=%s
                                  AND reviewchangesets.changeset=%s
                             GROUP BY reviewchangesets.review, reviewchangesets.changeset, fileversions.file""",
        reviewchangesets_values)

    new_reviewers, new_watchers = assignChanges(db,
                                                user,
                                                review,
                                                changesets=changesets)

    cursor.execute(
        "SELECT include FROM reviewrecipientfilters WHERE review=%s AND uid IS NULL",
        (review.id, ))

    try:
        opt_out = cursor.fetchone()[0] is True
    except:
        opt_out = True

    if not new_review:
        for user_id in new_reviewers:
            new_reviewuser = dbutils.User.fromId(db, user_id)
            print "Added reviewer: %s <%s>" % (new_reviewuser.fullname,
                                               new_reviewuser.email)

            if opt_out:
                # If the user has opted out from receiving e-mails about this
                # review while only watching it, clear the opt-out now that the
                # user becomes a reviewer.
                cursor.execute(
                    "DELETE FROM reviewrecipientfilters WHERE review=%s AND uid=%s AND include=FALSE",
                    (review.id, user_id))

        for user_id in new_watchers:
            new_reviewuser = dbutils.User.fromId(db, user_id)
            print "Added watcher:  %s <%s>" % (new_reviewuser.fullname,
                                               new_reviewuser.email)

        review.incrementSerial(db)

        reviewing.comment.propagateCommentChains(db, user, review, new_commits,
                                                 replayed_rebases)

    if pending_mails is None: pending_mails = []

    notify_commits = filter(lambda commit: commit not in silent_commits,
                            commits)
    notify_changesets = filter(
        lambda changeset: changeset not in silent_changesets, changesets)

    if not new_review and notify_changesets:
        recipients = review.getRecipients(db)
        for to_user in recipients:
            pending_mails.extend(
                mail.sendReviewAddedCommits(db,
                                            user,
                                            to_user,
                                            recipients,
                                            review,
                                            notify_commits,
                                            notify_changesets,
                                            tracked_branch=tracked_branch))

    mail.sendPendingMails(pending_mails)

    review.reviewers.extend(
        [User.fromId(db, user_id) for user_id in new_reviewers])

    for user_id in new_watchers:
        review.watchers[User.fromId(db, user_id)] = "automatic"

    return True
Example #4
0
def createReview(db,
                 user,
                 repository,
                 commits,
                 branch_name,
                 summary,
                 description,
                 from_branch_name=None,
                 via_push=False,
                 reviewfilters=None,
                 applyfilters=True,
                 applyparentfilters=False,
                 recipientfilters=None):
    cursor = db.cursor()

    if via_push:
        applyparentfilters = bool(
            user.getPreference(db, 'review.applyUpstreamFilters'))

    branch = dbutils.Branch.fromName(db, repository, branch_name)

    if branch is not None:
        raise OperationFailure(
            code="branchexists",
            title="Invalid review branch name",
            message="""\
<p>There is already a branch named <code>%s</code> in the repository.  You have
to select a different name.</p>

<p>If you believe the existing branch was created during an earlier (failed)
attempt to create this review, you can try to delete it from the repository
using the command<p>

<pre>  git push &lt;remote&gt; :%s</pre>

<p>and then press the "Submit Review" button on this page again.""" %
            (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)),
            is_html=True)

    commitset = log_commitset.CommitSet(commits)

    if len(commitset.getHeads()) != 1:
        raise Exception, "invalid commit-set; multiple heads"

    head = commitset.getHeads().pop()

    if len(commitset.getTails()) != 1:
        tail_id = None
    else:
        tail_id = gitutils.Commit.fromSHA1(
            db, repository,
            commitset.getTails().pop()).getId(db)

    if not via_push:
        repository.branch(branch_name, head.sha1)

    try:
        cursor.execute(
            "INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id",
            [repository.id, branch_name,
             head.getId(db), tail_id])

        branch_id = cursor.fetchone()[0]
        reachable_values = [(branch_id, commit.getId(db))
                            for commit in commits]

        cursor.executemany(
            "INSERT INTO reachable (branch, commit) VALUES (%s, %s)",
            reachable_values)

        cursor.execute(
            "INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id",
            (branch_id, summary, description, applyfilters,
             applyparentfilters))

        review = dbutils.Review.fromId(db, cursor.fetchone()[0])

        cursor.execute(
            "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)",
            (review.id, user.id))

        if reviewfilters is not None:
            cursor.executemany(
                "INSERT INTO reviewfilters (review, uid, directory, file, type, creator) VALUES (%s, %s, %s, %s, %s, %s)",
                [(review.id, filter_user_id, filter_directory_id,
                  filter_file_id, filter_type, user.id)
                 for filter_directory_id, filter_file_id, filter_type,
                 filter_delegate, filter_user_id in reviewfilters])

        if recipientfilters is not None:
            cursor.executemany(
                "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)",
                [(review.id, filter_user_id, filter_include)
                 for filter_user_id, filter_include in recipientfilters])

        addCommitsToReview(db, user, review, commits, new_review=True)

        if from_branch_name is not None:
            cursor.execute(
                "UPDATE branches SET review=%s WHERE repository=%s AND name=%s",
                (review.id, repository.id, from_branch_name))

        # Reload to get list of changesets added by addCommitsToReview().
        review = dbutils.Review.fromId(db, review.id)

        pending_mails = []
        recipients = review.getRecipients(db)
        for to_user in recipients:
            pending_mails.extend(
                mail.sendReviewCreated(db, user, to_user, recipients, review))

        db.commit()

        mail.sendPendingMails(pending_mails)

        return review
    except:
        if not via_push:
            repository.run("branch", "-D", branch_name)
        raise