def getReviewersAndWatchers(db, repository, commits=None, changesets=None, reviewfilters=None, applyfilters=True, applyparentfilters=False): """getReviewersAndWatchers(db, commits=None, changesets=None) -> tuple Returns a tuple containing two dictionaries, each mapping file IDs to dictionaries mapping user IDs to sets of changeset IDs. The first dictionary defines the reviwers of each file, the second dictionary defines the watchers of each file. For any changes in a file for which no reviewer is identified, None is used as a key in the dictionary instead of a real user ID.""" if changesets is None: changesets = [] changeset_utils.createChangesets(db, repository, commits) for commit in commits: changesets.extend(changeset_utils.createChangeset(db, None, repository, commit, do_highlight=False)) cursor = db.cursor() filters = Filters() filters.setFiles(db, list(getFileIdsFromChangesets(changesets))) if applyfilters: filters.load(db, repository=repository, recursive=applyparentfilters) if reviewfilters: filters.addFilters(reviewfilters) reviewers = {} watchers = {} for changeset in changesets: author_user_ids = changeset.child.author.getUserIds(db) if changeset.child else set() cursor.execute("SELECT DISTINCT file FROM fileversions WHERE changeset=%s", (changeset.id,)) for (file_id,) in cursor: reviewers_found = False for user_id, (filter_type, delegate) in filters.listUsers(file_id).items(): if filter_type == 'reviewer': if user_id not in author_user_ids: reviewer_user_ids = [user_id] elif delegate: reviewer_user_ids = [] for delegate_user_name in delegate.split(","): delegate_user = dbutils.User.fromName(db, delegate_user_name) reviewer_user_ids.append(delegate_user.id) else: reviewer_user_ids = [] for reviewer_user_id in reviewer_user_ids: reviewers.setdefault(file_id, {}).setdefault(reviewer_user_id, set()).add(changeset.id) reviewers_found = True else: watchers.setdefault(file_id, {}).setdefault(user_id, set()).add(changeset.id) if not reviewers_found: reviewers.setdefault(file_id, {}).setdefault(None, set()).add(changeset.id) return reviewers, watchers
def renderShowFilters(req, db, user): path = req.getParameter("path", "/") repo_name = req.getParameter("repository", user.getPreference(db, "defaultRepository")) repository = gitutils.Repository.fromParameter(db, repo_name) show_path = path if path.endswith("/") or repository.getHead(db).isDirectory(path): path = path.rstrip("/") + "/dummy.txt" file_id = dbutils.find_file(db, path=path) filters = reviewing.filters.Filters() filters.setFiles(db, [file_id]) filters.load(db, repository=repository, recursive=True) reviewers = [] watchers = [] for user_id, (filter_type, _delegate) in filters.listUsers(file_id).items(): if filter_type == 'reviewer': reviewers.append(user_id) else: watchers.append(user_id) result = "Path: %s\n" % show_path reviewers_found = False watchers_found = False for reviewer_id in sorted(reviewers): if not reviewers_found: result += "\nReviewers:\n" reviewers_found = True reviewer = dbutils.User.fromId(db, reviewer_id) result += " %s <%s>\n" % (reviewer.fullname, reviewer.email) for watcher_id in sorted(watchers): if not watchers_found: result += "\nWatchers:\n" watchers_found = True watcher = dbutils.User.fromId(db, watcher_id) result += " %s <%s>\n" % (watcher.fullname, watcher.email) if not reviewers_found and not watchers_found: result += "\nNo matching filters found.\n" return page.utils.ResponseBody(result, content_type="text/plain")
def renderShowFilters(req, db, user): path = req.getParameter("path", "/") repo_name = req.getParameter( "repository", user.getPreference(db, "defaultRepository")) repository = gitutils.Repository.fromParameter(db, repo_name) show_path = path if path.endswith("/") or repository.getHead(db).isDirectory(path): path = path.rstrip("/") + "/dummy.txt" file_id = dbutils.find_file(db, path=path) filters = reviewing.filters.Filters() filters.setFiles(db, [file_id]) filters.load(db, repository=repository, recursive=True) reviewers = [] watchers = [] for user_id, (filter_type, _delegate) in filters.listUsers(file_id).items(): if filter_type == 'reviewer': reviewers.append(user_id) else: watchers.append(user_id) result = "Path: %s\n" % show_path reviewers_found = False watchers_found = False for reviewer_id in sorted(reviewers): if not reviewers_found: result += "\nReviewers:\n" reviewers_found = True reviewer = dbutils.User.fromId(db, reviewer_id) result += " %s <%s>\n" % (reviewer.fullname, reviewer.email) for watcher_id in sorted(watchers): if not watchers_found: result += "\nWatchers:\n" watchers_found = True watcher = dbutils.User.fromId(db, watcher_id) result += " %s <%s>\n" % (watcher.fullname, watcher.email) if not reviewers_found and not watchers_found: result += "\nNo matching filters found.\n" return page.utils.ResponseBody(result, content_type="text/plain")
data = json_decode(sys.stdin.readline()) batch_id = data["batch_id"] was_accepted = data["was_accepted"] is_accepted = data["is_accepted"] pending_mails = reviewing.utils.generateMailsForBatch(db, batch_id, was_accepted, is_accepted) elif command == "generate-mails-for-assignments-transaction": data = json_decode(sys.stdin.readline()) transaction_id = data["transaction_id"] pending_mails = reviewing.utils.generateMailsForAssignmentsTransaction(db, transaction_id) elif command == "apply-filters": data = json_decode(sys.stdin.readline()) filters = reviewing.filters.Filters() user = dbutils.User.fromId(db, data["user_id"]) if "user_id" in data else None if "review_id" in data: review = dbutils.Review.fromId(db, data["review_id"], load_commits=False) filters.setFiles(db, review=review) filters.load(db, review=review, user=user, added_review_filters=data.get("added_review_filters", []), removed_review_filters=data.get("removed_review_filters", [])) else: repository = gitutils.Repository.fromId(db, data["repository_id"]) filters.setFiles(db, file_ids=data["file_ids"]) filters.load(db, repository=repository, recursive=data.get("recursive", False), user=user) sys.stdout.write(json_encode(filters.data) + "\n") else: print "unknown command: %s" % command sys.exit(1) if pending_mails is not None: sys.stdout.write(json_encode(pending_mails) + "\n")
def main(): parser = argparse.ArgumentParser() parser.add_argument("-u", dest="user_id", type=int) parser.add_argument("-l", dest="auth_labels", action="append", default=[]) parser.add_argument("command", nargs="*") arguments = parser.parse_args() try: init(arguments.user_id, arguments.auth_labels) for command in arguments.command: pending_mails = None if command == "generate-mails-for-batch": data = json_decode(sys.stdin.readline()) batch_id = data["batch_id"] was_accepted = data["was_accepted"] is_accepted = data["is_accepted"] pending_mails = reviewing.utils.generateMailsForBatch(db, batch_id, was_accepted, is_accepted) elif command == "generate-mails-for-assignments-transaction": data = json_decode(sys.stdin.readline()) transaction_id = data["transaction_id"] pending_mails = reviewing.utils.generateMailsForAssignmentsTransaction(db, transaction_id) elif command == "apply-filters": data = json_decode(sys.stdin.readline()) filters = reviewing.filters.Filters() user = dbutils.User.fromId(db, data["user_id"]) if "user_id" in data else None if "review_id" in data: review = dbutils.Review.fromId(db, data["review_id"]) filters.setFiles(db, review=review) filters.load(db, review=review, user=user, added_review_filters=data.get("added_review_filters", []), removed_review_filters=data.get("removed_review_filters", [])) else: repository = gitutils.Repository.fromId(db, data["repository_id"]) filters.setFiles(db, file_ids=data["file_ids"]) filters.load(db, repository=repository, recursive=data.get("recursive", False), user=user) sys.stdout.write(json_encode(filters.data) + "\n") elif command == "generate-custom-mails": pending_mails = [] for data in json_decode(sys.stdin.readline()): from_user = dbutils.User.fromId(db, data["sender"]) if data.get("recipients"): recipients = [dbutils.User.fromId(db, user_id) for user_id in data["recipients"]] else: recipients = None subject = data["subject"] headers = data.get("headers") body = data["body"] if "review_id" in data: review = dbutils.Review.fromId(db, data["review_id"]) else: review = None pending_mails.extend(sendCustomMail( from_user, recipients, subject, headers, body, review)) elif command == "set-review-state": data = json_decode(sys.stdin.readline()) error = "" try: user = dbutils.User.fromId(db, data["user_id"]) review = dbutils.Review.fromId(db, data["review_id"]) if review.state != data["old_state"]: error = "invalid old state" elif data["new_state"] == "open": review.reopen(db, user) elif data["new_state"] == "closed": review.close(db, user) elif data["new_state"] == "dropped": review.drop(db, user) else: error = "invalid new state" except dbutils.NoSuchUser: error = "invalid user id" except dbutils.NoSuchReview: error = "invalid review id" except Exception as error: error = str(error) sys.stdout.write(error + "\n") elif command in HANDLERS: data_in = json_decode(sys.stdin.readline()) data_out = HANDLERS[command](data_in) sys.stdout.write(json_encode(data_out) + "\n") else: sys.stdout.write(json_encode("unknown command: %s" % command) + "\n") sys.exit(0) if pending_mails is not None: sys.stdout.write(json_encode(pending_mails) + "\n") finish() except Exception: sys.stdout.write(json_encode(traceback.format_exc()) + "\n") finally: abort()
def process(self, db, user, repository_id=None, filter_id=None): if user.isAnonymous(): return OperationFailureMustLogin() cursor = db.cursor() if filter_id is not None: cursor.execute( """SELECT repository, path, type, delegate FROM filters WHERE id=%s""", (filter_id,), ) repository_id, filter_path, filter_type, filter_delegate = cursor.fetchone() if repository_id is None: cursor.execute( """SELECT reviews.id, applyfilters, applyparentfilters, branches.repository FROM reviews JOIN branches ON (reviews.branch=branches.id) WHERE reviews.state!='closed'""" ) else: cursor.execute( """SELECT reviews.id, applyfilters, applyparentfilters, branches.repository FROM reviews JOIN branches ON (reviews.branch=branches.id) WHERE reviews.state!='closed' AND branches.repository=%s""", (repository_id,), ) repositories = {} # list(review_file_id) assign_changes = [] # set(review_id) assigned_reviews = set() # set(review_id) watched_reviews = set() for review_id, applyfilters, applyparentfilters, repository_id in cursor.fetchall(): if repository_id in repositories: repository = repositories[repository_id] else: repository = gitutils.Repository.fromId(db, repository_id) repositories[repository_id] = repository review = reviewing.filters.Filters.Review(review_id, applyfilters, applyparentfilters, repository) filters = reviewing.filters.Filters() filters.setFiles(db, review=review) if filter_id is not None: filters.addFilter(user.id, filter_path, filter_type, filter_delegate) else: filters.load(db, review=review, user=user) cursor.execute( """SELECT commits.id, usergitemails.uid, reviewfiles.file, reviewfiles.id FROM commits JOIN gitusers ON (gitusers.id=commits.author_gituser) LEFT OUTER JOIN usergitemails ON (usergitemails.email=gitusers.email) JOIN changesets ON (changesets.child=commits.id) JOIN reviewfiles ON (reviewfiles.changeset=changesets.id) LEFT OUTER JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id AND reviewuserfiles.uid=%s) WHERE reviewfiles.review=%s AND reviewuserfiles.uid IS NULL""", (user.id, review_id), ) for commit_id, author_id, file_id, review_file_id in cursor.fetchall(): if author_id != user.id: association = filters.getUserFileAssociation(user.id, file_id) if association == "reviewer": assign_changes.append(review_file_id) assigned_reviews.add(review_id) elif association == "watcher": watched_reviews.add(review_id) cursor.execute( """SELECT reviews.id FROM reviews LEFT OUTER JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s) WHERE reviews.id=ANY (%s) AND reviewusers.uid IS NULL""", (user.id, list(assigned_reviews) + list(watched_reviews)), ) new_reviews = set(review_id for (review_id,) in cursor) cursor.executemany( """INSERT INTO reviewusers (review, uid) VALUES (%s, %s)""", [(review_id, user.id) for review_id in new_reviews], ) cursor.executemany( """INSERT INTO reviewuserfiles (file, uid) VALUES (%s, %s)""", [(review_file_id, user.id) for review_file_id in assign_changes], ) db.commit() watched_reviews &= new_reviews watched_reviews -= assigned_reviews cursor.execute( """SELECT id, summary FROM reviews WHERE id=ANY (%s)""", (list(assigned_reviews | watched_reviews),), ) return OperationResult( assigned_reviews=sorted(assigned_reviews), watched_reviews=sorted(watched_reviews), summaries=dict(cursor) )
def addReviewFilters(db, creator, user, review, reviewer_paths, watcher_paths): cursor = db.cursor() cursor.execute("INSERT INTO reviewassignmentstransactions (review, assigner) VALUES (%s, %s) RETURNING id", (review.id, creator.id)) transaction_id = cursor.fetchone()[0] def add(filter_type, paths): for path in paths: cursor.execute("""SELECT id, type FROM reviewfilters WHERE review=%s AND uid=%s AND path=%s""", (review.id, user.id, path)) row = cursor.fetchone() if row: old_filter_id, old_filter_type = row if old_filter_type == filter_type: continue else: cursor.execute("""DELETE FROM reviewfilters WHERE id=%s""", (old_filter_id,)) cursor.execute("""INSERT INTO reviewfilterchanges (transaction, uid, path, type, created) VALUES (%s, %s, %s, %s, false)""", (transaction_id, user.id, path, old_filter_type)) cursor.execute("""INSERT INTO reviewfilters (review, uid, path, type, creator) VALUES (%s, %s, %s, %s, %s)""", (review.id, user.id, path, filter_type, creator.id)) cursor.execute("""INSERT INTO reviewfilterchanges (transaction, uid, path, type, created) VALUES (%s, %s, %s, %s, true)""", (transaction_id, user.id, path, filter_type)) add("reviewer", reviewer_paths) add("watcher", watcher_paths) filters = Filters() filters.setFiles(db, review=review) filters.load(db, review=review, user=user) if user not in review.reviewers and user not in review.watchers and user not in review.owners: cursor.execute("""INSERT INTO reviewusers (review, uid, type) VALUES (%s, %s, 'manual')""", (review.id, user.id,)) delete_files = set() insert_files = set() if watcher_paths: # Unassign changes currently assigned to the affected user. cursor.execute("""SELECT reviewfiles.id, reviewfiles.file FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s AND reviewuserfiles.uid=%s""", (review.id, user.id)) for review_file_id, file_id in cursor: if not filters.isReviewer(user.id, file_id): delete_files.add(review_file_id) if reviewer_paths: # Assign changes currently not assigned to the affected user. cursor.execute("""SELECT reviewfiles.id, reviewfiles.file FROM reviewfiles JOIN changesets ON (changesets.id=reviewfiles.changeset) JOIN commits ON (commits.id=changesets.child) JOIN gitusers ON (gitusers.id=commits.author_gituser) LEFT OUTER JOIN usergitemails USING (email) LEFT OUTER JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id AND reviewuserfiles.uid=%s) WHERE reviewfiles.review=%s AND (usergitemails.uid IS NULL OR usergitemails.uid!=%s) AND reviewuserfiles.uid IS NULL""", (user.id, review.id, user.id)) for review_file_id, file_id in cursor: if filters.isReviewer(user.id, file_id): insert_files.add(review_file_id) if delete_files: cursor.executemany("DELETE FROM reviewuserfiles WHERE file=%s AND uid=%s", izip(delete_files, repeat(user.id))) cursor.executemany("INSERT INTO reviewassignmentchanges (transaction, file, uid, assigned) VALUES (%s, %s, %s, false)", izip(repeat(transaction_id), delete_files, repeat(user.id))) if insert_files: cursor.executemany("INSERT INTO reviewuserfiles (file, uid) VALUES (%s, %s)", izip(insert_files, repeat(user.id))) cursor.executemany("INSERT INTO reviewassignmentchanges (transaction, file, uid, assigned) VALUES (%s, %s, %s, true)", izip(repeat(transaction_id), insert_files, repeat(user.id))) return generateMailsForAssignmentsTransaction(db, transaction_id)
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 addReviewFilters(db, creator, user, review, reviewer_paths, watcher_paths): cursor = db.cursor() cursor.execute( "INSERT INTO reviewassignmentstransactions (review, assigner) VALUES (%s, %s) RETURNING id", (review.id, creator.id)) transaction_id = cursor.fetchone()[0] def add(filter_type, paths): for path in paths: cursor.execute( """SELECT id, type FROM reviewfilters WHERE review=%s AND uid=%s AND path=%s""", (review.id, user.id, path)) row = cursor.fetchone() if row: old_filter_id, old_filter_type = row if old_filter_type == filter_type: continue else: cursor.execute( """DELETE FROM reviewfilters WHERE id=%s""", (old_filter_id, )) cursor.execute( """INSERT INTO reviewfilterchanges (transaction, uid, path, type, created) VALUES (%s, %s, %s, %s, false)""", (transaction_id, user.id, path, old_filter_type)) cursor.execute( """INSERT INTO reviewfilters (review, uid, path, type, creator) VALUES (%s, %s, %s, %s, %s)""", (review.id, user.id, path, filter_type, creator.id)) cursor.execute( """INSERT INTO reviewfilterchanges (transaction, uid, path, type, created) VALUES (%s, %s, %s, %s, true)""", (transaction_id, user.id, path, filter_type)) add("reviewer", reviewer_paths) add("watcher", watcher_paths) filters = Filters() filters.setFiles(db, review=review) filters.load(db, review=review, user=user) if user not in review.reviewers and user not in review.watchers and user not in review.owners: cursor.execute( """INSERT INTO reviewusers (review, uid, type) VALUES (%s, %s, 'manual')""", ( review.id, user.id, )) delete_files = set() insert_files = set() if watcher_paths: # Unassign changes currently assigned to the affected user. cursor.execute( """SELECT reviewfiles.id, reviewfiles.file FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s AND reviewuserfiles.uid=%s""", (review.id, user.id)) for review_file_id, file_id in cursor: if not filters.isReviewer(user.id, file_id): delete_files.add(review_file_id) if reviewer_paths: # Assign changes currently not assigned to the affected user. cursor.execute( """SELECT reviewfiles.id, reviewfiles.file FROM reviewfiles JOIN changesets ON (changesets.id=reviewfiles.changeset) JOIN commits ON (commits.id=changesets.child) JOIN gitusers ON (gitusers.id=commits.author_gituser) LEFT OUTER JOIN usergitemails ON (usergitemails.email=gitusers.email AND usergitemails.uid=%s) LEFT OUTER JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id AND reviewuserfiles.uid=%s) WHERE reviewfiles.review=%s AND usergitemails.uid IS NULL AND reviewuserfiles.uid IS NULL""", (user.id, user.id, review.id)) for review_file_id, file_id in cursor: if filters.isReviewer(user.id, file_id): insert_files.add(review_file_id) if delete_files: cursor.executemany( "DELETE FROM reviewuserfiles WHERE file=%s AND uid=%s", izip(delete_files, repeat(user.id))) cursor.executemany( "INSERT INTO reviewassignmentchanges (transaction, file, uid, assigned) VALUES (%s, %s, %s, false)", izip(repeat(transaction_id), delete_files, repeat(user.id))) if insert_files: cursor.executemany( "INSERT INTO reviewuserfiles (file, uid) VALUES (%s, %s)", izip(insert_files, repeat(user.id))) cursor.executemany( "INSERT INTO reviewassignmentchanges (transaction, file, uid, assigned) VALUES (%s, %s, %s, true)", izip(repeat(transaction_id), insert_files, repeat(user.id))) return generateMailsForAssignmentsTransaction(db, transaction_id)
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 assignChanges(db, user, review, commits=None, changesets=None, update=False): cursor = db.cursor() if changesets is None: assert commits is not None changesets = [] for commit in commits: changesets.extend( changeset_utils.createChangeset(db, user, review.repository, commit)) applyfilters = review.applyfilters applyparentfilters = review.applyparentfilters reviewers, watchers = getReviewersAndWatchers( db, review.repository, changesets=changesets, reviewfilters=review.getReviewFilters(db), applyfilters=applyfilters, applyparentfilters=applyparentfilters) cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (review.id, )) reviewusers = set([user_id for (user_id, ) in cursor]) reviewusers_values = set() reviewuserfiles_values = set() reviewuserfiles_existing = {} if update: cursor.execute( """SELECT reviewuserfiles.uid, reviewfiles.changeset, reviewfiles.file FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s""", (review.id, )) for user_id, changeset_id, file_id in cursor: reviewuserfiles_existing[(user_id, changeset_id, file_id)] = True new_reviewers = set() new_watchers = set() cursor.execute( """SELECT DISTINCT uid FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE review=%s""", (review.id, )) old_reviewers = set([user_id for (user_id, ) in cursor]) for file_id, file_users in reviewers.items(): for user_id, user_changesets in file_users.items(): if user_id: new_reviewers.add(user_id) if user_id not in reviewusers: reviewusers.add(user_id) reviewusers_values.add((review.id, user_id)) for changeset_id in user_changesets: if (user_id, changeset_id, file_id) not in reviewuserfiles_existing: reviewuserfiles_values.add( (user_id, review.id, changeset_id, file_id)) for file_id, file_users in watchers.items(): for user_id, user_changesets in file_users.items(): if user_id: if user_id not in reviewusers: new_watchers.add(user_id) reviewusers.add(user_id) reviewusers_values.add((review.id, user_id)) new_reviewers -= old_reviewers new_watchers -= old_reviewers | new_reviewers cursor.executemany("INSERT INTO reviewusers (review, uid) VALUES (%s, %s)", reviewusers_values) cursor.executemany( "INSERT INTO reviewuserfiles (file, uid) SELECT id, %s FROM reviewfiles WHERE review=%s AND changeset=%s AND file=%s", reviewuserfiles_values) if configuration.extensions.ENABLED: cursor.execute( """SELECT id, uid, extension, path FROM extensionhookfilters WHERE repository=%s""", (review.repository.id, )) rows = cursor.fetchall() if rows: if commits is None: commits = set() for changeset in changesets: commits.add(changeset.child) commits = list(commits) filters = Filters() filters.setFiles(db, list(getFileIdsFromChangesets(changesets))) for filter_id, user_id, extension_id, path in rows: filters.addFilter(user_id, path, None, None, filter_id) for filter_id, file_ids in filters.matched_files.items(): extensions.role.filterhook.queueFilterHookEvent( db, filter_id, review, user, commits, file_ids) return new_reviewers, new_watchers
def assignChanges(db, user, review, commits=None, changesets=None, update=False): cursor = db.cursor() if changesets is None: assert commits is not None changesets = [] for commit in commits: changesets.extend(changeset_utils.createChangeset(db, user, review.repository, commit)) applyfilters = review.applyfilters applyparentfilters = review.applyparentfilters reviewers, watchers = getReviewersAndWatchers(db, review.repository, changesets=changesets, reviewfilters=review.getReviewFilters(db), applyfilters=applyfilters, applyparentfilters=applyparentfilters) cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (review.id,)) reviewusers = set([user_id for (user_id,) in cursor]) reviewusers_values = set() reviewuserfiles_values = set() reviewuserfiles_existing = {} if update: cursor.execute("""SELECT reviewuserfiles.uid, reviewfiles.changeset, reviewfiles.file FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE reviewfiles.review=%s""", (review.id,)) for user_id, changeset_id, file_id in cursor: reviewuserfiles_existing[(user_id, changeset_id, file_id)] = True new_reviewers = set() new_watchers = set() cursor.execute("""SELECT DISTINCT uid FROM reviewfiles JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id) WHERE review=%s""", (review.id,)) old_reviewers = set([user_id for (user_id,) in cursor]) for file_id, file_users in reviewers.items(): for user_id, user_changesets in file_users.items(): if user_id: new_reviewers.add(user_id) if user_id not in reviewusers: reviewusers.add(user_id) reviewusers_values.add((review.id, user_id)) for changeset_id in user_changesets: if (user_id, changeset_id, file_id) not in reviewuserfiles_existing: reviewuserfiles_values.add((user_id, review.id, changeset_id, file_id)) for file_id, file_users in watchers.items(): for user_id, user_changesets in file_users.items(): if user_id: if user_id not in reviewusers: new_watchers.add(user_id) reviewusers.add(user_id) reviewusers_values.add((review.id, user_id)) new_reviewers -= old_reviewers new_watchers -= old_reviewers | new_reviewers cursor.executemany("INSERT INTO reviewusers (review, uid) VALUES (%s, %s)", reviewusers_values) cursor.executemany("INSERT INTO reviewuserfiles (file, uid) SELECT id, %s FROM reviewfiles WHERE review=%s AND changeset=%s AND file=%s", reviewuserfiles_values) if configuration.extensions.ENABLED: cursor.execute("""SELECT id, uid, extension, path FROM extensionhookfilters WHERE repository=%s""", (review.repository.id,)) rows = cursor.fetchall() if rows: if commits is None: commits = set() for changeset in changesets: commits.add(changeset.child) commits = list(commits) filters = Filters() filters.setFiles(db, list(getFileIdsFromChangesets(changesets))) for filter_id, user_id, extension_id, path in rows: filters.addFilter(user_id, path, None, None, filter_id) for filter_id, file_ids in filters.matched_files.items(): extensions.role.filterhook.queueFilterHookEvent( db, filter_id, review, user, commits, file_ids) return new_reviewers, new_watchers
data = json_decode(sys.stdin.readline()) batch_id = data["batch_id"] was_accepted = data["was_accepted"] is_accepted = data["is_accepted"] pending_mails = reviewing.utils.generateMailsForBatch(db, batch_id, was_accepted, is_accepted) elif command == "generate-mails-for-assignments-transaction": data = json_decode(sys.stdin.readline()) transaction_id = data["transaction_id"] pending_mails = reviewing.utils.generateMailsForAssignmentsTransaction(db, transaction_id) elif command == "apply-filters": data = json_decode(sys.stdin.readline()) filters = reviewing.filters.Filters() user = dbutils.User.fromId(db, data["user_id"]) if "user_id" in data else None if "review_id" in data: review = dbutils.Review.fromId(db, data["review_id"], load_commits=False) filters.setFiles(db, review=review) filters.load(db, review=review, user=user, added_review_filters=data.get("added_review_filters", []), removed_review_filters=data.get("removed_review_filters", [])) else: repository = gitutils.Repository.fromId(db, data["repository_id"]) filters.setFiles(db, file_ids=data["file_ids"]) filters.load(db, repository=repository, recursive=data.get("recursive", False), user=user) sys.stdout.write(json_encode(filters.data) + "\n") elif command == "generate-custom-mails": pending_mails = [] for data in json_decode(sys.stdin.readline()): from_user = dbutils.User.fromId(db, data["sender"]) if data.get("recipients"): recipients = [dbutils.User.fromId(db, user_id) for user_id in data["recipients"]]
def process(self, db, user, repository_id=None, filter_id=None): if user.isAnonymous(): return OperationFailureMustLogin() cursor = db.cursor() if filter_id is not None: cursor.execute( """SELECT repository, path, type, delegate FROM filters WHERE id=%s""", (filter_id, )) repository_id, filter_path, filter_type, filter_delegate = cursor.fetchone( ) if repository_id is None: cursor.execute( """SELECT reviews.id, applyfilters, applyparentfilters, branches.repository FROM reviews JOIN branches ON (reviews.branch=branches.id) WHERE reviews.state!='closed'""") else: cursor.execute( """SELECT reviews.id, applyfilters, applyparentfilters, branches.repository FROM reviews JOIN branches ON (reviews.branch=branches.id) WHERE reviews.state!='closed' AND branches.repository=%s""", (repository_id, )) repositories = {} # list(review_file_id) assign_changes = [] # set(review_id) assigned_reviews = set() # set(review_id) watched_reviews = set() for review_id, applyfilters, applyparentfilters, repository_id in cursor.fetchall( ): if repository_id in repositories: repository = repositories[repository_id] else: repository = gitutils.Repository.fromId(db, repository_id) repositories[repository_id] = repository review = reviewing.filters.Filters.Review(review_id, applyfilters, applyparentfilters, repository) filters = reviewing.filters.Filters() filters.setFiles(db, review=review) if filter_id is not None: filters.addFilter(user.id, filter_path, filter_type, filter_delegate, filter_id) else: filters.load(db, review=review, user=user) cursor.execute( """SELECT commits.id, reviewfiles.file, reviewfiles.id FROM commits JOIN gitusers ON (gitusers.id=commits.author_gituser) LEFT OUTER JOIN usergitemails ON (usergitemails.email=gitusers.email AND usergitemails.uid=%s) JOIN changesets ON (changesets.child=commits.id) JOIN reviewfiles ON (reviewfiles.changeset=changesets.id) LEFT OUTER JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id AND reviewuserfiles.uid=%s) WHERE reviewfiles.review=%s AND usergitemails.uid IS NULL AND reviewuserfiles.uid IS NULL""", (user.id, user.id, review_id)) for commit_id, file_id, review_file_id in cursor.fetchall(): association = filters.getUserFileAssociation(user.id, file_id) if association == 'reviewer': assign_changes.append(review_file_id) assigned_reviews.add(review_id) elif association == 'watcher': watched_reviews.add(review_id) cursor.execute( """SELECT reviews.id FROM reviews LEFT OUTER JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s) WHERE reviews.id=ANY (%s) AND reviewusers.uid IS NULL""", (user.id, list(assigned_reviews) + list(watched_reviews))) new_reviews = set(review_id for (review_id, ) in cursor) cursor.executemany( """INSERT INTO reviewusers (review, uid) VALUES (%s, %s)""", [(review_id, user.id) for review_id in new_reviews]) cursor.executemany( """INSERT INTO reviewuserfiles (file, uid) VALUES (%s, %s)""", [(review_file_id, user.id) for review_file_id in assign_changes]) db.commit() watched_reviews &= new_reviews watched_reviews -= assigned_reviews cursor.execute( """SELECT id, summary FROM reviews WHERE id=ANY (%s)""", (list(assigned_reviews | watched_reviews), )) return OperationResult(assigned_reviews=sorted(assigned_reviews), watched_reviews=sorted(watched_reviews), summaries=dict(cursor))