Esempio n. 1
0
File: mail.py Progetto: Aessy/critic
def sendReviewRebased(db, from_user, to_user, recipients, review, new_upstream, rebased_commits, onto_branch=None):
    # First check if we can/should send emails to the user at all.
    try:
        checkEmailEnabled(db, to_user)
        subject = generateSubjectLine(db, to_user, review, "updatedReview.reviewRebased")
    except MailDisabled:
        return []

    if from_user == to_user and to_user.getPreference(db, "email.ignoreOwnChanges"):
        return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': review.repository.getURL(db, to_user),
             'from.fullname': from_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    if new_upstream:
        data["new_upstream"] = new_upstream.oneline(db, decorate=True)
        text = """\
%(from.fullname)s has rebased the review branch onto:

%(new_upstream)s""" % data
    else:
        text = "%(from.fullname)s has rewritten the history on the review branch." % data

    body += """%s


""" % textutils.reflow(text, line_length)

    body += """The new branch log is:

"""

    for commit in rebased_commits:
        body += commit.oneline(db) + "\n"

    files = []

    parent_message_id = getReviewMessageId(db, to_user, review, files)
    message_id = generateMessageId(len(files) + 1)

    files.append(sendMail(
        db, review, message_id, from_user, to_user, recipients, subject, body,
        parent_message_id=parent_message_id))

    return files
Esempio n. 2
0
File: mail.py Progetto: ryfow/critic
def sendFiltersApplied(db, from_user, to_user, review, globalfilters, parentfilters, assigned):
    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': review.repository.getURL(db, to_user),
             'from.fullname': from_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    if globalfilters:
        what = "global filters"
    else:
        what = "global filters from upstream repositories"

    text = ("%s has modified the assignments in the review by making %s apply, "
            "which they previously did not.  This had the effect that you are "
            "now a %s the review."
            % (from_user.fullname,
               what,
               "reviewer of changes in" if assigned else "watcher of"))

    body += """%s


""" % textutils.reflow(text, line_length)

    if assigned:
        body += renderFiles(db, to_user, review, "The following changes are now assigned to you:", assigned)

    files = []
    parent_message_id = None

    cursor = db.cursor()
    cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
    row = cursor.fetchone()

    if not row:
        files.extend(sendReviewPlaceholder(db, to_user, review))
        cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
        row = cursor.fetchone()

    if row:
        parent_message_id = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)

    files.append(sendMail(db, review, generateMessageId(len(files) + 1), from_user, to_user, [to_user], generateSubjectLine(db, to_user, review, "updatedReview.parentFiltersApplied"), body, parent_message_id=parent_message_id))

    return files
Esempio n. 3
0
def reflow(text, indent):
    try:
        import textutils
        return textutils.reflow(text, indent=indent)
    except Exception:
        # The 'textutils' module depends on 'configuration', so make our
        # dependency on it conditional.
        return text
Esempio n. 4
0
def sendVerificationMail(db, user, email_id=None):
    cursor = db.cursor()

    if email_id is None:
        cursor.execute("""SELECT email
                            FROM users
                           WHERE id=%s""",
                       (user.id,))

        email_id, = cursor.fetchone()

    cursor.execute("""SELECT email, verification_token
                        FROM useremails
                       WHERE id=%s""",
                   (email_id,))

    email, verification_token = cursor.fetchone()

    if verification_token is None:
        verification_token = auth.getToken(encode=base64.b16encode)

        with db.updating_cursor("useremails") as cursor:
            cursor.execute("""UPDATE useremails
                                 SET verification_token=%s
                               WHERE id=%s""",
                           (verification_token, email_id))

    if configuration.base.ACCESS_SCHEME == "http":
        protocol = "http"
    else:
        protocol = "https"

    administrators = dbutils.getAdministratorContacts(db, indent=2)

    if administrators:
        administrators = ":\n\n%s" % administrators
    else:
        administrators = "."

    recipients = [mailutils.User(user.name, email, user.fullname)]
    subject = "[Critic] Please verify your email: %s" % email
    body = textutils.reflow("""
This is a message from the Critic code review system at %(hostname)s.  The user
'%(username)s' on this system has added this email address to his/her account.
If this is you, please confirm this by following this link:

  %(url_prefix)s/verifyemail?email=%(email)s&token=%(verification_token)s

If this is not you, you can safely ignore this email.  If you wish to report
abuse, please contact the Critic system's administrators%(administrators)s
""" % { "hostname": configuration.base.HOSTNAME,
        "username": user.name,
        "email": email,
        "url_prefix": "%s://%s" % (protocol, configuration.base.HOSTNAME),
        "verification_token": verification_token,
        "administrators": administrators })

    mailutils.sendMessage(recipients, subject, body)
Esempio n. 5
0
def sendVerificationMail(db, user, email_id=None):
    cursor = db.cursor()

    if email_id is None:
        cursor.execute("""SELECT email
                            FROM users
                           WHERE id=%s""",
                       (user.id,))

        email_id, = cursor.fetchone()

    cursor.execute("""SELECT email, verification_token
                        FROM useremails
                       WHERE id=%s""",
                   (email_id,))

    email, verification_token = cursor.fetchone()

    if verification_token is None:
        verification_token = auth.getToken(encode=base64.b16encode)

        cursor.execute("""UPDATE useremails
                             SET verification_token=%s
                           WHERE id=%s""",
                       (verification_token, email_id))

    if configuration.base.ACCESS_SCHEME == "http":
        protocol = "http"
    else:
        protocol = "https"

    administrators = dbutils.getAdministratorContacts(db, indent=2)

    if administrators:
        administrators = ":\n\n%s" % administrators
    else:
        administrators = "."

    recipients = [mailutils.User(user.name, email, user.fullname)]
    subject = "[Critic] Please verify your email: %s" % email
    body = textutils.reflow("""
This is a message from the Critic code review system at %(hostname)s.  The user
'%(username)s' on this system has added this email address to his/her account.
If this is you, please confirm this by following this link:

  %(url_prefix)s/verifyemail?email=%(email)s&token=%(verification_token)s

If this is not you, you can safely ignore this email.  If you wish to report
abuse, please contact the Critic system's administrators%(administrators)s
""" % { "hostname": configuration.base.HOSTNAME,
        "username": user.name,
        "email": email,
        "url_prefix": "%s://%s" % (protocol, configuration.base.HOSTNAME),
        "verification_token": verification_token,
        "administrators": administrators })

    mailutils.sendMessage(recipients, subject, body)
Esempio n. 6
0
File: mail.py Progetto: Aessy/critic
def sendFiltersApplied(db, from_user, to_user, review, globalfilters, parentfilters, assigned):
    # First check if we can/should send emails to the user at all.
    try:
        checkEmailEnabled(db, to_user)
        subject = generateSubjectLine(db, to_user, review, "updatedReview.parentFiltersApplied")
    except MailDisabled:
        return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': review.repository.getURL(db, to_user),
             'from.fullname': from_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    if globalfilters:
        what = "global filters"
    else:
        what = "global filters from upstream repositories"

    text = ("%s has modified the assignments in the review by making %s apply, "
            "which they previously did not.  This had the effect that you are "
            "now a %s the review."
            % (from_user.fullname,
               what,
               "reviewer of changes in" if assigned else "watcher of"))

    body += """%s


""" % textutils.reflow(text, line_length)

    if assigned:
        body += renderFiles(db, to_user, review, "The following changes are now assigned to you:", assigned)

    files = []

    parent_message_id = getReviewMessageId(db, to_user, review, files)
    message_id = generateMessageId(len(files) + 1)

    files.append(sendMail(
        db, review, message_id, from_user, to_user, [to_user], subject, body,
        parent_message_id=parent_message_id))

    return files
Esempio n. 7
0
File: mail.py Progetto: KurSh/critic
def sendReviewRebased(db, from_user, to_user, recipients, review, new_upstream, rebased_commits, onto_branch=None):
    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []
    if from_user == to_user and to_user.getPreference(db, "email.ignoreOwnChanges"): return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': "%s:%s" % (configuration.base.HOSTNAME, review.repository.path),
             'from.fullname': from_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    if new_upstream or onto_branch:
        if onto_branch is None:
            data['target'] = "the commit '%s'" % new_upstream
        else:
            data['target'] = "the branch '%s'" % onto_branch

        text = "%(from.fullname)s has rebased the review branch onto %(target)s." % data
    else:
        text = "%(from.fullname)s has rewritten the history on the review branch." % data

    body += """%s


""" % textutils.reflow(text, line_length)

    body += """The new branch log is:

"""

    for commit in rebased_commits:
        body += "%s %s\n" % (commit.sha1[:8], commit.niceSummary())

    cursor = db.cursor()
    cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
    row = cursor.fetchone()

    if row: parent_message_id = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)
    else: parent_message_id = None

    return [sendMail(db, review, generateMessageId(), from_user, to_user, recipients, generateSubjectLine(db, to_user, review, "updatedReview.reviewRebased"), body, parent_message_id=parent_message_id)]
Esempio n. 8
0
File: mail.py Progetto: Aessy/critic
def sendExtensionOutput(db, user_id, batch_id, output):
    to_user = dbutils.User.fromId(db, user_id)

    cursor = db.cursor()
    cursor.execute("SELECT review, uid FROM batches WHERE id=%s", (batch_id,))

    review_id, batch_user_id = cursor.fetchone()

    review = dbutils.Review.fromId(db, review_id)
    batch_user = dbutils.User.fromId(db, batch_user_id)

    # First check if we can/should send emails to the user at all.
    try:
        checkEmailEnabled(db, to_user)
        subject = generateSubjectLine(db, to_user, review, "extensionOutput")
    except MailDisabled:
        return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'batch.user.fullname': batch_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    text = "A batch of changes submitted by %(batch.user.fullname)s has been processed by your installed extensions." % data

    body += """%s


""" % textutils.reflow(text, line_length)

    body += "The extensions generated the following output:\n%s" % output

    files = []

    parent_message_id = getReviewMessageId(db, to_user, review, files)
    message_id = generateMessageId(len(files) + 1)

    files.append(sendMail(
        db, review, message_id, to_user, to_user, [to_user], subject, body,
        parent_message_id=parent_message_id))

    return files
Esempio n. 9
0
File: mail.py Progetto: KurSh/critic
def sendExtensionOutput(db, user_id, batch_id, output):
    user = dbutils.User.fromId(db, user_id)

    # Explicitly *don't* check if the user has activated email sending.  This
    # allows a user to disable email sending and then install extensions whose
    # output is still sent.
    #if not user.getPreference(db, "email.activated"): return []

    line_length = user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    cursor = db.cursor()
    cursor.execute("SELECT review, uid FROM batches WHERE id=%s", (batch_id,))

    review_id, batch_user_id = cursor.fetchone()

    review = dbutils.Review.fromId(db, review_id)
    batch_user = dbutils.User.fromId(db, batch_user_id)

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, user, 2),
             'batch.user.fullname': batch_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    text = "A batch of changes submitted by %(batch.user.fullname)s has been processed by your installed extensions." % data

    body += """%s


""" % textutils.reflow(text, line_length)

    body += "The extensions generated the following output:\n%s" % output

    cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [user.id, review.id])
    row = cursor.fetchone()

    if row: parent_message_id = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)
    else: parent_message_id = None

    return [sendMail(db, review, generateMessageId(), user, user, [user], generateSubjectLine(db, user, review, "extensionOutput"), body, parent_message_id=parent_message_id)]
Esempio n. 10
0
def updateBranch(user_name, repository_name, name, old, new, multiple):
    repository = gitutils.Repository.fromName(db, repository_name)

    processCommits(repository_name, new)

    try:
        branch = dbutils.Branch.fromName(db, repository, name)
        base_branch_id = branch.base.id if branch.base else None
    except:
        raise IndexException, "The branch '%s' is not in the database!  (This should never happen.)" % name

    if branch.head.sha1 != old:
        if new == branch.head.sha1:
            # This is what we think the ref ought to be already.  Do nothing,
            # and let the repository "catch up."
            return
        else:
            data = {
                "name": name,
                "old": old[:8],
                "new": new[:8],
                "current": branch.head.sha1[:8]
            }

            message = """CONFUSED!  Git thinks %(name)s points to %(old)s, but Critic thinks it points to %(current)s.  Rejecting push since it would only makes matters worse.  To resolve this problem, use

  git push critic %(current)s:%(name)s

to resynchronize the Git repository with Critic's database.""" % data

            raise IndexException, textutils.reflow(message,
                                                   line_length=80 -
                                                   len("remote: "))

    cursor = db.cursor()
    cursor.execute(
        "SELECT remote, remote_name, forced FROM trackedbranches WHERE repository=%s AND local_name=%s AND NOT disabled",
        (repository.id, name))
    row = cursor.fetchone()

    if row:
        remote, remote_name, forced = row
        tracked_branch = "%s in %s" % (remote_name, remote)

        assert not forced or not name.startswith("r/")

        if user_name != configuration.base.SYSTEM_USER_NAME:
            raise IndexException, """\
The branch '%s' is set up to track '%s' in
  %s
Please don't push it manually to this repository.""" % (name, remote_name,
                                                        remote)
        elif not name.startswith("r/"):
            conflicting = repository.revlist([branch.head.sha1], [new])
            added = repository.revlist([new], [branch.head.sha1])

            if conflicting:
                if forced:
                    if branch.base is None:
                        cursor.executemany(
                            """DELETE FROM reachable
                                                    USING commits
                                                    WHERE reachable.branch=%s
                                                      AND reachable.commit=commits.id
                                                      AND commits.sha1=%s""",
                            [(branch.id, sha1) for sha1 in conflicting])
                    else:
                        output = "Non-fast-forward update detected; deleting and recreating branch."

                        deleteBranch(repository.name, branch.name)
                        createBranch(None, repository, branch.name, new)

                        return output
                else:
                    raise IndexException, """\
Rejecting non-fast-forward update of branch.  To perform the update, you
can delete the branch using
  git push critic :%s
first, and then repeat this push.""" % name

            cursor.executemany(
                """INSERT INTO reachable (branch, commit)
                                       SELECT %s, commits.id
                                         FROM commits
                                        WHERE sha1=%s""",
                [(branch.id, sha1) for sha1 in added])

            new_head = gitutils.Commit.fromSHA1(db, repository, new)

            cursor.execute("UPDATE branches SET head=%s WHERE id=%s",
                           (new_head.getId(db), branch.id))

            output = ""

            if conflicting:
                output += "Pruned %d conflicting commits." % len(conflicting)
            if added: output += "\nAdded %d new commits." % len(added)

            return output.strip() if output else None
    else:
        tracked_branch = False

    user = getUser(db, user_name)

    if isinstance(user, str):
        if not tracked_branch:
            commit = gitutils.Commit.fromSHA1(db, repository, new)
            user = dbutils.User.fromId(db, commit.committer.getUserId(db))
        else:
            user = dbutils.User(0, configuration.base.SYSTEM_USER_NAME,
                                configuration.base.SYSTEM_USER_EMAIL,
                                "Critic System", "current")

    cursor.execute("SELECT id FROM reviews WHERE branch=%s", (branch.id, ))
    row = cursor.fetchone()

    is_review = bool(row)

    if is_review:
        if multiple:
            raise IndexException, """\
Refusing to update review in push of multiple refs.  Please push one
review branch at a time."""

        review_id = row[0]

        cursor.execute(
            """SELECT id, old_head, old_upstream, new_upstream, uid, branch
                            FROM reviewrebases
                           WHERE review=%s AND new_head IS NULL""",
            (review_id, ))
        row = cursor.fetchone()

        if row:
            if tracked_branch:
                raise IndexException, "Refusing to perform a review rebase via an automatic update."

            rebase_id, old_head_id, old_upstream_id, new_upstream_id, rebaser_id, onto_branch = row

            review = dbutils.Review.fromId(db, review_id)
            rebaser = dbutils.User.fromId(db, rebaser_id)

            if isinstance(user, dbutils.User):
                if rebaser.id != user.id:
                    if user_name == configuration.base.SYSTEM_USER_NAME:
                        user = rebaser
                    else:
                        raise IndexException, """\
This review is currently being rebased by
  %s <%s>
and can't be otherwise updated right now.""" % (rebaser.fullname,
                                                rebaser.email)
            else:
                assert user == configuration.base.SYSTEM_USER_NAME
                user = rebaser

            old_head = gitutils.Commit.fromId(db, repository, old_head_id)
            old_commitset = log.commitset.CommitSet(review.branch.commits)

            if old_head.sha1 != old:
                raise IndexException, """\
Unexpected error.  The branch appears to have been updated since your
rebase was prepared.  You need to cancel the rebase via the review
front-page and then try again, and/or report a bug about this error."""

            if old_upstream_id is not None:
                new_head = gitutils.Commit.fromSHA1(db, repository, new)

                old_upstream = gitutils.Commit.fromId(db, repository,
                                                      old_upstream_id)

                if new_upstream_id is not None:
                    new_upstream = gitutils.Commit.fromId(
                        db, repository, new_upstream_id)
                else:
                    if len(new_head.parents) != 1:
                        raise IndexException, "Invalid rebase: New head can't be a merge commit."

                    new_upstream = gitutils.Commit.fromSHA1(
                        db, repository, new_head.parents[0])

                    if new_upstream in old_commitset.getTails():
                        old_upstream = new_upstream = None
            else:
                old_upstream = None

            if old_upstream:
                if old_upstream.sha1 != repository.mergebase(
                    [old_upstream.sha1, new_upstream.sha1]):
                    raise IndexException, """\
Invalid rebase: The new upstream commit is not a descendant of the old
upstream commit.  You may want to cancel the rebase via the review
front-page, and prepare another one specifying the correct new
upstream commit; or rebase the branch onto the new upstream specified
and then push that instead."""
                if new_upstream.sha1 != repository.mergebase(
                    [new_upstream.sha1, new]):
                    raise IndexException, """\
Invalid rebase: The new upstream commit you specified when the rebase
was prepared is not an ancestor of the commit now pushed.  You may want
to cancel the rebase via the review front-page, and prepare another one
specifying the correct new upstream commit; or rebase the branch onto
the new upstream specified and then push that instead."""

                old_upstream_name = repository.findInterestingTag(
                    db, old_upstream.sha1) or old_upstream.sha1
                new_upstream_name = repository.findInterestingTag(
                    db, new_upstream.sha1) or new_upstream.sha1

                if onto_branch:
                    merged_thing = "branch '%s'" % onto_branch
                else:
                    merged_thing = "commit '%s'" % new_upstream_name

                merge_sha1 = repository.run('commit-tree',
                                            new_head.tree,
                                            '-p',
                                            old_head.sha1,
                                            '-p',
                                            new_upstream.sha1,
                                            input="""\
Merge %s into %s

This commit was generated automatically by Critic as an equivalent merge
to the rebase of the commits

  %s..%s

onto the %s.""" % (merged_thing, review.branch.name, old_upstream_name,
                   old_head.sha1, merged_thing),
                                            env={
                                                'GIT_AUTHOR_NAME':
                                                user.fullname,
                                                'GIT_AUTHOR_EMAIL': user.email,
                                                'GIT_COMMITTER_NAME':
                                                user.fullname,
                                                'GIT_COMMITTER_EMAIL':
                                                user.email
                                            }).strip()
                merge = gitutils.Commit.fromSHA1(db, repository, merge_sha1)

                gituser_id = merge.author.getGitUserId(db)

                cursor.execute(
                    """INSERT INTO commits (sha1, author_gituser, commit_gituser, author_time, commit_time)
                                       VALUES (%s, %s, %s, %s, %s)
                                    RETURNING id""",
                    (merge_sha1, gituser_id, gituser_id,
                     timestamp(
                         merge.author.time), timestamp(merge.committer.time)))
                merge.id = cursor.fetchone()[0]

                cursor.executemany(
                    "INSERT INTO edges (parent, child) VALUES (%s, %s)",
                    [(old_head.getId(db), merge.id),
                     (new_upstream.getId(db), merge.id)])

                # Have to commit to make the new commit available to other DB
                # sessions right away, specifically so that the changeset
                # creation server can see it.
                db.commit()

                cursor.execute(
                    """UPDATE reviewrebases
                                     SET old_head=%s, new_head=%s, new_upstream=%s
                                   WHERE review=%s AND new_head IS NULL""",
                    (merge.id, new_head.getId(db), new_upstream.getId(db),
                     review.id))

                new_sha1s = repository.revlist([new], [new_upstream.sha1],
                                               '--topo-order')
                rebased_commits = [
                    gitutils.Commit.fromSHA1(db, repository, sha1)
                    for sha1 in new_sha1s
                ]
                reachable_values = [(review.branch.id, sha1)
                                    for sha1 in new_sha1s]

                cursor.execute(
                    "INSERT INTO previousreachable (rebase, commit) SELECT %s, commit FROM reachable WHERE branch=%s",
                    (rebase_id, review.branch.id))
                cursor.execute("DELETE FROM reachable WHERE branch=%s",
                               (review.branch.id, ))
                cursor.executemany(
                    "INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s",
                    reachable_values)
                cursor.execute(
                    "UPDATE branches SET head=%s WHERE id=%s",
                    (gitutils.Commit.fromSHA1(
                        db, repository, new).getId(db), review.branch.id))

                pending_mails = []

                cursor.execute("SELECT uid FROM reviewusers WHERE review=%s",
                               (review.id, ))
                recipients = []
                for (user_id, ) in cursor.fetchall():
                    recipients.append(dbutils.User.fromId(db, user_id))
                for to_user in recipients:
                    pending_mails.extend(
                        review_mail.sendReviewRebased(db, user, to_user,
                                                      recipients, review,
                                                      new_upstream_name,
                                                      rebased_commits,
                                                      onto_branch))

                print "Rebase performed."

                review_utils.addCommitsToReview(db,
                                                user,
                                                review, [merge],
                                                pending_mails=pending_mails,
                                                silent_if_empty=set([merge]),
                                                full_merges=set([merge]))

                repository.keepalive(merge)
            else:
                old_commitset = log.commitset.CommitSet(review.branch.commits)
                new_sha1s = repository.revlist([new], old_commitset.getTails(),
                                               '--topo-order')

                if old_head.sha1 in new_sha1s:
                    raise IndexException, """\
Invalid history rewrite: Old head of the branch reachable from the
pushed ref; no history rewrite performed.  (Cancel the rebase via
the review front-page if you've changed your mind.)"""

                for new_sha1 in new_sha1s:
                    new_head = gitutils.Commit.fromSHA1(
                        db, repository, new_sha1)
                    if new_head.tree == old_head.tree: break
                else:
                    raise IndexException, """\
Invalid history rewrite: No commit on the rebased branch references
the same tree as the old head of the branch."""

                cursor.execute(
                    """UPDATE reviewrebases
                                     SET new_head=%s
                                   WHERE review=%s AND new_head IS NULL""",
                    (new_head.getId(db), review.id))

                rebased_commits = [
                    gitutils.Commit.fromSHA1(db, repository, sha1)
                    for sha1 in repository.revlist(
                        [new_head], old_commitset.getTails(), '--topo-order')
                ]
                new_commits = [
                    gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in
                    repository.revlist([new], [new_head], '--topo-order')
                ]
                reachable_values = [(review.branch.id, sha1)
                                    for sha1 in new_sha1s]

                cursor.execute(
                    "INSERT INTO previousreachable (rebase, commit) SELECT %s, commit FROM reachable WHERE branch=%s",
                    (rebase_id, review.branch.id))
                cursor.execute("DELETE FROM reachable WHERE branch=%s",
                               (review.branch.id, ))
                cursor.executemany(
                    "INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s",
                    reachable_values)
                cursor.execute(
                    "UPDATE branches SET head=%s WHERE id=%s",
                    (gitutils.Commit.fromSHA1(
                        db, repository, new).getId(db), review.branch.id))

                pending_mails = []

                cursor.execute("SELECT uid FROM reviewusers WHERE review=%s",
                               (review.id, ))
                recipients = []
                for (user_id, ) in cursor.fetchall():
                    recipients.append(dbutils.User.fromId(db, user_id))
                for to_user in recipients:
                    pending_mails.extend(
                        review_mail.sendReviewRebased(db, user, to_user,
                                                      recipients, review, None,
                                                      rebased_commits))

                print "History rewrite performed."

                if new_commits:
                    review_utils.addCommitsToReview(
                        db,
                        user,
                        review,
                        new_commits,
                        pending_mails=pending_mails)
                else:
                    review_mail.sendPendingMails(pending_mails)

                repository.run('update-ref', 'refs/keepalive/%s' % old, old)

            return True
        elif old != repository.mergebase([old, new]):
            raise IndexException, "Rejecting non-fast-forward update of review branch."
    elif old != repository.mergebase([old, new]):
        raise IndexException, """\
Rejecting non-fast-forward update of branch.  To perform the update, you
can delete the branch using
  git push critic :%s
first, and then repeat this push.""" % name

    cursor.execute(
        "SELECT id FROM branches WHERE repository=%s AND base IS NULL ORDER BY id ASC LIMIT 1",
        (repository.id, ))
    root_branch_id = cursor.fetchone()[0]

    def isreachable(sha1):
        #if rescan: return False
        #if is_review: cursor.execute("SELECT 1 FROM commits, reachable, branches WHERE commits.sha1=%s AND commits.id=reachable.commit AND reachable.branch=branches.id AND branches.repository=%s", [sha1, repository.id])
        if is_review and sha1 == branch.tail: return True
        if base_branch_id:
            cursor.execute(
                "SELECT 1 FROM commits, reachable WHERE commits.sha1=%s AND commits.id=reachable.commit AND reachable.branch IN (%s, %s, %s)",
                [sha1, branch.id, base_branch_id, root_branch_id])
        else:
            cursor.execute(
                "SELECT 1 FROM commits, reachable WHERE commits.sha1=%s AND commits.id=reachable.commit AND reachable.branch IN (%s, %s)",
                [sha1, branch.id, root_branch_id])
        return cursor.fetchone() is not None

    stack = [new]
    commits = set()
    commit_list = []
    processed = set()
    count = 0

    while stack:
        sha1 = stack.pop()

        count += 1
        if (count % 1000) == 0:
            stdout.write(".")
            if (count % 10000) == 0:
                stdout.write("\n")
            stdout.flush()

        if sha1 not in commits and not isreachable(sha1):
            commits.add(sha1)
            commit_list.append(sha1)

            #if is_review:
            #    stack.append(gitutils.Commit.fromSHA1(repository, sha1).parents[0])
            #else:
            stack.extend([
                parent_sha1 for parent_sha1 in gitutils.Commit.fromSHA1(
                    db, repository, sha1).parents
                if parent_sha1 not in processed
            ])

        processed.add(sha1)

    branch = dbutils.Branch.fromName(db, repository, name)
    review = dbutils.Review.fromBranch(db, branch)

    if review:
        if review.state != "open":
            raise IndexException, """\
The review is closed and can't be extended.  You need to reopen it at
%s
before you can add commits to it.""" % review.getURL(db, user, 2)

        all_commits = [
            gitutils.Commit.fromSHA1(db, repository, sha1)
            for sha1 in reversed(commit_list)
        ]

        tails = CommitSet(all_commits).getTails()

        if old not in tails:
            raise IndexException, """\
Push rejected; would break the review.

It looks like some of the pushed commits are reachable from the
repository's main branch, and thus consequently the commits currently
included in the review are too.

Perhaps you should request a new review of the follow-up commits?"""

        review_utils.addCommitsToReview(db,
                                        user,
                                        review,
                                        all_commits,
                                        commitset=commits,
                                        tracked_branch=tracked_branch)

    reachable_values = [(branch.id, sha1) for sha1 in reversed(commit_list)
                        if sha1 in commits]

    cursor.executemany(
        "INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s",
        reachable_values)
    cursor.execute(
        "UPDATE branches SET head=%s WHERE id=%s",
        (gitutils.Commit.fromSHA1(db, repository, new).getId(db), branch.id))

    db.commit()

    if configuration.extensions.ENABLED and review:
        extensions.executeProcessCommits(
            db, user, review, all_commits,
            gitutils.Commit.fromSHA1(db, repository, old),
            gitutils.Commit.fromSHA1(db, repository, new), stdout)
Esempio n. 11
0
def createBranch(user, repository, name, head):
    processCommits(repository.name, head)

    cursor = db.cursor()

    def commit_id(sha1):
        cursor.execute("SELECT id FROM commits WHERE sha1=%s", [sha1])
        return cursor.fetchone()[0]

    components = name.split("/")
    for index in range(1, len(components)):
        try:
            repository.revparse("refs/heads/%s" % "/".join(components[:index]))
        except:
            continue

        message = (
            "Cannot create branch with name '%s' since there is already a branch named '%s' in the repository."
            % (name, "/".join(components[:index])))
        raise IndexException, textutils.reflow(message,
                                               line_length=80 -
                                               len("remote: "))

    if name.startswith("r/"):
        try:
            review_id = int(name[2:])

            cursor.execute(
                "SELECT branches.name FROM reviews JOIN branches ON (branches.id=reviews.branch) WHERE reviews.id=%s",
                (review_id, ))
            row = cursor.fetchone()

            message = "Refusing to create review named as a number."

            if row:
                message += "\nDid you mean to push to the branch '%s', perhaps?" % row[
                    0]

            raise IndexException, message
        except ValueError:
            pass

        if user.getPreference(db, "review.createViaPush"):
            the_commit = gitutils.Commit.fromSHA1(db, repository, head,
                                                  commit_id(head))
            all_commits = [the_commit]
            review = review_utils.createReview(db,
                                               user,
                                               repository,
                                               all_commits,
                                               name,
                                               the_commit.summary(),
                                               None,
                                               via_push=True)

            print "Submitted review: %s/r/%d" % (dbutils.getURLPrefix(db),
                                                 review.id)

            if review.reviewers:
                print "  Reviewers:"
                for reviewer in review.reviewers:
                    print "    %s <%s>" % (reviewer.fullname, reviewer.email)

            if review.watchers:
                print "  Watchers:"
                for watcher in review.watchers:
                    print "    %s <%s>" % (watcher.fullname, watcher.email)

            if configuration.extensions.ENABLED:
                if extensions.executeProcessCommits(db, user, review,
                                                    all_commits, None,
                                                    the_commit, stdout):
                    print

            print "Thank you!"
            return True
        else:
            raise IndexException, "Refusing to create review; user preference 'review.createViaPush' is not enabled."

    sha1 = head
    base = None
    tail = None

    cursor.execute(
        """SELECT 1
                        FROM reachable
                        JOIN branches ON (branches.id=reachable.branch)
                        JOIN repositories ON (repositories.id=branches.repository)
                       WHERE repositories.id=%s
                       LIMIT 1""", (repository.id, ))

    if cursor.fetchone():

        def reachable(sha1):
            cursor.execute(
                """SELECT branches.id
                                FROM branches
                                JOIN reachable ON (reachable.branch=branches.id)
                                JOIN commits ON (commits.id=reachable.commit)
                               WHERE branches.repository=%s
                                 AND branches.type='normal'
                                 AND commits.sha1=%s
                            ORDER BY reachable.branch ASC
                               LIMIT 1""", (repository.id, sha1))
            return cursor.fetchone()
    else:

        def reachable(sha1):
            return None

    commit_map = {}
    commit_list = []

    row = reachable(sha1)
    if row:
        # Head of branch is reachable from an existing branch.  Could be because
        # this branch is actually empty (just created with no "own" commits) or
        # it could have been merged into some other already existing branch.  We
        # can't tell, so we just record it as empty.

        base = row[0]
        tail = sha1
    else:
        stack = []

        while True:
            if sha1 not in commit_map:
                commit = gitutils.Commit.fromSHA1(db, repository, sha1)
                commit_map[sha1] = commit
                commit_list.append(commit)

                for sha1 in commit.parents:
                    if sha1 not in commit_map:
                        row = reachable(sha1)
                        if not row:
                            stack.append(sha1)
                        elif base is None:
                            base = row[0]
                            tail = sha1

                            base_chain = [base]

                            while True:
                                cursor.execute(
                                    "SELECT base FROM branches WHERE id=%s",
                                    (base_chain[-1], ))
                                next = cursor.fetchone()[0]
                                if next is None: break
                                else: base_chain.append(next)

                            def reachable(sha1):
                                cursor.execute(
                                    """SELECT 1
                                                    FROM reachable
                                                    JOIN commits ON (commits.id=reachable.commit)
                                                   WHERE reachable.branch=ANY (%s)
                                                     AND commits.sha1=%s""",
                                    (base_chain, sha1))
                                return cursor.fetchone()

            if stack: sha1 = stack.pop(0)
            else: break

        if len(commit_list) % 10000 > 1000:
            stdout.write("\n")
            stdout.flush()

    if not base:
        cursor.execute(
            "INSERT INTO branches (repository, name, head) VALUES (%s, %s, %s) RETURNING id",
            (repository.id, name, commit_id(head)))
        branch_id = cursor.fetchone()[0]
    else:
        cursor.execute(
            "INSERT INTO branches (repository, name, head, base, tail) VALUES (%s, %s, %s, %s, %s) RETURNING id",
            (repository.id, name, commit_id(head), base, commit_id(tail)))
        branch_id = cursor.fetchone()[0]

        cursor.execute("SELECT name FROM branches WHERE id=%s", [base])

        print "Added branch based on %s containing %d commit%s:" % (
            cursor.fetchone()[0], len(commit_list),
            "s" if len(commit_list) > 1 else "")
        print "  %s/log?repository=%d&branch=%s" % (dbutils.getURLPrefix(db),
                                                    repository.id, name)
        if len(commit_list) > 1:
            print "To create a review of all %d commits:" % len(commit_list)
        else:
            print "To create a review of the commit:"
        print "  %s/createreview?repository=%d&branch=%s" % (
            dbutils.getURLPrefix(db), repository.id, name)

    reachable_values = [(branch_id, commit.sha1) for commit in commit_list]
    cursor.executemany(
        "INSERT INTO reachable (branch, commit) SELECT %s, id FROM commits WHERE sha1=%s",
        reachable_values)

    if isinstance(user, str): user_name = user
    else: user_name = user.name

    if not repository.hasMainBranch(
    ) and user_name == configuration.base.SYSTEM_USER_NAME:
        cursor.execute("UPDATE repositories SET branch=%s WHERE id=%s",
                       (branch_id, repository.id))
Esempio n. 12
0
File: mail.py Progetto: KurSh/critic
def sendPing(db, from_user, to_user, recipients, review, note):
    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': "%s:%s" % (configuration.base.HOSTNAME, review.repository.path),
             'from.fullname': from_user.fullname,
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    body += """%(from.fullname)s has pinged the review!


""" % data

    if note:
        body += """Additional information from %s:
%s


""" % (from_user.getFirstName(), textutils.reflow(note, line_length, indent=2))

    cursor = db.cursor()

    cursor.execute("""SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted)
                        FROM reviewfiles
                        JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id)
                       WHERE reviewfiles.review=%s
                         AND reviewfiles.state='pending'
                         AND reviewuserfiles.uid=%s
                    GROUP BY reviewfiles.file""",
                   (review.id, to_user.id))
    pending_files_lines = cursor.fetchall()

    cursor.execute("""SELECT DISTINCT changesets.child
                        FROM reviewfiles
                        JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id)
                        JOIN changesets ON (changesets.id=reviewfiles.changeset)
                       WHERE reviewfiles.review=%s
                         AND reviewfiles.state='pending'
                         AND reviewuserfiles.uid=%s""",
                   (review.id, to_user.id))
    pending_commits = cursor.fetchall()

    body += renderFiles(db, to_user, review, "These pending changes are assigned to you:", pending_files_lines, pending_commits, showcommit_link=True)

    cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
    row = cursor.fetchone()

    if row: parent_message_id = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)
    else: parent_message_id = None

    return [sendMail(db, review, generateMessageId(), from_user, to_user, recipients, generateSubjectLine(db, to_user, review, "pingedReview"), body, parent_message_id=parent_message_id)]
Esempio n. 13
0
File: mail.py Progetto: KurSh/critic
def sendReviewAddedCommits(db, from_user, to_user, recipients, review, changesets, tracked_branch=False):
    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []
    if from_user == to_user and to_user.getPreference(db, "email.ignoreOwnChanges"): return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length
    relevant_only = to_user not in review.owners and to_user != from_user and to_user.getPreference(db, "email.updatedReview.relevantChangesOnly")

    cursor = db.cursor()

    if relevant_only:
        cursor.execute("SELECT type FROM reviewusers WHERE review=%s AND uid=%s", (review.id, to_user.id))
        if cursor.fetchone()[0] == 'manual': relevant_only = False

    if relevant_only:
        relevant_files = review.getRelevantFiles(db, to_user)
        relevant_commits = set()

        for changeset in changesets:
            for file in changeset.files:
                if file.id in relevant_files:
                    relevant_commits.add(changeset.child.getId(db))
                    break
            else:
                cursor.execute("SELECT id FROM commentchains WHERE review=%s AND state='addressed' AND addressed_by=%s", (review.id, changeset.child.getId(db)))
                for chain_id in cursor.fetchall():
                    cursor.execute("SELECT 1 FROM commentchainusers WHERE chain=%s AND uid=%s", (chain_id, to_user.id))
                    if cursor.fetchone():
                        relevant_commits.add(changeset.child.getId(db))
                        break

        if not relevant_commits:
            return []
    else:
        relevant_commits = None

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': "%s:%s" % (configuration.base.HOSTNAME, review.repository.path),
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    commits = []

    for index, changeset in enumerate(changesets):
        if changeset.parent.sha1 == changeset.child.parents[0]:
            commits.append(changeset.child)

    commitset = log_commitset.CommitSet(commits)

    if tracked_branch:
        body += "The automatic tracking of\n  %s\n" % tracked_branch
        body += textutils.reflow("has updated the review by pushing %sadditional commit%s to the branch" % ("an " if len(commits) == 1 else "", "s" if len(commits) > 1 else ""), line_length)
    else:
        body += textutils.reflow("%s has updated the review by pushing %sadditional commit%s to the branch" % (from_user.fullname, "an " if len(commits) == 1 else "", "s" if len(commits) > 1 else ""), line_length)

    body += "\n  %s\n" % review.branch.name
    body += textutils.reflow("in the repository", line_length)
    body += "\n  %s:%s\n\n\n" % (configuration.base.HOSTNAME, review.repository.path)

    cursor.execute("""SELECT file, SUM(deleted), SUM(inserted)
                        FROM fullreviewuserfiles
                       WHERE review=%%s
                         AND changeset IN (%s)
                         AND state='pending'
                         AND assignee=%%s
                    GROUP BY file""" % ",".join(["%s"] * len(changesets)),
                   [review.id] + [changeset.id for changeset in changesets] + [to_user.id])
    pending_files_lines = cursor.fetchall()

    if pending_files_lines:
        heads = commitset.getHeads()
        tails = commitset.getFilteredTails(review.repository)

        if len(heads) == 1 and len(tails) == 1:
            showcommit_link = (tails.pop()[:8], heads.pop().sha1[:8])
        else:
            showcommit_link = False

        body += renderFiles(db, to_user, review, "These changes were assigned to you:", pending_files_lines, showcommit_link=showcommit_link)

    all_commits = to_user.getPreference(db, "email.updatedReview.displayCommits")
    context_lines = to_user.getPreference(db, "email.comment.contextLines")

    if all_commits:
        body += "The additional commit%s requested to be reviewed are:\n\n" % ("s" if len(commits) > 1 else "")

        contextLines = to_user.getPreference(db, "email.updatedReview.diff.contextLines")
        diffMaxLines = to_user.getPreference(db, "email.updatedReview.diff.maxLines")

        displayStats = to_user.getPreference(db, "email.updatedReview.displayStats")
        statsMaxLines = to_user.getPreference(db, "email.updatedReview.stats.maxLines")

        if contextLines < 0: contextLines = 0

        if diffMaxLines == 0: diffs = None
        else:
            diffs = {}
            lines = 0

            for commit in commits:
                if len(commit.parents) == 1 and (relevant_commits is None or commit.getId(db) in relevant_commits):
                    cursor.execute("""SELECT id
                                        FROM reviewchangesets
                                        JOIN changesets ON (id=changeset)
                                       WHERE review=%s
                                         AND child=%s""", (review.id, commit.getId(db)))

                    (changeset_id,) = cursor.fetchone()

                    diff = changeset_text.unified(db, changeset_load.loadChangeset(db, review.repository, changeset_id), contextLines)
                    diffs[commit] = diff
                    lines += diff.count("\n")
                    if lines > diffMaxLines:
                        diffs = None
                        break

        if not displayStats or statsMaxLines == 0: stats = None
        else:
            stats = {}
            lines = 0

            for commit in commits:
                commit_stats = review.repository.run("show", "--oneline", "--stat", commit.sha1).split('\n', 1)[1]
                stats[commit] = commit_stats
                lines += commit_stats.count('\n')
                if lines > statsMaxLines:
                    stats = None
                    break

        for index, commit in enumerate(commits):
            if index > 0: body += "\n\n\n"

            body += """Commit: %(sha1)s
Author: %(author.fullname)s <%(author.email)s> at %(author.time)s

%(message)s
""" % { 'sha1': commit.sha1,
        'author.fullname': commit.author.getFullname(db),
        'author.email': commit.author.email,
        'author.time': time.strftime("%Y-%m-%d %H:%M:%S", commit.author.time),
        'message': textutils.reflow(commit.message.strip(), line_length, indent=2) }

            if stats and commit in stats:
                body += "---\n" + stats[commit]

            if diffs and commit in diffs:
                body += "\n" + diffs[commit]

            cursor.execute("SELECT id FROM commentchains WHERE review=%s AND state='addressed' AND addressed_by=%s", (review.id, commit.getId(db)))
            rows = cursor.fetchall()

            if rows:
                for (chain_id,) in rows:
                    chain = review_comment.CommentChain.fromId(db, chain_id, to_user, review=review)
                    chain.loadComments(db, to_user, include_draft_comments=False)
                    body += "\n\n" + renderChainInMail(db, to_user, chain, None, "addressed", None, line_length, context_lines)

    cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
    row = cursor.fetchone()

    files = []

    if not row:
        files = sendReviewPlaceholder(db, to_user, review)
        cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
        row = cursor.fetchone()

    if row: parent_message_id = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)
    else: parent_message_id = None

    return files + [sendMail(db, review, generateMessageId(), from_user, to_user, recipients, generateSubjectLine(db, to_user, review, "updatedReview.commitsPushed"), body, parent_message_id=parent_message_id)]
Esempio n. 14
0
File: mail.py Progetto: KurSh/critic
def sendReviewBatch(db, from_user, to_user, recipients, review, batch_id, was_accepted, is_accepted, profiler=None):
    if profiler: profiler.check("generate mail: start")

    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []
    if from_user == to_user and to_user.getPreference(db, "email.ignoreOwnChanges"): return []

    cursor = db.cursor()

    line_length = to_user.getPreference(db, "email.lineLength")
    relevant_only = to_user not in review.owners and to_user != from_user and to_user.getPreference(db, "email.updatedReview.relevantChangesOnly")
    mail_index = [0]

    if relevant_only:
        cursor.execute("SELECT type FROM reviewusers WHERE review=%s AND uid=%s", (review.id, to_user.id))
        if cursor.fetchone()[0] == 'manual': relevant_only = False

    def localGenerateMessageId():
        mail_index[0] += 1
        return generateMessageId(mail_index[0])

    if profiler: profiler.check("generate mail: prologue")

    if relevant_only:
        relevant_files = review.getRelevantFiles(db, to_user)
    else:
        relevant_files = None

    if profiler: profiler.check("generate mail: get relevant files")

    cursor.execute("SELECT comment FROM batches WHERE id=%s", [batch_id])
    batch_chain_id = cursor.fetchone()[0]

    if profiler: profiler.check("generate mail: batch chain")

    cursor.execute("""SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE reviewfilechanges.batch=%s
                         AND reviewfilechanges.to='reviewed'
                    GROUP BY reviewfiles.file""",
                       (batch_id,))
    reviewed_files_lines = cursor.fetchall()

    if profiler: profiler.check("generate mail: reviewed files/lines")

    cursor.execute("""SELECT DISTINCT changesets.child
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                        JOIN changesets ON (changesets.id=reviewfiles.changeset)
                       WHERE reviewfilechanges.batch=%s
                         AND reviewfilechanges.to='reviewed'""",
                       (batch_id,))
    reviewed_commits = cursor.fetchall()

    if profiler: profiler.check("generate mail: reviewed commits")

    cursor.execute("""SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE reviewfilechanges.batch=%s
                         AND reviewfilechanges.to='pending'
                    GROUP BY reviewfiles.file""",
                   (batch_id,))
    unreviewed_files_lines = cursor.fetchall()

    if profiler: profiler.check("generate mail: unreviewed files/lines")

    cursor.execute("""SELECT DISTINCT changesets.child
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                        JOIN changesets ON (changesets.id=reviewfiles.changeset)
                       WHERE reviewfilechanges.batch=%s
                         AND reviewfilechanges.to='pending'""",
                       (batch_id,))
    unreviewed_commits = cursor.fetchall()

    if profiler: profiler.check("generate mail: unreviewed commits")

    reviewed_files = renderFiles(db, to_user, review, "Reviewed Files:", reviewed_files_lines, reviewed_commits, relevant_only, relevant_files)
    unreviewed_files = renderFiles(db, to_user, review, "Unreviewed Files:", unreviewed_files_lines, unreviewed_commits, relevant_only, relevant_files)

    if profiler: profiler.check("generate mail: render files")

    context_lines = to_user.getPreference(db, "email.comment.contextLines")

    comment_ids = set()

    def isRelevantComment(chain):
        if chain.file_id is None or chain.file_id in relevant_files: return True

        cursor.execute("SELECT 1 FROM commentchainusers WHERE chain=%s AND uid=%s", (chain.id, to_user.id))
        return cursor.fetchone() is not None

    def fetchNewCommentChains():
        chains = []
        for (chain_id,) in cursor.fetchall():
            if chain_id != batch_chain_id:
                chain = review_comment.CommentChain.fromId(db, chain_id, from_user, review=review)
                if not relevant_only or isRelevantComment(chain):
                    chain.loadComments(db, from_user)
                    chains.append((chain, None, None))
        return chains

    def fetchAdditionalCommentChains():
        chains = []
        for chain_id, comment_id, new_state, new_type in cursor.fetchall():
            if comment_id is not None or new_state is not None or new_type is not None:
                chain = review_comment.CommentChain.fromId(db, chain_id, from_user, review=review)
                if not relevant_only or isRelevantComment(chain):
                    chain.loadComments(db, from_user)
                    chains.append((chain, new_state, new_type))
        return chains

    cursor.execute("SELECT id FROM commentchains WHERE batch=%s AND type='issue' ORDER BY id ASC", [batch_id])
    new_issues = fetchNewCommentChains()

    if profiler: profiler.check("generate mail: new issues")

    cursor.execute("SELECT id FROM commentchains WHERE batch=%s AND type='note' ORDER BY id ASC", [batch_id])
    new_notes = fetchNewCommentChains()

    if profiler: profiler.check("generate mail: new notes")

    cursor.execute("""SELECT commentchains.id, comments.id, commentchainchanges.to_state, commentchainchanges.to_type
                        FROM commentchains
             LEFT OUTER JOIN comments ON (commentchains.id=comments.chain
                                      AND comments.batch=%s)
             LEFT OUTER JOIN commentchainchanges ON (commentchains.id=commentchainchanges.chain
                                                 AND commentchainchanges.batch=%s)
                       WHERE commentchains.review=%s
                         AND commentchains.batch!=%s""",
                   [batch_id, batch_id, review.id, batch_id])
    additional_comments = fetchAdditionalCommentChains()

    if profiler: profiler.check("generate mail: additional comments")

    if is_accepted != was_accepted and not reviewed_files and not unreviewed_files and not new_issues and not new_notes and not additional_comments:
        return []

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': "%s:%s" % (configuration.base.HOSTNAME, review.repository.path),
             'hr': "-" * line_length }

    header = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    if batch_chain_id is not None:
        batch_chain = review_comment.CommentChain.fromId(db, batch_chain_id, from_user, review=review)
    else:
        batch_chain = None

    data["batch.author.fullname"] = from_user.fullname

    first_name = from_user.getFirstName()

    if batch_chain is not None:
        batch_chain.loadComments(db, from_user)

        comment_ids.add(batch_chain.comments[0].id)

        remark = """%s'%s comment:
%s


""" % (first_name, first_name[-1] != 's' and 's' or '', textutils.reflow(batch_chain.comments[0].comment, line_length, indent=2))
    else:
        remark = ""

    body = header
    body += textutils.reflow("%(batch.author.fullname)s has submitted a batch of changes to the review." % data, line_length)
    body += "\n\n\n"
    body += remark

    if not was_accepted and is_accepted:
        state_change = textutils.reflow("The review is now ACCEPTED!", line_length) + "\n\n\n"
    elif was_accepted and not is_accepted:
        state_change = textutils.reflow("The review is NO LONGER ACCEPTED!", line_length) + "\n\n\n"
    else:
        state_change = ""

    body += state_change
    body += reviewed_files
    body += unreviewed_files

    subject = generateSubjectLine(db, to_user, review, "updatedReview.submittedChanges")

    def renderCommentChains(chains):
        result = ""
        if chains:
            for chain, new_state, new_type in chains:
                for focus_comment in chain.comments:
                    if focus_comment.batch_id == batch_id:
                        break
                else:
                    focus_comment = None
                if focus_comment is not None or new_state is not None or new_type is not None:
                    result += renderChainInMail(db, to_user, chain, focus_comment, new_state, new_type, line_length, context_lines) + "\n\n"
                if focus_comment is not None:
                    comment_ids.add(focus_comment.id)
        return result

    body += renderCommentChains(new_issues)
    body += renderCommentChains(new_notes)

    if profiler: profiler.check("generate mail: render new comment chains")

    comment_threading = to_user.getPreference(db, "email.updatedReview.commentThreading")

    send_main_mail = state_change or reviewed_files or unreviewed_files or new_issues or new_notes

    if not comment_threading:
        send_main_mail = send_main_mail or additional_comments
        body += renderCommentChains(additional_comments)

        if profiler: profiler.check("generate mail: render additional comments")

    review_message_id = [None]
    files = []

    def getReviewMessageId():
        if review_message_id[0] is None:
            cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
            row = cursor.fetchone()

            if not row:
                files.extend(sendReviewPlaceholder(db, to_user, review))
                mail_index[0] += 1
                cursor.execute("SELECT messageid FROM reviewmessageids WHERE uid=%s AND review=%s", [to_user.id, review.id])
                row = cursor.fetchone()

            if row:
                review_message_id[0] = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)

        return review_message_id[0]

    if send_main_mail:
        message_id = localGenerateMessageId()

        cursor.executemany("INSERT INTO commentmessageids (uid, comment, messageid) VALUES (%s, %s, %s)",
                           [(to_user.id, comment_id, message_id) for comment_id in comment_ids])

        files.append(sendMail(db, review, message_id, from_user, to_user, recipients, subject, body, parent_message_id=getReviewMessageId()))

    if comment_threading:
        threads = {}

        for chain, new_state, new_type in additional_comments:
            if chain.comments[-1].batch_id == batch_id:
                parent_comment_id = chain.comments[-2].id
            else:
                parent_comment_id = chain.comments[-1].id

            cursor.execute("""SELECT messageid
                                FROM commentmessageids
                               WHERE comment=%s
                                 AND uid=%s""",
                           [parent_comment_id, to_user.id])
            row = cursor.fetchone()

            if row: parent_message_id = "<%s@%s>" % (row[0], configuration.base.HOSTNAME)
            else: parent_message_id = getReviewMessageId()

            threads.setdefault(parent_message_id, []).append((chain, new_state, new_type))

        for parent_message_id, chains in threads.items():
            comment_ids = set()

            body = header + remark + renderCommentChains(chains)

            message_id = localGenerateMessageId()

            cursor.executemany("INSERT INTO commentmessageids (uid, comment, messageid) VALUES (%s, %s, %s)",
                               [(to_user.id, comment_id, message_id) for comment_id in comment_ids])

            files.append(sendMail(db, review, message_id, from_user, to_user, recipients, subject, body, parent_message_id=parent_message_id))

    if profiler: profiler.check("generate mail: finished")

    return files
Esempio n. 15
0
def reflow(message):
    return textutils.reflow(message, line_length=80 - len("remote: "))
Esempio n. 16
0
def updateBranch(user_name, repository_name, name, old, new, multiple):
    repository = gitutils.Repository.fromName(db, repository_name)

    processCommits(repository_name, new)

    try:
        branch = dbutils.Branch.fromName(db, repository, name)
        base_branch_id = branch.base.id if branch.base else None
    except:
        raise IndexException, "The branch '%s' is not in the database!  (This should never happen.)" % name

    if branch.head.sha1 != old:
        if new == branch.head.sha1:
            # This is what we think the ref ought to be already.  Do nothing,
            # and let the repository "catch up."
            return
        else:
            data = { "name": name,
                     "old": old[:8],
                     "new": new[:8],
                     "current": branch.head.sha1[:8] }

            message = """CONFUSED!  Git thinks %(name)s points to %(old)s, but Critic thinks it points to %(current)s.  Rejecting push since it would only makes matters worse.  To resolve this problem, use

  git push critic %(current)s:%(name)s

to resynchronize the Git repository with Critic's database.""" % data

            raise IndexException, textutils.reflow(message, line_length=80 - len("remote: "))

    cursor = db.cursor()
    cursor.execute("SELECT remote, remote_name, forced FROM trackedbranches WHERE repository=%s AND local_name=%s AND NOT disabled", (repository.id, name))
    row = cursor.fetchone()

    if row:
        remote, remote_name, forced = row
        tracked_branch = "%s in %s" % (remote_name, remote)

        assert not forced or not name.startswith("r/")

        if user_name != configuration.base.SYSTEM_USER_NAME:
            raise IndexException, """\
The branch '%s' is set up to track '%s' in
  %s
Please don't push it manually to this repository.""" % (name, remote_name, remote)
        elif not name.startswith("r/"):
            conflicting = repository.revlist([branch.head.sha1], [new])
            added = repository.revlist([new], [branch.head.sha1])

            if conflicting:
                if forced:
                    if branch.base is None:
                        cursor.executemany("""DELETE FROM reachable
                                                    USING commits
                                                    WHERE reachable.branch=%s
                                                      AND reachable.commit=commits.id
                                                      AND commits.sha1=%s""",
                                           [(branch.id, sha1) for sha1 in conflicting])
                    else:
                        output = "Non-fast-forward update detected; deleting and recreating branch."

                        deleteBranch(repository.name, branch.name)
                        createBranch(None, repository, branch.name, new)

                        return output
                else:
                    raise IndexException, """\
Rejecting non-fast-forward update of branch.  To perform the update, you
can delete the branch using
  git push critic :%s
first, and then repeat this push.""" % name

            cursor.executemany("""INSERT INTO reachable (branch, commit)
                                       SELECT %s, commits.id
                                         FROM commits
                                        WHERE sha1=%s""",
                               [(branch.id, sha1) for sha1 in added])

            new_head = gitutils.Commit.fromSHA1(db, repository, new)

            cursor.execute("UPDATE branches SET head=%s WHERE id=%s",
                           (new_head.getId(db), branch.id))

            output = ""

            if conflicting: output += "Pruned %d conflicting commits." % len(conflicting)
            if added: output += "\nAdded %d new commits." % len(added)

            return output.strip() if output else None
    else:
        tracked_branch = False

    user = getUser(db, user_name)

    if isinstance(user, str):
        if not tracked_branch:
            commit = gitutils.Commit.fromSHA1(db, repository, new)
            user = dbutils.User.fromId(db, commit.committer.getUserId(db))
        else:
            user = dbutils.User(0, configuration.base.SYSTEM_USER_NAME, configuration.base.SYSTEM_USER_EMAIL, "Critic System", "current")

    cursor.execute("SELECT id FROM reviews WHERE branch=%s", (branch.id,))
    row = cursor.fetchone()

    is_review = bool(row)

    if is_review:
        if multiple:
            raise IndexException, """\
Refusing to update review in push of multiple refs.  Please push one
review branch at a time."""

        review_id = row[0]

        cursor.execute("""SELECT id, old_head, old_upstream, new_upstream, uid, branch
                            FROM reviewrebases
                           WHERE review=%s AND new_head IS NULL""",
                       (review_id,))
        row = cursor.fetchone()

        if row:
            if tracked_branch:
                raise IndexException, "Refusing to perform a review rebase via an automatic update."

            rebase_id, old_head_id, old_upstream_id, new_upstream_id, rebaser_id, onto_branch = row

            review = dbutils.Review.fromId(db, review_id)
            rebaser = dbutils.User.fromId(db, rebaser_id)

            if isinstance(user, dbutils.User):
                if rebaser.id != user.id:
                    if user_name == configuration.base.SYSTEM_USER_NAME:
                        user = rebaser
                    else:
                        raise IndexException, """\
This review is currently being rebased by
  %s <%s>
and can't be otherwise updated right now.""" % (rebaser.fullname, rebaser.email)
            else:
                assert user == configuration.base.SYSTEM_USER_NAME
                user = rebaser

            old_head = gitutils.Commit.fromId(db, repository, old_head_id)
            old_commitset = log.commitset.CommitSet(review.branch.commits)

            if old_head.sha1 != old:
                raise IndexException, """\
Unexpected error.  The branch appears to have been updated since your
rebase was prepared.  You need to cancel the rebase via the review
front-page and then try again, and/or report a bug about this error."""

            if old_upstream_id is not None:
                new_head = gitutils.Commit.fromSHA1(db, repository, new)

                old_upstream = gitutils.Commit.fromId(db, repository, old_upstream_id)

                if new_upstream_id is not None:
                    new_upstream = gitutils.Commit.fromId(db, repository, new_upstream_id)
                else:
                    if len(new_head.parents) != 1:
                        raise IndexException, "Invalid rebase: New head can't be a merge commit."

                    new_upstream = gitutils.Commit.fromSHA1(db, repository, new_head.parents[0])

                    if new_upstream in old_commitset.getTails():
                        old_upstream = new_upstream = None
            else:
                old_upstream = None

            if old_upstream:
                if old_upstream.sha1 != repository.mergebase([old_upstream.sha1, new_upstream.sha1]):
                    raise IndexException, """\
Invalid rebase: The new upstream commit is not a descendant of the old
upstream commit.  You may want to cancel the rebase via the review
front-page, and prepare another one specifying the correct new
upstream commit; or rebase the branch onto the new upstream specified
and then push that instead."""
                if new_upstream.sha1 != repository.mergebase([new_upstream.sha1, new]):
                    raise IndexException, """\
Invalid rebase: The new upstream commit you specified when the rebase
was prepared is not an ancestor of the commit now pushed.  You may want
to cancel the rebase via the review front-page, and prepare another one
specifying the correct new upstream commit; or rebase the branch onto
the new upstream specified and then push that instead."""

                old_upstream_name = repository.findInterestingTag(db, old_upstream.sha1) or old_upstream.sha1
                new_upstream_name = repository.findInterestingTag(db, new_upstream.sha1) or new_upstream.sha1

                if onto_branch:
                    merged_thing = "branch '%s'" % onto_branch
                else:
                    merged_thing = "commit '%s'" % new_upstream_name

                merge_sha1 = repository.run('commit-tree', new_head.tree, '-p', old_head.sha1, '-p', new_upstream.sha1,
                                            input="""\
Merge %s into %s

This commit was generated automatically by Critic as an equivalent merge
to the rebase of the commits

  %s..%s

onto the %s.""" % (merged_thing, review.branch.name, old_upstream_name, old_head.sha1, merged_thing),
                                            env={ 'GIT_AUTHOR_NAME': user.fullname,
                                                  'GIT_AUTHOR_EMAIL': user.email,
                                                  'GIT_COMMITTER_NAME': user.fullname,
                                                  'GIT_COMMITTER_EMAIL': user.email }).strip()
                merge = gitutils.Commit.fromSHA1(db, repository, merge_sha1)

                gituser_id = merge.author.getGitUserId(db)

                cursor.execute("""INSERT INTO commits (sha1, author_gituser, commit_gituser, author_time, commit_time)
                                       VALUES (%s, %s, %s, %s, %s)
                                    RETURNING id""",
                               (merge_sha1, gituser_id, gituser_id, timestamp(merge.author.time), timestamp(merge.committer.time)))
                merge.id = cursor.fetchone()[0]

                cursor.executemany("INSERT INTO edges (parent, child) VALUES (%s, %s)",
                                   [(old_head.getId(db), merge.id),
                                    (new_upstream.getId(db), merge.id)])

                # Have to commit to make the new commit available to other DB
                # sessions right away, specifically so that the changeset
                # creation server can see it.
                db.commit()

                cursor.execute("""UPDATE reviewrebases
                                     SET old_head=%s, new_head=%s, new_upstream=%s
                                   WHERE review=%s AND new_head IS NULL""",
                               (merge.id, new_head.getId(db), new_upstream.getId(db), review.id))

                new_sha1s = repository.revlist([new], [new_upstream.sha1], '--topo-order')
                rebased_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in new_sha1s]
                reachable_values = [(review.branch.id, sha1) for sha1 in new_sha1s]

                cursor.execute("INSERT INTO previousreachable (rebase, commit) SELECT %s, commit FROM reachable WHERE branch=%s", (rebase_id, review.branch.id))
                cursor.execute("DELETE FROM reachable WHERE branch=%s", (review.branch.id,))
                cursor.executemany("INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s", reachable_values)
                cursor.execute("UPDATE branches SET head=%s WHERE id=%s", (gitutils.Commit.fromSHA1(db, repository, new).getId(db), review.branch.id))

                pending_mails = []

                cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (review.id,))
                recipients = []
                for (user_id,) in cursor.fetchall():
                    recipients.append(dbutils.User.fromId(db, user_id))
                for to_user in recipients:
                    pending_mails.extend(review_mail.sendReviewRebased(db, user, to_user, recipients, review, new_upstream_name, rebased_commits, onto_branch))

                print "Rebase performed."

                review_utils.addCommitsToReview(db, user, review, [merge], pending_mails=pending_mails, silent_if_empty=set([merge]), full_merges=set([merge]))

                repository.keepalive(merge)
            else:
                old_commitset = log.commitset.CommitSet(review.branch.commits)
                new_sha1s = repository.revlist([new], old_commitset.getTails(), '--topo-order')

                if old_head.sha1 in new_sha1s:
                    raise IndexException, """\
Invalid history rewrite: Old head of the branch reachable from the
pushed ref; no history rewrite performed.  (Cancel the rebase via
the review front-page if you've changed your mind.)"""

                for new_sha1 in new_sha1s:
                    new_head = gitutils.Commit.fromSHA1(db, repository, new_sha1)
                    if new_head.tree == old_head.tree: break
                else:
                    raise IndexException, """\
Invalid history rewrite: No commit on the rebased branch references
the same tree as the old head of the branch."""

                cursor.execute("""UPDATE reviewrebases
                                     SET new_head=%s
                                   WHERE review=%s AND new_head IS NULL""",
                               (new_head.getId(db), review.id))

                rebased_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in repository.revlist([new_head], old_commitset.getTails(), '--topo-order')]
                new_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in repository.revlist([new], [new_head], '--topo-order')]
                reachable_values = [(review.branch.id, sha1) for sha1 in new_sha1s]

                cursor.execute("INSERT INTO previousreachable (rebase, commit) SELECT %s, commit FROM reachable WHERE branch=%s", (rebase_id, review.branch.id))
                cursor.execute("DELETE FROM reachable WHERE branch=%s", (review.branch.id,))
                cursor.executemany("INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s", reachable_values)
                cursor.execute("UPDATE branches SET head=%s WHERE id=%s", (gitutils.Commit.fromSHA1(db, repository, new).getId(db), review.branch.id))

                pending_mails = []

                cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (review.id,))
                recipients = []
                for (user_id,) in cursor.fetchall():
                    recipients.append(dbutils.User.fromId(db, user_id))
                for to_user in recipients:
                    pending_mails.extend(review_mail.sendReviewRebased(db, user, to_user, recipients, review, None, rebased_commits))

                print "History rewrite performed."

                if new_commits:
                    review_utils.addCommitsToReview(db, user, review, new_commits, pending_mails=pending_mails)
                else:
                    review_mail.sendPendingMails(pending_mails)

                repository.run('update-ref', 'refs/keepalive/%s' % old, old)

            return True
        elif old != repository.mergebase([old, new]):
            raise IndexException, "Rejecting non-fast-forward update of review branch."
    elif old != repository.mergebase([old, new]):
        raise IndexException, """\
Rejecting non-fast-forward update of branch.  To perform the update, you
can delete the branch using
  git push critic :%s
first, and then repeat this push.""" % name

    cursor.execute("SELECT id FROM branches WHERE repository=%s AND base IS NULL ORDER BY id ASC LIMIT 1", (repository.id,))
    root_branch_id = cursor.fetchone()[0]

    def isreachable(sha1):
        #if rescan: return False
        #if is_review: cursor.execute("SELECT 1 FROM commits, reachable, branches WHERE commits.sha1=%s AND commits.id=reachable.commit AND reachable.branch=branches.id AND branches.repository=%s", [sha1, repository.id])
        if is_review and sha1 == branch.tail: return True
        if base_branch_id: cursor.execute("SELECT 1 FROM commits, reachable WHERE commits.sha1=%s AND commits.id=reachable.commit AND reachable.branch IN (%s, %s, %s)", [sha1, branch.id, base_branch_id, root_branch_id])
        else: cursor.execute("SELECT 1 FROM commits, reachable WHERE commits.sha1=%s AND commits.id=reachable.commit AND reachable.branch IN (%s, %s)", [sha1, branch.id, root_branch_id])
        return cursor.fetchone() is not None

    stack = [new]
    commits = set()
    commit_list = []
    processed = set()
    count = 0

    while stack:
        sha1 = stack.pop()

        count += 1
        if (count % 1000) == 0:
            stdout.write(".")
            if (count % 10000) == 0:
                stdout.write("\n")
            stdout.flush()

        if sha1 not in commits and not isreachable(sha1):
            commits.add(sha1)
            commit_list.append(sha1)

            #if is_review:
            #    stack.append(gitutils.Commit.fromSHA1(repository, sha1).parents[0])
            #else:
            stack.extend([parent_sha1 for parent_sha1 in gitutils.Commit.fromSHA1(db, repository, sha1).parents if parent_sha1 not in processed])

        processed.add(sha1)

    branch = dbutils.Branch.fromName(db, repository, name)
    review = dbutils.Review.fromBranch(db, branch)

    if review:
        if review.state != "open":
            raise IndexException, """\
The review is closed and can't be extended.  You need to reopen it at
%s
before you can add commits to it.""" % review.getURL(db, user, 2)

        all_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in reversed(commit_list)]

        tails = CommitSet(all_commits).getTails()

        if old not in tails:
            raise IndexException, """\
Push rejected; would break the review.

It looks like some of the pushed commits are reachable from the
repository's main branch, and thus consequently the commits currently
included in the review are too.

Perhaps you should request a new review of the follow-up commits?"""

        review_utils.addCommitsToReview(db, user, review, all_commits, commitset=commits, tracked_branch=tracked_branch)

    reachable_values = [(branch.id, sha1) for sha1 in reversed(commit_list) if sha1 in commits]

    cursor.executemany("INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s", reachable_values)
    cursor.execute("UPDATE branches SET head=%s WHERE id=%s", (gitutils.Commit.fromSHA1(db, repository, new).getId(db), branch.id))

    db.commit()

    if configuration.extensions.ENABLED and review:
        extensions.executeProcessCommits(db, user, review, all_commits, gitutils.Commit.fromSHA1(db, repository, old), gitutils.Commit.fromSHA1(db, repository, new), stdout)
Esempio n. 17
0
def process_request(environ, start_response):
    request_start = time.time()

    critic = api.critic.startSession()
    db = critic.database
    user = None

    try:
        try:
            req = request.Request(db, environ, start_response)
            req.setUser(db)

            if req.user is None:
                if configuration.base.AUTHENTICATION_MODE == "host":
                    user = dbutils.User.makeAnonymous()
                elif configuration.base.SESSION_TYPE == "httpauth":
                    req.requestHTTPAuthentication()
                    return []
                elif req.path.startswith("externalauth/"):
                    provider_name = req.path[len("externalauth/"):]
                    raise request.DoExternalAuthentication(provider_name)
                elif req.path.startswith("oauth/"):
                    provider_name = req.path[len("oauth/"):]
                    if provider_name in auth.PROVIDERS:
                        provider = auth.PROVIDERS[provider_name]
                        if isinstance(provider, auth.OAuthProvider):
                            if finishOAuth(db, req, provider):
                                return []
                elif configuration.base.SESSION_TYPE == "cookie":
                    if req.cookies.get("has_sid") == "1":
                        req.ensureSecure()
                    if configuration.base.ALLOW_ANONYMOUS_USER \
                            or req.path in request.INSECURE_PATHS \
                            or req.path.startswith("static-resource/"):
                        user = dbutils.User.makeAnonymous()
                    # Don't try to redirect POST requests to the login page.
                    elif req.method == "GET":
                        if configuration.base.AUTHENTICATION_MODE == "critic":
                            raise request.NeedLogin(req)
                        else:
                            raise request.DoExternalAuthentication(
                                configuration.base.AUTHENTICATION_MODE,
                                req.getTargetURL())
                if not user:
                    req.setStatus(403)
                    req.start()
                    return []
            else:
                try:
                    user = dbutils.User.fromName(db, req.user)
                except dbutils.NoSuchUser:
                    if configuration.base.AUTHENTICATION_MODE == "host":
                        email = getUserEmailAddress(req.user)
                        user = dbutils.User.create(db,
                                                   req.user,
                                                   req.user,
                                                   email,
                                                   email_verified=None)
                        db.commit()
                    else:
                        # This can't really happen.
                        raise

            if not user.isAnonymous():
                critic.setActualUser(api.user.fetch(critic, user_id=user.id))

            user.loadPreferences(db)

            if user.status == 'retired':
                cursor = db.cursor()
                cursor.execute("UPDATE users SET status='current' WHERE id=%s",
                               (user.id, ))
                user = dbutils.User.fromId(db, user.id)
                db.commit()

            if not user.getPreference(db, "debug.profiling.databaseQueries"):
                db.disableProfiling()

            if not req.path:
                if user.isAnonymous():
                    location = "tutorial"
                else:
                    location = user.getPreference(db, "defaultPage")

                if req.query:
                    location += "?" + req.query

                req.setStatus(307)
                req.addResponseHeader("Location", location)
                req.start()
                return []

            if req.path == "redirect":
                target = req.getParameter("target", "/")

                if req.method == "POST":
                    # Don't use HTTP redirect for POST requests.

                    req.setContentType("text/html")
                    req.start()

                    return [
                        "<meta http-equiv='refresh' content='0; %s'>" %
                        htmlify(target)
                    ]
                else:
                    raise request.MovedTemporarily(target)

            # Require a .git suffix on HTTP(S) repository URLs unless the user-
            # agent starts with "git/" (as Git's normally does.)
            #
            # Our objectives are:
            #
            # 1) Not to require Git's user-agent to be its default value, since
            #    the user might have to override it to get through firewalls.
            # 2) Never to send regular user requests to 'git http-backend' by
            #    mistake.
            #
            # This is a compromise.

            if req.getRequestHeader("User-Agent", "").startswith("git/"):
                suffix = None
            else:
                suffix = ".git"

            if handleRepositoryPath(db, req, user, suffix):
                db = None
                return []

            if req.path.startswith("!/"):
                req.path = req.path[2:]
            elif configuration.extensions.ENABLED:
                handled = extensions.role.page.execute(db, req, user)
                if isinstance(handled, basestring):
                    req.start()
                    return [handled]

            if req.path.startswith("static-resource/"):
                return handleStaticResource(req)

            if req.path.startswith("r/"):
                req.updateQuery({"id": [req.path[2:]]})
                req.path = "showreview"

            if configuration.extensions.ENABLED:
                match = RE_EXTENSION_RESOURCE.match(req.path)
                if match:
                    content_type, resource = extensions.resource.get(
                        req, db, user, match.group(1))
                    if resource:
                        req.setContentType(content_type)
                        if content_type.startswith("image/"):
                            req.addResponseHeader("Cache-Control",
                                                  "max-age=3600")
                        req.start()
                        return [resource]
                    else:
                        req.setStatus(404)
                        req.start()
                        return []

            if req.path.startswith("download/"):
                operationfn = download
            elif req.path == "api" or req.path.startswith("api/"):
                try:
                    result = jsonapi.handle(critic, req)
                except jsonapi.Error as error:
                    req.setStatus(error.http_status)
                    result = {
                        "error": {
                            "title": error.title,
                            "message": error.message
                        }
                    }
                else:
                    req.setStatus(200)

                accept_header = req.getRequestHeader("Accept")
                if accept_header == "application/vnd.api+json":
                    default_indent = None
                else:
                    default_indent = 2
                indent = req.getParameter("indent", default_indent, filter=int)
                if indent == 0:
                    # json.encode(..., indent=0) still gives line-breaks, just
                    # no indentation.  This is not so useful, so set indent to
                    # None instead, which disables formatting entirely.
                    indent = None

                req.setContentType("application/vnd.api+json")
                req.start()
                return [json_encode(result, indent=indent)]
            else:
                operationfn = OPERATIONS.get(req.path)

            if operationfn:
                result = operationfn(req, db, user)

                if isinstance(result, (OperationResult, OperationError)):
                    req.setContentType("text/json")

                    if isinstance(result, OperationResult):
                        if db.profiling:
                            result.set("__profiling__", formatDBProfiling(db))
                            result.set("__time__", time.time() - request_start)
                elif not req.hasContentType():
                    req.setContentType("text/plain")

                req.start()

                if isinstance(result, unicode):
                    return [result.encode("utf8")]
                else:
                    return [str(result)]

            override_user = req.getParameter("user", None)

            while True:
                pagefn = PAGES.get(req.path)
                if pagefn:
                    try:
                        if not user.isAnonymous() and override_user:
                            user = dbutils.User.fromName(db, override_user)

                        req.setContentType("text/html")

                        result = pagefn(req, db, user)

                        if db.profiling and not (isinstance(result, str) or
                                                 isinstance(result, Document)):
                            source = ""
                            for fragment in result:
                                source += fragment
                            result = source

                        if isinstance(result, str) or isinstance(
                                result, Document):
                            req.start()
                            result = str(result)
                            result += ("<!-- total request time: %.2f ms -->" %
                                       ((time.time() - request_start) * 1000))
                            if db.profiling:
                                result += ("<!--\n\n%s\n\n -->" %
                                           formatDBProfiling(db))
                            return [result]
                        else:
                            result = WrappedResult(db, req, user, result)
                            req.start()

                            # Prevent the finally clause below from closing the
                            # connection.  WrappedResult does it instead.
                            db = None

                            return result
                    except gitutils.NoSuchRepository as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!", body=error.message)
                    except gitutils.GitReferenceError as error:
                        if error.ref:
                            raise page.utils.DisplayMessage(
                                title="Specified ref not found",
                                body=("There is no ref named \"%s\" in %s." %
                                      (error.ref, error.repository)))
                        elif error.sha1:
                            raise page.utils.DisplayMessage(
                                title="SHA-1 not found", body=error.message)
                        else:
                            raise
                    except dbutils.NoSuchUser as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!", body=error.message)
                    except dbutils.NoSuchReview as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!", body=error.message)

                path = req.path

                if "/" in path:
                    repository = gitutils.Repository.fromName(
                        db,
                        path.split("/", 1)[0])
                    if repository: path = path.split("/", 1)[1]
                else:
                    repository = None

                def revparsePlain(item):
                    try:
                        return gitutils.getTaggedCommit(
                            repository, repository.revparse(item))
                    except:
                        raise

                revparse = revparsePlain

                if repository is None:
                    review_id = req.getParameter("review", None, filter=int)

                    if review_id:
                        cursor = db.cursor()
                        cursor.execute(
                            """SELECT repository
                                            FROM branches
                                            JOIN reviews ON (reviews.branch=branches.id)
                                           WHERE reviews.id=%s""",
                            (review_id, ))
                        row = cursor.fetchone()
                        if row:
                            repository = gitutils.Repository.fromId(db, row[0])

                            def revparseWithReview(item):
                                if re.match("^[0-9a-f]+$", item):
                                    cursor.execute(
                                        """SELECT sha1
                                                        FROM commits
                                                        JOIN changesets ON (changesets.child=commits.id)
                                                        JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                                       WHERE reviewchangesets.review=%s
                                                         AND commits.sha1 LIKE %s""",
                                        (review_id, item + "%"))
                                    row = cursor.fetchone()
                                    if row: return row[0]
                                    else: return revparsePlain(item)

                            revparse = revparseWithReview

                if repository is None:
                    repository = gitutils.Repository.fromName(
                        db, user.getPreference(db, "defaultRepository"))

                    if gitutils.re_sha1.match(path):
                        if repository and not repository.iscommit(path):
                            repository = None

                        if not repository:
                            try:
                                repository = gitutils.Repository.fromSHA1(
                                    db, path)
                            except gitutils.GitReferenceError:
                                repository = None

                if repository:
                    try:
                        items = filter(None, map(revparse, path.split("..")))
                        updated_query = {}

                        if len(items) == 1:
                            updated_query["repository"] = [repository.name]
                            updated_query["sha1"] = [items[0]]
                        elif len(items) == 2:
                            updated_query["repository"] = [repository.name]
                            updated_query["from"] = [items[0]]
                            updated_query["to"] = [items[1]]

                        if updated_query:
                            req.updateQuery(updated_query)
                            req.path = "showcommit"
                            continue
                    except gitutils.GitReferenceError:
                        pass

                break

            raise page.utils.DisplayMessage(title="Not found!",
                                            body="Page not handled: /%s" %
                                            path,
                                            status=404)
        except GeneratorExit:
            raise
        except page.utils.NotModified:
            req.setStatus(304)
            req.start()
            return []
        except request.MovedTemporarily as redirect:
            req.setStatus(307)
            req.addResponseHeader("Location", redirect.location)
            if redirect.no_cache:
                req.addResponseHeader("Cache-Control", "no-cache")
            req.start()
            return []
        except request.DoExternalAuthentication as command:
            command.execute(db, req)
            return []
        except request.MissingWSGIRemoteUser as error:
            # req object is not initialized yet.
            start_response("200 OK", [("Content-Type", "text/html")])
            return [
                """\
<pre>error: Critic was configured with '--auth-mode host' but there was no
REMOTE_USER variable in the WSGI environ dict provided by the web server.

To fix this you can either reinstall Critic using '--auth-mode critic' (to let
Critic handle user authentication automatically), or you can configure user
authentication properly in the web server.  For apache2, the latter can be done
by adding the something like the following to the apache site configuration for
Critic:

        &lt;Location /&gt;
                AuthType Basic
                AuthName "Authentication Required"
                AuthUserFile "/path/to/critic-main.htpasswd.users"
                Require valid-user
        &lt;/Location&gt;

If you need more dynamic http authentication you can instead setup mod_wsgi with
a custom WSGIAuthUserScript directive.  This will cause the provided credentials
to be passed to a Python function called check_password() that you can implement
yourself.  This way you can validate the user/pass via any existing database or
for example an LDAP server.  For more information on setting up such
authentication in apache2, see:

  <a href="%(url)s">%(url)s</a></pre>""" % {
                    "url":
                    "http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider"
                }
            ]
        except page.utils.DisplayMessage as message:
            if user is None:
                user = dbutils.User.makeAnonymous()

            document = page.utils.displayMessage(db,
                                                 req,
                                                 user,
                                                 title=message.title,
                                                 message=message.body,
                                                 review=message.review,
                                                 is_html=message.html)

            req.setContentType("text/html")
            req.setStatus(message.status)
            req.start()

            return [str(document)]
        except page.utils.DisplayFormattedText as formatted_text:
            if user is None:
                user = dbutils.User.makeAnonymous()

            document = page.utils.displayFormattedText(db, req, user,
                                                       formatted_text.source)

            req.setContentType("text/html")
            req.start()

            return [str(document)]
        except Exception:
            # crash might be psycopg2.ProgrammingError so rollback to avoid
            # "InternalError: current transaction is aborted" inside handleException()
            if db and db.closed():
                db = None
            elif db:
                db.rollback()

            error_title, error_body = handleException(db, req, user)
            error_body = reflow("\n\n".join(error_body))
            error_message = "\n".join(
                [error_title, "=" * len(error_title), "", error_body])

            assert not req.isStarted()

            req.setStatus(500)
            req.setContentType("text/plain")
            req.start()

            return [error_message]
    finally:
        if db:
            db.close()
Esempio n. 18
0
def createBranch(user, repository, name, head, flags):
    processCommits(repository.name, head)

    try:
        update(repository.path, "refs/heads/" + name, None, head)
    except Reject as rejected:
        raise IndexException(str(rejected))
    except Exception:
        pass

    cursor = db.cursor()

    def commit_id(sha1):
        cursor.execute("SELECT id FROM commits WHERE sha1=%s", [sha1])
        return cursor.fetchone()[0]

    components = name.split("/")
    for index in range(1, len(components)):
        try: repository.revparse("refs/heads/%s" % "/".join(components[:index]))
        except: continue

        message = ("Cannot create branch with name '%s' since there is already a branch named '%s' in the repository." %
                   (name, "/".join(components[:index])))
        raise IndexException(textutils.reflow(message, line_length=80 - len("remote: ")))

    if name.startswith("r/"):
        try:
            review_id = int(name[2:])

            cursor.execute("SELECT branches.name FROM reviews JOIN branches ON (branches.id=reviews.branch) WHERE reviews.id=%s", (review_id,))
            row = cursor.fetchone()

            message = "Refusing to create review named as a number."

            if row:
                message += "\nDid you mean to push to the branch '%s', perhaps?" % row[0]

            raise IndexException(message)
        except ValueError:
            pass

        if user.getPreference(db, "review.createViaPush"):
            the_commit = gitutils.Commit.fromSHA1(db, repository, head, commit_id(head))
            all_commits = [the_commit]

            review = reviewing.utils.createReview(
                db, user, repository, all_commits, name,
                the_commit.niceSummary(include_tag=False), None, via_push=True)

            print "Submitted review:"
            print review.getURL(db, user, indent=2)

            if review.reviewers:
                print "  Reviewers:"
                for reviewer in review.reviewers:
                    print "    %s <%s>" % (reviewer.fullname, reviewer.email)

            if review.watchers:
                print "  Watchers:"
                for watcher in review.watchers:
                    print "    %s <%s>" % (watcher.fullname, watcher.email)

            if configuration.extensions.ENABLED:
                if extensions.role.processcommits.execute(db, user, review, all_commits, None, the_commit, sys.stdout):
                    print

            print "Thank you!"
            return True
        else:
            raise IndexException("Refusing to create review; user preference 'review.createViaPush' is not enabled.")

    sha1 = head
    base = None
    tail = None

    cursor.execute("""SELECT 1
                        FROM reachable
                        JOIN branches ON (branches.id=reachable.branch)
                        JOIN repositories ON (repositories.id=branches.repository)
                       WHERE repositories.id=%s
                       LIMIT 1""",
                   (repository.id,))

    if cursor.fetchone():
        def reachable(sha1):
            cursor.execute("""SELECT branches.id
                                FROM branches
                                JOIN reachable ON (reachable.branch=branches.id)
                                JOIN commits ON (commits.id=reachable.commit)
                               WHERE branches.repository=%s
                                 AND branches.type='normal'
                                 AND commits.sha1=%s
                            ORDER BY reachable.branch ASC
                               LIMIT 1""",
                           (repository.id, sha1))
            return cursor.fetchone()
    else:
        def reachable(sha1):
            return None

    commit_map = {}
    commit_list = []

    row = reachable(sha1)
    if row:
        # Head of branch is reachable from an existing branch.  Could be because
        # this branch is actually empty (just created with no "own" commits) or
        # it could have been merged into some other already existing branch.  We
        # can't tell, so we just record it as empty.

        base = row[0]
        tail = sha1
    else:
        stack = []

        while True:
            if sha1 not in commit_map:
                commit = gitutils.Commit.fromSHA1(db, repository, sha1)
                commit_map[sha1] = commit
                commit_list.append(commit)

                for sha1 in commit.parents:
                    if sha1 not in commit_map:
                        row = reachable(sha1)
                        if not row:
                            stack.append(sha1)
                        elif base is None:
                            base = row[0]
                            tail = sha1

                            base_chain = [base]

                            while True:
                                cursor.execute("SELECT base FROM branches WHERE id=%s", (base_chain[-1],))
                                next = cursor.fetchone()[0]
                                if next is None: break
                                else: base_chain.append(next)

                            def reachable(sha1):
                                cursor.execute("""SELECT 1
                                                    FROM reachable
                                                    JOIN commits ON (commits.id=reachable.commit)
                                                   WHERE reachable.branch=ANY (%s)
                                                     AND commits.sha1=%s""",
                                               (base_chain, sha1))
                                return cursor.fetchone()

            if stack: sha1 = stack.pop(0)
            else: break

    if isinstance(user, dbutils.User):
        # Push by regular user.
        user_name = user.name
    else:
        # Push by the Critic system user, i.e. by the branch tracker service or
        # other internal mechanism.
        user_name = user

    if not base:
        cursor.execute("INSERT INTO branches (repository, name, head) VALUES (%s, %s, %s) RETURNING id", (repository.id, name, commit_id(head)))
        branch_id = cursor.fetchone()[0]
    else:
        cursor.execute("INSERT INTO branches (repository, name, head, base, tail) VALUES (%s, %s, %s, %s, %s) RETURNING id", (repository.id, name, commit_id(head), base, commit_id(tail)))
        branch_id = cursor.fetchone()[0]

        # Suppress the "user friendly" feedback if the push is performed by the
        # Critic system user, since there wouldn't be a human being reading it.
        #
        # Also, the calls to user.getCriticURLs() obvious don't work if 'user'
        # isn't a dbutils.User object, which it isn't in that case.
        if user_name != configuration.base.SYSTEM_USER_NAME:
            cursor.execute("SELECT name FROM branches WHERE id=%s", [base])

            print "Added branch based on %s containing %d commit%s:" % (cursor.fetchone()[0], len(commit_list), "s" if len(commit_list) > 1 else "")
            for url_prefix in user.getCriticURLs(db):
                print "  %s/log?repository=%d&branch=%s" % (url_prefix, repository.id, name)
            if len(commit_list) > 1:
                print "To create a review of all %d commits:" % len(commit_list)
            else:
                print "To create a review of the commit:"
            for url_prefix in user.getCriticURLs(db):
                print "  %s/createreview?repository=%d&branch=%s" % (url_prefix, repository.id, name)

    reachable_values = [(branch_id, commit.sha1) for commit in commit_list]
    cursor.executemany("INSERT INTO reachable (branch, commit) SELECT %s, id FROM commits WHERE sha1=%s", reachable_values)

    if not repository.hasMainBranch() and user_name == configuration.base.SYSTEM_USER_NAME:
        cursor.execute("UPDATE repositories SET branch=%s WHERE id=%s", (branch_id, repository.id))
Esempio n. 19
0
def process_request(environ, start_response):
    request_start = time.time()

    db = dbutils.Database()
    user = None

    try:
        try:
            req = request.Request(db, environ, start_response)
            req.setUser(db)

            if req.user is None:
                if configuration.base.AUTHENTICATION_MODE == "host":
                    user = dbutils.User.makeAnonymous()
                elif configuration.base.SESSION_TYPE == "httpauth":
                    req.requestHTTPAuthentication()
                    return []
                elif req.path.startswith("externalauth/"):
                    provider_name = req.path[len("externalauth/"):]
                    raise request.DoExternalAuthentication(provider_name)
                elif req.path.startswith("oauth/"):
                    provider_name = req.path[len("oauth/"):]
                    if provider_name in auth.PROVIDERS:
                        provider = auth.PROVIDERS[provider_name]
                        if isinstance(provider, auth.OAuthProvider):
                            if finishOAuth(db, req, provider):
                                return []
                elif configuration.base.SESSION_TYPE == "cookie":
                    if req.cookies.get("has_sid") == "1":
                        req.ensureSecure()
                    if configuration.base.ALLOW_ANONYMOUS_USER \
                            or req.path in request.INSECURE_PATHS \
                            or req.path.startswith("static-resource/"):
                        user = dbutils.User.makeAnonymous()
                    # Don't try to redirect POST requests to the login page.
                    elif req.method == "GET":
                        if configuration.base.AUTHENTICATION_MODE == "critic":
                            raise request.NeedLogin(req)
                        else:
                            raise request.DoExternalAuthentication(
                                configuration.base.AUTHENTICATION_MODE,
                                req.getTargetURL())
                if not user:
                    req.setStatus(403)
                    req.start()
                    return []
            else:
                try:
                    user = dbutils.User.fromName(db, req.user)
                except dbutils.NoSuchUser:
                    if configuration.base.AUTHENTICATION_MODE == "host":
                        email = getUserEmailAddress(req.user)
                        user = dbutils.User.create(
                            db, req.user, req.user, email, email_verified=None)
                        db.commit()
                    else:
                        # This can't really happen.
                        raise

            user.loadPreferences(db)

            if user.status == 'retired':
                cursor = db.cursor()
                cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id,))
                user = dbutils.User.fromId(db, user.id)
                db.commit()

            if not user.getPreference(db, "debug.profiling.databaseQueries"):
                db.disableProfiling()

            if not req.path:
                if user.isAnonymous():
                    location = "tutorial"
                else:
                    location = user.getPreference(db, "defaultPage")

                if req.query:
                    location += "?" + req.query

                req.setStatus(307)
                req.addResponseHeader("Location", location)
                req.start()
                return []

            if req.path == "redirect":
                target = req.getParameter("target", "/")

                if req.method == "POST":
                    # Don't use HTTP redirect for POST requests.

                    req.setContentType("text/html")
                    req.start()

                    return ["<meta http-equiv='refresh' content='0; %s'>" % htmlify(target)]
                else:
                    raise request.MovedTemporarily(target)

            # Require a .git suffix on HTTP(S) repository URLs unless the user-
            # agent starts with "git/" (as Git's normally does.)
            #
            # Our objectives are:
            #
            # 1) Not to require Git's user-agent to be its default value, since
            #    the user might have to override it to get through firewalls.
            # 2) Never to send regular user requests to 'git http-backend' by
            #    mistake.
            #
            # This is a compromise.

            if req.getRequestHeader("User-Agent", "").startswith("git/"):
                suffix = None
            else:
                suffix = ".git"

            if handleRepositoryPath(db, req, user, suffix):
                db = None
                return []

            if req.path.startswith("!/"):
                req.path = req.path[2:]
            elif configuration.extensions.ENABLED:
                handled = extensions.role.page.execute(db, req, user)
                if isinstance(handled, basestring):
                    req.start()
                    return [handled]

            if req.path.startswith("static-resource/"):
                return handleStaticResource(req)

            if req.path.startswith("r/"):
                req.query = "id=" + req.path[2:] + ("&" + req.query if req.query else "")
                req.path = "showreview"

            if configuration.extensions.ENABLED:
                match = RE_EXTENSION_RESOURCE.match(req.path)
                if match:
                    content_type, resource = extensions.resource.get(req, db, user, match.group(1))
                    if resource:
                        req.setContentType(content_type)
                        if content_type.startswith("image/"):
                            req.addResponseHeader("Cache-Control", "max-age=3600")
                        req.start()
                        return [resource]
                    else:
                        req.setStatus(404)
                        req.start()
                        return []

            if req.path.startswith("download/"):
                operationfn = download
            else:
                operationfn = OPERATIONS.get(req.path)

            if operationfn:
                result = operationfn(req, db, user)

                if isinstance(result, (OperationResult, OperationError)):
                    req.setContentType("text/json")

                    if isinstance(result, OperationResult):
                        if db.profiling:
                            result.set("__profiling__", formatDBProfiling(db))
                            result.set("__time__", time.time() - request_start)
                elif not req.hasContentType():
                    req.setContentType("text/plain")

                req.start()

                if isinstance(result, unicode):
                    return [result.encode("utf8")]
                else:
                    return [str(result)]

            override_user = req.getParameter("user", None)

            while True:
                pagefn = PAGES.get(req.path)
                if pagefn:
                    try:
                        if not user.isAnonymous() and override_user:
                            user = dbutils.User.fromName(db, override_user)

                        req.setContentType("text/html")

                        result = pagefn(req, db, user)

                        if db.profiling and not (isinstance(result, str) or
                                                 isinstance(result, Document)):
                            source = ""
                            for fragment in result:
                                source += fragment
                            result = source

                        if isinstance(result, str) or isinstance(result, Document):
                            req.start()
                            result = str(result)
                            result += ("<!-- total request time: %.2f ms -->"
                                       % ((time.time() - request_start) * 1000))
                            if db.profiling:
                                result += ("<!--\n\n%s\n\n -->"
                                           % formatDBProfiling(db))
                            return [result]
                        else:
                            result = WrappedResult(db, req, user, result)
                            req.start()

                            # Prevent the finally clause below from closing the
                            # connection.  WrappedResult does it instead.
                            db = None

                            return result
                    except gitutils.NoSuchRepository as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)
                    except gitutils.GitReferenceError as error:
                        if error.ref:
                            raise page.utils.DisplayMessage(
                                title="Specified ref not found",
                                body=("There is no ref named \"%s\" in %s."
                                      % (error.ref, error.repository)))
                        elif error.sha1:
                            raise page.utils.DisplayMessage(
                                title="SHA-1 not found",
                                body=error.message)
                        else:
                            raise
                    except dbutils.NoSuchUser as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)
                    except dbutils.NoSuchReview as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)

                path = req.path

                if "/" in path:
                    repository = gitutils.Repository.fromName(db, path.split("/", 1)[0])
                    if repository: path = path.split("/", 1)[1]
                else:
                    repository = None

                def revparsePlain(item):
                    try: return gitutils.getTaggedCommit(repository, repository.revparse(item))
                    except: raise
                revparse = revparsePlain

                if repository is None:
                    review_id = req.getParameter("review", None, filter=int)

                    if review_id:
                        cursor = db.cursor()
                        cursor.execute("""SELECT repository
                                            FROM branches
                                            JOIN reviews ON (reviews.branch=branches.id)
                                           WHERE reviews.id=%s""",
                                       (review_id,))
                        row = cursor.fetchone()
                        if row:
                            repository = gitutils.Repository.fromId(db, row[0])
                            def revparseWithReview(item):
                                if re.match("^[0-9a-f]+$", item):
                                    cursor.execute("""SELECT sha1
                                                        FROM commits
                                                        JOIN changesets ON (changesets.child=commits.id)
                                                        JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                                       WHERE reviewchangesets.review=%s
                                                         AND commits.sha1 LIKE %s""",
                                                   (review_id, item + "%"))
                                    row = cursor.fetchone()
                                    if row: return row[0]
                                    else: return revparsePlain(item)
                            revparse = revparseWithReview

                if repository is None:
                    repository = gitutils.Repository.fromName(
                        db, user.getPreference(db, "defaultRepository"))

                    if gitutils.re_sha1.match(path):
                        if repository and not repository.iscommit(path):
                            repository = None

                        if not repository:
                            try:
                                repository = gitutils.Repository.fromSHA1(db, path)
                            except gitutils.GitReferenceError:
                                repository = None

                if repository:
                    try:
                        items = filter(None, map(revparse, path.split("..")))
                        query = None

                        if len(items) == 1:
                            query = ("repository=%d&sha1=%s"
                                     % (repository.id, items[0]))
                        elif len(items) == 2:
                            query = ("repository=%d&from=%s&to=%s"
                                     % (repository.id, items[0], items[1]))

                        if query:
                            if req.query:
                                query += "&" + req.query

                            req.query = query
                            req.path = "showcommit"
                            continue
                    except gitutils.GitReferenceError:
                        pass

                break

            req.setStatus(404)
            raise page.utils.DisplayMessage(
                title="Not found!",
                body="Page not handled: /%s" % path)
        except GeneratorExit:
            raise
        except page.utils.NotModified:
            req.setStatus(304)
            req.start()
            return []
        except request.MovedTemporarily as redirect:
            req.setStatus(307)
            req.addResponseHeader("Location", redirect.location)
            if redirect.no_cache:
                req.addResponseHeader("Cache-Control", "no-cache")
            req.start()
            return []
        except request.DoExternalAuthentication as command:
            command.execute(db, req)
            return []
        except request.MissingWSGIRemoteUser as error:
            # req object is not initialized yet.
            start_response("200 OK", [("Content-Type", "text/html")])
            return ["""\
<pre>error: Critic was configured with '--auth-mode host' but there was no
REMOTE_USER variable in the WSGI environ dict provided by the web server.

To fix this you can either reinstall Critic using '--auth-mode critic' (to let
Critic handle user authentication automatically), or you can configure user
authentication properly in the web server.  For apache2, the latter can be done
by adding the something like the following to the apache site configuration for
Critic:

        &lt;Location /&gt;
                AuthType Basic
                AuthName "Authentication Required"
                AuthUserFile "/path/to/critic-main.htpasswd.users"
                Require valid-user
        &lt;/Location&gt;

If you need more dynamic http authentication you can instead setup mod_wsgi with
a custom WSGIAuthUserScript directive.  This will cause the provided credentials
to be passed to a Python function called check_password() that you can implement
yourself.  This way you can validate the user/pass via any existing database or
for example an LDAP server.  For more information on setting up such
authentication in apache2, see:

  <a href="%(url)s">%(url)s</a></pre>""" % { "url": "http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider" }]
        except page.utils.DisplayMessage as message:
            if user is None:
                user = dbutils.User.makeAnonymous()

            document = page.utils.displayMessage(
                db, req, user, title=message.title, message=message.body,
                review=message.review, is_html=message.html)

            req.setContentType("text/html")
            req.start()

            return [str(document)]
        except Exception:
            # crash might be psycopg2.ProgrammingError so rollback to avoid
            # "InternalError: current transaction is aborted" inside handleException()
            if db and db.closed():
                db = None
            elif db:
                db.rollback()

            error_title, error_body = handleException(db, req, user)
            error_body = reflow("\n\n".join(error_body))
            error_message = "\n".join([error_title,
                                       "=" * len(error_title),
                                       "",
                                       error_body])

            assert not req.isStarted()

            req.setStatus(500)
            req.setContentType("text/plain")
            req.start()

            return [error_message]
    finally:
        if db:
            db.close()
Esempio n. 20
0
def sendReviewAddedCommits(db, from_user, to_user, recipients, review, commits, changesets, tracked_branch=False):
    # First check if we can send emails to the user at all.
    if not checkEmailEnabled(db, to_user):
        return []

    if from_user == to_user and to_user.getPreference(db, "email.ignoreOwnChanges"):
        return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length
    relevant_only = to_user not in review.owners and to_user != from_user and to_user.getPreference(db, "email.updatedReview.relevantChangesOnly")

    cursor = db.cursor()

    if relevant_only:
        cursor.execute("SELECT type FROM reviewusers WHERE review=%s AND uid=%s", (review.id, to_user.id))
        if cursor.fetchone()[0] == 'manual': relevant_only = False

    all_commits = dict((commit.sha1, commit) for commit in commits)
    changeset_for_commit = {}

    for changeset in changesets:
        # We don't include diffs for merge commits in mails.
        if len(changeset.child.parents) == 1:
            if changeset.child in all_commits:
                changeset_for_commit[changeset.child] = changeset
            else:
                # An added changeset where the child isn't part of the added
                # commits will be a changeset between a "replayed rebase" commit
                # and the new head commit, generated when doing a non-fast-
                # forward rebase.  The relevant commit from such a changeset is
                # the first (and only) parent.
                changeset_for_commit[changeset.parent] = changeset

    if relevant_only:
        relevant_files = review.getRelevantFiles(db, to_user)
        relevant_commits = set()

        for changeset in changesets:
            for file in changeset.files:
                if file.id in relevant_files:
                    if changeset.child in all_commits:
                        relevant_commits.add(changeset.child)
                    else:
                        # "Replayed rebase" commit; see comment above.
                        relevant_commits.add(all_commits[changeset.parent])
                    break
            else:
                cursor.execute("SELECT id FROM commentchains WHERE review=%s AND state='addressed' AND addressed_by=%s", (review.id, changeset.child.getId(db)))
                for chain_id in cursor.fetchall():
                    cursor.execute("SELECT 1 FROM commentchainusers WHERE chain=%s AND uid=%s", (chain_id, to_user.id))
                    if cursor.fetchone():
                        relevant_commits.add(changeset.child)
                        break

        if not relevant_commits:
            return []
    else:
        relevant_commits = None

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.branch.name': review.branch.name,
             'review.branch.repository': review.repository.getURL(db, to_user),
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    commitset = log_commitset.CommitSet(commits)

    if tracked_branch:
        body += "The automatic tracking of\n  %s\n" % tracked_branch
        body += textutils.reflow("has updated the review by pushing %sadditional commit%s to the branch" % ("an " if len(commits) == 1 else "", "s" if len(commits) > 1 else ""), line_length)
    else:
        body += textutils.reflow("%s has updated the review by pushing %sadditional commit%s to the branch" % (from_user.fullname, "an " if len(commits) == 1 else "", "s" if len(commits) > 1 else ""), line_length)

    body += "\n  %s\n" % review.branch.name
    body += textutils.reflow("in the repository", line_length)
    body += "\n  %s\n\n\n" % review.repository.getURL(db, to_user)

    cursor.execute("""SELECT file, SUM(deleted), SUM(inserted)
                        FROM fullreviewuserfiles
                       WHERE review=%%s
                         AND changeset IN (%s)
                         AND state='pending'
                         AND assignee=%%s
                    GROUP BY file""" % ",".join(["%s"] * len(changesets)),
                   [review.id] + [changeset.id for changeset in changesets] + [to_user.id])
    pending_files_lines = cursor.fetchall()

    if pending_files_lines:
        heads = commitset.getHeads()
        tails = commitset.getFilteredTails(review.repository)

        if len(heads) == 1 and len(tails) == 1:
            showcommit_link = (tails.pop()[:8], heads.pop().sha1[:8])
        else:
            showcommit_link = False

        body += renderFiles(db, to_user, review, "These changes were assigned to you:", pending_files_lines, showcommit_link=showcommit_link)

    all_commits = to_user.getPreference(db, "email.updatedReview.displayCommits")
    context_lines = to_user.getPreference(db, "email.comment.contextLines")

    if all_commits:
        body += "The additional commit%s requested to be reviewed are:\n\n" % ("s" if len(commits) > 1 else "")

        contextLines = to_user.getPreference(db, "email.updatedReview.diff.contextLines")
        diffMaxLines = to_user.getPreference(db, "email.updatedReview.diff.maxLines")

        displayStats = to_user.getPreference(db, "email.updatedReview.displayStats")
        statsMaxLines = to_user.getPreference(db, "email.updatedReview.stats.maxLines")

        if contextLines < 0: contextLines = 0

        if diffMaxLines == 0: diffs = None
        else:
            diffs = {}
            lines = 0

            for commit in commits:
                if commit in changeset_for_commit:
                    diff = changeset_text.unified(db, changeset_for_commit[commit], contextLines)
                    diffs[commit] = diff
                    lines += diff.count("\n")
                    if lines > diffMaxLines:
                        diffs = None
                        break

        if not displayStats or statsMaxLines == 0: stats = None
        else:
            stats = {}
            lines = 0

            for commit in commits:
                commit_stats = review.repository.run("show", "--oneline", "--stat", commit.sha1).split('\n', 1)[1]
                stats[commit] = commit_stats
                lines += commit_stats.count('\n')
                if lines > statsMaxLines:
                    stats = None
                    break

        for index, commit in enumerate(commits):
            if index > 0: body += "\n\n\n"

            body += """Commit: %(sha1)s
Author: %(author.fullname)s <%(author.email)s> at %(author.time)s

%(message)s
""" % { 'sha1': commit.sha1,
        'author.fullname': commit.author.getFullname(db),
        'author.email': commit.author.email,
        'author.time': time.strftime("%Y-%m-%d %H:%M:%S", commit.author.time),
        'message': textutils.reflow(commit.message.strip(), line_length, indent=2) }

            if stats and commit in stats:
                body += "---\n" + stats[commit]

            if diffs and commit in diffs:
                body += "\n" + diffs[commit]

            cursor.execute("SELECT id FROM commentchains WHERE review=%s AND state='addressed' AND addressed_by=%s", (review.id, commit.getId(db)))
            rows = cursor.fetchall()

            if rows:
                for (chain_id,) in rows:
                    chain = review_comment.CommentChain.fromId(db, chain_id, to_user, review=review)
                    chain.loadComments(db, to_user, include_draft_comments=False)
                    body += "\n\n" + renderChainInMail(db, to_user, chain, None, "addressed", None, line_length, context_lines)

    files = []

    parent_message_id = getReviewMessageId(db, to_user, review, files)
    message_id = generateMessageId(len(files) + 1)
    subject = generateSubjectLine(db, to_user, review,
                                  "updatedReview.commitsPushed")

    files.append(sendMail(
        db, review, message_id, from_user, to_user, recipients, subject, body,
        parent_message_id=parent_message_id))

    return files
Esempio n. 21
0
def createBranch(user, repository, name, head):
    processCommits(repository.name, head)

    cursor = db.cursor()

    def commit_id(sha1):
        cursor.execute("SELECT id FROM commits WHERE sha1=%s", [sha1])
        return cursor.fetchone()[0]

    components = name.split("/")
    for index in range(1, len(components)):
        try: repository.revparse("refs/heads/%s" % "/".join(components[:index]))
        except: continue

        message = ("Cannot create branch with name '%s' since there is already a branch named '%s' in the repository." %
                   (name, "/".join(components[:index])))
        raise IndexException, textutils.reflow(message, line_length=80 - len("remote: "))

    if name.startswith("r/"):
        try:
            review_id = int(name[2:])

            cursor.execute("SELECT branches.name FROM reviews JOIN branches ON (branches.id=reviews.branch) WHERE reviews.id=%s", (review_id,))
            row = cursor.fetchone()

            message = "Refusing to create review named as a number."

            if row:
                message += "\nDid you mean to push to the branch '%s', perhaps?" % row[0]

            raise IndexException, message
        except ValueError:
            pass

        if user.getPreference(db, "review.createViaPush"):
            the_commit = gitutils.Commit.fromSHA1(db, repository, head, commit_id(head))
            all_commits = [the_commit]
            review = review_utils.createReview(db, user, repository, all_commits, name, the_commit.summary(), None, via_push=True)

            print "Submitted review: %s/r/%d" % (dbutils.getURLPrefix(db), review.id)

            if review.reviewers:
                print "  Reviewers:"
                for reviewer in review.reviewers:
                    print "    %s <%s>" % (reviewer.fullname, reviewer.email)

            if review.watchers:
                print "  Watchers:"
                for watcher in review.watchers:
                    print "    %s <%s>" % (watcher.fullname, watcher.email)

            if configuration.extensions.ENABLED:
                if extensions.executeProcessCommits(db, user, review, all_commits, None, the_commit, stdout):
                    print

            print "Thank you!"
            return True
        else:
            raise IndexException, "Refusing to create review; user preference 'review.createViaPush' is not enabled."

    sha1 = head
    base = None
    tail = None

    cursor.execute("""SELECT 1
                        FROM reachable
                        JOIN branches ON (branches.id=reachable.branch)
                        JOIN repositories ON (repositories.id=branches.repository)
                       WHERE repositories.id=%s
                       LIMIT 1""",
                   (repository.id,))

    if cursor.fetchone():
        def reachable(sha1):
            cursor.execute("""SELECT branches.id
                                FROM branches
                                JOIN reachable ON (reachable.branch=branches.id)
                                JOIN commits ON (commits.id=reachable.commit)
                               WHERE branches.repository=%s
                                 AND branches.type='normal'
                                 AND commits.sha1=%s
                            ORDER BY reachable.branch ASC
                               LIMIT 1""",
                           (repository.id, sha1))
            return cursor.fetchone()
    else:
        def reachable(sha1):
            return None

    commit_map = {}
    commit_list = []

    row = reachable(sha1)
    if row:
        # Head of branch is reachable from an existing branch.  Could be because
        # this branch is actually empty (just created with no "own" commits) or
        # it could have been merged into some other already existing branch.  We
        # can't tell, so we just record it as empty.

        base = row[0]
        tail = sha1
    else:
        stack = []

        while True:
            if sha1 not in commit_map:
                commit = gitutils.Commit.fromSHA1(db, repository, sha1)
                commit_map[sha1] = commit
                commit_list.append(commit)

                for sha1 in commit.parents:
                    if sha1 not in commit_map:
                        row = reachable(sha1)
                        if not row:
                            stack.append(sha1)
                        elif base is None:
                            base = row[0]
                            tail = sha1

                            base_chain = [base]

                            while True:
                                cursor.execute("SELECT base FROM branches WHERE id=%s", (base_chain[-1],))
                                next = cursor.fetchone()[0]
                                if next is None: break
                                else: base_chain.append(next)

                            def reachable(sha1):
                                cursor.execute("""SELECT 1
                                                    FROM reachable
                                                    JOIN commits ON (commits.id=reachable.commit)
                                                   WHERE reachable.branch=ANY (%s)
                                                     AND commits.sha1=%s""",
                                               (base_chain, sha1))
                                return cursor.fetchone()

            if stack: sha1 = stack.pop(0)
            else: break

        if len(commit_list) % 10000 > 1000:
            stdout.write("\n")
            stdout.flush()

    if not base:
        cursor.execute("INSERT INTO branches (repository, name, head) VALUES (%s, %s, %s) RETURNING id", (repository.id, name, commit_id(head)))
        branch_id = cursor.fetchone()[0]
    else:
        cursor.execute("INSERT INTO branches (repository, name, head, base, tail) VALUES (%s, %s, %s, %s, %s) RETURNING id", (repository.id, name, commit_id(head), base, commit_id(tail)))
        branch_id = cursor.fetchone()[0]

        cursor.execute("SELECT name FROM branches WHERE id=%s", [base])

        print "Added branch based on %s containing %d commit%s:" % (cursor.fetchone()[0], len(commit_list), "s" if len(commit_list) > 1 else "")
        print "  %s/log?repository=%d&branch=%s" % (dbutils.getURLPrefix(db), repository.id, name)
        if len(commit_list) > 1:
            print "To create a review of all %d commits:" % len(commit_list)
        else:
            print "To create a review of the commit:"
        print "  %s/createreview?repository=%d&branch=%s" % (dbutils.getURLPrefix(db), repository.id, name)

    reachable_values = [(branch_id, commit.sha1) for commit in commit_list]
    cursor.executemany("INSERT INTO reachable (branch, commit) SELECT %s, id FROM commits WHERE sha1=%s", reachable_values)

    if isinstance(user, str): user_name = user
    else: user_name = user.name

    if not repository.hasMainBranch() and user_name == configuration.base.SYSTEM_USER_NAME:
        cursor.execute("UPDATE repositories SET branch=%s WHERE id=%s", (branch_id, repository.id))
Esempio n. 22
0
File: mail.py Progetto: KurSh/critic
 def formatComment(comment):
     return "%s at %s:\n%s\n" % (comment.user.fullname, comment.when(), textutils.reflow(comment.comment, line_length, indent=2))
Esempio n. 23
0
def updateBranch(db, user, repository, name, old, new, multiple, flags):
    try:
        update(repository.path, "refs/heads/" + name, old, new)
    except Reject as rejected:
        raise IndexException(str(rejected))
    except Exception:
        pass

    try:
        branch = dbutils.Branch.fromName(db, repository, name, for_update=dbutils.NOWAIT)
    except dbutils.FailedToLock:
        raise IndexException(reflow(
                "The branch '%s' is currently locked since it is being updated "
                "by another push.  Please fetch and try again." % name))
    else:
        if not branch:
            # FIXME: We should handle this better.  Maybe just redirect to
            # createBranch()?
            raise IndexException("The branch '%s' is not in the database!" % name)
        base_branch_id = branch.base.id if branch.base else None

    if branch.head_sha1 != old:
        if new == branch.head_sha1:
            # This is what we think the ref ought to be already.  Do nothing,
            # and let the repository "catch up."
            return
        else:
            data = { "name": name,
                     "old": old[:8],
                     "new": new[:8],
                     "current": branch.head_sha1[:8] }

            message = """CONFUSED!  Git thinks %(name)s points to %(old)s, but Critic thinks it points to %(current)s.  Rejecting push since it would only makes matters worse.  To resolve this problem, use

  git push -f critic %(current)s:%(name)s

to resynchronize the Git repository with Critic's database.  Note that 'critic' above must be replaced by the actual name of your Critic remote, if not 'critic'.""" % data

            raise IndexException(textutils.reflow(message, line_length=80 - len("remote: ")))

    cursor = db.cursor()
    cursor.execute("""SELECT id, remote, remote_name, forced, updating
                        FROM trackedbranches
                       WHERE repository=%s
                         AND local_name=%s
                         AND NOT disabled""",
                   (repository.id, name))
    row = cursor.fetchone()

    if row:
        trackedbranch_id, remote, remote_name, forced, updating = row
        tracked_branch = "%s in %s" % (remote_name, remote)

        assert not forced or not name.startswith("r/")

        if not user.isSystem() \
                or flags.get("trackedbranch_id") != str(trackedbranch_id):
            raise IndexException("""\
The branch '%s' is set up to track '%s' in
  %s
Please don't push it manually to this repository.""" % (name, remote_name, remote))

        assert updating

        if not name.startswith("r/"):
            conflicting = repository.revlist([branch.head_sha1], [new])
            added = repository.revlist([new], [branch.head_sha1])

            if conflicting:
                if forced:
                    if branch.base is None:
                        cursor.executemany("""DELETE FROM reachable
                                                    WHERE branch=%s
                                                      AND commit IN (SELECT id
                                                                       FROM commits
                                                                      WHERE sha1=%s)""",
                                           [(branch.id, sha1) for sha1 in conflicting])
                    else:
                        print "Non-fast-forward update detected; deleting and recreating branch."

                        deleteBranch(db, user, repository, branch.name, old)
                        createBranches(db, user, repository, [(branch.name, new)], flags)

                        return
                else:
                    raise IndexException("""\
Rejecting non-fast-forward update of branch.  To perform the update, you
can delete the branch using
  git push critic :%s
first, and then repeat this push.""" % name)

            cursor.executemany("""INSERT INTO reachable (branch, commit)
                                       SELECT %s, commits.id
                                         FROM commits
                                        WHERE sha1=%s""",
                               [(branch.id, sha1) for sha1 in added])

            new_head = gitutils.Commit.fromSHA1(db, repository, new)

            cursor.execute("UPDATE branches SET head=%s WHERE id=%s",
                           (new_head.getId(db), branch.id))

            output = []

            if conflicting:
                output.append("Pruned %d conflicting commits." % len(conflicting))
            if added:
                output.append("Added %d new commits." % len(added))

            if output:
                print "\n".join(output)

            return
    else:
        tracked_branch = False

    cursor.execute("SELECT id FROM reviews WHERE branch=%s", (branch.id,))
    row = cursor.fetchone()

    is_review = bool(row)

    if is_review:
        if multiple:
            raise IndexException("""\
Refusing to update review in push of multiple refs.  Please push one
review branch at a time.""")

        review_id = row[0]

        cursor.execute("""SELECT id, old_head, old_upstream, new_upstream, uid, branch
                            FROM reviewrebases
                           WHERE review=%s AND new_head IS NULL""",
                       (review_id,))
        row = cursor.fetchone()

        if row:
            if tracked_branch:
                raise IndexException("Refusing to perform a review rebase via an automatic update.")

            rebase_id, old_head_id, old_upstream_id, new_upstream_id, rebaser_id, onto_branch = row

            review = dbutils.Review.fromId(db, review_id)
            rebaser = dbutils.User.fromId(db, rebaser_id)

            if rebaser.id != user.id:
                if user.isSystem():
                    user = rebaser
                else:
                    raise IndexException("""\
This review is currently being rebased by
  %s <%s>
and can't be otherwise updated right now.""" % (rebaser.fullname, rebaser.email))

            old_head = gitutils.Commit.fromId(db, repository, old_head_id)
            old_commitset = log.commitset.CommitSet(review.branch.getCommits(db))

            if old_head.sha1 != old:
                raise IndexException("""\
Unexpected error.  The branch appears to have been updated since your
rebase was prepared.  You need to cancel the rebase via the review
front-page and then try again, and/or report a bug about this error.""")

            if old_upstream_id is not None:
                new_head = gitutils.Commit.fromSHA1(db, repository, new)

                old_upstream = gitutils.Commit.fromId(db, repository, old_upstream_id)

                if new_upstream_id is not None:
                    new_upstream = gitutils.Commit.fromId(db, repository, new_upstream_id)
                else:
                    if len(new_head.parents) != 1:
                        raise IndexException("Invalid rebase: New head can't be a merge commit.")

                    new_upstream = gitutils.Commit.fromSHA1(db, repository, new_head.parents[0])

                    if new_upstream in old_commitset.getTails():
                        old_upstream = new_upstream = None
            else:
                old_upstream = None

            if old_upstream:
                unrelated_move = False

                if not new_upstream.isAncestorOf(new):
                    raise IndexException("""\
Invalid rebase: The new upstream commit you specified when the rebase
was prepared is not an ancestor of the commit now pushed.  You may want
to cancel the rebase via the review front-page, and prepare another one
specifying the correct new upstream commit; or rebase the branch onto
the new upstream specified and then push that instead.""")

                if not old_upstream.isAncestorOf(new_upstream):
                    unrelated_move = True

                equivalent_merge = replayed_rebase = None

                if unrelated_move:
                    replayed_rebase = reviewing.rebase.replayRebase(
                        db, review, user, old_head, old_upstream, new_head,
                        new_upstream, onto_branch)
                else:
                    equivalent_merge = reviewing.rebase.createEquivalentMergeCommit(
                        db, review, user, old_head, old_upstream, new_head,
                        new_upstream, onto_branch)

                new_sha1s = repository.revlist([new_head.sha1], [new_upstream.sha1], '--topo-order')
                rebased_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in new_sha1s]
                reachable_values = [(review.branch.id, sha1) for sha1 in new_sha1s]

                pending_mails = []

                recipients = review.getRecipients(db)
                for to_user in recipients:
                    pending_mails.extend(reviewing.mail.sendReviewRebased(
                            db, user, to_user, recipients, review,
                            new_upstream, rebased_commits, onto_branch))

                print "Rebase performed."

                review.setPerformedRebase(old_head, new_head, old_upstream, new_upstream, user,
                                          equivalent_merge, replayed_rebase)

                if unrelated_move:
                    reviewing.utils.addCommitsToReview(
                        db, user, review, [replayed_rebase],
                        pending_mails=pending_mails,
                        silent_if_empty=set([replayed_rebase]),
                        replayed_rebases={ replayed_rebase: new_head })

                    repository.keepalive(old_head)
                    repository.keepalive(replayed_rebase)

                    cursor.execute("""UPDATE reviewrebases
                                         SET replayed_rebase=%s
                                       WHERE id=%s""",
                                   (replayed_rebase.getId(db), rebase_id))
                else:
                    reviewing.utils.addCommitsToReview(
                        db, user, review, [equivalent_merge], pending_mails=pending_mails,
                        silent_if_empty=set([equivalent_merge]), full_merges=set([equivalent_merge]))

                    repository.keepalive(equivalent_merge)

                    cursor.execute("""UPDATE reviewrebases
                                         SET equivalent_merge=%s
                                       WHERE id=%s""",
                                   (equivalent_merge.getId(db), rebase_id))

                cursor.execute("""UPDATE reviewrebases
                                     SET new_head=%s,
                                         new_upstream=%s
                                   WHERE id=%s""",
                               (new_head.getId(db), new_upstream.getId(db), rebase_id))

                cursor.execute("""INSERT INTO previousreachable (rebase, commit)
                                       SELECT %s, commit
                                         FROM reachable
                                        WHERE branch=%s""",
                               (rebase_id, review.branch.id))
                cursor.execute("DELETE FROM reachable WHERE branch=%s",
                               (review.branch.id,))
                cursor.executemany("""INSERT INTO reachable (branch, commit)
                                           SELECT %s, commits.id
                                             FROM commits
                                            WHERE commits.sha1=%s""",
                                   reachable_values)
                cursor.execute("UPDATE branches SET head=%s WHERE id=%s",
                               (new_head.getId(db), review.branch.id))
            else:
                old_commitset = log.commitset.CommitSet(review.branch.getCommits(db))
                new_sha1s = repository.revlist([new], old_commitset.getTails(), '--topo-order')

                if old_head.sha1 in new_sha1s:
                    raise IndexException("""\
Invalid history rewrite: Old head of the branch reachable from the
pushed ref; no history rewrite performed.  (Cancel the rebase via
the review front-page if you've changed your mind.)""")

                for new_sha1 in new_sha1s:
                    new_head = gitutils.Commit.fromSHA1(db, repository, new_sha1)
                    if new_head.tree == old_head.tree: break
                else:
                    raise IndexException("""\
Invalid history rewrite: The rebase introduced unexpected code changes.
Use git diff between the review branch in Critic's repository and
the rebased local branch to see what those changes are.""")

                rebased_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in repository.revlist([new_head], old_commitset.getTails(), '--topo-order')]
                new_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in repository.revlist([new], [new_head], '--topo-order')]
                reachable_values = [(review.branch.id, sha1) for sha1 in new_sha1s]

                pending_mails = []

                recipients = review.getRecipients(db)
                for to_user in recipients:
                    pending_mails.extend(reviewing.mail.sendReviewRebased(db, user, to_user, recipients, review, None, rebased_commits))

                print "History rewrite performed."

                if new_commits:
                    reviewing.utils.addCommitsToReview(db, user, review, new_commits, pending_mails=pending_mails)
                else:
                    reviewing.mail.sendPendingMails(pending_mails)

                cursor.execute("""UPDATE reviewrebases
                                     SET new_head=%s
                                   WHERE id=%s""",
                               (new_head.getId(db), rebase_id))

                cursor.execute("""INSERT INTO previousreachable (rebase, commit)
                                       SELECT %s, commit
                                         FROM reachable
                                        WHERE branch=%s""",
                               (rebase_id, review.branch.id))
                cursor.execute("DELETE FROM reachable WHERE branch=%s",
                               (review.branch.id,))
                cursor.executemany("""INSERT INTO reachable (branch, commit)
                                           SELECT %s, commits.id
                                             FROM commits
                                            WHERE commits.sha1=%s""",
                                   reachable_values)
                cursor.execute("UPDATE branches SET head=%s WHERE id=%s",
                               (gitutils.Commit.fromSHA1(db, repository, new).getId(db),
                                review.branch.id))

                repository.keepalive(old)

            review.incrementSerial(db)

            return True
        elif old != repository.mergebase([old, new]):
            raise IndexException("Rejecting non-fast-forward update of review branch.")
    elif old != repository.mergebase([old, new]):
        raise IndexException("""\
Rejecting non-fast-forward update of branch.  To perform the update, you
can delete the branch using
  git push critic :%s
first, and then repeat this push.""" % name)

    cursor.execute("SELECT id FROM branches WHERE repository=%s AND base IS NULL ORDER BY id ASC LIMIT 1", (repository.id,))
    root_branch_id = cursor.fetchone()[0]

    def isreachable(sha1):
        if is_review and sha1 == branch.tail_sha1:
            return True
        if base_branch_id:
            cursor.execute("""SELECT 1
                                FROM commits
                                JOIN reachable ON (reachable.commit=commits.id)
                               WHERE commits.sha1=%s
                                 AND reachable.branch IN (%s, %s, %s)""",
                           (sha1, branch.id, base_branch_id, root_branch_id))
        else:
            cursor.execute("""SELECT 1
                                FROM commits
                                JOIN reachable ON (reachable.commit=commits.id)
                               WHERE commits.sha1=%s
                                 AND reachable.branch IN (%s, %s)""",
                           (sha1, branch.id, root_branch_id))
        return cursor.fetchone() is not None

    stack = [new]
    commits = set()
    commit_list = []
    processed = set()

    while stack:
        sha1 = stack.pop()

        if sha1 not in commits and not isreachable(sha1):
            commits.add(sha1)
            commit_list.append(sha1)

            stack.extend([parent_sha1 for parent_sha1 in gitutils.Commit.fromSHA1(db, repository, sha1).parents if parent_sha1 not in processed])

        processed.add(sha1)

    branch = dbutils.Branch.fromName(db, repository, name)
    review = dbutils.Review.fromBranch(db, branch)

    if review:
        if review.state != "open":
            raise IndexException("""\
The review is closed and can't be extended.  You need to reopen it at
%s
before you can add commits to it.""" % review.getURL(db, user, 2))

        all_commits = [gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in reversed(commit_list)]

        tails = CommitSet(all_commits).getTails()

        if old not in tails:
            raise IndexException("""\
Push rejected; would break the review.

It looks like some of the pushed commits are reachable from the
repository's main branch, and thus consequently the commits currently
included in the review are too.

Perhaps you should request a new review of the follow-up commits?""")

        reviewing.utils.addCommitsToReview(db, user, review, all_commits, commitset=commits, tracked_branch=tracked_branch)

    reachable_values = [(branch.id, sha1) for sha1 in reversed(commit_list) if sha1 in commits]

    cursor.executemany("INSERT INTO reachable (branch, commit) SELECT %s, commits.id FROM commits WHERE commits.sha1=%s", reachable_values)
    cursor.execute("UPDATE branches SET head=%s WHERE id=%s", (gitutils.Commit.fromSHA1(db, repository, new).getId(db), branch.id))

    db.commit()

    if configuration.extensions.ENABLED and review:
        extensions.role.processcommits.execute(db, user, review, all_commits,
                                               gitutils.Commit.fromSHA1(db, repository, old),
                                               gitutils.Commit.fromSHA1(db, repository, new),
                                               sys.stdout)
Esempio n. 24
0
File: mail.py Progetto: KurSh/critic
def sendReviewCreated(db, from_user, to_user, recipients, review):
    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.owner.fullname': review.owners[0].fullname,
             'review.branch.name': review.branch.name,
             'review.branch.repository': "%s:%s" % (configuration.base.HOSTNAME, review.repository.path),
             'hr': hr }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


""" % data

    body += """%(review.owner.fullname)s has requested a review of the changes on the branch
  %(review.branch.name)s
in the repository
  %(review.branch.repository)s


""" % data

    all_reviewers = to_user.getPreference(db, "email.newReview.displayReviewers")
    all_watchers = to_user.getPreference(db, "email.newReview.displayWatchers")

    if all_reviewers or all_watchers:
        if all_reviewers:
            if review.reviewers:
                body += "The users assigned to review the changes on the review branch are:\n"

                for reviewer in review.reviewers:
                    body += "  " + reviewer.fullname + "\n"

                body += "\n"
            else:
                body += """No reviewers have been identified for the changes in this review.  This means
the review is currently stuck; it cannot finish unless there are reviewers.

"""

        if all_watchers and review.watchers:
            body += "The following additional users are following the review:\n"

            for watcher in review.watchers:
                body += "  " + watcher.fullname + "\n"

            body += "\n"

        body += "\n"

    if review.description:
        body += """Description:
%s


""" % textutils.reflow(review.description, line_length, indent=2)

    cursor = db.cursor()
    cursor.execute("""SELECT file, SUM(deleted), SUM(inserted)
                        FROM fullreviewuserfiles
                       WHERE review=%s
                         AND assignee=%s
                    GROUP BY file""",
                   (review.id, to_user.id))
    pending_files_lines = cursor.fetchall()

    if pending_files_lines:
        body += renderFiles(db, to_user, review, "These changes were assigned to you:", pending_files_lines, showcommit_link=True)

    all_commits = to_user.getPreference(db, "email.newReview.displayCommits")

    if all_commits:
        body += "The commits requested to be reviewed are:\n\n"

        contextLines = to_user.getPreference(db, "email.newReview.diff.contextLines")
        diffMaxLines = to_user.getPreference(db, "email.newReview.diff.maxLines")

        displayStats = to_user.getPreference(db, "email.newReview.displayStats")
        statsMaxLines = to_user.getPreference(db, "email.newReview.stats.maxLines")

        if contextLines < 0: contextLines = 0

        commits = list(reversed(review.branch.commits))

        if diffMaxLines == 0: diffs = None
        else:
            diffs = {}
            lines = 0

            for commit in commits:
                if len(commit.parents) == 1:
                    cursor.execute("""SELECT id
                                        FROM reviewchangesets
                                        JOIN changesets ON (id=changeset)
                                       WHERE review=%s
                                         AND child=%s""", (review.id, commit.getId(db)))

                    (changeset_id,) = cursor.fetchone()

                    diff = changeset_text.unified(db, changeset_load.loadChangeset(db, review.repository, changeset_id), contextLines)
                    diffs[commit] = diff
                    lines += diff.count("\n")
                    if lines > diffMaxLines:
                        diffs = None
                        break

        if not displayStats or statsMaxLines == 0: stats = None
        else:
            stats = {}
            lines = 0

            for commit in commits:
                commit_stats = review.repository.run("show", "--oneline", "--stat", commit.sha1).split('\n', 1)[1]
                stats[commit] = commit_stats
                lines += commit_stats.count('\n')
                if lines > statsMaxLines:
                    stats = None
                    break

        for index, commit in enumerate(commits):
            if index > 0: body += "\n\n\n"

            body += """Commit: %(sha1)s
Author: %(author.fullname)s <%(author.email)s> at %(author.time)s

%(message)s
""" % { 'sha1': commit.sha1,
        'author.fullname': commit.author.getFullname(db),
        'author.email': commit.author.email,
        'author.time': time.strftime("%Y-%m-%d %H:%M:%S", commit.author.time),
        'message': textutils.reflow(commit.message.strip(), line_length, indent=2) }

            if stats and commit in stats:
                body += "---\n" + stats[commit]

            if diffs and commit in diffs:
                body += "\n" + diffs[commit]

    message_id = generateMessageId()

    cursor.execute("INSERT INTO reviewmessageids (uid, review, messageid) VALUES (%s, %s, %s)",
                   [to_user.id, review.id, message_id])

    return [sendMail(db, review, message_id, from_user, to_user, recipients, generateSubjectLine(db, to_user, review, 'newReview'), body)]
Esempio n. 25
0
def process_request(environ, start_response):
    request_start = time.time()

    critic = api.critic.startSession(for_user=True)
    db = critic.database
    user = None

    try:
        try:
            req = request.Request(db, environ, start_response)

            # Handle static resources very early.  We don't bother with checking
            # for an authenticated user; static resources aren't sensitive, and
            # are referenced from special-case resources like the login page and
            # error messages that, that we want to display even if something
            # went wrong with the authentication.
            if req.path.startswith("static-resource/"):
                return handleStaticResource(req)

            if req.path.startswith("externalauth/"):
                provider_name = req.path[len("externalauth/"):]
                if provider_name in auth.PROVIDERS:
                    provider = auth.PROVIDERS[provider_name]
                    authorize_url = provider.start(db, req)
                    if authorize_url:
                        raise request.Found(authorize_url)

            if req.path.startswith("oauth/"):
                provider_name = req.path[len("oauth/"):]
                if provider_name in auth.PROVIDERS:
                    provider = auth.PROVIDERS[provider_name]
                    if isinstance(provider, auth.OAuthProvider):
                        finishOAuth(db, req, provider)

            auth.checkSession(db, req)
            auth.AccessControl.accessHTTP(db, req)

            user = req.user
            user.loadPreferences(db)

            if user.status == 'retired':
                # If a retired user accesses the system, change the status back
                # to 'current' again.
                with db.updating_cursor("users") as cursor:
                    cursor.execute("""UPDATE users
                                         SET status='current'
                                       WHERE id=%s""",
                                   (user.id,))
                user.status = 'current'

            if not user.getPreference(db, "debug.profiling.databaseQueries"):
                db.disableProfiling()

            original_path = req.path

            if not req.path:
                if user.isAnonymous():
                    location = "tutorial"
                else:
                    location = user.getPreference(db, "defaultPage")

                if req.query:
                    location += "?" + req.query

                raise request.MovedTemporarily(location)

            if req.path == "redirect":
                target = req.getParameter("target", "/")
                raise request.SeeOther(target)

            if req.path == "findreview":
                # This raises either DisplayMessage or MovedTemporarily.
                findreview(req, db)

            # Require a .git suffix on HTTP(S) repository URLs unless the user-
            # agent starts with "git/" (as Git's normally does.)
            #
            # Our objectives are:
            #
            # 1) Not to require Git's user-agent to be its default value, since
            #    the user might have to override it to get through firewalls.
            # 2) Never to send regular user requests to 'git http-backend' by
            #    mistake.
            #
            # This is a compromise.

            if req.getRequestHeader("User-Agent", "").startswith("git/"):
                suffix = None
            else:
                suffix = ".git"

            if handleRepositoryPath(db, req, user, suffix):
                db = None
                return []

            # Extension "page" roles.  Prefixing a path with "!/" bypasses all
            # extensions.
            #
            # Also bypass extensions if the user is anonymous unless general
            # anonymous access is allowed.  If it's not and the user is still
            # anonymous, access was allowed because of a path-based exception,
            # which was not intended to allow access to an extension.
            if req.path.startswith("!/"):
                req.path = req.path[2:]
            elif configuration.extensions.ENABLED:
                handled = extensions.role.page.execute(db, req, user)
                if isinstance(handled, basestring):
                    req.start()
                    return [handled]

            if req.path.startswith("r/"):
                req.updateQuery({ "id": [req.path[2:]] })
                req.path = "showreview"

            if configuration.extensions.ENABLED:
                match = RE_EXTENSION_RESOURCE.match(req.path)
                if match:
                    content_type, resource = extensions.resource.get(req, db, user, match.group(1))
                    if resource:
                        req.setContentType(content_type)
                        if content_type.startswith("image/"):
                            req.addResponseHeader("Cache-Control", "max-age=3600")
                        req.start()
                        return [resource]
                    else:
                        req.setStatus(404)
                        req.start()
                        return []

            if req.path.startswith("download/"):
                return handleDownload(db, req, user)

            if req.path == "api" or req.path.startswith("api/"):
                try:
                    result = jsonapi.handleRequest(critic, req)
                except jsonapi.Error as error:
                    req.setStatus(error.http_status)
                    result = { "error": { "title": error.title,
                                          "message": error.message }}
                else:
                    req.setStatus(200)

                accept_header = req.getRequestHeader("Accept")
                if accept_header == "application/vnd.api+json":
                    default_indent = None
                else:
                    default_indent = 2
                indent = req.getParameter("indent", default_indent, filter=int)
                if indent == 0:
                    # json.encode(..., indent=0) still gives line-breaks, just
                    # no indentation.  This is not so useful, so set indent to
                    # None instead, which disables formatting entirely.
                    indent = None

                req.setContentType("application/vnd.api+json")
                req.start()
                return [json_encode(result, indent=indent)]

            operationfn = OPERATIONS.get(req.path)
            if operationfn:
                result = operationfn(req, db, user)

                if isinstance(result, (OperationResult, OperationError)):
                    req.setContentType("text/json")

                    if isinstance(result, OperationResult):
                        if db.profiling:
                            result.set("__profiling__", formatDBProfiling(db))
                            result.set("__time__", time.time() - request_start)
                elif not req.hasContentType():
                    req.setContentType("text/plain")

                req.start()

                if isinstance(result, unicode):
                    return [result.encode("utf8")]
                else:
                    return [str(result)]

            impersonate_user = user

            if not user.isAnonymous():
                user_parameter = req.getParameter("user", None)
                if user_parameter:
                    impersonate_user = dbutils.User.fromName(db, user_parameter)

            while True:
                pagefn = PAGES.get(req.path)
                if pagefn:
                    try:
                        result = pagefn(req, db, impersonate_user)

                        if db.profiling and not (isinstance(result, str) or
                                                 isinstance(result, Document)):
                            source = ""
                            for fragment in result:
                                source += fragment
                            result = source

                        if isinstance(result, page.utils.ResponseBody):
                            req.setContentType(result.content_type)
                            req.start()
                            return [result.data]

                        if isinstance(result, str) or isinstance(result, Document):
                            req.setContentType("text/html")
                            req.start()
                            result = str(result)
                            result += ("<!-- total request time: %.2f ms -->"
                                       % ((time.time() - request_start) * 1000))
                            if db.profiling:
                                result += ("<!--\n\n%s\n\n -->"
                                           % formatDBProfiling(db))
                            return [result]

                        result = WrappedResult(db, req, user, result)

                        req.setContentType("text/html")
                        req.start()

                        # Prevent the finally clause below from closing the
                        # connection.  WrappedResult does it instead.
                        db = None

                        return result
                    except gitutils.NoSuchRepository as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)
                    except gitutils.GitReferenceError as error:
                        if error.ref:
                            raise page.utils.DisplayMessage(
                                title="Specified ref not found",
                                body=("There is no ref named \"%s\" in %s."
                                      % (error.ref, error.repository)))
                        elif error.sha1:
                            raise page.utils.DisplayMessage(
                                title="SHA-1 not found",
                                body=error.message)
                        else:
                            raise
                    except dbutils.NoSuchUser as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)
                    except dbutils.NoSuchReview as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)

                if "/" in req.path:
                    repository_name, _, rest = req.path.partition("/")
                    repository = gitutils.Repository.fromName(db, repository_name)
                    if repository:
                        req.path = rest
                else:
                    repository = None

                def revparsePlain(item):
                    try: return gitutils.getTaggedCommit(repository, repository.revparse(item))
                    except: raise
                revparse = revparsePlain

                if repository is None:
                    review_id = req.getParameter("review", None, filter=int)

                    if review_id:
                        cursor = db.cursor()
                        cursor.execute("""SELECT repository
                                            FROM branches
                                            JOIN reviews ON (reviews.branch=branches.id)
                                           WHERE reviews.id=%s""",
                                       (review_id,))
                        row = cursor.fetchone()
                        if row:
                            repository = gitutils.Repository.fromId(db, row[0])
                            def revparseWithReview(item):
                                if re.match("^[0-9a-f]+$", item):
                                    cursor.execute("""SELECT sha1
                                                        FROM commits
                                                        JOIN changesets ON (changesets.child=commits.id)
                                                        JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                                       WHERE reviewchangesets.review=%s
                                                         AND commits.sha1 LIKE %s""",
                                                   (review_id, item + "%"))
                                    row = cursor.fetchone()
                                    if row: return row[0]
                                    else: return revparsePlain(item)
                            revparse = revparseWithReview

                if repository is None:
                    repository = user.getDefaultRepository(db)

                    if gitutils.re_sha1.match(req.path):
                        if repository and not repository.iscommit(req.path):
                            repository = None

                        if not repository:
                            try:
                                repository = gitutils.Repository.fromSHA1(db, req.path)
                            except gitutils.GitReferenceError:
                                repository = None

                if repository:
                    try:
                        items = filter(None, map(revparse, req.path.split("..")))
                        updated_query = {}

                        if len(items) == 1:
                            updated_query["repository"] = [repository.name]
                            updated_query["sha1"] = [items[0]]
                        elif len(items) == 2:
                            updated_query["repository"] = [repository.name]
                            updated_query["from"] = [items[0]]
                            updated_query["to"] = [items[1]]

                        if updated_query:
                            req.updateQuery(updated_query)
                            req.path = "showcommit"
                            continue
                    except gitutils.GitReferenceError:
                        pass

                break

            raise page.utils.DisplayMessage(
                title="Not found!",
                body="Page not handled: /%s" % original_path,
                status=404)
        except GeneratorExit:
            raise
        except auth.AccessDenied as error:
            return handleDisplayMessage(
                db, req, request.DisplayMessage(
                    title="Access denied",
                    body=error.message,
                    status=403))
        except request.HTTPResponse as response:
            return response.execute(db, req)
        except request.MissingWSGIRemoteUser as error:
            return handleMissingWSGIRemoteUser(db, req)
        except page.utils.DisplayMessage as message:
            return handleDisplayMessage(db, req, message)
        except page.utils.DisplayFormattedText as formatted_text:
            return handleDisplayFormattedText(db, req, formatted_text)
        except Exception:
            # crash might be psycopg2.ProgrammingError so rollback to avoid
            # "InternalError: current transaction is aborted" inside handleException()
            if db and db.closed():
                db = None
            elif db:
                db.rollback()

            error_title, error_body = handleException(db, req, user)
            error_body = reflow("\n\n".join(error_body))
            error_message = "\n".join([error_title,
                                       "=" * len(error_title),
                                       "",
                                       error_body])

            assert not req.isStarted()

            req.setStatus(500)
            req.setContentType("text/plain")
            req.start()

            return [error_message]
    finally:
        if db:
            db.close()
Esempio n. 26
0
File: mail.py Progetto: KurSh/critic
def sendReviewPlaceholder(db, to_user, review):
    # First check if the user has activated email sending at all.
    if not to_user.getPreference(db, "email.activated"): return []

    line_length = to_user.getPreference(db, "email.lineLength")
    hr = "-" * line_length

    why = "This message is sent to you when you become associated with a review after the review was initially requested.  It is then sent instead of the regular \"New Review\" message, for the purpose of using as the reference/in-reply-to message for other messages sent about this review."

    data = { 'review.id': review.id,
             'review.url': review.getURL(db, to_user, 2),
             'review.owner.fullname': review.owners[0].fullname,
             'review.branch.name': review.branch.name,
             'review.branch.repository': "%s:%s" % (configuration.base.HOSTNAME, review.repository.path),
             'hr': hr,
             'why': textutils.reflow(why, line_length) }

    body = """%(hr)s
This is an automatic message generated by the review at:
%(review.url)s
%(hr)s


%(why)s


%(hr)s


""" % data

    body += """%(review.owner.fullname)s has requested a review of the changes on the branch
  %(review.branch.name)s
in the repository
  %(review.branch.repository)s


""" % data

    all_reviewers = to_user.getPreference(db, "email.newReview.displayReviewers")
    all_watchers = to_user.getPreference(db, "email.newReview.displayWatchers")

    if all_reviewers or all_watchers:
        if all_reviewers:
            if review.reviewers:
                body += "The users assigned to review the changes on the review branch are:\n"

                for reviewer in review.reviewers:
                    body += "  " + reviewer.fullname + "\n"

                body += "\n"
            else:
                body += """No reviewers have been identified for the changes in this review.  This means
the review is currently stuck; it cannot finish unless there are reviewers.

"""

        if all_watchers and review.watchers:
            body += "The following additional users are following the review:\n"

            for watcher in review.watchers:
                body += "  " + watcher.fullname + "\n"

            body += "\n"

        body += "\n"

    if review.description:
        body += """Description:
%s


""" % textutils.reflow(review.description, line_length, indent=2)

    message_id = generateMessageId()

    cursor = db.cursor()

    cursor.execute("INSERT INTO reviewmessageids (uid, review, messageid) VALUES (%s, %s, %s)",
                   [to_user.id, review.id, message_id])

    return [sendMail(db, review, message_id, review.owners[0], to_user, [to_user], generateSubjectLine(db, to_user, review, "newishReview"), body)]