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
class Review: def __init__(self, review_id, owners, review_type, branch, state, serial, summary, description, applyfilters, applyparentfilters): self.id = review_id self.owners = owners self.type = review_type self.repository = branch.repository self.branch = branch self.state = state self.serial = serial self.summary = summary self.description = description self.reviewers = [] self.watchers = {} self.changesets = [] self.commentchains = None self.applyfilters = applyfilters self.applyparentfilters = applyparentfilters self.filters = None self.relevant_files = None self.draft_status = None @staticmethod def isAccepted(db, review_id): cursor = db.cursor() cursor.execute( "SELECT 1 FROM reviewfiles WHERE review=%s AND state='pending' LIMIT 1", (review_id, )) if cursor.fetchone(): return False cursor.execute( "SELECT 1 FROM commentchains WHERE review=%s AND type='issue' AND state='open' LIMIT 1", (review_id, )) if cursor.fetchone(): return False return True def accepted(self, db): if self.state != 'open': return False else: return Review.isAccepted(db, self.id) def getReviewState(self, db): cursor = db.cursor() cursor.execute( """SELECT state, SUM(deleted) + SUM(inserted) FROM reviewfiles WHERE reviewfiles.review=%s GROUP BY state""", (self.id, )) pending = 0 reviewed = 0 for state, count in cursor.fetchall(): if state == "pending": pending = count else: reviewed = count cursor.execute( """SELECT count(id) FROM commentchains WHERE review=%s AND type='issue' AND state='open'""", (self.id, )) issues = cursor.fetchone()[0] return ReviewState(self, self.accepted(db), pending, reviewed, issues) def containsCommit(self, db, commit): commit_id = None commit_sha1 = None if isinstance(commit, gitutils.Commit): if commit.id: commit_id = commit.id else: commit_sha1 = commit.sha1 elif isinstance(commit, str): commit_sha1 = self.repository.revparse(commit) elif isinstance(commit, int): commit_id = commit else: raise TypeError cursor = db.cursor() if commit_id is not None: cursor.execute( """SELECT 1 FROM reviewchangesets JOIN changesets ON (id=changeset) WHERE reviewchangesets.review=%s AND changesets.child=%s""", (self.id, commit_id)) else: cursor.execute( """SELECT 1 FROM reviewchangesets JOIN changesets ON (changesets.id=reviewchangesets.changeset) JOIN commits ON (commits.id=changesets.child) WHERE reviewchangesets.review=%s AND commits.sha1=%s""", (self.id, commit_sha1)) return cursor.fetchone() is not None def getCommentChains(self, db, user, skip=None): import review.comment import time if self.commentchains is None: if "commits" in skip and "lines" in skip: self.commentchains = review.comment.CommentChain.fromReview( db, self, user) else: cursor = db.cursor() if user: cursor.execute( "SELECT id FROM commentchains WHERE review=%s AND (state!='draft' OR uid=%s) ORDER BY id DESC", [self.id, user.id]) else: cursor.execute( "SELECT id FROM commentchains WHERE review=%s AND state!='draft' ORDER BY id DESC", [self.id]) self.commentchains = [ review.comment.CommentChain.fromId(db, id, user, review=self, skip=skip) for (id, ) in cursor.fetchall() ] return self.commentchains def getJS(self): return "var review = critic.review = { id: %d, branch: { id: %d, name: %r }, owners: [ %s ], serial: %d };" % ( self.id, self.branch.id, self.branch.name, ", ".join( owner.getJSConstructor() for owner in self.owners), self.serial) def getETag(self, db, user=None): etag = "review%d.serial%d" % (self.id, self.serial) if user: items = self.getDraftStatus(db, user) if any(items.values()): etag += ".draft%d" % hash(tuple(sorted(items.items()))) cursor = db.cursor() cursor.execute( "SELECT id FROM reviewrebases WHERE review=%s AND uid=%s AND new_head IS NULL", (self.id, user.id)) row = cursor.fetchone() if row: etag += ".rebase%d" % row[0] return '"%s"' % etag def getURL(self, db, user=None, indent=0): indent = " " * indent if db and user: url_prefixes = user.getCriticURLs(db) else: url_prefixes = [getURLPrefix(db)] return "\n".join([ "%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes ]) def getRecipients(self, db): cursor = db.cursor() cursor.execute( "SELECT uid, include FROM reviewrecipientfilters WHERE review=%s ORDER BY uid ASC", (self.id, )) included = set(owner.id for owner in self.owners) excluded = set() for uid, include in cursor: if include: included.add(uid) elif uid not in self.owners: excluded.add(uid) cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (self.id, )) recipients = [] for (user_id, ) in cursor: if user_id in excluded: continue elif user_id not in included and 0 in excluded: continue user = User.fromId(db, user_id) if user.status != "retired": recipients.append(user) return recipients def getDraftStatus(self, db, user): if self.draft_status is None: import review.utils self.draft_status = review.utils.countDraftItems(db, user, self) return self.draft_status def incrementSerial(self, db): self.serial += 1 db.cursor().execute("UPDATE reviews SET serial=%s WHERE id=%s", [self.serial, self.id]) def close(self, db, user): self.serial += 1 db.cursor().execute( "UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) def drop(self, db, user): self.serial += 1 db.cursor().execute( "UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) def reopen(self, db, user): self.serial += 1 db.cursor().execute( "UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s", (self.serial, self.id)) def setSummary(self, db, summary): self.serial += 1 self.summary = summary db.cursor().execute( "UPDATE reviews SET summary=%s, serial=%s WHERE id=%s", [self.summary, self.serial, self.id]) def setDescription(self, db, description): self.serial += 1 self.description = description db.cursor().execute( "UPDATE reviews SET description=%s, serial=%s WHERE id=%s", [self.description, self.serial, self.id]) def addOwner(self, db, owner): if not owner in self.owners: self.serial += 1 self.owners.append(owner) cursor = db.cursor() cursor.execute( "SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, owner.id)) if cursor.fetchone(): cursor.execute( "UPDATE reviewusers SET owner=TRUE WHERE review=%s AND uid=%s", (self.id, owner.id)) else: cursor.execute( "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (self.id, owner.id)) cursor.execute( "SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) row = cursor.fetchone() if row: trackedbranch_id = row[0] cursor.execute( "INSERT INTO trackedbranchusers (branch, uid) VALUES (%s, %s)", (trackedbranch_id, owner.id)) def removeOwner(self, db, owner): if owner in self.owners: self.serial += 1 self.owners.remove(owner) cursor = db.cursor() cursor.execute( "UPDATE reviewusers SET owner=FALSE WHERE review=%s AND uid=%s", (self.id, owner.id)) cursor.execute( "SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) row = cursor.fetchone() if row: trackedbranch_id = row[0] cursor.execute( "DELETE FROM trackedbranchusers WHERE branch=%s AND uid=%s", (trackedbranch_id, owner.id)) def getReviewFilters(self, db): cursor = db.cursor() cursor.execute( "SELECT directory, file, type, NULL, uid FROM reviewfilters WHERE review=%s", (self.id, )) return cursor.fetchall() or None def getFilteredTails(self): commitset = CommitSet(self.branch.commits) return commitset.getFilteredTails(self.branch.repository) def getRelevantFiles(self, db, user): if not self.filters: from review.filters import Filters self.filters = Filters() self.filters.load(db, review=self) self.relevant_files = self.filters.getRelevantFiles(db, self) cursor = db.cursor() cursor.execute( "SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s", (self.id, )) for user_id, file_id in cursor: self.relevant_files.setdefault(user_id, set()).add(file_id) return self.relevant_files.get(user.id, set()) def getUserAssociation(self, db, user): cursor = db.cursor() association = [] if user in self.owners: association.append("owner") cursor.execute( """SELECT 1 FROM reviewchangesets JOIN changesets ON (changesets.id=reviewchangesets.changeset) JOIN commits ON (commits.id=changesets.child) JOIN gitusers ON (gitusers.id=commits.author_gituser) JOIN usergitemails USING (email) WHERE reviewchangesets.review=%s AND usergitemails.uid=%s""", (self.id, user.id)) if cursor.fetchone(): association.append("author") cursor.execute( "SELECT COUNT(*) FROM fullreviewuserfiles WHERE review=%s AND assignee=%s", (self.id, user.id)) if cursor.fetchone()[0] != 0: association.append("reviewer") elif user not in self.owners: cursor.execute( "SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, user.id)) if cursor.fetchone(): association.append("watcher") if not association: association.append("none") return ", ".join(association) @staticmethod def fromId(db, review_id, branch=None, load_commits=True, profiler=None): cursor = db.cursor() cursor.execute( "SELECT type, branch, state, serial, summary, description, applyfilters, applyparentfilters FROM reviews WHERE id=%s", [review_id]) row = cursor.fetchone() if not row: return None type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row if profiler: profiler.check("Review.fromId: basic") if branch is None: branch = Branch.fromId(db, branch_id, load_review=False, load_commits=load_commits, profiler=profiler) cursor.execute("SELECT uid FROM reviewusers WHERE review=%s AND owner", (review_id, )) owners = User.fromIds(db, [user_id for (user_id, ) in cursor]) if profiler: profiler.check("Review.fromId: owners") review = Review(review_id, owners, type, branch, state, serial, summary, description, applyfilters, applyparentfilters) branch.review = review # Reviewers: all users that have at least one review file assigned to them. cursor.execute( """SELECT DISTINCT uid, assignee IS NOT NULL, type FROM reviewusers LEFT OUTER JOIN fullreviewuserfiles ON (fullreviewuserfiles.review=reviewusers.review AND assignee=uid) WHERE reviewusers.review=%s""", (review_id, )) reviewers = [] watchers = [] watcher_types = {} for user_id, is_reviewer, user_type in cursor.fetchall(): if is_reviewer: reviewers.append(user_id) elif user_id not in review.owners: watchers.append(user_id) watcher_types[user_id] = user_type review.reviewers = User.fromIds(db, reviewers) for watcher in User.fromIds(db, watchers): review.watchers[watcher] = watcher_types[watcher] if profiler: profiler.check("Review.fromId: users") if load_commits: review.branch.loadCommits(db) cursor.execute( """SELECT id FROM reviewchangesets JOIN changesets ON (id=changeset) WHERE review=%s AND child=ANY (%s)""", (review_id, [commit.id for commit in review.branch.commits])) review.changesets = [ changeset_id for (changeset_id, ) in cursor.fetchall() ] if profiler: profiler.check("Review.fromId: load commits") return review @staticmethod def fromBranch(db, branch): if branch: cursor = db.cursor() cursor.execute("SELECT id FROM reviews WHERE branch=%s", [branch.id]) row = cursor.fetchone() if not row: return None else: return Review.fromId(db, row[0], branch) else: return None @staticmethod def fromName(db, repository, name): return Review.fromBranch(db, Branch.fromName(db, repository, name)) @staticmethod def fromArgument(db, argument): try: return Review.fromId(db, int(argument)) except: branch = Branch.fromName(db, str(argument)) if not branch: return None return Review.fromBranch(db, branch)
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
class Review: def __init__(self, review_id, owners, review_type, branch, state, serial, summary, description, applyfilters, applyparentfilters): self.id = review_id self.owners = owners self.type = review_type self.repository = branch.repository self.branch = branch self.state = state self.serial = serial self.summary = summary self.description = description self.reviewers = [] self.watchers = {} self.changesets = [] self.commentchains = None self.applyfilters = applyfilters self.applyparentfilters = applyparentfilters self.filters = None self.relevant_files = None self.draft_status = None @staticmethod def isAccepted(db, review_id): cursor = db.cursor() cursor.execute("SELECT 1 FROM reviewfiles WHERE review=%s AND state='pending' LIMIT 1", (review_id,)) if cursor.fetchone(): return False cursor.execute("SELECT 1 FROM commentchains WHERE review=%s AND type='issue' AND state='open' LIMIT 1", (review_id,)) if cursor.fetchone(): return False return True def accepted(self, db): if self.state != 'open': return False else: return Review.isAccepted(db, self.id) def getReviewState(self, db): cursor = db.cursor() cursor.execute("""SELECT state, SUM(deleted) + SUM(inserted) FROM reviewfiles WHERE reviewfiles.review=%s GROUP BY state""", (self.id,)) pending = 0 reviewed = 0 for state, count in cursor.fetchall(): if state == "pending": pending = count else: reviewed = count cursor.execute("""SELECT count(id) FROM commentchains WHERE review=%s AND type='issue' AND state='open'""", (self.id,)) issues = cursor.fetchone()[0] return ReviewState(self, self.accepted(db), pending, reviewed, issues) def containsCommit(self, db, commit): commit_id = None commit_sha1 = None if isinstance(commit, gitutils.Commit): if commit.id: commit_id = commit.id else: commit_sha1 = commit.sha1 elif isinstance(commit, str): commit_sha1 = self.repository.revparse(commit) elif isinstance(commit, int): commit_id = commit else: raise TypeError cursor = db.cursor() if commit_id is not None: cursor.execute("""SELECT 1 FROM reviewchangesets JOIN changesets ON (id=changeset) WHERE reviewchangesets.review=%s AND changesets.child=%s""", (self.id, commit_id)) else: cursor.execute("""SELECT 1 FROM reviewchangesets JOIN changesets ON (changesets.id=reviewchangesets.changeset) JOIN commits ON (commits.id=changesets.child) WHERE reviewchangesets.review=%s AND commits.sha1=%s""", (self.id, commit_sha1)) return cursor.fetchone() is not None def getCommentChains(self, db, user, skip=None): import review.comment import time if self.commentchains is None: if "commits" in skip and "lines" in skip: self.commentchains = review.comment.CommentChain.fromReview(db, self, user) else: cursor = db.cursor() if user: cursor.execute("SELECT id FROM commentchains WHERE review=%s AND (state!='draft' OR uid=%s) ORDER BY id DESC", [self.id, user.id]) else: cursor.execute("SELECT id FROM commentchains WHERE review=%s AND state!='draft' ORDER BY id DESC", [self.id]) self.commentchains = [review.comment.CommentChain.fromId(db, id, user, review=self, skip=skip) for (id,) in cursor.fetchall()] return self.commentchains def getJS(self): return "var review = critic.review = { id: %d, branch: { id: %d, name: %r }, owners: [ %s ], serial: %d };" % (self.id, self.branch.id, self.branch.name, ", ".join(owner.getJSConstructor() for owner in self.owners), self.serial) def getETag(self, db, user=None): etag = "review%d.serial%d" % (self.id, self.serial) if user: items = self.getDraftStatus(db, user) if any(items.values()): etag += ".draft%d" % hash(tuple(sorted(items.items()))) cursor = db.cursor() cursor.execute("SELECT id FROM reviewrebases WHERE review=%s AND uid=%s AND new_head IS NULL", (self.id, user.id)) row = cursor.fetchone() if row: etag += ".rebase%d" % row[0] return '"%s"' % etag def getURL(self, db, user=None, indent=0): indent = " " * indent if db and user: url_prefixes = user.getCriticURLs(db) else: url_prefixes = [getURLPrefix(db)] return "\n".join(["%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes]) def getRecipients(self, db): cursor = db.cursor() cursor.execute("SELECT uid, include FROM reviewrecipientfilters WHERE review=%s ORDER BY uid ASC", (self.id,)) included = set(owner.id for owner in self.owners) excluded = set() for uid, include in cursor: if include: included.add(uid) elif uid not in self.owners: excluded.add(uid) cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (self.id,)) recipients = [] for (user_id,) in cursor: if user_id in excluded: continue elif user_id not in included and 0 in excluded: continue user = User.fromId(db, user_id) if user.status != "retired": recipients.append(user) return recipients def getDraftStatus(self, db, user): if self.draft_status is None: import review.utils self.draft_status = review.utils.countDraftItems(db, user, self) return self.draft_status def incrementSerial(self, db): self.serial += 1 db.cursor().execute("UPDATE reviews SET serial=%s WHERE id=%s", [self.serial, self.id]) def close(self, db, user): self.serial += 1 db.cursor().execute("UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) def drop(self, db, user): self.serial += 1 db.cursor().execute("UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) def reopen(self, db, user): self.serial += 1 db.cursor().execute("UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s", (self.serial, self.id)) def setSummary(self, db, summary): self.serial += 1 self.summary = summary db.cursor().execute("UPDATE reviews SET summary=%s, serial=%s WHERE id=%s", [self.summary, self.serial, self.id]) def setDescription(self, db, description): self.serial += 1 self.description = description db.cursor().execute("UPDATE reviews SET description=%s, serial=%s WHERE id=%s", [self.description, self.serial, self.id]) def addOwner(self, db, owner): if not owner in self.owners: self.serial += 1 self.owners.append(owner) cursor = db.cursor() cursor.execute("SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, owner.id)) if cursor.fetchone(): cursor.execute("UPDATE reviewusers SET owner=TRUE WHERE review=%s AND uid=%s", (self.id, owner.id)) else: cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (self.id, owner.id)) cursor.execute("SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) row = cursor.fetchone() if row: trackedbranch_id = row[0] cursor.execute("INSERT INTO trackedbranchusers (branch, uid) VALUES (%s, %s)", (trackedbranch_id, owner.id)) def removeOwner(self, db, owner): if owner in self.owners: self.serial += 1 self.owners.remove(owner) cursor = db.cursor() cursor.execute("UPDATE reviewusers SET owner=FALSE WHERE review=%s AND uid=%s", (self.id, owner.id)) cursor.execute("SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) row = cursor.fetchone() if row: trackedbranch_id = row[0] cursor.execute("DELETE FROM trackedbranchusers WHERE branch=%s AND uid=%s", (trackedbranch_id, owner.id)) def getReviewFilters(self, db): cursor = db.cursor() cursor.execute("SELECT directory, file, type, NULL, uid FROM reviewfilters WHERE review=%s", (self.id,)) return cursor.fetchall() or None def getFilteredTails(self): commitset = CommitSet(self.branch.commits) return commitset.getFilteredTails(self.branch.repository) def getRelevantFiles(self, db, user): if not self.filters: from review.filters import Filters self.filters = Filters() self.filters.load(db, review=self) self.relevant_files = self.filters.getRelevantFiles(db, self) cursor = db.cursor() cursor.execute("SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s", (self.id,)) for user_id, file_id in cursor: self.relevant_files.setdefault(user_id, set()).add(file_id) return self.relevant_files.get(user.id, set()) def getUserAssociation(self, db, user): cursor = db.cursor() association = [] if user in self.owners: association.append("owner") cursor.execute("""SELECT 1 FROM reviewchangesets JOIN changesets ON (changesets.id=reviewchangesets.changeset) JOIN commits ON (commits.id=changesets.child) JOIN gitusers ON (gitusers.id=commits.author_gituser) JOIN usergitemails USING (email) WHERE reviewchangesets.review=%s AND usergitemails.uid=%s""", (self.id, user.id)) if cursor.fetchone(): association.append("author") cursor.execute("SELECT COUNT(*) FROM fullreviewuserfiles WHERE review=%s AND assignee=%s", (self.id, user.id)) if cursor.fetchone()[0] != 0: association.append("reviewer") elif user not in self.owners: cursor.execute("SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, user.id)) if cursor.fetchone(): association.append("watcher") if not association: association.append("none") return ", ".join(association) @staticmethod def fromId(db, review_id, branch=None, load_commits=True, profiler=None): cursor = db.cursor() cursor.execute("SELECT type, branch, state, serial, summary, description, applyfilters, applyparentfilters FROM reviews WHERE id=%s", [review_id]) row = cursor.fetchone() if not row: return None type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row if profiler: profiler.check("Review.fromId: basic") if branch is None: branch = Branch.fromId(db, branch_id, load_review=False, load_commits=load_commits, profiler=profiler) cursor.execute("SELECT uid FROM reviewusers WHERE review=%s AND owner", (review_id,)) owners = User.fromIds(db, [user_id for (user_id,) in cursor]) if profiler: profiler.check("Review.fromId: owners") review = Review(review_id, owners, type, branch, state, serial, summary, description, applyfilters, applyparentfilters) branch.review = review # Reviewers: all users that have at least one review file assigned to them. cursor.execute("""SELECT DISTINCT uid, assignee IS NOT NULL, type FROM reviewusers LEFT OUTER JOIN fullreviewuserfiles ON (fullreviewuserfiles.review=reviewusers.review AND assignee=uid) WHERE reviewusers.review=%s""", (review_id,)) reviewers = [] watchers = [] watcher_types = {} for user_id, is_reviewer, user_type in cursor.fetchall(): if is_reviewer: reviewers.append(user_id) elif user_id not in review.owners: watchers.append(user_id) watcher_types[user_id] = user_type review.reviewers = User.fromIds(db, reviewers) for watcher in User.fromIds(db, watchers): review.watchers[watcher] = watcher_types[watcher] if profiler: profiler.check("Review.fromId: users") if load_commits: review.branch.loadCommits(db) cursor.execute("""SELECT id FROM reviewchangesets JOIN changesets ON (id=changeset) WHERE review=%s AND child=ANY (%s)""", (review_id, [commit.id for commit in review.branch.commits])) review.changesets = [changeset_id for (changeset_id,) in cursor.fetchall()] if profiler: profiler.check("Review.fromId: load commits") return review @staticmethod def fromBranch(db, branch): if branch: cursor = db.cursor() cursor.execute("SELECT id FROM reviews WHERE branch=%s", [branch.id]) row = cursor.fetchone() if not row: return None else: return Review.fromId(db, row[0], branch) else: return None @staticmethod def fromName(db, repository, name): return Review.fromBranch(db, Branch.fromName(db, repository, name)) @staticmethod def fromArgument(db, argument): try: return Review.fromId(db, int(argument)) except: branch = Branch.fromName(db, str(argument)) if not branch: return None return dbutils.Review.fromBranch(db, branch)
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