def getRelevantFiles(self, db, user): if not self.filters: from reviewing.filters import Filters self.filters = Filters() self.filters.setFiles(db, review=self) self.filters.load(db, review=self) self.relevant_files = self.filters.getRelevantFiles() 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 getRelevantFiles(self, db, user): if not self.filters: from reviewing.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())
class Review(object): 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 getReviewRebases(self, db): return ReviewRebases(db, self) def getCommitSet(self, db): import gitutils import log.commitset cursor = db.cursor() cursor.execute("""SELECT DISTINCT commits.id, commits.sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s""", (self.id,)) commits = [] for commit_id, commit_sha1 in cursor: commits.append(gitutils.Commit.fromSHA1(db, self.repository, commit_sha1, commit_id)) return log.commitset.CommitSet(commits) def containsCommit(self, db, commit, include_head_and_tails=False): import gitutils 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) commit = None elif isinstance(commit, int): commit_id = commit commit = None 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)) if cursor.fetchone() is not None: return True if include_head_and_tails: head_and_tails = set([self.branch.head]) commitset = self.getCommitSet(db) if commitset: head_and_tails |= commitset.getTails() if commit_sha1 is None: if commit is None: commit = gitutils.Commit.fromId(db, self.repository, commit_id) commit_sha1 = commit.sha1 if commit_sha1 in head_and_tails: return True return False 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): import dbutils indent = " " * indent if db and user: url_prefixes = user.getCriticURLs(db) else: url_prefixes = [dbutils.getURLPrefix(db)] return "\n".join(["%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes]) def getRecipients(self, db): from dbutils import User 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: self.draft_status = 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 disableTracking(self, db): db.cursor().execute("UPDATE trackedbranches SET disabled=TRUE WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) 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): import log.commitset commitset = log.commitset.CommitSet(self.branch.commits) return commitset.getFilteredTails(self.branch.repository) def getRelevantFiles(self, db, user): if not self.filters: from reviewing.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): from dbutils import User 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: from dbutils import Branch 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): from dbutils import Branch return Review.fromBranch(db, Branch.fromName(db, repository, name)) @staticmethod def fromArgument(db, argument): try: return Review.fromId(db, int(argument)) except: from dbutils import Branch 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): import reviewing.comment.propagate 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: if origin == "old": commit = gitutils.Commit.fromId(db, review.repository, parent_id) else: commit = gitutils.Commit.fromId(db, review.repository, child_id) propagation = reviewing.comment.propagate.Propagation(db) if not propagation.setCustom(review, commit, file_id, offset, offset + count - 1): raise OperationFailure(code="invalidoperation", title="Invalid operation", message="It's not possible to create a comment here.") propagation.calculateInitialLines() 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] commentchainlines_values = [] for sha1, (first_line, last_line) in propagation.new_lines.items(): commentchainlines_values.append((chain_id, user.id, sha1, first_line, last_line)) cursor.executemany("""INSERT INTO commentchainlines (chain, uid, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s)""", 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, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s)""", (chain_id, user.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.setFiles(db, review=review) filters.load(db, review=review) for user_id in filters.listUsers(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(object): 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.commentchains = None self.applyfilters = applyfilters self.applyparentfilters = applyparentfilters self.filters = None self.relevant_files = None self.draft_status = None self.performed_rebase = 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 setPerformedRebase(self, old_head, new_head, old_upstream, new_upstream, user): self.performed_rebase = ReviewRebase(self, old_head, new_head, old_upstream, new_upstream, user) def getReviewRebases(self, db): return ReviewRebases(db, self) def getTrackedBranch(self, db): cursor = db.cursor() cursor.execute("""SELECT trackedbranches.id, remote, remote_name, disabled FROM trackedbranches JOIN branches ON (trackedbranches.repository=branches.repository AND trackedbranches.local_name=branches.name) JOIN reviews ON (branches.id=reviews.branch) WHERE reviews.id=%s""", (self.id,)) for trackedbranch_id, remote, name, disabled in cursor: return ReviewTrackedBranch(self, trackedbranch_id, remote, name, disabled) def getCommitSet(self, db): import gitutils import log.commitset cursor = db.cursor() cursor.execute("""SELECT DISTINCT commits.id, commits.sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s""", (self.id,)) commits = [] for commit_id, commit_sha1 in cursor: commits.append(gitutils.Commit.fromSHA1(db, self.repository, commit_sha1, commit_id)) return log.commitset.CommitSet(commits) def containsCommit(self, db, commit, include_head_and_tails=False, include_actual_log=False): import gitutils commit_id = None commit_sha1 = None if isinstance(commit, gitutils.Commit): commit_id = commit.id commit_sha1 = commit.sha1 elif isinstance(commit, str): commit_sha1 = self.repository.revparse(commit) commit = None elif isinstance(commit, int): commit_id = commit commit = None 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 AND changesets.type!='conflicts'""", (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 changesets.type!='conflicts' AND commits.sha1=%s""", (self.id, commit_sha1)) if cursor.fetchone() is not None: return True if include_head_and_tails: head_and_tails = set([self.branch.getHead(db)]) commitset = self.getCommitSet(db) if commitset: head_and_tails |= commitset.getTails() if commit_sha1 is None: if commit is None: commit = gitutils.Commit.fromId(db, self.repository, commit_id) commit_sha1 = commit.sha1 if commit_sha1 in head_and_tails: return True if include_actual_log: if commit_id is not None: cursor.execute("""SELECT 1 FROM reachable JOIN branches ON (branches.id=reachable.branch) JOIN reviews ON (reviews.branch=branches.id) WHERE reachable.commit=%s AND reviews.id=%s""", (commit_id, self.id)) else: cursor.execute("""SELECT 1 FROM commits JOIN reachable ON (reachable.commit=commits.id) JOIN branches ON (branches.id=reachable.branch) JOIN reviews ON (reviews.branch=branches.id) WHERE commits.sha1=%s AND reviews.id=%s""", (commit_sha1, self.id)) if cursor.fetchone() is not None: return True return False 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): import configuration cursor = db.cursor() etag = "" if configuration.debug.IS_DEVELOPMENT: cursor.execute("SELECT installed_at FROM systemidentities WHERE name=%s", (configuration.base.SYSTEM_IDENTITY,)) installed_at = cursor.fetchone()[0] etag += "install%s." % time.mktime(installed_at.timetuple()) if user and not user.isAnonymous(): etag += "user%d." % user.id 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.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, separator="\n"): import dbutils indent = " " * indent if user: url_prefixes = user.getCriticURLs(db) else: url_prefixes = [dbutils.getURLPrefix(db)] return separator.join(["%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes]) def getRecipients(self, db): from dbutils import User cursor = db.cursor() cursor.execute("SELECT uid, include FROM reviewrecipientfilters WHERE review=%s", (self.id,)) default_include = True included = set(owner.id for owner in self.owners) excluded = set() for uid, include in cursor: if uid is None: default_include = include elif 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 not default_include: 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: self.draft_status = 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 scheduleBranchArchival(self, db, delay=None): import dbutils # First, cancel current scheduled archival, if there is one. self.cancelScheduledBranchArchival(db) # If review is not closed or dropped, don't schedule a branch archival. # Also don't schedule one if the branch has already been archived. if self.state not in ("closed", "dropped") or self.branch.archived: return if delay is None: # Configuration policy: # # Any owner of a review can, by having changed the relevant # preference setting, increase the time before a review branch is # archived, or disable archival entirely, but they can't make it # happen sooner than the system or repository default, or what any # other owner has requested. # Find configured value for each owner, and also the per-repository # (or per-system) default, in case each owner has changed the # setting. preference_item = "review.branchArchiveDelay." + self.state repository_default = dbutils.User.fetchPreference( db, preference_item, repository=self.repository) delays = set([repository_default]) for owner in self.owners: delays.add(owner.getPreference(db, preference_item, repository=self.repository)) # If configured to zero (by any owner,) don't schedule a branch # archival. if min(delays) <= 0: return # Otherwise, use maximum configured value for any owner. delay = max(delays) cursor = db.cursor() cursor.execute("""INSERT INTO scheduledreviewbrancharchivals (review, deadline) VALUES (%s, NOW() + INTERVAL %s)""", (self.id, "%d DAYS" % delay)) return delay def cancelScheduledBranchArchival(self, db): cursor = db.cursor() cursor.execute("""DELETE FROM scheduledreviewbrancharchivals WHERE review=%s""", (self.id,)) def close(self, db, user): self.serial += 1 self.state = "closed" db.cursor().execute("UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) self.scheduleBranchArchival(db) def drop(self, db, user): self.serial += 1 self.state = "dropped" db.cursor().execute("UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) self.scheduleBranchArchival(db) def reopen(self, db, user): self.serial += 1 if self.branch.archived: self.branch.resurrect(db) db.cursor().execute("UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s", (self.serial, self.id)) self.cancelScheduledBranchArchival(db) def disableTracking(self, db): db.cursor().execute("UPDATE trackedbranches SET disabled=TRUE WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) 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 uid, path, type, NULL FROM reviewfilters WHERE review=%s", (self.id,)) return cursor.fetchall() or None def getFilteredTails(self, db): import log.commitset commitset = log.commitset.CommitSet(self.branch.getCommits(db)) return commitset.getFilteredTails(self.branch.repository) def getRelevantFiles(self, db, user): if not self.filters: from reviewing.filters import Filters self.filters = Filters() self.filters.setFiles(db, review=self) self.filters.load(db, review=self) self.relevant_files = self.filters.getRelevantFiles() 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, profiler=None): from dbutils import User 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: raise NoSuchReview(review_id) type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row if profiler: profiler.check("Review.fromId: basic") if branch is None: from dbutils import Branch branch = Branch.fromId(db, branch_id, load_review=False, 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") 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): from dbutils import Branch return Review.fromBranch(db, Branch.fromName(db, repository, name)) @staticmethod def fromArgument(db, argument): try: return Review.fromId(db, int(argument)) except: from dbutils import Branch branch = Branch.fromName(db, str(argument)) if not branch: return None return Review.fromBranch(db, branch)
class Review(object): 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.commentchains = None self.applyfilters = applyfilters self.applyparentfilters = applyparentfilters self.filters = None self.relevant_files = None self.draft_status = None self.performed_rebase = 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 setPerformedRebase(self, old_head, new_head, old_upstream, new_upstream, user, equivalent_merge, replayed_rebase): self.performed_rebase = ReviewRebase(self, old_head, new_head, old_upstream, new_upstream, user, equivalent_merge, replayed_rebase) def getReviewRebases(self, db): return ReviewRebases(db, self) def getTrackedBranch(self, db): cursor = db.cursor() cursor.execute( """SELECT trackedbranches.id, remote, remote_name, disabled FROM trackedbranches JOIN branches ON (trackedbranches.repository=branches.repository AND trackedbranches.local_name=branches.name) JOIN reviews ON (branches.id=reviews.branch) WHERE reviews.id=%s""", (self.id, )) for trackedbranch_id, remote, name, disabled in cursor: return ReviewTrackedBranch(self, trackedbranch_id, remote, name, disabled) def getCommitSet(self, db): import gitutils import log.commitset cursor = db.cursor() cursor.execute( """SELECT DISTINCT commits.id, commits.sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s""", (self.id, )) commits = [] for commit_id, commit_sha1 in cursor: commits.append( gitutils.Commit.fromSHA1(db, self.repository, commit_sha1, commit_id)) return log.commitset.CommitSet(commits) def containsCommit(self, db, commit, include_head_and_tails=False, include_actual_log=False): import gitutils commit_id = None commit_sha1 = None if isinstance(commit, gitutils.Commit): commit_id = commit.id commit_sha1 = commit.sha1 elif isinstance(commit, str): commit_sha1 = self.repository.revparse(commit) commit = None elif isinstance(commit, int): commit_id = commit commit = None 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 AND changesets.type!='conflicts'""", (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 changesets.type!='conflicts' AND commits.sha1=%s""", (self.id, commit_sha1)) if cursor.fetchone() is not None: return True if include_head_and_tails: head_and_tails = set([self.branch.getHead(db)]) commitset = self.getCommitSet(db) if commitset: head_and_tails |= commitset.getTails() if commit_sha1 is None: if commit is None: commit = gitutils.Commit.fromId(db, self.repository, commit_id) commit_sha1 = commit.sha1 if commit_sha1 in head_and_tails: return True if include_actual_log: if commit_id is not None: cursor.execute( """SELECT 1 FROM reachable JOIN branches ON (branches.id=reachable.branch) JOIN reviews ON (reviews.branch=branches.id) WHERE reachable.commit=%s AND reviews.id=%s""", (commit_id, self.id)) else: cursor.execute( """SELECT 1 FROM commits JOIN reachable ON (reachable.commit=commits.id) JOIN branches ON (branches.id=reachable.branch) JOIN reviews ON (reviews.branch=branches.id) WHERE commits.sha1=%s AND reviews.id=%s""", (commit_sha1, self.id)) if cursor.fetchone() is not None: return True return False 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): import configuration cursor = db.cursor() etag = "" if configuration.debug.IS_DEVELOPMENT: cursor.execute( "SELECT installed_at FROM systemidentities WHERE name=%s", (configuration.base.SYSTEM_IDENTITY, )) installed_at = cursor.fetchone()[0] etag += "install%s." % time.mktime(installed_at.timetuple()) if user and not user.isAnonymous(): etag += "user%d." % user.id 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.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, separator="\n"): import dbutils indent = " " * indent if user: url_prefixes = user.getCriticURLs(db) else: url_prefixes = [dbutils.getURLPrefix(db)] return separator.join([ "%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes ]) def getRecipients(self, db): from dbutils import User cursor = db.cursor() cursor.execute( "SELECT uid, include FROM reviewrecipientfilters WHERE review=%s", (self.id, )) default_include = True included = set(owner.id for owner in self.owners) excluded = set() for uid, include in cursor: if uid is None: default_include = include elif 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 not default_include: 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: self.draft_status = 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 scheduleBranchArchival(self, db, delay=None): import dbutils # First, cancel current scheduled archival, if there is one. self.cancelScheduledBranchArchival(db) # If review is not closed or dropped, don't schedule a branch archival. # Also don't schedule one if the branch has already been archived. if self.state not in ("closed", "dropped") or self.branch.archived: return if delay is None: # Configuration policy: # # Any owner of a review can, by having changed the relevant # preference setting, increase the time before a review branch is # archived, or disable archival entirely, but they can't make it # happen sooner than the system or repository default, or what any # other owner has requested. # Find configured value for each owner, and also the per-repository # (or per-system) default, in case each owner has changed the # setting. preference_item = "review.branchArchiveDelay." + self.state repository_default = dbutils.User.fetchPreference( db, preference_item, repository=self.repository) delays = set([repository_default]) for owner in self.owners: delays.add( owner.getPreference(db, preference_item, repository=self.repository)) # If configured to zero (by any owner,) don't schedule a branch # archival. if min(delays) <= 0: return # Otherwise, use maximum configured value for any owner. delay = max(delays) cursor = db.cursor() cursor.execute( """INSERT INTO scheduledreviewbrancharchivals (review, deadline) VALUES (%s, NOW() + INTERVAL %s)""", (self.id, "%d DAYS" % delay)) return delay def cancelScheduledBranchArchival(self, db): cursor = db.cursor() cursor.execute( """DELETE FROM scheduledreviewbrancharchivals WHERE review=%s""", (self.id, )) def close(self, db, user): self.serial += 1 self.state = "closed" db.cursor().execute( "UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) self.scheduleBranchArchival(db) def drop(self, db, user): self.serial += 1 self.state = "dropped" db.cursor().execute( "UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id)) self.scheduleBranchArchival(db) def reopen(self, db, user): self.serial += 1 if self.branch.archived: self.branch.resurrect(db) db.cursor().execute( "UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s", (self.serial, self.id)) self.cancelScheduledBranchArchival(db) def disableTracking(self, db): db.cursor().execute( "UPDATE trackedbranches SET disabled=TRUE WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name)) 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 uid, path, type, NULL FROM reviewfilters WHERE review=%s", (self.id, )) return cursor.fetchall() or None def getFilteredTails(self, db): import log.commitset commitset = log.commitset.CommitSet(self.branch.getCommits(db)) return commitset.getFilteredTails(self.branch.repository) def getRelevantFiles(self, db, user): if not self.filters: from reviewing.filters import Filters self.filters = Filters() self.filters.setFiles(db, review=self) self.filters.load(db, review=self) self.relevant_files = self.filters.getRelevantFiles() 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, profiler=None): from dbutils import User 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: raise NoSuchReview(review_id) type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row if profiler: profiler.check("Review.fromId: basic") if branch is None: from dbutils import Branch branch = Branch.fromId(db, branch_id, load_review=False, 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") 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): from dbutils import Branch return Review.fromBranch(db, Branch.fromName(db, repository, name)) @staticmethod def fromArgument(db, argument): try: return Review.fromId(db, int(argument)) except: from dbutils import Branch branch = Branch.fromName(db, str(argument)) if not branch: return None return Review.fromBranch(db, branch) @staticmethod def fromAPI(api_review): return Review.fromId(api_review.critic.database, api_review.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