Beispiel #1
0
    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))
Beispiel #3
0
    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()
Beispiel #4
0
    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()
Beispiel #5
0
    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()
Beispiel #7
0
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
Beispiel #8
0
    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"])
Beispiel #9
0
    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)
Beispiel #10
0
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()]
Beispiel #11
0
    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()
Beispiel #12
0
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()
Beispiel #13
0
    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:])
Beispiel #14
0
    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))
Beispiel #15
0
    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()
Beispiel #16
0
    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()
Beispiel #17
0
    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))
Beispiel #19
0
    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))
Beispiel #20
0
    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)
Beispiel #21
0
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
Beispiel #22
0
    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()
Beispiel #23
0
    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)
Beispiel #24
0
    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()