Example #1
0
 def searchOrigin(changeset_id, sha1, search_space, first_line, last_line):
     try:
         while sha1 not in processed:
             processed.add(sha1)
             changeset_id, next_sha1 = search_space[sha1]
             changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))
             if len(changeset.child.parents) > 1: break
             verdict, next_first_line, next_last_line = updateCommentChain(first_line, last_line, changeset.files[0].chunks, forward)
             if verdict == "modified": break
             sha1 = next_sha1
             first_line = next_first_line
             last_line = next_last_line
     except:
         pass
     return changeset_id, sha1, first_line, last_line
Example #2
0
 def searchOrigin(changeset_id, sha1, search_space, first_line,
                  last_line):
     try:
         while sha1 not in processed:
             processed.add(sha1)
             changeset_id, next_sha1 = search_space[sha1]
             changeset = changeset_load.loadChangeset(
                 db,
                 review.repository,
                 changeset_id,
                 filtered_file_ids=set([file_id]))
             if len(changeset.child.parents) > 1: break
             verdict, next_first_line, next_last_line = updateCommentChain(
                 first_line, last_line, changeset.files[0].chunks,
                 forward)
             if verdict == "modified": break
             sha1 = next_sha1
             first_line = next_first_line
             last_line = next_last_line
     except:
         pass
     return changeset_id, sha1, first_line, last_line
Example #3
0
File: mail.py Project: 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)]
Example #4
0
File: mail.py Project: 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)]
Example #5
0
def createCommentChain(db,
                       user,
                       review,
                       chain_type,
                       commit_id=None,
                       origin=None,
                       file_id=None,
                       parent_id=None,
                       child_id=None,
                       old_sha1=None,
                       new_sha1=None,
                       offset=None,
                       count=None):
    if chain_type == "issue" and review.state != "open":
        raise OperationFailure(
            code="reviewclosed",
            title="Review is closed!",
            message=
            "You need to reopen the review before you can raise new issues.")

    cursor = db.cursor()

    if file_id is not None and (parent_id == child_id or parent_id is None):
        cursor.execute(
            """SELECT 1
                            FROM reviewchangesets
                            JOIN fileversions USING (changeset)
                           WHERE reviewchangesets.review=%s
                             AND fileversions.file=%s
                             AND fileversions.old_sha1!='0000000000000000000000000000000000000000'
                             AND fileversions.new_sha1!='0000000000000000000000000000000000000000'""",
            (review.id, file_id))

        if cursor.fetchone():
            cursor.execute(
                """SELECT parent, child
                                FROM changesets
                                JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                JOIN fileversions ON (fileversions.changeset=changesets.id)
                               WHERE fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                (file_id, new_sha1))

            rows = cursor.fetchall()

            if not rows:
                cursor.execute(
                    """SELECT parent, child
                                    FROM changesets
                                    JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                    JOIN fileversions ON (fileversions.changeset=changesets.id)
                                   WHERE fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                    (file_id, new_sha1))

                rows = cursor.fetchall()

            parent = child = None

            for row_parent_id, row_child_id in rows:
                if row_child_id == child_id:
                    parent = gitutils.Commit.fromId(db, review.repository,
                                                    row_parent_id)
                    child = gitutils.Commit.fromId(db, review.repository,
                                                   row_child_id)
                    break
                elif row_parent_id == child_id and parent is None:
                    parent = gitutils.Commit.fromId(db, review.repository,
                                                    row_parent_id)
                    child = gitutils.Commit.fromId(db, review.repository,
                                                   row_child_id)

            if parent and child:
                url = "/%s/%s..%s?review=%d&file=%d" % (
                    review.repository.name, parent.sha1[:8], child.sha1[:8],
                    review.id, file_id)
                link = (
                    "<p>The link below goes to a diff that can be use to create the comment:</p>"
                    + "<p style='padding-left: 2em'><a href='%s'>%s%s</a></p>"
                ) % (url, dbutils.getURLPrefix(db), url)
            else:
                link = ""

            raise OperationFailure(
                code="notsupported",
                title="File changed in review",
                message=
                ("<p>Due to limitations in the code used to create comments, "
                 +
                 "it's only possible to create comments via a diff view if " +
                 "the commented file has been changed in the review.</p>" +
                 link),
                is_html=True)

        cursor.execute(
            """INSERT INTO commentchains (review, uid, type, file, first_commit, last_commit)
                               VALUES (%s, %s, %s, %s, %s, %s)
                            RETURNING id""",
            (review.id, user.id, chain_type, file_id, child_id, child_id))
        chain_id = cursor.fetchone()[0]

        cursor.execute(
            """INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line)
                               VALUES (%s, %s, %s, %s, %s, %s)""",
            (chain_id, user.id, child_id, new_sha1, offset,
             offset + count - 1))
    elif file_id is not None:
        parents_returned = set()

        def getFileParent(new_sha1):
            cursor.execute(
                """SELECT changesets.id, fileversions.old_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                [review.id, file_id, new_sha1])
            try:
                changeset_id, old_sha1 = cursor.fetchone()
                if old_sha1 in parents_returned: return None, None
                parents_returned.add(old_sha1)
                return changeset_id, old_sha1
            except:
                return None, None

        children_returned = set()

        def getFileChild(old_sha1):
            cursor.execute(
                """SELECT changesets.id, fileversions.new_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.old_sha1=%s""",
                [review.id, file_id, old_sha1])
            try:
                changeset_id, new_sha1 = cursor.fetchone()
                if new_sha1 in children_returned: return None, None
                children_returned.add(new_sha1)
                return changeset_id, new_sha1
            except:
                return None, None

        cursor.execute(
            """SELECT changesets.id
                            FROM changesets, reviewchangesets, fileversions
                           WHERE reviewchangesets.review=%s
                             AND reviewchangesets.changeset=changesets.id
                             AND changesets.child=%s
                             AND fileversions.changeset=changesets.id
                             AND fileversions.file=%s
                             AND fileversions.old_sha1=%s
                             AND fileversions.new_sha1=%s""",
            [review.id, child_id, file_id, old_sha1, new_sha1])

        row = cursor.fetchone()

        if not row:
            if origin == "old":
                cursor.execute(
                    """SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                    [review.id, file_id, old_sha1])
            else:
                cursor.execute(
                    """SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.new_sha1=%s""",
                    [review.id, file_id, new_sha1])

            row = cursor.fetchone()

        primary_changeset_id = row[0]

        sha1s_older = {}
        sha1s_newer = {old_sha1: (primary_changeset_id, new_sha1)}

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileParent(sha1)
            if changeset_id:
                sha1s_older[sha1] = changeset_id, next_sha1
                sha1s_newer[next_sha1] = changeset_id, sha1
                sha1 = next_sha1
            else:
                break

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileChild(sha1)
            if changeset_id:
                sha1s_newer[sha1] = changeset_id, next_sha1
                sha1 = next_sha1
            else:
                break

        commentchainlines_values = []
        processed = set()

        def searchOrigin(changeset_id, sha1, search_space, first_line,
                         last_line):
            try:
                while sha1 not in processed:
                    processed.add(sha1)
                    changeset_id, next_sha1 = search_space[sha1]
                    changeset = changeset_load.loadChangeset(
                        db,
                        review.repository,
                        changeset_id,
                        filtered_file_ids=set([file_id]))
                    if len(changeset.child.parents) > 1: break
                    verdict, next_first_line, next_last_line = updateCommentChain(
                        first_line, last_line, changeset.files[0].chunks,
                        forward)
                    if verdict == "modified": break
                    sha1 = next_sha1
                    first_line = next_first_line
                    last_line = next_last_line
            except:
                pass
            return changeset_id, sha1, first_line, last_line

        first_line = offset
        last_line = offset + count - 1

        if origin == 'old':
            changeset_id, sha1, first_line, last_line = searchOrigin(
                primary_changeset_id, old_sha1, sha1s_older, first_line,
                last_line)
            commit_id = diff.Changeset.fromId(db, review.repository,
                                              changeset_id).parent.id
        else:
            changeset_id, sha1, first_line, last_line = searchOrigin(
                primary_changeset_id, new_sha1, sha1s_older, first_line,
                last_line)
            commit_id = diff.Changeset.fromId(db, review.repository,
                                              changeset_id).child.id

        commentchainlines_values.append(
            (user.id, commit_id, sha1, first_line, last_line))
        processed = set()
        processed.add(sha1)

        while sha1 in sha1s_newer:
            changeset_id, sha1 = sha1s_newer[sha1]

            if sha1 in processed: break
            else: processed.add(sha1)

            changeset = changeset_load.loadChangeset(db,
                                                     review.repository,
                                                     changeset_id,
                                                     filtered_file_ids=set(
                                                         [file_id]))

            if len(changeset.child.parents) != 1:
                chunks = diff.parse.parseDifferences(
                    review.repository,
                    from_commit=changeset.parent,
                    to_commit=changeset.child,
                    selected_path=dbutils.describe_file(db, file_id)).chunks
            else:
                chunks = changeset.files[0].chunks

            verdict, first_line, last_line = updateCommentChain(
                first_line, last_line, chunks)

            if verdict == "transfer":
                commentchainlines_values.append(
                    (user.id, changeset.child.getId(db), sha1, first_line,
                     last_line))
            else:
                break

        cursor.execute(
            "INSERT INTO commentchains (review, uid, type, origin, file, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id",
            [
                review.id, user.id, chain_type, origin, file_id, parent_id,
                child_id
            ])
        chain_id = cursor.fetchone()[0]

        try:
            cursor.executemany(
                "INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)",
                [(chain_id, ) + values for values in commentchainlines_values])
        except:
            raise Exception, repr(commentchainlines_values)
    elif commit_id is not None:
        commit = gitutils.Commit.fromId(db, review.repository, commit_id)

        cursor.execute(
            "INSERT INTO commentchains (review, uid, type, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s) RETURNING id",
            [review.id, user.id, chain_type, commit_id, commit_id])
        chain_id = cursor.fetchone()[0]

        cursor.execute(
            "INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)",
            (chain_id, user.id, commit_id, commit.sha1, offset,
             offset + count - 1))
    else:
        cursor.execute(
            "INSERT INTO commentchains (review, uid, type) VALUES (%s, %s, %s) RETURNING id",
            [review.id, user.id, chain_type])
        chain_id = cursor.fetchone()[0]

    commentchainusers = set([user.id] + map(int, review.owners))

    if file_id is not None:
        filters = Filters()
        filters.load(db, review=review)

        for user_id in filters.listUsers(db, file_id):
            commentchainusers.add(user_id)

    cursor.executemany(
        "INSERT INTO commentchainusers (chain, uid) VALUES (%s, %s)",
        [(chain_id, user_id) for user_id in commentchainusers])

    return chain_id
Example #6
0
def createCommentChain(db, user, review, chain_type, commit_id=None, origin=None, file_id=None, parent_id=None, child_id=None, old_sha1=None, new_sha1=None, offset=None, count=None):
    if chain_type == "issue" and review.state != "open":
        raise OperationFailure(code="reviewclosed",
                               title="Review is closed!",
                               message="You need to reopen the review before you can raise new issues.")

    cursor = db.cursor()

    if file_id is not None and (parent_id == child_id or parent_id is None):
        cursor.execute("""SELECT 1
                            FROM reviewchangesets
                            JOIN fileversions USING (changeset)
                           WHERE reviewchangesets.review=%s
                             AND fileversions.file=%s
                             AND fileversions.old_sha1!='0000000000000000000000000000000000000000'
                             AND fileversions.new_sha1!='0000000000000000000000000000000000000000'""",
                       (review.id, file_id))

        if cursor.fetchone():
            cursor.execute("""SELECT parent, child
                                FROM changesets
                                JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                JOIN fileversions ON (fileversions.changeset=changesets.id)
                               WHERE fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                           (file_id, new_sha1))

            rows = cursor.fetchall()

            if not rows:
                cursor.execute("""SELECT parent, child
                                    FROM changesets
                                    JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                    JOIN fileversions ON (fileversions.changeset=changesets.id)
                                   WHERE fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                               (file_id, new_sha1))

                rows = cursor.fetchall()

            parent = child = None

            for row_parent_id, row_child_id in rows:
                if row_child_id == child_id:
                    parent = gitutils.Commit.fromId(db, review.repository, row_parent_id)
                    child = gitutils.Commit.fromId(db, review.repository, row_child_id)
                    break
                elif row_parent_id == child_id and parent is None:
                    parent = gitutils.Commit.fromId(db, review.repository, row_parent_id)
                    child = gitutils.Commit.fromId(db, review.repository, row_child_id)

            if parent and child:
                url = "/%s/%s..%s?review=%d&file=%d" % (review.repository.name, parent.sha1[:8], child.sha1[:8], review.id, file_id)
                link = ("<p>The link below goes to a diff that can be use to create the comment:</p>" +
                        "<p style='padding-left: 2em'><a href='%s'>%s%s</a></p>") % (url, dbutils.getURLPrefix(db), url)
            else:
                link = ""

            raise OperationFailure(code="notsupported",
                                   title="File changed in review",
                                   message=("<p>Due to limitations in the code used to create comments, " +
                                            "it's only possible to create comments via a diff view if " +
                                            "the commented file has been changed in the review.</p>" +
                                            link),
                                   is_html=True)

        cursor.execute("""INSERT INTO commentchains (review, uid, type, file, first_commit, last_commit)
                               VALUES (%s, %s, %s, %s, %s, %s)
                            RETURNING id""",
                       (review.id, user.id, chain_type, file_id, child_id, child_id))
        chain_id = cursor.fetchone()[0]

        cursor.execute("""INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line)
                               VALUES (%s, %s, %s, %s, %s, %s)""",
                       (chain_id, user.id, child_id, new_sha1, offset, offset + count - 1))
    elif file_id is not None:
        parents_returned = set()

        def getFileParent(new_sha1):
            cursor.execute("""SELECT changesets.id, fileversions.old_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                           [review.id, file_id, new_sha1])
            try:
                changeset_id, old_sha1 = cursor.fetchone()
                if old_sha1 in parents_returned: return None, None
                parents_returned.add(old_sha1)
                return changeset_id, old_sha1
            except:
                return None, None

        children_returned = set()

        def getFileChild(old_sha1):
            cursor.execute("""SELECT changesets.id, fileversions.new_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.old_sha1=%s""",
                           [review.id, file_id, old_sha1])
            try:
                changeset_id, new_sha1 = cursor.fetchone()
                if new_sha1 in children_returned: return None, None
                children_returned.add(new_sha1)
                return changeset_id, new_sha1
            except:
                return None, None

        cursor.execute("""SELECT changesets.id
                            FROM changesets, reviewchangesets, fileversions
                           WHERE reviewchangesets.review=%s
                             AND reviewchangesets.changeset=changesets.id
                             AND changesets.child=%s
                             AND fileversions.changeset=changesets.id
                             AND fileversions.file=%s
                             AND fileversions.old_sha1=%s
                             AND fileversions.new_sha1=%s""",
                       [review.id, child_id, file_id, old_sha1, new_sha1])

        row = cursor.fetchone()

        if not row:
            if origin == "old":
                cursor.execute("""SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                               [review.id, file_id, old_sha1])
            else:
                cursor.execute("""SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.new_sha1=%s""",
                               [review.id, file_id, new_sha1])

            row = cursor.fetchone()

        primary_changeset_id = row[0]

        sha1s_older = { }
        sha1s_newer = { old_sha1: (primary_changeset_id, new_sha1) }

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileParent(sha1)
            if changeset_id:
                sha1s_older[sha1] = changeset_id, next_sha1
                sha1s_newer[next_sha1] = changeset_id, sha1
                sha1 = next_sha1
            else:
                break

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileChild(sha1)
            if changeset_id:
                sha1s_newer[sha1] = changeset_id, next_sha1
                sha1 = next_sha1
            else:
                break

        commentchainlines_values = []
        processed = set()

        def searchOrigin(changeset_id, sha1, search_space, first_line, last_line):
            try:
                while sha1 not in processed:
                    processed.add(sha1)
                    changeset_id, next_sha1 = search_space[sha1]
                    changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))
                    if len(changeset.child.parents) > 1: break
                    verdict, next_first_line, next_last_line = updateCommentChain(first_line, last_line, changeset.files[0].chunks, forward)
                    if verdict == "modified": break
                    sha1 = next_sha1
                    first_line = next_first_line
                    last_line = next_last_line
            except:
                pass
            return changeset_id, sha1, first_line, last_line

        first_line = offset
        last_line = offset + count - 1

        if origin == 'old':
            changeset_id, sha1, first_line, last_line = searchOrigin(primary_changeset_id, old_sha1, sha1s_older, first_line, last_line)
            commit_id = diff.Changeset.fromId(db, review.repository, changeset_id).parent.id
        else:
            changeset_id, sha1, first_line, last_line = searchOrigin(primary_changeset_id, new_sha1, sha1s_older, first_line, last_line)
            commit_id = diff.Changeset.fromId(db, review.repository, changeset_id).child.id

        commentchainlines_values.append((user.id, commit_id, sha1, first_line, last_line))
        processed = set()
        processed.add(sha1)

        while sha1 in sha1s_newer:
            changeset_id, sha1 = sha1s_newer[sha1]

            if sha1 in processed: break
            else: processed.add(sha1)

            changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))

            if len(changeset.child.parents) != 1:
                chunks = diff.parse.parseDifferences(review.repository, from_commit=changeset.parent, to_commit=changeset.child, selected_path=dbutils.describe_file(db, file_id)).chunks
            else:
                chunks = changeset.files[0].chunks

            verdict, first_line, last_line = updateCommentChain(first_line, last_line, chunks)

            if verdict == "transfer":
                commentchainlines_values.append((user.id, changeset.child.getId(db), sha1, first_line, last_line))
            else:
                break

        cursor.execute("INSERT INTO commentchains (review, uid, type, origin, file, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, origin, file_id, parent_id, child_id])
        chain_id = cursor.fetchone()[0]

        try: cursor.executemany("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", [(chain_id,) + values for values in commentchainlines_values])
        except: raise Exception, repr(commentchainlines_values)
    elif commit_id is not None:
        commit = gitutils.Commit.fromId(db, review.repository, commit_id)

        cursor.execute("INSERT INTO commentchains (review, uid, type, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, commit_id, commit_id])
        chain_id = cursor.fetchone()[0]

        cursor.execute("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", (chain_id, user.id, commit_id, commit.sha1, offset, offset + count - 1))
    else:
        cursor.execute("INSERT INTO commentchains (review, uid, type) VALUES (%s, %s, %s) RETURNING id", [review.id, user.id, chain_type])
        chain_id = cursor.fetchone()[0]

    commentchainusers = set([user.id] + map(int, review.owners))

    if file_id is not None:
        filters = Filters()
        filters.load(db, review=review)

        for user_id in filters.listUsers(db, file_id):
            commentchainusers.add(user_id)

    cursor.executemany("INSERT INTO commentchainusers (chain, uid) VALUES (%s, %s)", [(chain_id, user_id) for user_id in commentchainusers])

    return chain_id
Example #7
0
def createCommentChain(db, user, review, chain_type, commit_id=None, origin=None, file_id=None, parent_id=None, child_id=None, old_sha1=None, new_sha1=None, offset=None, count=None):
    if chain_type == "issue" and review.state != "open":
        raise Exception, "review not open; can't raise issue"

    cursor = db.cursor()

    if file_id is not None and (parent_id == child_id or parent_id is None):
        cursor.execute("""SELECT 1
                            FROM reviewchangesets
                            JOIN fileversions USING (changeset)
                           WHERE reviewchangesets.review=%s
                             AND fileversions.file=%s
                             AND fileversions.old_sha1!='0000000000000000000000000000000000000000'
                             AND fileversions.new_sha1!='0000000000000000000000000000000000000000'""",
                       (review.id, file_id))

        if cursor.fetchone(): raise Exception, "file changed in review"

        cursor.execute("INSERT INTO commentchains (review, uid, type, file, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, file_id, child_id, child_id])
        chain_id = cursor.fetchone()[0]

        cursor.execute("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", (chain_id, user.id, child_id, new_sha1, offset, offset + count - 1))
    elif file_id is not None:
        parents_returned = set()

        def getFileParent(new_sha1):
            cursor.execute("""SELECT changesets.id, fileversions.old_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                           [review.id, file_id, new_sha1])
            try:
                changeset_id, old_sha1 = cursor.fetchone()
                if old_sha1 in parents_returned: return None, None
                parents_returned.add(old_sha1)
                return changeset_id, old_sha1
            except:
                return None, None

        children_returned = set()

        def getFileChild(old_sha1):
            cursor.execute("""SELECT changesets.id, fileversions.new_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.old_sha1=%s""",
                           [review.id, file_id, old_sha1])
            try:
                changeset_id, new_sha1 = cursor.fetchone()
                if new_sha1 in children_returned: return None, None
                children_returned.add(new_sha1)
                return changeset_id, new_sha1
            except:
                return None, None

        cursor.execute("""SELECT changesets.id
                            FROM changesets, reviewchangesets, fileversions
                           WHERE reviewchangesets.review=%s
                             AND reviewchangesets.changeset=changesets.id
                             AND changesets.child=%s
                             AND fileversions.changeset=changesets.id
                             AND fileversions.file=%s
                             AND fileversions.old_sha1=%s
                             AND fileversions.new_sha1=%s""",
                       [review.id, child_id, file_id, old_sha1, new_sha1])

        row = cursor.fetchone()

        if not row:
            if origin == "old":
                cursor.execute("""SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                               [review.id, file_id, old_sha1])
            else:
                cursor.execute("""SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.new_sha1=%s""",
                               [review.id, file_id, new_sha1])

            row = cursor.fetchone()

        primary_changeset_id = row[0]

        sha1s_older = { }
        sha1s_newer = { old_sha1: (primary_changeset_id, new_sha1) }

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileParent(sha1)
            if changeset_id:
                sha1s_older[sha1] = changeset_id, next_sha1
                sha1s_newer[next_sha1] = changeset_id, sha1
                sha1 = next_sha1
            else:
                break

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileChild(sha1)
            if changeset_id:
                sha1s_newer[sha1] = changeset_id, next_sha1
                sha1 = next_sha1
            else:
                break

        commentchainlines_values = []
        processed = set()

        def searchOrigin(changeset_id, sha1, search_space, first_line, last_line):
            try:
                while sha1 not in processed:
                    processed.add(sha1)
                    changeset_id, next_sha1 = search_space[sha1]
                    changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))
                    if len(changeset.child.parents) > 1: break
                    verdict, next_first_line, next_last_line = updateCommentChain(first_line, last_line, changeset.files[0].chunks, forward)
                    if verdict == "modified": break
                    sha1 = next_sha1
                    first_line = next_first_line
                    last_line = next_last_line
            except:
                pass
            return changeset_id, sha1, first_line, last_line

        first_line = offset
        last_line = offset + count - 1

        if origin == 'old':
            changeset_id, sha1, first_line, last_line = searchOrigin(primary_changeset_id, old_sha1, sha1s_older, first_line, last_line)
            commit_id = diff.Changeset.fromId(db, review.repository, changeset_id).parent.id
        else:
            changeset_id, sha1, first_line, last_line = searchOrigin(primary_changeset_id, new_sha1, sha1s_older, first_line, last_line)
            commit_id = diff.Changeset.fromId(db, review.repository, changeset_id).child.id

        commentchainlines_values.append((user.id, commit_id, sha1, first_line, last_line))
        processed = set()
        processed.add(sha1)

        while sha1 in sha1s_newer:
            changeset_id, sha1 = sha1s_newer[sha1]

            if sha1 in processed: break
            else: processed.add(sha1)

            changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))

            if len(changeset.child.parents) != 1:
                chunks = diff.parse.parseDifferences(review.repository, from_commit=changeset.parent, to_commit=changeset.child, selected_path=dbutils.describe_file(db, file_id)).chunks
            else:
                chunks = changeset.files[0].chunks

            verdict, first_line, last_line = updateCommentChain(first_line, last_line, chunks)

            if verdict == "transfer":
                commentchainlines_values.append((user.id, changeset.child.getId(db), sha1, first_line, last_line))
            else:
                break

        cursor.execute("INSERT INTO commentchains (review, uid, type, origin, file, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, origin, file_id, parent_id, child_id])
        chain_id = cursor.fetchone()[0]

        try: cursor.executemany("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", [(chain_id,) + values for values in commentchainlines_values])
        except: raise Exception, repr(commentchainlines_values)
    elif commit_id is not None:
        commit = gitutils.Commit.fromId(db, review.repository, commit_id)

        cursor.execute("INSERT INTO commentchains (review, uid, type, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, commit_id, commit_id])
        chain_id = cursor.fetchone()[0]

        cursor.execute("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", (chain_id, user.id, commit_id, commit.sha1, offset, offset + count - 1))
    else:
        cursor.execute("INSERT INTO commentchains (review, uid, type) VALUES (%s, %s, %s) RETURNING id", [review.id, user.id, chain_type])
        chain_id = cursor.fetchone()[0]

    commentchainusers = set([user.id] + map(int, review.owners))

    if file_id is not None:
        filters = Filters()
        filters.load(db, review=review)

        for user_id in filters.listUsers(db, file_id):
            commentchainusers.add(user_id)

    cursor.executemany("INSERT INTO commentchainusers (chain, uid) VALUES (%s, %s)", [(chain_id, user_id) for user_id in commentchainusers])

    return chain_id