def applyParentFilters(db, user, review): assert review.applyfilters assert not review.applyparentfilters cursor = db.cursor() cursor.execute("UPDATE reviews SET applyparentfilters=TRUE WHERE id=%s", (review.id,)) review.applyparentfilters = True review.branch.loadCommits(db) new_reviewers, new_watchers = assignChanges(db, user, review, commits=review.branch.commits, update=True, parentfiltersonly=True) pending_mails = [] for user_id in new_reviewers: new_reviewer = dbutils.User.fromId(db, user_id) cursor.execute("""SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s AND reviewuserfiles.uid=%s GROUP BY reviewfiles.file""", (review.id, user_id)) pending_mails.extend(mail.sendParentFiltersApplied(db, user, new_reviewer, review, cursor.fetchall())) for user_id in new_watchers: new_watcher = dbutils.User.fromId(db, user_id) pending_mails.extend(mail.sendParentFiltersApplied(db, user, new_watcher, review, None)) db.commit() mail.sendPendingMails(pending_mails)
def applyFilters(db, user, review, globalfilters=False, parentfilters=False): new_reviewers, new_watchers = queryFilters(db, user, review, globalfilters, parentfilters) pending_mails = [] cursor = db.cursor() for user_id in new_reviewers: new_reviewer = dbutils.User.fromId(db, user_id) cursor.execute("""SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s AND reviewuserfiles.uid=%s GROUP BY reviewfiles.file""", (review.id, user_id)) pending_mails.extend(mail.sendFiltersApplied( db, user, new_reviewer, review, globalfilters, parentfilters, cursor.fetchall())) for user_id in new_watchers: new_watcher = dbutils.User.fromId(db, user_id) pending_mails.extend(mail.sendFiltersApplied( db, user, new_watcher, review, globalfilters, parentfilters, None)) review.incrementSerial(db) db.commit() mail.sendPendingMails(pending_mails)
def applyFilters(db, user, review, globalfilters=False, parentfilters=False): new_reviewers, new_watchers = queryFilters(db, user, review, globalfilters, parentfilters) pending_mails = [] cursor = db.cursor() for user_id in new_reviewers: new_reviewer = dbutils.User.fromId(db, user_id) cursor.execute( """SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s AND reviewuserfiles.uid=%s GROUP BY reviewfiles.file""", (review.id, user_id)) pending_mails.extend( mail.sendFiltersApplied(db, user, new_reviewer, review, globalfilters, parentfilters, cursor.fetchall())) for user_id in new_watchers: new_watcher = dbutils.User.fromId(db, user_id) pending_mails.extend( mail.sendFiltersApplied(db, user, new_watcher, review, globalfilters, parentfilters, None)) review.incrementSerial(db) db.commit() mail.sendPendingMails(pending_mails)
def applyParentFilters(db, user, review): assert review.applyfilters assert not review.applyparentfilters cursor = db.cursor() cursor.execute("UPDATE reviews SET applyparentfilters=TRUE WHERE id=%s", (review.id, )) review.applyparentfilters = True review.branch.loadCommits(db) new_reviewers, new_watchers = assignChanges(db, user, review, commits=review.branch.commits, update=True, parentfiltersonly=True) pending_mails = [] for user_id in new_reviewers: new_reviewer = dbutils.User.fromId(db, user_id) cursor.execute( """SELECT reviewfiles.file, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s AND reviewuserfiles.uid=%s GROUP BY reviewfiles.file""", (review.id, user_id)) pending_mails.extend( mail.sendParentFiltersApplied(db, user, new_reviewer, review, cursor.fetchall())) for user_id in new_watchers: new_watcher = dbutils.User.fromId(db, user_id) pending_mails.extend( mail.sendParentFiltersApplied(db, user, new_watcher, review, None)) db.commit() mail.sendPendingMails(pending_mails)
def createReview(db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None): cursor = db.cursor() if via_push: applyparentfilters = bool(user.getPreference(db, 'review.applyUpstreamFilters')) branch = dbutils.Branch.fromName(db, repository, branch_name) if branch is not None: raise OperationFailure( code="branchexists", title="Invalid review branch name", message="""\ <p>There is already a branch named <code>%s</code> in the repository. You have to select a different name.</p> <p>If you believe the existing branch was created during an earlier (failed) attempt to create this review, you can try to delete it from the repository using the command<p> <pre> git push <remote> :%s</pre> <p>and then press the "Submit Review" button on this page again.""" % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)), is_html=True) if not commits: raise OperationFailure( code="nocommits", title="No commits specified", message="You need at least one commit to create a review.") commitset = log_commitset.CommitSet(commits) heads = commitset.getHeads() if len(heads) != 1: # There is really no plausible way for this error to occur. raise OperationFailure( code="disconnectedtree", title="Disconnected tree", message=("The specified commits do do not form a single connected " "tree. Creating a review of them is not supported.")) head = heads.pop() if len(commitset.getTails()) != 1: tail_id = None else: tail_id = gitutils.Commit.fromSHA1(db, repository, commitset.getTails().pop()).getId(db) if not via_push: try: repository.createBranch(branch_name, head.sha1) except gitutils.GitCommandError as error: raise OperationFailure( code="branchfailed", title="Failed to create review branch", message=("<p><b>Output from git:</b></p>" "<code style='padding-left: 1em'>%s</code>" % htmlutils.htmlify(error.output)), is_html=True) try: cursor.execute("INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id]) branch_id = cursor.fetchone()[0] reachable_values = [(branch_id, commit.getId(db)) for commit in commits] cursor.executemany("INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values) cursor.execute("INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters)) review = dbutils.Review.fromId(db, cursor.fetchone()[0]) cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id)) if reviewfilters is not None: cursor.executemany("""INSERT INTO reviewfilters (review, uid, path, type, creator) VALUES (%s, %s, %s, %s, %s)""", [(review.id, filter_user_id, filter_path, filter_type, user.id) for filter_user_id, filter_path, filter_type, filter_delegate in reviewfilters]) is_opt_in = False if recipientfilters is not None: cursor.executemany("INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)", [(review.id, filter_user_id, filter_include) for filter_user_id, filter_include in recipientfilters]) for filter_user_id, filter_include in recipientfilters: if filter_user_id is None and not filter_include: is_opt_in = True addCommitsToReview(db, user, review, commits, new_review=True) if from_branch_name is not None: cursor.execute("UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name)) # Reload to get list of changesets added by addCommitsToReview(). review = dbutils.Review.fromId(db, review.id) pending_mails = [] recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend(mail.sendReviewCreated(db, user, to_user, recipients, review)) if not is_opt_in: recipient_by_id = dict((to_user.id, to_user) for to_user in recipients) cursor.execute("""SELECT userpreferences.uid, userpreferences.repository, userpreferences.filter, userpreferences.integer FROM userpreferences LEFT OUTER JOIN filters ON (filters.id=userpreferences.filter) WHERE userpreferences.item='review.defaultOptOut' AND userpreferences.uid=ANY (%s) AND (userpreferences.filter IS NULL OR filters.repository=%s) AND (userpreferences.repository IS NULL OR userpreferences.repository=%s)""", (recipient_by_id.keys(), repository.id, repository.id)) user_settings = {} has_filter_settings = False for user_id, repository_id, filter_id, integer in cursor: settings = user_settings.setdefault(user_id, [None, None, {}]) value = bool(integer) if repository_id is None and filter_id is None: settings[0] = value elif repository_id is not None: settings[1] = value else: settings[2][filter_id] = value has_filter_settings = True if has_filter_settings: filters = Filters() filters.setFiles(db, review=review) for user_id, (global_default, repository_default, filter_settings) in user_settings.items(): to_user = recipient_by_id[user_id] opt_out = None if repository_default is not None: opt_out = repository_default elif global_default is not None: opt_out = global_default if filter_settings: # Policy: # # If all of the user's filters that matched files in the # review have review.defaultOptOut enabled, then opt out. # When determining this, any review filters of the user's # that match files in the review count as filters that don't # have the review.defaultOptOut enabled. # # If any of the user's filters that matched files in the # review have review.defaultOptOut disabled, then don't opt # out. When determining this, review filters are ignored. # # Otherwise, ignore the filter settings, and go with either # the user's per-repository or global setting (as set # above.) filters.load(db, review=review, user=to_user) # A set of filter ids. If None is in the set, the user has # one or more review filters in the review. (These do not # have ids.) active_filters = filters.getActiveFilters(to_user) for filter_id in active_filters: if filter_id is None: continue elif filter_id in filter_settings: if not filter_settings[filter_id]: opt_out = False break else: break else: if None not in active_filters: opt_out = True if opt_out: cursor.execute("""INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, FALSE)""", (review.id, to_user.id)) db.commit() mail.sendPendingMails(pending_mails) return review except: if not via_push: repository.run("branch", "-D", branch_name) raise
def addCommitsToReview(db, user, review, commits, new_review=False, commitset=None, pending_mails=None, silent_if_empty=set(), full_merges=set(), replayed_rebases={}, tracked_branch=False): cursor = db.cursor() if not new_review: import index new_commits = log_commitset.CommitSet(commits) old_commits = log_commitset.CommitSet(review.branch.commits) merges = new_commits.getMerges() for merge in merges: # We might have stripped it in a previous pass. if not merge in new_commits: continue tails = filter(lambda sha1: sha1 not in old_commits and sha1 not in merge.parents, new_commits.getTailsFrom(merge)) if tails: if tracked_branch: raise index.IndexException("""\ Merge %s adds merged-in commits. Please push the merge manually and follow the instructions.""" % merge.sha1[:8]) cursor.execute("SELECT id, confirmed, tail FROM reviewmergeconfirmations WHERE review=%s AND uid=%s AND merge=%s", (review.id, user.id, merge.getId(db))) row = cursor.fetchone() if not row or not row[1]: if not row: cursor.execute("INSERT INTO reviewmergeconfirmations (review, uid, merge) VALUES (%s, %s, %s) RETURNING id", (review.id, user.id, merge.getId(db))) confirmation_id = cursor.fetchone()[0] merged = set() for tail_sha1 in tails: children = new_commits.getChildren(tail_sha1) while children: child = children.pop() if child not in merged and new_commits.isAncestorOf(child, merge): merged.add(child) children.update(new_commits.getChildren(child) - merged) merged_values = [(confirmation_id, commit.getId(db)) for commit in merged] cursor.executemany("INSERT INTO reviewmergecontributions (id, merged) VALUES (%s, %s)", merged_values) db.commit() else: confirmation_id = row[0] message = "Merge %s adds merged-in commits:" % merge.sha1[:8] for tail_sha1 in tails: for parent_sha1 in merge.parents: if parent_sha1 in new_commits: parent = new_commits.get(parent_sha1) if tail_sha1 in new_commits.getTailsFrom(parent): message += "\n %s..%s" % (tail_sha1[:8], parent_sha1[:8]) message += """ Please confirm that this is intended by loading: %s/confirmmerge?id=%d""" % (dbutils.getURLPrefix(db, user), confirmation_id) raise index.IndexException(message) elif row[2] is not None: if row[2] == merge.getId(db): cursor.execute("SELECT merged FROM reviewmergecontributions WHERE id=%s", (row[0],)) for (merged_id,) in cursor: merged = gitutils.Commit.fromId(db, review.repository, merged_id) if merged.sha1 in merge.parents: new_commits = new_commits.without([merged]) break else: tail = gitutils.Commit.fromId(db, review.repository, row[2]) cut = [gitutils.Commit.fromSHA1(db, review.repository, sha1) for sha1 in tail.parents if sha1 in new_commits] new_commits = new_commits.without(cut) if commitset: commitset &= set(new_commits) commits = [commit for commit in commits if commit in commitset] changesets = [] silent_commits = set() silent_changesets = set() simple_commits = [] for commit in commits: if commit not in full_merges and commit not in replayed_rebases: simple_commits.append(commit) if simple_commits: changeset_utils.createChangesets(db, review.repository, simple_commits) for commit in commits: if commit in full_merges: commit_changesets = changeset_utils.createFullMergeChangeset( db, user, review.repository, commit, do_highlight=False) elif commit in replayed_rebases: commit_changesets = changeset_utils.createChangeset( db, user, review.repository, from_commit=commit, to_commit=replayed_rebases[commit], conflicts=True, do_highlight=False) else: commit_changesets = changeset_utils.createChangeset( db, user, review.repository, commit, do_highlight=False) if commit in silent_if_empty: for commit_changeset in commit_changesets: if commit_changeset.files: break else: silent_commits.add(commit) silent_changesets.update(commit_changesets) changesets.extend(commit_changesets) if not new_review: print "Adding %d commit%s to the review at:\n %s" % (len(commits), len(commits) > 1 and "s" or "", review.getURL(db)) reviewchangesets_values = [(review.id, changeset.id) for changeset in changesets] cursor.executemany("""INSERT INTO reviewchangesets (review, changeset) VALUES (%s, %s)""", reviewchangesets_values) cursor.executemany("""INSERT INTO reviewfiles (review, changeset, file, deleted, inserted) SELECT reviewchangesets.review, reviewchangesets.changeset, fileversions.file, COALESCE(SUM(chunks.deleteCount), 0), COALESCE(SUM(chunks.insertCount), 0) FROM reviewchangesets JOIN fileversions USING (changeset) LEFT OUTER JOIN chunks USING (changeset, file) WHERE reviewchangesets.review=%s AND reviewchangesets.changeset=%s GROUP BY reviewchangesets.review, reviewchangesets.changeset, fileversions.file""", reviewchangesets_values) new_reviewers, new_watchers = assignChanges(db, user, review, changesets=changesets) cursor.execute("SELECT include FROM reviewrecipientfilters WHERE review=%s AND uid IS NULL", (review.id,)) try: opt_out = cursor.fetchone()[0] is True except: opt_out = True if not new_review: for user_id in new_reviewers: new_reviewuser = dbutils.User.fromId(db, user_id) print "Added reviewer: %s <%s>" % (new_reviewuser.fullname, new_reviewuser.email) if opt_out: # If the user has opted out from receiving e-mails about this # review while only watching it, clear the opt-out now that the # user becomes a reviewer. cursor.execute("DELETE FROM reviewrecipientfilters WHERE review=%s AND uid=%s AND include=FALSE", (review.id, user_id)) for user_id in new_watchers: new_reviewuser = dbutils.User.fromId(db, user_id) print "Added watcher: %s <%s>" % (new_reviewuser.fullname, new_reviewuser.email) review.incrementSerial(db) reviewing.comment.propagateCommentChains(db, user, review, new_commits, replayed_rebases) if pending_mails is None: pending_mails = [] notify_commits = filter(lambda commit: commit not in silent_commits, commits) notify_changesets = filter(lambda changeset: changeset not in silent_changesets, changesets) if not new_review and notify_changesets: recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend(mail.sendReviewAddedCommits( db, user, to_user, recipients, review, notify_commits, notify_changesets, tracked_branch=tracked_branch)) mail.sendPendingMails(pending_mails) review.reviewers.extend([User.fromId(db, user_id) for user_id in new_reviewers]) for user_id in new_watchers: review.watchers[User.fromId(db, user_id)] = "automatic" return True
def createReview(db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None): cursor = db.cursor() if via_push: applyparentfilters = bool(user.getPreference(db, 'review.applyUpstreamFilters')) branch = dbutils.Branch.fromName(db, repository, branch_name) if branch is not None: raise OperationFailure(code="branchexists", title="Invalid review branch name", message="""\ <p>There is already a branch named <code>%s</code> in the repository. You have to select a different name.</p> <p>If you believe the existing branch was created during an earlier (failed) attempt to create this review, you can try to delete it from the repository using the command<p> <pre> git push <remote> :%s</pre> <p>and then press the "Submit Review" button on this page again.""" % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)), is_html=True) commitset = log_commitset.CommitSet(commits) if len(commitset.getHeads()) != 1: raise Exception, "invalid commit-set; multiple heads" head = commitset.getHeads().pop() if len(commitset.getTails()) != 1: tail_id = None else: tail_id = gitutils.Commit.fromSHA1(db, repository, commitset.getTails().pop()).getId(db) if not via_push: repository.branch(branch_name, head.sha1) try: cursor.execute("INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id]) branch_id = cursor.fetchone()[0] reachable_values = [(branch_id, commit.getId(db)) for commit in commits] cursor.executemany("INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values) cursor.execute("INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters)) review = dbutils.Review.fromId(db, cursor.fetchone()[0]) cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id)) if reviewfilters is not None: cursor.executemany("INSERT INTO reviewfilters (review, uid, directory, file, type, creator) VALUES (%s, %s, %s, %s, %s, %s)", [(review.id, filter_user_id, filter_directory_id, filter_file_id, filter_type, user.id) for filter_directory_id, filter_file_id, filter_type, filter_delegate, filter_user_id in reviewfilters]) if recipientfilters is not None: cursor.executemany("INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)", [(review.id, filter_user_id, filter_include) for filter_user_id, filter_include in recipientfilters]) addCommitsToReview(db, user, review, commits, new_review=True) if from_branch_name is not None: cursor.execute("UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name)) # Reload to get list of changesets added by addCommitsToReview(). review = dbutils.Review.fromId(db, review.id) pending_mails = [] recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend(mail.sendReviewCreated(db, user, to_user, recipients, review)) db.commit() mail.sendPendingMails(pending_mails) return review except: if not via_push: repository.run("branch", "-D", branch_name) raise
def createReview(db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None): cursor = db.cursor() if via_push: applyparentfilters = bool( user.getPreference(db, 'review.applyUpstreamFilters')) branch = dbutils.Branch.fromName(db, repository, branch_name) if branch is not None: raise OperationFailure( code="branchexists", title="Invalid review branch name", message="""\ <p>There is already a branch named <code>%s</code> in the repository. You have to select a different name.</p> <p>If you believe the existing branch was created during an earlier (failed) attempt to create this review, you can try to delete it from the repository using the command<p> <pre> git push <remote> :%s</pre> <p>and then press the "Submit Review" button on this page again.""" % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)), is_html=True) if not commits: raise OperationFailure( code="nocommits", title="No commits specified", message="You need at least one commit to create a review.") commitset = log_commitset.CommitSet(commits) heads = commitset.getHeads() if len(heads) != 1: # There is really no plausible way for this error to occur. raise OperationFailure( code="disconnectedtree", title="Disconnected tree", message=("The specified commits do do not form a single connected " "tree. Creating a review of them is not supported.")) head = heads.pop() if len(commitset.getTails()) != 1: tail_id = None else: tail_id = gitutils.Commit.fromSHA1( db, repository, commitset.getTails().pop()).getId(db) if not via_push: try: repository.createBranch(branch_name, head.sha1) except gitutils.GitCommandError as error: raise OperationFailure( code="branchfailed", title="Failed to create review branch", message=("<p><b>Output from git:</b></p>" "<code style='padding-left: 1em'>%s</code>" % htmlutils.htmlify(error.output)), is_html=True) createChangesetsForCommits(db, commits) try: cursor.execute( "INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id]) branch_id = cursor.fetchone()[0] reachable_values = [(branch_id, commit.getId(db)) for commit in commits] cursor.executemany( "INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values) cursor.execute( "INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters)) review = dbutils.Review.fromId(db, cursor.fetchone()[0]) cursor.execute( "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id)) if reviewfilters is not None: cursor.executemany( """INSERT INTO reviewfilters (review, uid, path, type, creator) VALUES (%s, %s, %s, %s, %s)""", [(review.id, filter_user_id, filter_path, filter_type, user.id) for filter_user_id, filter_path, filter_type, filter_delegate in reviewfilters]) is_opt_in = False if recipientfilters is not None: cursor.executemany( "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)", [(review.id, filter_user_id, filter_include) for filter_user_id, filter_include in recipientfilters]) for filter_user_id, filter_include in recipientfilters: if filter_user_id is None and not filter_include: is_opt_in = True addCommitsToReview(db, user, review, commits, new_review=True) if from_branch_name is not None: cursor.execute( "UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name)) # Reload to get list of changesets added by addCommitsToReview(). review = dbutils.Review.fromId(db, review.id) pending_mails = [] recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend( mail.sendReviewCreated(db, user, to_user, recipients, review)) if not is_opt_in: recipient_by_id = dict( (to_user.id, to_user) for to_user in recipients) cursor.execute( """SELECT userpreferences.uid, userpreferences.repository, userpreferences.filter, userpreferences.integer FROM userpreferences LEFT OUTER JOIN filters ON (filters.id=userpreferences.filter) WHERE userpreferences.item='review.defaultOptOut' AND userpreferences.uid=ANY (%s) AND (userpreferences.filter IS NULL OR filters.repository=%s) AND (userpreferences.repository IS NULL OR userpreferences.repository=%s)""", (recipient_by_id.keys(), repository.id, repository.id)) user_settings = {} has_filter_settings = False for user_id, repository_id, filter_id, integer in cursor: settings = user_settings.setdefault(user_id, [None, None, {}]) value = bool(integer) if repository_id is None and filter_id is None: settings[0] = value elif repository_id is not None: settings[1] = value else: settings[2][filter_id] = value has_filter_settings = True if has_filter_settings: filters = Filters() filters.setFiles(db, review=review) for user_id, (global_default, repository_default, filter_settings) in user_settings.items(): to_user = recipient_by_id[user_id] opt_out = None if repository_default is not None: opt_out = repository_default elif global_default is not None: opt_out = global_default if filter_settings: # Policy: # # If all of the user's filters that matched files in the # review have review.defaultOptOut enabled, then opt out. # When determining this, any review filters of the user's # that match files in the review count as filters that don't # have the review.defaultOptOut enabled. # # If any of the user's filters that matched files in the # review have review.defaultOptOut disabled, then don't opt # out. When determining this, review filters are ignored. # # Otherwise, ignore the filter settings, and go with either # the user's per-repository or global setting (as set # above.) filters.load(db, review=review, user=to_user) # A set of filter ids. If None is in the set, the user has # one or more review filters in the review. (These do not # have ids.) active_filters = filters.getActiveFilters(to_user) for filter_id in active_filters: if filter_id is None: continue elif filter_id in filter_settings: if not filter_settings[filter_id]: opt_out = False break else: break else: if None not in active_filters: opt_out = True if opt_out: cursor.execute( """INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, FALSE)""", (review.id, to_user.id)) db.commit() mail.sendPendingMails(pending_mails) return review except: if not via_push: repository.run("branch", "-D", branch_name) raise
def addCommitsToReview(db, user, review, commits, new_review=False, commitset=None, pending_mails=None, silent_if_empty=set(), full_merges=set(), replayed_rebases={}, tracked_branch=False): cursor = db.cursor() if not new_review: import index new_commits = log_commitset.CommitSet(commits) old_commits = log_commitset.CommitSet(review.branch.commits) merges = new_commits.getMerges() for merge in merges: # We might have stripped it in a previous pass. if not merge in new_commits: continue tails = filter( lambda sha1: sha1 not in old_commits and sha1 not in merge. parents, new_commits.getTailsFrom(merge)) if tails: if tracked_branch: raise index.IndexException("""\ Merge %s adds merged-in commits. Please push the merge manually and follow the instructions.""" % merge.sha1[:8]) cursor.execute( "SELECT id, confirmed, tail FROM reviewmergeconfirmations WHERE review=%s AND uid=%s AND merge=%s", (review.id, user.id, merge.getId(db))) row = cursor.fetchone() if not row or not row[1]: if not row: cursor.execute( "INSERT INTO reviewmergeconfirmations (review, uid, merge) VALUES (%s, %s, %s) RETURNING id", (review.id, user.id, merge.getId(db))) confirmation_id = cursor.fetchone()[0] merged = set() for tail_sha1 in tails: children = new_commits.getChildren(tail_sha1) while children: child = children.pop() if child not in merged and new_commits.isAncestorOf( child, merge): merged.add(child) children.update( new_commits.getChildren(child) - merged) merged_values = [(confirmation_id, commit.getId(db)) for commit in merged] cursor.executemany( "INSERT INTO reviewmergecontributions (id, merged) VALUES (%s, %s)", merged_values) db.commit() else: confirmation_id = row[0] message = "Merge %s adds merged-in commits:" % merge.sha1[: 8] for tail_sha1 in tails: for parent_sha1 in merge.parents: if parent_sha1 in new_commits: parent = new_commits.get(parent_sha1) if tail_sha1 in new_commits.getTailsFrom( parent): message += "\n %s..%s" % (tail_sha1[:8], parent_sha1[:8]) message += """ Please confirm that this is intended by loading: %s/confirmmerge?id=%d""" % (dbutils.getURLPrefix(db, user), confirmation_id) raise index.IndexException(message) elif row[2] is not None: if row[2] == merge.getId(db): cursor.execute( "SELECT merged FROM reviewmergecontributions WHERE id=%s", (row[0], )) for (merged_id, ) in cursor: merged = gitutils.Commit.fromId( db, review.repository, merged_id) if merged.sha1 in merge.parents: new_commits = new_commits.without([merged]) break else: tail = gitutils.Commit.fromId(db, review.repository, row[2]) cut = [ gitutils.Commit.fromSHA1(db, review.repository, sha1) for sha1 in tail.parents if sha1 in new_commits ] new_commits = new_commits.without(cut) if commitset: commitset &= set(new_commits) commits = [commit for commit in commits if commit in commitset] changesets, silent_commits, silent_changesets = \ createChangesetsForCommits(db, commits, silent_if_empty, full_merges, replayed_rebases) if not new_review: print "Adding %d commit%s to the review at:\n %s" % ( len(commits), len(commits) > 1 and "s" or "", review.getURL(db)) reviewchangesets_values = [(review.id, changeset.id) for changeset in changesets] cursor.executemany( """INSERT INTO reviewchangesets (review, changeset) VALUES (%s, %s)""", reviewchangesets_values) cursor.executemany( """INSERT INTO reviewfiles (review, changeset, file, deleted, inserted) SELECT reviewchangesets.review, reviewchangesets.changeset, fileversions.file, COALESCE(SUM(chunks.deleteCount), 0), COALESCE(SUM(chunks.insertCount), 0) FROM reviewchangesets JOIN fileversions USING (changeset) LEFT OUTER JOIN chunks USING (changeset, file) WHERE reviewchangesets.review=%s AND reviewchangesets.changeset=%s GROUP BY reviewchangesets.review, reviewchangesets.changeset, fileversions.file""", reviewchangesets_values) new_reviewers, new_watchers = assignChanges(db, user, review, changesets=changesets) cursor.execute( "SELECT include FROM reviewrecipientfilters WHERE review=%s AND uid IS NULL", (review.id, )) try: opt_out = cursor.fetchone()[0] is True except: opt_out = True if not new_review: for user_id in new_reviewers: new_reviewuser = dbutils.User.fromId(db, user_id) print "Added reviewer: %s <%s>" % (new_reviewuser.fullname, new_reviewuser.email) if opt_out: # If the user has opted out from receiving e-mails about this # review while only watching it, clear the opt-out now that the # user becomes a reviewer. cursor.execute( "DELETE FROM reviewrecipientfilters WHERE review=%s AND uid=%s AND include=FALSE", (review.id, user_id)) for user_id in new_watchers: new_reviewuser = dbutils.User.fromId(db, user_id) print "Added watcher: %s <%s>" % (new_reviewuser.fullname, new_reviewuser.email) review.incrementSerial(db) reviewing.comment.propagateCommentChains(db, user, review, new_commits, replayed_rebases) if pending_mails is None: pending_mails = [] notify_commits = filter(lambda commit: commit not in silent_commits, commits) notify_changesets = filter( lambda changeset: changeset not in silent_changesets, changesets) if not new_review and notify_changesets: recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend( mail.sendReviewAddedCommits(db, user, to_user, recipients, review, notify_commits, notify_changesets, tracked_branch=tracked_branch)) mail.sendPendingMails(pending_mails) review.reviewers.extend( [User.fromId(db, user_id) for user_id in new_reviewers]) for user_id in new_watchers: review.watchers[User.fromId(db, user_id)] = "automatic" return True
def createReview( db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None, ): cursor = db.cursor() if via_push: applyparentfilters = bool(user.getPreference(db, "review.applyUpstreamFilters")) branch = dbutils.Branch.fromName(db, repository, branch_name) if branch is not None: raise OperationFailure( code="branchexists", title="Invalid review branch name", message="""\ <p>There is already a branch named <code>%s</code> in the repository. You have to select a different name.</p> <p>If you believe the existing branch was created during an earlier (failed) attempt to create this review, you can try to delete it from the repository using the command<p> <pre> git push <remote> :%s</pre> <p>and then press the "Submit Review" button on this page again.""" % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)), is_html=True, ) if not commits: raise OperationFailure( code="nocommits", title="No commits specified", message="You need at least one commit to create a review." ) commitset = log_commitset.CommitSet(commits) heads = commitset.getHeads() if len(heads) != 1: # There is really no plausible way for this error to occur. raise OperationFailure( code="disconnectedtree", title="Disconnected tree", message=( "The specified commits do do not form a single connected " "tree. Creating a review of them is not supported." ), ) head = heads.pop() if len(commitset.getTails()) != 1: tail_id = None else: tail_id = gitutils.Commit.fromSHA1(db, repository, commitset.getTails().pop()).getId(db) if not via_push: try: repository.createBranch(branch_name, head.sha1) except gitutils.GitCommandError as error: raise OperationFailure( code="branchfailed", title="Failed to create review branch", message=( "<p><b>Output from git:</b></p>" "<code style='padding-left: 1em'>%s</code>" % htmlutils.htmlify(error.output) ), is_html=True, ) try: cursor.execute( "INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id], ) branch_id = cursor.fetchone()[0] reachable_values = [(branch_id, commit.getId(db)) for commit in commits] cursor.executemany("INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values) cursor.execute( "INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters), ) review = dbutils.Review.fromId(db, cursor.fetchone()[0]) cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id)) if reviewfilters is not None: cursor.executemany( """INSERT INTO reviewfilters (review, uid, path, type, creator) VALUES (%s, %s, %s, %s, %s)""", [ (review.id, filter_user_id, filter_path, filter_type, user.id) for filter_user_id, filter_path, filter_type, filter_delegate in reviewfilters ], ) is_opt_in = False if recipientfilters is not None: cursor.executemany( "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)", [(review.id, filter_user_id, filter_include) for filter_user_id, filter_include in recipientfilters], ) for filter_user_id, filter_include in recipientfilters: if filter_user_id is None and not filter_include: is_opt_in = True addCommitsToReview(db, user, review, commits, new_review=True) if from_branch_name is not None: cursor.execute( "UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name), ) # Reload to get list of changesets added by addCommitsToReview(). review = dbutils.Review.fromId(db, review.id) pending_mails = [] recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend(mail.sendReviewCreated(db, user, to_user, recipients, review)) if not is_opt_in and to_user.getPreference(db, "review.defaultOptOut"): cursor.execute( "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, FALSE)", (review.id, to_user.id), ) db.commit() mail.sendPendingMails(pending_mails) return review except: if not via_push: repository.run("branch", "-D", branch_name) raise
def createReview(db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None): cursor = db.cursor() if via_push: applyparentfilters = bool( user.getPreference(db, 'review.applyUpstreamFilters')) branch = dbutils.Branch.fromName(db, repository, branch_name) if branch is not None: raise OperationFailure( code="branchexists", title="Invalid review branch name", message="""\ <p>There is already a branch named <code>%s</code> in the repository. You have to select a different name.</p> <p>If you believe the existing branch was created during an earlier (failed) attempt to create this review, you can try to delete it from the repository using the command<p> <pre> git push <remote> :%s</pre> <p>and then press the "Submit Review" button on this page again.""" % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)), is_html=True) commitset = log_commitset.CommitSet(commits) if len(commitset.getHeads()) != 1: raise Exception, "invalid commit-set; multiple heads" head = commitset.getHeads().pop() if len(commitset.getTails()) != 1: tail_id = None else: tail_id = gitutils.Commit.fromSHA1( db, repository, commitset.getTails().pop()).getId(db) if not via_push: repository.branch(branch_name, head.sha1) try: cursor.execute( "INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id]) branch_id = cursor.fetchone()[0] reachable_values = [(branch_id, commit.getId(db)) for commit in commits] cursor.executemany( "INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values) cursor.execute( "INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters)) review = dbutils.Review.fromId(db, cursor.fetchone()[0]) cursor.execute( "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id)) if reviewfilters is not None: cursor.executemany( "INSERT INTO reviewfilters (review, uid, directory, file, type, creator) VALUES (%s, %s, %s, %s, %s, %s)", [(review.id, filter_user_id, filter_directory_id, filter_file_id, filter_type, user.id) for filter_directory_id, filter_file_id, filter_type, filter_delegate, filter_user_id in reviewfilters]) if recipientfilters is not None: cursor.executemany( "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)", [(review.id, filter_user_id, filter_include) for filter_user_id, filter_include in recipientfilters]) addCommitsToReview(db, user, review, commits, new_review=True) if from_branch_name is not None: cursor.execute( "UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name)) # Reload to get list of changesets added by addCommitsToReview(). review = dbutils.Review.fromId(db, review.id) pending_mails = [] recipients = review.getRecipients(db) for to_user in recipients: pending_mails.extend( mail.sendReviewCreated(db, user, to_user, recipients, review)) db.commit() mail.sendPendingMails(pending_mails) return review except: if not via_push: repository.run("branch", "-D", branch_name) raise