def process(self, db, user, user_id, value): if user.id != user_id and not user.hasRole(db, "administrator"): raise OperationFailure(code="notallowed", title="Not allowed!", message="Operation not permitted.") for address in value: if not address.strip(): raise OperationError("empty email address is not allowed") if address.count("@") != 1: raise OperationError("invalid email address") cursor = db.cursor() cursor.execute("SELECT email FROM usergitemails WHERE uid=%s", (user_id,)) current_addresses = set(address for (address,) in cursor) new_addresses = set(address.strip() for address in value) for address in (current_addresses - new_addresses): cursor.execute("DELETE FROM usergitemails WHERE uid=%s AND email=%s", (user_id, address)) for address in (new_addresses - current_addresses): cursor.execute("INSERT INTO usergitemails (uid, email) VALUES (%s, %s)", (user_id, address)) db.commit() return OperationResult()
def process(self, db, user, comment_id): comment = Comment.fromId(db, comment_id, user) if user != comment.user: raise OperationError( "can't delete comment written by another user") if comment.state != "draft": raise OperationError( "can't delete comment that has been submitted") cursor = db.cursor() cursor.execute( """UPDATE comments SET state='deleted' WHERE id=%s""", (comment.id, )) if comment.chain.state == "draft": # If the comment chain was a draft, then delete it as well. cursor.execute( """UPDATE commentchains SET state='empty' WHERE id=%s""", (comment.chain.id, )) db.commit() return OperationResult( draft_status=comment.chain.review.getDraftStatus(db, user))
def process(self, db, user, subject, value): if user != subject: Operation.requireRole(db, "administrator", user) for address in value: if not address.strip(): raise OperationError("empty email address is not allowed") if address.count("@") != 1: raise OperationError("invalid email address") cursor = db.cursor() cursor.execute("SELECT email FROM usergitemails WHERE uid=%s", (subject.id, )) current_addresses = set(address for (address, ) in cursor) new_addresses = set(address.strip() for address in value) for address in (current_addresses - new_addresses): cursor.execute( "DELETE FROM usergitemails WHERE uid=%s AND email=%s", (subject.id, address)) for address in (new_addresses - current_addresses): cursor.execute( "INSERT INTO usergitemails (uid, email) VALUES (%s, %s)", (subject.id, address)) db.commit() return OperationResult()
def process(self, db, user, review_id): review = dbutils.Review.fromId(db, review_id) if review.state != "open": raise OperationError("review not open; can't close") if not review.accepted(db): raise OperationError("review is not accepted; can't close") review.close(db, user) db.commit() return OperationResult()
def process(self, db, user, user_id, value): if user.id != user_id and not user.hasRole(db, "administrator"): raise OperationFailure(code="notallowed", title="Not allowed!", message="Operation not permitted.") if not value.strip(): raise OperationError("empty email address is not allowed") if value.count("@") != 1: raise OperationError("invalid email address") db.cursor().execute("UPDATE users SET email=%s WHERE id=%s", (value.strip(), user_id)) db.commit() return OperationResult()
def process(self, db, user, extension_name, author_name=None, version=None, universal=False): if universal: if not user.hasRole(db, "administrator"): raise OperationFailure(code="notallowed", title="Not allowed!", message="Operation not permitted.") user = None if version is not None: if version == "live": version = None elif version.startswith("version/"): version = version[8:] else: raise OperationError( "invalid version, got '%s', expected 'live' or 'version/*'" % version) try: self.perform(db, user, author_name, extension_name, version) except InstallationError as error: raise OperationFailure(code="installationerror", title=error.title, message=error.message, is_html=error.is_html) return OperationResult()
def parseReviewFilters(db, data): reviewfilters = [] for filter_data in data: filter_username = filter_data["username"] filter_type = filter_data["type"] filter_path = filter_data["path"] filter_user = dbutils.User.fromName(db, filter_username) if not filter_user: raise OperationError("no such user: '******'" % filter_username) filter_directory_id = 0 filter_file_id = 0 if filter_path != "/": if filter_path[-1] == "/" or dbutils.is_directory(filter_path): filter_directory_id = dbutils.find_directory(db, path=filter_path) else: filter_file_id = dbutils.find_file(db, path=filter_path) reviewfilters.append((filter_directory_id, filter_file_id, filter_type, None, filter_user.id)) return reviewfilters
def process(self, db, user, service_name): Operation.requireRole(db, "administrator", user) if service_name == "wsgi": for pid in os.listdir(configuration.paths.WSGI_PIDFILE_DIR): try: os.kill(int(pid), signal.SIGINT) except: pass return OperationResult() else: connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) connection.connect( configuration.services.SERVICEMANAGER["address"]) connection.send( textutils.json_encode({ "command": "restart", "service": service_name })) connection.shutdown(socket.SHUT_WR) data = "" while True: received = connection.recv(4096) if not received: break data += received result = textutils.json_decode(data) if result["status"] == "ok": return OperationResult() else: raise OperationError(result["error"])
def process(self, db, user, review_id): review = dbutils.Review.fromId(db, review_id) tails = review.getFilteredTails() if len(tails) > 1: raise OperationError("Multiple tail commits.") try: from customization.filtertags import getUpstreamPattern except ImportError: def getUpstreamTagPattern(review): pass tail = tails.pop() tags = review.branch.repository.run("tag", "-l", "--contains", tail, getUpstreamTagPattern(review) or "*").splitlines() cursor = db.cursor() upstreams = [] for tag in tags: cursor.execute("SELECT sha1 FROM tags WHERE repository=%s AND name=%s", (review.branch.repository.id, tag)) row = cursor.fetchone() if row and row[0] != tail: upstreams.append(tag) return OperationResult(upstreams=upstreams)
def handleStaticResource(req): if req.path == "static-resource/": req.setStatus(403) req.setContentType("text/plain") req.start() return ["Directory listing disabled!"] resource_path = os.path.join(configuration.paths.INSTALL_DIR, "resources", req.path.split("/", 1)[1]) if os.path.abspath(resource_path) != resource_path: raise OperationError("invalid path") if not os.path.isfile(resource_path): req.setStatus(404) req.setContentType("text/plain") req.start() return ["No such resource!"] last_modified = htmlutils.mtime(resource_path) if req.query and req.query == htmlutils.base36(last_modified): HTTP_DATE = "%a, %d %b %Y %H:%M:%S GMT" req.addResponseHeader( "Last-Modified", time.strftime(HTTP_DATE, time.gmtime(last_modified))) req.addResponseHeader( "Expires", time.strftime(HTTP_DATE, time.gmtime(time.time() + 2592000))) req.addResponseHeader("Cache-Control", "max-age=2592000") setContentTypeFromPath(req) req.start() with open(resource_path, "r") as resource_file: return [resource_file.read()]
def process(self, db, user, review_id, new_summary=None, new_description=None, new_owners=None): review = dbutils.Review.fromId(db, review_id) if new_summary is not None: if not new_summary: raise OperationError("invalid new summary") review.setSummary(db, new_summary) if new_description is not None: review.setDescription(db, new_description if new_description else None) if new_owners is not None: remove_owners = set(review.owners) for user_name in new_owners: owner = dbutils.User.fromName(db, user_name) if owner in remove_owners: remove_owners.remove(owner) else: review.addOwner(db, owner) for owner in remove_owners: review.removeOwner(db, owner) db.commit() return OperationResult()
def doPrepareRebase(db, user, review, new_upstream_arg=None, branch=None): commitset = log.commitset.CommitSet(review.branch.commits) tails = commitset.getFilteredTails(review.branch.repository) cursor = db.cursor() cursor.execute("SELECT uid FROM reviewrebases WHERE review=%s AND new_head IS NULL", (review.id,)) row = cursor.fetchone() if row: rebaser = dbutils.User.fromId(db, row[0]) raise OperationError("The review is already being rebased by %s <%s>." % (rebaser.fullname, rebaser.email)) head = commitset.getHeads().pop() head_id = head.getId(db) if new_upstream_arg is not None: if len(tails) > 1: raise OperationError("Rebase to new upstream commit not supported.") tail = gitutils.Commit.fromSHA1(db, review.branch.repository, tails.pop()) old_upstream_id = tail.getId(db) if new_upstream_arg == "0" * 40: new_upstream_id = None else: if not gitutils.re_sha1.match(new_upstream_arg): cursor.execute("SELECT sha1 FROM tags WHERE repository=%s AND name=%s", (review.branch.repository.id, new_upstream_arg)) row = cursor.fetchone() if row: new_upstream_arg = row[0] else: raise OperationError("Specified new upstream is invalid.") try: new_upstream = gitutils.Commit.fromSHA1(db, review.branch.repository, new_upstream_arg) except: raise OperationError("The specified new upstream commit does not exist in Critic's repository.") new_upstream_id = new_upstream.getId(db) else: old_upstream_id = None new_upstream_id = None cursor.execute("""INSERT INTO reviewrebases (review, old_head, new_head, old_upstream, new_upstream, uid, branch) VALUES (%s, %s, NULL, %s, %s, %s, %s)""", (review.id, head_id, old_upstream_id, new_upstream_id, user.id, branch)) review.incrementSerial(db) db.commit()
def process(self, db, user, service_name, lines=40): logfile_paths = { "manager": configuration.services.SERVICEMANAGER["logfile_path"] } for service in configuration.services.SERVICEMANAGER["services"]: logfile_paths[service["name"]] = service["logfile_path"] logfile_path = logfile_paths.get(service_name) if not logfile_path: raise OperationError("unknown service: %s" % service_name) try: logfile = open(logfile_path) except OSError as error: raise OperationError("failed to open logfile: %s" % error.message) return OperationResult(lines=logfile.read().splitlines()[-lines:])
def process(self, db, user, comment_id, new_text): comment = Comment.fromId(db, comment_id, user) if user != comment.user: raise OperationError("can't edit comment written by another user") if comment.state != "draft": raise OperationError("can't edit comment that has been submitted") if not new_text.strip(): raise OperationError("empty comment") cursor = db.cursor() cursor.execute("""UPDATE comments SET comment=%s, time=now() WHERE id=%s""", (new_text, comment.id)) db.commit() return OperationResult(draft_status=comment.chain.review.getDraftStatus(db, user))
def process(self, db, user, review_id): review = dbutils.Review.fromId(db, review_id) if review.state == "open": raise OperationError("review already open; can't reopen") review.reopen(db, user) db.commit() return OperationResult()
def process(self, db, user, review_id): review = dbutils.Review.fromId(db, review_id) if review.state != "open": raise OperationError("review not open; can't drop") review.drop(db, user) db.commit() return OperationResult()
def process(self, db, user, user_id, value): if user.id != user_id: Operation.requireRole(db, "administrator", user) if not value.strip(): raise OperationError("empty display name is not allowed") db.cursor().execute("UPDATE users SET fullname=%s WHERE id=%s", (value.strip(), user_id)) db.commit() return OperationResult()
def process(self, db, user, chain_id, new_type): chain = CommentChain.fromId(db, chain_id, user) review = chain.review if chain.type == new_type: raise OperationError("the comment chain's type is already '%s'" % new_type) elif new_type == "note" and chain.state in ("closed", "addressed"): raise OperationError( "can't convert resolved or addressed issue to a note") cursor = db.cursor() if chain.state == "draft": # The chain is still a draft; just change its type directly. cursor.execute( """UPDATE commentchains SET type=%s WHERE id=%s""", (new_type, chain.id)) elif chain.type_is_draft: # The user is reverting a draft chain type change; just undo the # draft change. cursor.execute( """DELETE FROM commentchainchanges WHERE chain=%s AND uid=%s AND to_type IS NOT NULL""", (chain.id, user.id)) else: # Otherwise insert a new row into the commentchainchanges table. cursor.execute( """INSERT INTO commentchainchanges (uid, chain, from_type, to_type) VALUES (%s, %s, %s, %s)""", (user.id, chain.id, chain.type, new_type)) db.commit() return OperationResult(draft_status=review.getDraftStatus(db, user))
def process(self, db, user, settings, subject=None, repository=None, filter_id=None, defaults=False): if repository is not None and filter_id is not None: raise OperationError( "invalid input: both 'repository' and 'filter_id' set") if (subject and subject != user) or defaults: Operation.requireRole(db, "administrator", user) else: subject = user cursor = db.cursor() if filter_id is not None: # Check that the filter exists and that it's one of the user's # filters (or that the user has the administrator role.) cursor.execute("SELECT uid FROM filters WHERE id=%s", (filter_id, )) row = cursor.fetchone() if not row: raise OperationFailure( code="nosuchfilter", title="No such filter!", message=("Maybe the filter has been deleted since you " "loaded this page?")) elif row[0] != subject.id: raise OperationFailure( code="invalidfilter", title="The filter belongs to someone else!", message=("What are you up to?")) saved_settings = [] for setting in settings: item = setting["item"] value = setting.get("value") if dbutils.User.storePreference(db, item, value, subject, repository, filter_id): saved_settings.append(setting["item"]) db.commit() return OperationResult(saved_settings=sorted(saved_settings))
def process(self, db, user, values, review_id=None, changeset_ids=None): cursor = db.cursor() data = {} if "users" in values: cursor.execute("SELECT name, fullname FROM users WHERE status!='retired'") data["users"] = dict(cursor) if "paths" in values: if review_id is not None: cursor.execute("""SELECT files.path, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM files JOIN reviewfiles ON (reviewfiles.file=files.id) WHERE reviewfiles.review=%s GROUP BY files.id""", (review_id,)) elif changeset_ids is not None: cursor.execute("""SELECT files.path, SUM(chunks.deleteCount), SUM(chunks.insertCount) FROM files JOIN chunks ON (chunks.file=files.id) WHERE chunks.changeset=ANY (%s) GROUP BY files.id""", (changeset_ids,)) else: raise OperationError("paths requested, but neither review_id nor changeset_ids given") paths = {} for filename, deleted, inserted in cursor: paths[filename] = (0, deleted, inserted) components = filename.split("/") for index in range(len(components) - 1, 0, -1): directory = "/".join(components[:index]) + "/" nfiles, current_deleted, current_inserted = paths.get(directory, (0, 0, 0)) paths[directory] = nfiles + 1, current_deleted + deleted, current_inserted + inserted data["paths"] = paths return OperationResult(**data)
def parseRecipientFilters(db, data): mode = data.get("mode", "opt-out") included = data.get("included", []) excluded = data.get("excluded", []) recipientfilters = [] if mode == "opt-in": recipientfilters.append((None, False)) filter_usernames = included filter_include = True else: filter_usernames = excluded filter_include = False for filter_username in filter_usernames: filter_user = dbutils.User.fromName(db, filter_username) if not filter_user: raise OperationError("no such user: '******'" % filter_username) recipientfilters.append((filter_user.id, filter_include)) return recipientfilters
def process(self, db, user, review_id, rebase_id): review = dbutils.Review.fromId(db, review_id) cursor = db.cursor() cursor.execute("SELECT old_head, new_head, new_upstream FROM reviewrebases WHERE id=%s", (rebase_id,)) old_head_id, new_head_id, new_upstream_id = cursor.fetchone() cursor.execute("SELECT commit FROM previousreachable WHERE rebase=%s", (rebase_id,)) reachable = [commit_id for (commit_id,) in cursor] if not reachable: # Fail if rebase was done before the 'previousreachable' table was # added, and we thus don't know what commits the branch contained # before the rebase. raise OperationError("Automatic revert not supported; rebase is pre-historic.") if review.branch.head.getId(db) != new_head_id: raise OperationError("Commits added to review after rebase; need to remove them first.") old_head = gitutils.Commit.fromId(db, review.repository, old_head_id) new_head = gitutils.Commit.fromId(db, review.repository, new_head_id) cursor.execute("DELETE FROM reachable WHERE branch=%s", (review.branch.id,)) cursor.executemany("INSERT INTO reachable (branch, commit) VALUES (%s, %s)", [(review.branch.id, commit_id) for commit_id in reachable]) if new_upstream_id: new_upstream = gitutils.Commit.fromId(db, review.repository, new_upstream_id) if len(old_head.parents) == 2 and old_head.parents[1] == new_upstream.sha1: # Equivalent merge commit was added; remove it too. # Reopen any issues marked as addressed by the merge commit. cursor.execute("""UPDATE commentchains SET state='open', addressed_by=NULL WHERE review=%s AND state='addressed' AND addressed_by=%s""", (review.id, old_head_id)) # Delete any mappings of issues (or notes) to lines in the merge # commit. cursor.execute("""DELETE FROM commentchainlines USING commentchains WHERE commentchains.review=%s AND commentchains.id=commentchainlines.chain AND commentchainlines.commit=%s""", (review.id, old_head_id)) # Delete the review changesets (and, via cascade, all related # assignments.) cursor.execute("""DELETE FROM reviewchangesets USING changesets WHERE reviewchangesets.review=%s AND reviewchangesets.changeset=changesets.id AND changesets.child=%s""", (review.id, old_head_id)) old_head = gitutils.Commit.fromSHA1(db, review.repository, old_head.parents[0]) old_head_id = old_head.getId(db) cursor.execute("UPDATE branches SET head=%s WHERE id=%s", (old_head_id, review.branch.id)) cursor.execute("DELETE FROM reviewrebases WHERE id=%s", (rebase_id,)) db.commit() review.repository.run("update-ref", "refs/heads/%s" % review.branch.name, old_head.sha1, new_head.sha1) return OperationResult()
def process(self, db, user, repository_id, source_location, source_name, target_name, users, forced=None): cursor = db.cursor() cursor.execute( """SELECT 1 FROM trackedbranches WHERE repository=%s AND local_name=%s""", (repository_id, target_name)) if cursor.fetchone(): raise OperationError("branch '%s' already tracks another branch" % target_name) users = [dbutils.User.fromName(db, username) for username in users] if target_name.startswith("r/"): cursor.execute( """SELECT 1 FROM reviews JOIN branches ON (branches.id=reviews.branch) WHERE branches.repository=%s AND branches.name=%s""", (repository_id, target_name)) if not cursor.fetchone(): raise OperationError( "non-existing review branch can't track another branch") if forced is None: forced = True elif forced is None: forced = False cursor.execute( """SELECT 1 FROM knownremotes WHERE url=%s AND pushing""", (source_location, )) if cursor.fetchone(): delay = "1 week" else: delay = "1 hour" cursor.execute( """INSERT INTO trackedbranches (repository, local_name, remote, remote_name, forced, delay) VALUES (%s, %s, %s, %s, %s, INTERVAL %s) RETURNING id""", (repository_id, target_name, source_location, source_name, forced, delay)) branch_id = cursor.fetchone()[0] for user in users: cursor.execute( """INSERT INTO trackedbranchusers (branch, uid) VALUES (%s, %s)""", (branch_id, user.id)) db.commit() pid = int( open(configuration.services.BRANCHTRACKER["pidfile_path"]).read(). strip()) os.kill(pid, signal.SIGHUP) return OperationResult(branch_id=branch_id)
def process(self, db, user, review, rebase_id): cursor = db.cursor() cursor.execute( """SELECT old_head, new_head, new_upstream, equivalent_merge, replayed_rebase FROM reviewrebases WHERE id=%s""", (rebase_id, )) old_head_id, new_head_id, new_upstream_id, equivalent_merge_id, replayed_rebase_id = cursor.fetchone( ) cursor.execute("SELECT commit FROM previousreachable WHERE rebase=%s", (rebase_id, )) reachable = [commit_id for (commit_id, ) in cursor] if not reachable: # Fail if rebase was done before the 'previousreachable' table was # added, and we thus don't know what commits the branch contained # before the rebase. raise OperationError( "Automatic revert not supported; rebase is pre-historic.") if review.branch.getHead(db).getId(db) != new_head_id: raise OperationError( "Commits added to review after rebase; need to remove them first." ) old_head = gitutils.Commit.fromId(db, review.repository, old_head_id) new_head = gitutils.Commit.fromId(db, review.repository, new_head_id) cursor.execute("DELETE FROM reachable WHERE branch=%s", (review.branch.id, )) cursor.executemany( "INSERT INTO reachable (branch, commit) VALUES (%s, %s)", ((review.branch.id, commit_id) for commit_id in reachable)) if new_upstream_id: generated_commit_id = equivalent_merge_id or replayed_rebase_id if generated_commit_id is not None: # A generated commit (equivalent merge or replayed rebase) was # added when performing the rebase; remove it. # Reopen any issues marked as addressed by the rebase. If the # rebase was a fast-forward one, issues will have been addressed # by the equivalent merge commit. Otherwise, issues will have # been addressed by the new head commit (not the replayed rebase # commit.) addressed_by_commit_id = equivalent_merge_id or new_head_id cursor.execute( """UPDATE commentchains SET state='open', addressed_by=NULL WHERE review=%s AND state='addressed' AND addressed_by=%s""", (review.id, addressed_by_commit_id)) # Delete the review changesets (and, via cascade, all related # assignments and state changes.) cursor.execute( """DELETE FROM reviewchangesets WHERE review=%s AND changeset IN (SELECT id FROM changesets WHERE child=%s OR parent=%s)""", (review.id, generated_commit_id, generated_commit_id)) cursor.execute("UPDATE branches SET head=%s WHERE id=%s", (old_head_id, review.branch.id)) cursor.execute("DELETE FROM reviewrebases WHERE id=%s", (rebase_id, )) review.incrementSerial(db) db.commit() review.repository.run("update-ref", "refs/heads/%s" % review.branch.name, old_head.sha1, new_head.sha1) return OperationResult()