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
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
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)]
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)]
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
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
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