Ejemplo n.º 1
0
    def getRelevantFiles(self, db, user):
        if not self.filters:
            from reviewing.filters import Filters

            self.filters = Filters()
            self.filters.setFiles(db, review=self)
            self.filters.load(db, review=self)
            self.relevant_files = self.filters.getRelevantFiles()

            cursor = db.cursor()
            cursor.execute("SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s", (self.id,))
            for user_id, file_id in cursor:
                self.relevant_files.setdefault(user_id, set()).add(file_id)

        return self.relevant_files.get(user.id, set())
Ejemplo n.º 2
0
    def getRelevantFiles(self, db, user):
        if not self.filters:
            from reviewing.filters import Filters

            self.filters = Filters()
            self.filters.load(db, review=self)
            self.relevant_files = self.filters.getRelevantFiles(db, self)

            cursor = db.cursor()
            cursor.execute("SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s", (self.id,))
            for user_id, file_id in cursor:
                self.relevant_files.setdefault(user_id, set()).add(file_id)

        return self.relevant_files.get(user.id, set())
Ejemplo n.º 3
0
class Review(object):
    def __init__(self, review_id, owners, review_type, branch, state, serial, summary, description, applyfilters, applyparentfilters):
        self.id = review_id
        self.owners = owners
        self.type = review_type
        self.repository = branch.repository
        self.branch = branch
        self.state = state
        self.serial = serial
        self.summary = summary
        self.description = description
        self.reviewers = []
        self.watchers = {}
        self.changesets = []
        self.commentchains = None
        self.applyfilters = applyfilters
        self.applyparentfilters = applyparentfilters
        self.filters = None
        self.relevant_files = None
        self.draft_status = None

    @staticmethod
    def isAccepted(db, review_id):
        cursor = db.cursor()

        cursor.execute("SELECT 1 FROM reviewfiles WHERE review=%s AND state='pending' LIMIT 1", (review_id,))
        if cursor.fetchone(): return False

        cursor.execute("SELECT 1 FROM commentchains WHERE review=%s AND type='issue' AND state='open' LIMIT 1", (review_id,))
        if cursor.fetchone(): return False

        return True

    def accepted(self, db):
        if self.state != 'open': return False
        else: return Review.isAccepted(db, self.id)

    def getReviewState(self, db):
        cursor = db.cursor()

        cursor.execute("""SELECT state, SUM(deleted) + SUM(inserted)
                            FROM reviewfiles
                           WHERE reviewfiles.review=%s
                        GROUP BY state""",
                       (self.id,))

        pending = 0
        reviewed = 0

        for state, count in cursor.fetchall():
            if state == "pending": pending = count
            else: reviewed = count

        cursor.execute("""SELECT count(id)
                            FROM commentchains
                           WHERE review=%s
                             AND type='issue'
                             AND state='open'""",
                       (self.id,))

        issues = cursor.fetchone()[0]

        return ReviewState(self, self.accepted(db), pending, reviewed, issues)

    def getReviewRebases(self, db):
        return ReviewRebases(db, self)

    def getCommitSet(self, db):
        import gitutils
        import log.commitset

        cursor = db.cursor()
        cursor.execute("""SELECT DISTINCT commits.id, commits.sha1
                            FROM commits
                            JOIN changesets ON (changesets.child=commits.id)
                            JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                           WHERE reviewchangesets.review=%s""",
                       (self.id,))

        commits = []

        for commit_id, commit_sha1 in cursor:
            commits.append(gitutils.Commit.fromSHA1(db, self.repository, commit_sha1, commit_id))

        return log.commitset.CommitSet(commits)

    def containsCommit(self, db, commit, include_head_and_tails=False):
        import gitutils

        commit_id = None
        commit_sha1 = None

        if isinstance(commit, gitutils.Commit):
            if commit.id: commit_id = commit.id
            else: commit_sha1 = commit.sha1
        elif isinstance(commit, str):
            commit_sha1 = self.repository.revparse(commit)
            commit = None
        elif isinstance(commit, int):
            commit_id = commit
            commit = None
        else:
            raise TypeError

        cursor = db.cursor()

        if commit_id is not None:
            cursor.execute("""SELECT 1
                                FROM reviewchangesets
                                JOIN changesets ON (id=changeset)
                               WHERE reviewchangesets.review=%s
                                 AND changesets.child=%s""",
                           (self.id, commit_id))
        else:
            cursor.execute("""SELECT 1
                                FROM reviewchangesets
                                JOIN changesets ON (changesets.id=reviewchangesets.changeset)
                                JOIN commits ON (commits.id=changesets.child)
                               WHERE reviewchangesets.review=%s
                                 AND commits.sha1=%s""",
                           (self.id, commit_sha1))

        if cursor.fetchone() is not None:
            return True

        if include_head_and_tails:
            head_and_tails = set([self.branch.head])

            commitset = self.getCommitSet(db)

            if commitset:
                head_and_tails |= commitset.getTails()

            if commit_sha1 is None:
                if commit is None:
                    commit = gitutils.Commit.fromId(db, self.repository, commit_id)
                commit_sha1 = commit.sha1

            if commit_sha1 in head_and_tails:
                return True

        return False

    def getJS(self):
        return "var review = critic.review = { id: %d, branch: { id: %d, name: %r }, owners: [ %s ], serial: %d };" % (self.id, self.branch.id, self.branch.name, ", ".join(owner.getJSConstructor() for owner in self.owners), self.serial)

    def getETag(self, db, user=None):
        etag = "review%d.serial%d" % (self.id, self.serial)

        if user:
            items = self.getDraftStatus(db, user)
            if any(items.values()):
                etag += ".draft%d" % hash(tuple(sorted(items.items())))

            cursor = db.cursor()
            cursor.execute("SELECT id FROM reviewrebases WHERE review=%s AND uid=%s AND new_head IS NULL", (self.id, user.id))
            row = cursor.fetchone()
            if row:
                etag += ".rebase%d" % row[0]

        return '"%s"' % etag

    def getURL(self, db, user=None, indent=0):
        import dbutils

        indent = " " * indent

        if db and user:
            url_prefixes = user.getCriticURLs(db)
        else:
            url_prefixes = [dbutils.getURLPrefix(db)]

        return "\n".join(["%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes])

    def getRecipients(self, db):
        from dbutils import User

        cursor = db.cursor()
        cursor.execute("SELECT uid, include FROM reviewrecipientfilters WHERE review=%s ORDER BY uid ASC", (self.id,))

        included = set(owner.id for owner in self.owners)
        excluded = set()
        for uid, include in cursor:
            if include: included.add(uid)
            elif uid not in self.owners: excluded.add(uid)

        cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (self.id,))

        recipients = []
        for (user_id,) in cursor:
            if user_id in excluded: continue
            elif user_id not in included and 0 in excluded: continue

            user = User.fromId(db, user_id)
            if user.status != "retired":
                recipients.append(user)

        return recipients

    def getDraftStatus(self, db, user):
        if self.draft_status is None:
            self.draft_status = countDraftItems(db, user, self)
        return self.draft_status

    def incrementSerial(self, db):
        self.serial += 1
        db.cursor().execute("UPDATE reviews SET serial=%s WHERE id=%s", [self.serial, self.id])

    def close(self, db, user):
        self.serial += 1
        db.cursor().execute("UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id))

    def drop(self, db, user):
        self.serial += 1
        db.cursor().execute("UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id))

    def reopen(self, db, user):
        self.serial += 1
        db.cursor().execute("UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s", (self.serial, self.id))

    def disableTracking(self, db):
        db.cursor().execute("UPDATE trackedbranches SET disabled=TRUE WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name))

    def setSummary(self, db, summary):
        self.serial += 1
        self.summary = summary
        db.cursor().execute("UPDATE reviews SET summary=%s, serial=%s WHERE id=%s", [self.summary, self.serial, self.id])

    def setDescription(self, db, description):
        self.serial += 1
        self.description = description
        db.cursor().execute("UPDATE reviews SET description=%s, serial=%s WHERE id=%s", [self.description, self.serial, self.id])

    def addOwner(self, db, owner):
        if not owner in self.owners:
            self.serial += 1
            self.owners.append(owner)

            cursor = db.cursor()
            cursor.execute("SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, owner.id))

            if cursor.fetchone():
                cursor.execute("UPDATE reviewusers SET owner=TRUE WHERE review=%s AND uid=%s", (self.id, owner.id))
            else:
                cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (self.id, owner.id))

            cursor.execute("SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name))

            row = cursor.fetchone()
            if row:
                trackedbranch_id = row[0]
                cursor.execute("INSERT INTO trackedbranchusers (branch, uid) VALUES (%s, %s)", (trackedbranch_id, owner.id))

    def removeOwner(self, db, owner):
        if owner in self.owners:
            self.serial += 1
            self.owners.remove(owner)

            cursor = db.cursor()
            cursor.execute("UPDATE reviewusers SET owner=FALSE WHERE review=%s AND uid=%s", (self.id, owner.id))
            cursor.execute("SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name))

            row = cursor.fetchone()
            if row:
                trackedbranch_id = row[0]
                cursor.execute("DELETE FROM trackedbranchusers WHERE branch=%s AND uid=%s", (trackedbranch_id, owner.id))

    def getReviewFilters(self, db):
        cursor = db.cursor()
        cursor.execute("SELECT directory, file, type, NULL, uid FROM reviewfilters WHERE review=%s", (self.id,))
        return cursor.fetchall() or None

    def getFilteredTails(self):
        import log.commitset
        commitset = log.commitset.CommitSet(self.branch.commits)
        return commitset.getFilteredTails(self.branch.repository)

    def getRelevantFiles(self, db, user):
        if not self.filters:
            from reviewing.filters import Filters

            self.filters = Filters()
            self.filters.load(db, review=self)
            self.relevant_files = self.filters.getRelevantFiles(db, self)

            cursor = db.cursor()
            cursor.execute("SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s", (self.id,))
            for user_id, file_id in cursor:
                self.relevant_files.setdefault(user_id, set()).add(file_id)

        return self.relevant_files.get(user.id, set())

    def getUserAssociation(self, db, user):
        cursor = db.cursor()

        association = []

        if user in self.owners:
            association.append("owner")

        cursor.execute("""SELECT 1
                            FROM reviewchangesets
                            JOIN changesets ON (changesets.id=reviewchangesets.changeset)
                            JOIN commits ON (commits.id=changesets.child)
                            JOIN gitusers ON (gitusers.id=commits.author_gituser)
                            JOIN usergitemails USING (email)
                           WHERE reviewchangesets.review=%s
                             AND usergitemails.uid=%s""",
                       (self.id, user.id))
        if cursor.fetchone():
            association.append("author")

        cursor.execute("SELECT COUNT(*) FROM fullreviewuserfiles WHERE review=%s AND assignee=%s", (self.id, user.id))
        if cursor.fetchone()[0] != 0:
            association.append("reviewer")
        elif user not in self.owners:
            cursor.execute("SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, user.id))
            if cursor.fetchone():
                association.append("watcher")

        if not association:
            association.append("none")

        return ", ".join(association)

    @staticmethod
    def fromId(db, review_id, branch=None, load_commits=True, profiler=None):
        from dbutils import User

        cursor = db.cursor()
        cursor.execute("SELECT type, branch, state, serial, summary, description, applyfilters, applyparentfilters FROM reviews WHERE id=%s", [review_id])
        row = cursor.fetchone()
        if not row: return None

        type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row

        if profiler: profiler.check("Review.fromId: basic")

        if branch is None:
            from dbutils import Branch
            branch = Branch.fromId(db, branch_id, load_review=False, load_commits=load_commits, profiler=profiler)

        cursor.execute("SELECT uid FROM reviewusers WHERE review=%s AND owner", (review_id,))

        owners = User.fromIds(db, [user_id for (user_id,) in cursor])

        if profiler: profiler.check("Review.fromId: owners")

        review = Review(review_id, owners, type, branch, state, serial, summary, description, applyfilters, applyparentfilters)
        branch.review = review

        # Reviewers: all users that have at least one review file assigned to them.
        cursor.execute("""SELECT DISTINCT uid, assignee IS NOT NULL, type
                            FROM reviewusers
                 LEFT OUTER JOIN fullreviewuserfiles ON (fullreviewuserfiles.review=reviewusers.review AND assignee=uid)
                           WHERE reviewusers.review=%s""",
                       (review_id,))

        reviewers = []
        watchers = []
        watcher_types = {}

        for user_id, is_reviewer, user_type in cursor.fetchall():
            if is_reviewer:
                reviewers.append(user_id)
            elif user_id not in review.owners:
                watchers.append(user_id)
                watcher_types[user_id] = user_type

        review.reviewers = User.fromIds(db, reviewers)

        for watcher in User.fromIds(db, watchers):
            review.watchers[watcher] = watcher_types[watcher]

        if profiler: profiler.check("Review.fromId: users")

        if load_commits:
            review.branch.loadCommits(db)

            cursor.execute("""SELECT id
                                FROM reviewchangesets
                                JOIN changesets ON (id=changeset)
                               WHERE review=%s
                                 AND child=ANY (%s)""", (review_id, [commit.id for commit in review.branch.commits]))

            review.changesets = [changeset_id for (changeset_id,) in cursor.fetchall()]

            if profiler: profiler.check("Review.fromId: load commits")

        return review

    @staticmethod
    def fromBranch(db, branch):
        if branch:
            cursor = db.cursor()
            cursor.execute("SELECT id FROM reviews WHERE branch=%s", [branch.id])
            row = cursor.fetchone()
            if not row: return None
            else: return Review.fromId(db, row[0], branch)
        else:
            return None

    @staticmethod
    def fromName(db, repository, name):
        from dbutils import Branch
        return Review.fromBranch(db, Branch.fromName(db, repository, name))

    @staticmethod
    def fromArgument(db, argument):
        try:
            return Review.fromId(db, int(argument))
        except:
            from dbutils import Branch
            branch = Branch.fromName(db, str(argument))
            if not branch: return None
            return Review.fromBranch(db, branch)
Ejemplo n.º 4
0
def createCommentChain(db, user, review, chain_type, commit_id=None, origin=None, file_id=None, parent_id=None, child_id=None, old_sha1=None, new_sha1=None, offset=None, count=None):
    import reviewing.comment.propagate

    if chain_type == "issue" and review.state != "open":
        raise OperationFailure(code="reviewclosed",
                               title="Review is closed!",
                               message="You need to reopen the review before you can raise new issues.")

    cursor = db.cursor()

    if file_id is not None:
        if origin == "old":
            commit = gitutils.Commit.fromId(db, review.repository, parent_id)
        else:
            commit = gitutils.Commit.fromId(db, review.repository, child_id)

        propagation = reviewing.comment.propagate.Propagation(db)

        if not propagation.setCustom(review, commit, file_id, offset, offset + count - 1):
            raise OperationFailure(code="invalidoperation",
                                   title="Invalid operation",
                                   message="It's not possible to create a comment here.")

        propagation.calculateInitialLines()

        cursor.execute("""INSERT INTO commentchains (review, uid, type, origin, file, first_commit, last_commit)
                               VALUES (%s, %s, %s, %s, %s, %s, %s)
                            RETURNING id""",
                       (review.id, user.id, chain_type, origin, file_id, parent_id, child_id))

        chain_id = cursor.fetchone()[0]
        commentchainlines_values = []

        for sha1, (first_line, last_line) in propagation.new_lines.items():
            commentchainlines_values.append((chain_id, user.id, sha1, first_line, last_line))

        cursor.executemany("""INSERT INTO commentchainlines (chain, uid, sha1, first_line, last_line)
                                   VALUES (%s, %s, %s, %s, %s)""",
                           commentchainlines_values)
    elif commit_id is not None:
        commit = gitutils.Commit.fromId(db, review.repository, commit_id)

        cursor.execute("""INSERT INTO commentchains (review, uid, type, first_commit, last_commit)
                               VALUES (%s, %s, %s, %s, %s)
                            RETURNING id""",
                       (review.id, user.id, chain_type, commit_id, commit_id))
        chain_id = cursor.fetchone()[0]

        cursor.execute("""INSERT INTO commentchainlines (chain, uid, sha1, first_line, last_line)
                               VALUES (%s, %s, %s, %s, %s)""",
                       (chain_id, user.id, commit.sha1, offset, offset + count - 1))
    else:
        cursor.execute("""INSERT INTO commentchains (review, uid, type)
                               VALUES (%s, %s, %s)
                            RETURNING id""",
                       (review.id, user.id, chain_type))
        chain_id = cursor.fetchone()[0]

    commentchainusers = set([user.id] + map(int, review.owners))

    if file_id is not None:
        filters = Filters()
        filters.setFiles(db, review=review)
        filters.load(db, review=review)

        for user_id in filters.listUsers(file_id):
            commentchainusers.add(user_id)

    cursor.executemany("INSERT INTO commentchainusers (chain, uid) VALUES (%s, %s)", [(chain_id, user_id) for user_id in commentchainusers])

    return chain_id
Ejemplo n.º 5
0
class Review(object):
    def __init__(self, review_id, owners, review_type, branch, state, serial, summary, description, applyfilters, applyparentfilters):
        self.id = review_id
        self.owners = owners
        self.type = review_type
        self.repository = branch.repository
        self.branch = branch
        self.state = state
        self.serial = serial
        self.summary = summary
        self.description = description
        self.reviewers = []
        self.watchers = {}
        self.commentchains = None
        self.applyfilters = applyfilters
        self.applyparentfilters = applyparentfilters
        self.filters = None
        self.relevant_files = None
        self.draft_status = None
        self.performed_rebase = None

    @staticmethod
    def isAccepted(db, review_id):
        cursor = db.cursor()

        cursor.execute("SELECT 1 FROM reviewfiles WHERE review=%s AND state='pending' LIMIT 1", (review_id,))
        if cursor.fetchone(): return False

        cursor.execute("SELECT 1 FROM commentchains WHERE review=%s AND type='issue' AND state='open' LIMIT 1", (review_id,))
        if cursor.fetchone(): return False

        return True

    def accepted(self, db):
        if self.state != 'open': return False
        else: return Review.isAccepted(db, self.id)

    def getReviewState(self, db):
        cursor = db.cursor()

        cursor.execute("""SELECT state, SUM(deleted) + SUM(inserted)
                            FROM reviewfiles
                           WHERE reviewfiles.review=%s
                        GROUP BY state""",
                       (self.id,))

        pending = 0
        reviewed = 0

        for state, count in cursor.fetchall():
            if state == "pending": pending = count
            else: reviewed = count

        cursor.execute("""SELECT count(id)
                            FROM commentchains
                           WHERE review=%s
                             AND type='issue'
                             AND state='open'""",
                       (self.id,))

        issues = cursor.fetchone()[0]

        return ReviewState(self, self.accepted(db), pending, reviewed, issues)

    def setPerformedRebase(self, old_head, new_head, old_upstream, new_upstream, user):
        self.performed_rebase = ReviewRebase(self, old_head, new_head, old_upstream, new_upstream, user)

    def getReviewRebases(self, db):
        return ReviewRebases(db, self)

    def getTrackedBranch(self, db):
        cursor = db.cursor()
        cursor.execute("""SELECT trackedbranches.id, remote, remote_name, disabled
                            FROM trackedbranches
                            JOIN branches ON (trackedbranches.repository=branches.repository
                                          AND trackedbranches.local_name=branches.name)
                            JOIN reviews ON (branches.id=reviews.branch)
                           WHERE reviews.id=%s""",
                       (self.id,))

        for trackedbranch_id, remote, name, disabled in cursor:
            return ReviewTrackedBranch(self, trackedbranch_id, remote, name, disabled)

    def getCommitSet(self, db):
        import gitutils
        import log.commitset

        cursor = db.cursor()
        cursor.execute("""SELECT DISTINCT commits.id, commits.sha1
                            FROM commits
                            JOIN changesets ON (changesets.child=commits.id)
                            JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                           WHERE reviewchangesets.review=%s""",
                       (self.id,))

        commits = []

        for commit_id, commit_sha1 in cursor:
            commits.append(gitutils.Commit.fromSHA1(db, self.repository, commit_sha1, commit_id))

        return log.commitset.CommitSet(commits)

    def containsCommit(self, db, commit, include_head_and_tails=False, include_actual_log=False):
        import gitutils

        commit_id = None
        commit_sha1 = None

        if isinstance(commit, gitutils.Commit):
            commit_id = commit.id
            commit_sha1 = commit.sha1
        elif isinstance(commit, str):
            commit_sha1 = self.repository.revparse(commit)
            commit = None
        elif isinstance(commit, int):
            commit_id = commit
            commit = None
        else:
            raise TypeError

        cursor = db.cursor()

        if commit_id is not None:
            cursor.execute("""SELECT 1
                                FROM reviewchangesets
                                JOIN changesets ON (id=changeset)
                               WHERE reviewchangesets.review=%s
                                 AND changesets.child=%s
                                 AND changesets.type!='conflicts'""",
                           (self.id, commit_id))
        else:
            cursor.execute("""SELECT 1
                                FROM reviewchangesets
                                JOIN changesets ON (changesets.id=reviewchangesets.changeset)
                                JOIN commits ON (commits.id=changesets.child)
                               WHERE reviewchangesets.review=%s
                                 AND changesets.type!='conflicts'
                                 AND commits.sha1=%s""",
                           (self.id, commit_sha1))

        if cursor.fetchone() is not None:
            return True

        if include_head_and_tails:
            head_and_tails = set([self.branch.getHead(db)])

            commitset = self.getCommitSet(db)

            if commitset:
                head_and_tails |= commitset.getTails()

            if commit_sha1 is None:
                if commit is None:
                    commit = gitutils.Commit.fromId(db, self.repository, commit_id)
                commit_sha1 = commit.sha1

            if commit_sha1 in head_and_tails:
                return True

        if include_actual_log:
            if commit_id is not None:
                cursor.execute("""SELECT 1
                                    FROM reachable
                                    JOIN branches ON (branches.id=reachable.branch)
                                    JOIN reviews ON (reviews.branch=branches.id)
                                   WHERE reachable.commit=%s
                                     AND reviews.id=%s""",
                               (commit_id, self.id))
            else:
                cursor.execute("""SELECT 1
                                    FROM commits
                                    JOIN reachable ON (reachable.commit=commits.id)
                                    JOIN branches ON (branches.id=reachable.branch)
                                    JOIN reviews ON (reviews.branch=branches.id)
                                   WHERE commits.sha1=%s
                                     AND reviews.id=%s""",
                               (commit_sha1, self.id))

            if cursor.fetchone() is not None:
                return True

        return False

    def getJS(self):
        return "var review = critic.review = { id: %d, branch: { id: %d, name: %r }, owners: [ %s ], serial: %d };" % (self.id, self.branch.id, self.branch.name, ", ".join(owner.getJSConstructor() for owner in self.owners), self.serial)

    def getETag(self, db, user=None):
        import configuration

        cursor = db.cursor()
        etag = ""

        if configuration.debug.IS_DEVELOPMENT:
            cursor.execute("SELECT installed_at FROM systemidentities WHERE name=%s", (configuration.base.SYSTEM_IDENTITY,))
            installed_at = cursor.fetchone()[0]
            etag += "install%s." % time.mktime(installed_at.timetuple())

        if user and not user.isAnonymous():
            etag += "user%d." % user.id

        etag += "review%d.serial%d" % (self.id, self.serial)

        if user:
            items = self.getDraftStatus(db, user)
            if any(items.values()):
                etag += ".draft%d" % hash(tuple(sorted(items.items())))

            cursor.execute("SELECT id FROM reviewrebases WHERE review=%s AND uid=%s AND new_head IS NULL", (self.id, user.id))
            row = cursor.fetchone()
            if row:
                etag += ".rebase%d" % row[0]

        return '"%s"' % etag

    def getURL(self, db, user=None, indent=0, separator="\n"):
        import dbutils

        indent = " " * indent

        if user:
            url_prefixes = user.getCriticURLs(db)
        else:
            url_prefixes = [dbutils.getURLPrefix(db)]

        return separator.join(["%s%s/r/%d" % (indent, url_prefix, self.id) for url_prefix in url_prefixes])

    def getRecipients(self, db):
        from dbutils import User

        cursor = db.cursor()
        cursor.execute("SELECT uid, include FROM reviewrecipientfilters WHERE review=%s", (self.id,))

        default_include = True
        included = set(owner.id for owner in self.owners)
        excluded = set()

        for uid, include in cursor:
            if uid is None:
                default_include = include
            elif include:
                included.add(uid)
            elif uid not in self.owners:
                excluded.add(uid)

        cursor.execute("SELECT uid FROM reviewusers WHERE review=%s", (self.id,))

        recipients = []
        for (user_id,) in cursor:
            if user_id in excluded:
                continue
            elif user_id not in included and not default_include:
                continue

            user = User.fromId(db, user_id)
            if user.status != "retired":
                recipients.append(user)

        return recipients

    def getDraftStatus(self, db, user):
        if self.draft_status is None:
            self.draft_status = countDraftItems(db, user, self)
        return self.draft_status

    def incrementSerial(self, db):
        self.serial += 1
        db.cursor().execute("UPDATE reviews SET serial=%s WHERE id=%s", [self.serial, self.id])

    def scheduleBranchArchival(self, db, delay=None):
        import dbutils

        # First, cancel current scheduled archival, if there is one.
        self.cancelScheduledBranchArchival(db)

        # If review is not closed or dropped, don't schedule a branch archival.
        # Also don't schedule one if the branch has already been archived.
        if self.state not in ("closed", "dropped") or self.branch.archived:
            return

        if delay is None:
            # Configuration policy:
            #
            # Any owner of a review can, by having changed the relevant
            # preference setting, increase the time before a review branch is
            # archived, or disable archival entirely, but they can't make it
            # happen sooner than the system or repository default, or what any
            # other owner has requested.

            # Find configured value for each owner, and also the per-repository
            # (or per-system) default, in case each owner has changed the
            # setting.
            preference_item = "review.branchArchiveDelay." + self.state
            repository_default = dbutils.User.fetchPreference(
                db, preference_item, repository=self.repository)
            delays = set([repository_default])
            for owner in self.owners:
                delays.add(owner.getPreference(db, preference_item,
                                               repository=self.repository))

            # If configured to zero (by any owner,) don't schedule a branch
            # archival.
            if min(delays) <= 0:
                return

            # Otherwise, use maximum configured value for any owner.
            delay = max(delays)

        cursor = db.cursor()
        cursor.execute("""INSERT INTO scheduledreviewbrancharchivals (review, deadline)
                               VALUES (%s, NOW() + INTERVAL %s)""",
                       (self.id, "%d DAYS" % delay))

        return delay

    def cancelScheduledBranchArchival(self, db):
        cursor = db.cursor()
        cursor.execute("""DELETE FROM scheduledreviewbrancharchivals
                                WHERE review=%s""",
                       (self.id,))

    def close(self, db, user):
        self.serial += 1
        self.state = "closed"
        db.cursor().execute("UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id))
        self.scheduleBranchArchival(db)

    def drop(self, db, user):
        self.serial += 1
        self.state = "dropped"
        db.cursor().execute("UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s", (self.serial, user.id, self.id))
        self.scheduleBranchArchival(db)

    def reopen(self, db, user):
        self.serial += 1
        if self.branch.archived:
            self.branch.resurrect(db)
        db.cursor().execute("UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s", (self.serial, self.id))
        self.cancelScheduledBranchArchival(db)

    def disableTracking(self, db):
        db.cursor().execute("UPDATE trackedbranches SET disabled=TRUE WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name))

    def setSummary(self, db, summary):
        self.serial += 1
        self.summary = summary
        db.cursor().execute("UPDATE reviews SET summary=%s, serial=%s WHERE id=%s", [self.summary, self.serial, self.id])

    def setDescription(self, db, description):
        self.serial += 1
        self.description = description
        db.cursor().execute("UPDATE reviews SET description=%s, serial=%s WHERE id=%s", [self.description, self.serial, self.id])

    def addOwner(self, db, owner):
        if not owner in self.owners:
            self.serial += 1
            self.owners.append(owner)

            cursor = db.cursor()
            cursor.execute("SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, owner.id))

            if cursor.fetchone():
                cursor.execute("UPDATE reviewusers SET owner=TRUE WHERE review=%s AND uid=%s", (self.id, owner.id))
            else:
                cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (self.id, owner.id))

            cursor.execute("SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name))

            row = cursor.fetchone()
            if row:
                trackedbranch_id = row[0]
                cursor.execute("INSERT INTO trackedbranchusers (branch, uid) VALUES (%s, %s)", (trackedbranch_id, owner.id))

    def removeOwner(self, db, owner):
        if owner in self.owners:
            self.serial += 1
            self.owners.remove(owner)

            cursor = db.cursor()
            cursor.execute("UPDATE reviewusers SET owner=FALSE WHERE review=%s AND uid=%s", (self.id, owner.id))
            cursor.execute("SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s", (self.repository.id, self.branch.name))

            row = cursor.fetchone()
            if row:
                trackedbranch_id = row[0]
                cursor.execute("DELETE FROM trackedbranchusers WHERE branch=%s AND uid=%s", (trackedbranch_id, owner.id))

    def getReviewFilters(self, db):
        cursor = db.cursor()
        cursor.execute("SELECT uid, path, type, NULL FROM reviewfilters WHERE review=%s", (self.id,))
        return cursor.fetchall() or None

    def getFilteredTails(self, db):
        import log.commitset
        commitset = log.commitset.CommitSet(self.branch.getCommits(db))
        return commitset.getFilteredTails(self.branch.repository)

    def getRelevantFiles(self, db, user):
        if not self.filters:
            from reviewing.filters import Filters

            self.filters = Filters()
            self.filters.setFiles(db, review=self)
            self.filters.load(db, review=self)
            self.relevant_files = self.filters.getRelevantFiles()

            cursor = db.cursor()
            cursor.execute("SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s", (self.id,))
            for user_id, file_id in cursor:
                self.relevant_files.setdefault(user_id, set()).add(file_id)

        return self.relevant_files.get(user.id, set())

    def getUserAssociation(self, db, user):
        cursor = db.cursor()

        association = []

        if user in self.owners:
            association.append("owner")

        cursor.execute("""SELECT 1
                            FROM reviewchangesets
                            JOIN changesets ON (changesets.id=reviewchangesets.changeset)
                            JOIN commits ON (commits.id=changesets.child)
                            JOIN gitusers ON (gitusers.id=commits.author_gituser)
                            JOIN usergitemails USING (email)
                           WHERE reviewchangesets.review=%s
                             AND usergitemails.uid=%s""",
                       (self.id, user.id))
        if cursor.fetchone():
            association.append("author")

        cursor.execute("SELECT COUNT(*) FROM fullreviewuserfiles WHERE review=%s AND assignee=%s", (self.id, user.id))
        if cursor.fetchone()[0] != 0:
            association.append("reviewer")
        elif user not in self.owners:
            cursor.execute("SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s", (self.id, user.id))
            if cursor.fetchone():
                association.append("watcher")

        if not association:
            association.append("none")

        return ", ".join(association)

    @staticmethod
    def fromId(db, review_id, branch=None, profiler=None):
        from dbutils import User

        cursor = db.cursor()
        cursor.execute("SELECT type, branch, state, serial, summary, description, applyfilters, applyparentfilters FROM reviews WHERE id=%s", [review_id])
        row = cursor.fetchone()
        if not row: raise NoSuchReview(review_id)

        type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row

        if profiler: profiler.check("Review.fromId: basic")

        if branch is None:
            from dbutils import Branch
            branch = Branch.fromId(db, branch_id, load_review=False, profiler=profiler)

        cursor.execute("SELECT uid FROM reviewusers WHERE review=%s AND owner", (review_id,))

        owners = User.fromIds(db, [user_id for (user_id,) in cursor])

        if profiler: profiler.check("Review.fromId: owners")

        review = Review(review_id, owners, type, branch, state, serial, summary, description, applyfilters, applyparentfilters)
        branch.review = review

        # Reviewers: all users that have at least one review file assigned to them.
        cursor.execute("""SELECT DISTINCT uid, assignee IS NOT NULL, type
                            FROM reviewusers
                 LEFT OUTER JOIN fullreviewuserfiles ON (fullreviewuserfiles.review=reviewusers.review AND assignee=uid)
                           WHERE reviewusers.review=%s""",
                       (review_id,))

        reviewers = []
        watchers = []
        watcher_types = {}

        for user_id, is_reviewer, user_type in cursor.fetchall():
            if is_reviewer:
                reviewers.append(user_id)
            elif user_id not in review.owners:
                watchers.append(user_id)
                watcher_types[user_id] = user_type

        review.reviewers = User.fromIds(db, reviewers)

        for watcher in User.fromIds(db, watchers):
            review.watchers[watcher] = watcher_types[watcher]

        if profiler: profiler.check("Review.fromId: users")

        return review

    @staticmethod
    def fromBranch(db, branch):
        if branch:
            cursor = db.cursor()
            cursor.execute("SELECT id FROM reviews WHERE branch=%s", [branch.id])
            row = cursor.fetchone()
            if not row: return None
            else: return Review.fromId(db, row[0], branch)
        else:
            return None

    @staticmethod
    def fromName(db, repository, name):
        from dbutils import Branch
        return Review.fromBranch(db, Branch.fromName(db, repository, name))

    @staticmethod
    def fromArgument(db, argument):
        try:
            return Review.fromId(db, int(argument))
        except:
            from dbutils import Branch
            branch = Branch.fromName(db, str(argument))
            if not branch: return None
            return Review.fromBranch(db, branch)
Ejemplo n.º 6
0
class Review(object):
    def __init__(self, review_id, owners, review_type, branch, state, serial,
                 summary, description, applyfilters, applyparentfilters):
        self.id = review_id
        self.owners = owners
        self.type = review_type
        self.repository = branch.repository
        self.branch = branch
        self.state = state
        self.serial = serial
        self.summary = summary
        self.description = description
        self.reviewers = []
        self.watchers = {}
        self.commentchains = None
        self.applyfilters = applyfilters
        self.applyparentfilters = applyparentfilters
        self.filters = None
        self.relevant_files = None
        self.draft_status = None
        self.performed_rebase = None

    @staticmethod
    def isAccepted(db, review_id):
        cursor = db.cursor()

        cursor.execute(
            "SELECT 1 FROM reviewfiles WHERE review=%s AND state='pending' LIMIT 1",
            (review_id, ))
        if cursor.fetchone(): return False

        cursor.execute(
            "SELECT 1 FROM commentchains WHERE review=%s AND type='issue' AND state='open' LIMIT 1",
            (review_id, ))
        if cursor.fetchone(): return False

        return True

    def accepted(self, db):
        if self.state != 'open': return False
        else: return Review.isAccepted(db, self.id)

    def getReviewState(self, db):
        cursor = db.cursor()

        cursor.execute(
            """SELECT state, SUM(deleted) + SUM(inserted)
                            FROM reviewfiles
                           WHERE reviewfiles.review=%s
                        GROUP BY state""", (self.id, ))

        pending = 0
        reviewed = 0

        for state, count in cursor.fetchall():
            if state == "pending": pending = count
            else: reviewed = count

        cursor.execute(
            """SELECT count(id)
                            FROM commentchains
                           WHERE review=%s
                             AND type='issue'
                             AND state='open'""", (self.id, ))

        issues = cursor.fetchone()[0]

        return ReviewState(self, self.accepted(db), pending, reviewed, issues)

    def setPerformedRebase(self, old_head, new_head, old_upstream,
                           new_upstream, user, equivalent_merge,
                           replayed_rebase):
        self.performed_rebase = ReviewRebase(self, old_head, new_head,
                                             old_upstream, new_upstream, user,
                                             equivalent_merge, replayed_rebase)

    def getReviewRebases(self, db):
        return ReviewRebases(db, self)

    def getTrackedBranch(self, db):
        cursor = db.cursor()
        cursor.execute(
            """SELECT trackedbranches.id, remote, remote_name, disabled
                            FROM trackedbranches
                            JOIN branches ON (trackedbranches.repository=branches.repository
                                          AND trackedbranches.local_name=branches.name)
                            JOIN reviews ON (branches.id=reviews.branch)
                           WHERE reviews.id=%s""", (self.id, ))

        for trackedbranch_id, remote, name, disabled in cursor:
            return ReviewTrackedBranch(self, trackedbranch_id, remote, name,
                                       disabled)

    def getCommitSet(self, db):
        import gitutils
        import log.commitset

        cursor = db.cursor()
        cursor.execute(
            """SELECT DISTINCT commits.id, commits.sha1
                            FROM commits
                            JOIN changesets ON (changesets.child=commits.id)
                            JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                           WHERE reviewchangesets.review=%s""", (self.id, ))

        commits = []

        for commit_id, commit_sha1 in cursor:
            commits.append(
                gitutils.Commit.fromSHA1(db, self.repository, commit_sha1,
                                         commit_id))

        return log.commitset.CommitSet(commits)

    def containsCommit(self,
                       db,
                       commit,
                       include_head_and_tails=False,
                       include_actual_log=False):
        import gitutils

        commit_id = None
        commit_sha1 = None

        if isinstance(commit, gitutils.Commit):
            commit_id = commit.id
            commit_sha1 = commit.sha1
        elif isinstance(commit, str):
            commit_sha1 = self.repository.revparse(commit)
            commit = None
        elif isinstance(commit, int):
            commit_id = commit
            commit = None
        else:
            raise TypeError

        cursor = db.cursor()

        if commit_id is not None:
            cursor.execute(
                """SELECT 1
                                FROM reviewchangesets
                                JOIN changesets ON (id=changeset)
                               WHERE reviewchangesets.review=%s
                                 AND changesets.child=%s
                                 AND changesets.type!='conflicts'""",
                (self.id, commit_id))
        else:
            cursor.execute(
                """SELECT 1
                                FROM reviewchangesets
                                JOIN changesets ON (changesets.id=reviewchangesets.changeset)
                                JOIN commits ON (commits.id=changesets.child)
                               WHERE reviewchangesets.review=%s
                                 AND changesets.type!='conflicts'
                                 AND commits.sha1=%s""",
                (self.id, commit_sha1))

        if cursor.fetchone() is not None:
            return True

        if include_head_and_tails:
            head_and_tails = set([self.branch.getHead(db)])

            commitset = self.getCommitSet(db)

            if commitset:
                head_and_tails |= commitset.getTails()

            if commit_sha1 is None:
                if commit is None:
                    commit = gitutils.Commit.fromId(db, self.repository,
                                                    commit_id)
                commit_sha1 = commit.sha1

            if commit_sha1 in head_and_tails:
                return True

        if include_actual_log:
            if commit_id is not None:
                cursor.execute(
                    """SELECT 1
                                    FROM reachable
                                    JOIN branches ON (branches.id=reachable.branch)
                                    JOIN reviews ON (reviews.branch=branches.id)
                                   WHERE reachable.commit=%s
                                     AND reviews.id=%s""",
                    (commit_id, self.id))
            else:
                cursor.execute(
                    """SELECT 1
                                    FROM commits
                                    JOIN reachable ON (reachable.commit=commits.id)
                                    JOIN branches ON (branches.id=reachable.branch)
                                    JOIN reviews ON (reviews.branch=branches.id)
                                   WHERE commits.sha1=%s
                                     AND reviews.id=%s""",
                    (commit_sha1, self.id))

            if cursor.fetchone() is not None:
                return True

        return False

    def getJS(self):
        return "var review = critic.review = { id: %d, branch: { id: %d, name: %r }, owners: [ %s ], serial: %d };" % (
            self.id, self.branch.id, self.branch.name, ", ".join(
                owner.getJSConstructor()
                for owner in self.owners), self.serial)

    def getETag(self, db, user=None):
        import configuration

        cursor = db.cursor()
        etag = ""

        if configuration.debug.IS_DEVELOPMENT:
            cursor.execute(
                "SELECT installed_at FROM systemidentities WHERE name=%s",
                (configuration.base.SYSTEM_IDENTITY, ))
            installed_at = cursor.fetchone()[0]
            etag += "install%s." % time.mktime(installed_at.timetuple())

        if user and not user.isAnonymous():
            etag += "user%d." % user.id

        etag += "review%d.serial%d" % (self.id, self.serial)

        if user:
            items = self.getDraftStatus(db, user)
            if any(items.values()):
                etag += ".draft%d" % hash(tuple(sorted(items.items())))

            cursor.execute(
                "SELECT id FROM reviewrebases WHERE review=%s AND uid=%s AND new_head IS NULL",
                (self.id, user.id))
            row = cursor.fetchone()
            if row:
                etag += ".rebase%d" % row[0]

        return '"%s"' % etag

    def getURL(self, db, user=None, indent=0, separator="\n"):
        import dbutils

        indent = " " * indent

        if user:
            url_prefixes = user.getCriticURLs(db)
        else:
            url_prefixes = [dbutils.getURLPrefix(db)]

        return separator.join([
            "%s%s/r/%d" % (indent, url_prefix, self.id)
            for url_prefix in url_prefixes
        ])

    def getRecipients(self, db):
        from dbutils import User

        cursor = db.cursor()
        cursor.execute(
            "SELECT uid, include FROM reviewrecipientfilters WHERE review=%s",
            (self.id, ))

        default_include = True
        included = set(owner.id for owner in self.owners)
        excluded = set()

        for uid, include in cursor:
            if uid is None:
                default_include = include
            elif include:
                included.add(uid)
            elif uid not in self.owners:
                excluded.add(uid)

        cursor.execute("SELECT uid FROM reviewusers WHERE review=%s",
                       (self.id, ))

        recipients = []
        for (user_id, ) in cursor:
            if user_id in excluded:
                continue
            elif user_id not in included and not default_include:
                continue

            user = User.fromId(db, user_id)
            if user.status != "retired":
                recipients.append(user)

        return recipients

    def getDraftStatus(self, db, user):
        if self.draft_status is None:
            self.draft_status = countDraftItems(db, user, self)
        return self.draft_status

    def incrementSerial(self, db):
        self.serial += 1
        db.cursor().execute("UPDATE reviews SET serial=%s WHERE id=%s",
                            [self.serial, self.id])

    def scheduleBranchArchival(self, db, delay=None):
        import dbutils

        # First, cancel current scheduled archival, if there is one.
        self.cancelScheduledBranchArchival(db)

        # If review is not closed or dropped, don't schedule a branch archival.
        # Also don't schedule one if the branch has already been archived.
        if self.state not in ("closed", "dropped") or self.branch.archived:
            return

        if delay is None:
            # Configuration policy:
            #
            # Any owner of a review can, by having changed the relevant
            # preference setting, increase the time before a review branch is
            # archived, or disable archival entirely, but they can't make it
            # happen sooner than the system or repository default, or what any
            # other owner has requested.

            # Find configured value for each owner, and also the per-repository
            # (or per-system) default, in case each owner has changed the
            # setting.
            preference_item = "review.branchArchiveDelay." + self.state
            repository_default = dbutils.User.fetchPreference(
                db, preference_item, repository=self.repository)
            delays = set([repository_default])
            for owner in self.owners:
                delays.add(
                    owner.getPreference(db,
                                        preference_item,
                                        repository=self.repository))

            # If configured to zero (by any owner,) don't schedule a branch
            # archival.
            if min(delays) <= 0:
                return

            # Otherwise, use maximum configured value for any owner.
            delay = max(delays)

        cursor = db.cursor()
        cursor.execute(
            """INSERT INTO scheduledreviewbrancharchivals (review, deadline)
                               VALUES (%s, NOW() + INTERVAL %s)""",
            (self.id, "%d DAYS" % delay))

        return delay

    def cancelScheduledBranchArchival(self, db):
        cursor = db.cursor()
        cursor.execute(
            """DELETE FROM scheduledreviewbrancharchivals
                                WHERE review=%s""", (self.id, ))

    def close(self, db, user):
        self.serial += 1
        self.state = "closed"
        db.cursor().execute(
            "UPDATE reviews SET state='closed', serial=%s, closed_by=%s WHERE id=%s",
            (self.serial, user.id, self.id))
        self.scheduleBranchArchival(db)

    def drop(self, db, user):
        self.serial += 1
        self.state = "dropped"
        db.cursor().execute(
            "UPDATE reviews SET state='dropped', serial=%s, closed_by=%s WHERE id=%s",
            (self.serial, user.id, self.id))
        self.scheduleBranchArchival(db)

    def reopen(self, db, user):
        self.serial += 1
        if self.branch.archived:
            self.branch.resurrect(db)
        db.cursor().execute(
            "UPDATE reviews SET state='open', serial=%s, closed_by=NULL WHERE id=%s",
            (self.serial, self.id))
        self.cancelScheduledBranchArchival(db)

    def disableTracking(self, db):
        db.cursor().execute(
            "UPDATE trackedbranches SET disabled=TRUE WHERE repository=%s AND local_name=%s",
            (self.repository.id, self.branch.name))

    def setSummary(self, db, summary):
        self.serial += 1
        self.summary = summary
        db.cursor().execute(
            "UPDATE reviews SET summary=%s, serial=%s WHERE id=%s",
            [self.summary, self.serial, self.id])

    def setDescription(self, db, description):
        self.serial += 1
        self.description = description
        db.cursor().execute(
            "UPDATE reviews SET description=%s, serial=%s WHERE id=%s",
            [self.description, self.serial, self.id])

    def addOwner(self, db, owner):
        if not owner in self.owners:
            self.serial += 1
            self.owners.append(owner)

            cursor = db.cursor()
            cursor.execute(
                "SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s",
                (self.id, owner.id))

            if cursor.fetchone():
                cursor.execute(
                    "UPDATE reviewusers SET owner=TRUE WHERE review=%s AND uid=%s",
                    (self.id, owner.id))
            else:
                cursor.execute(
                    "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)",
                    (self.id, owner.id))

            cursor.execute(
                "SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s",
                (self.repository.id, self.branch.name))

            row = cursor.fetchone()
            if row:
                trackedbranch_id = row[0]
                cursor.execute(
                    "INSERT INTO trackedbranchusers (branch, uid) VALUES (%s, %s)",
                    (trackedbranch_id, owner.id))

    def removeOwner(self, db, owner):
        if owner in self.owners:
            self.serial += 1
            self.owners.remove(owner)

            cursor = db.cursor()
            cursor.execute(
                "UPDATE reviewusers SET owner=FALSE WHERE review=%s AND uid=%s",
                (self.id, owner.id))
            cursor.execute(
                "SELECT id FROM trackedbranches WHERE repository=%s AND local_name=%s",
                (self.repository.id, self.branch.name))

            row = cursor.fetchone()
            if row:
                trackedbranch_id = row[0]
                cursor.execute(
                    "DELETE FROM trackedbranchusers WHERE branch=%s AND uid=%s",
                    (trackedbranch_id, owner.id))

    def getReviewFilters(self, db):
        cursor = db.cursor()
        cursor.execute(
            "SELECT uid, path, type, NULL FROM reviewfilters WHERE review=%s",
            (self.id, ))
        return cursor.fetchall() or None

    def getFilteredTails(self, db):
        import log.commitset
        commitset = log.commitset.CommitSet(self.branch.getCommits(db))
        return commitset.getFilteredTails(self.branch.repository)

    def getRelevantFiles(self, db, user):
        if not self.filters:
            from reviewing.filters import Filters

            self.filters = Filters()
            self.filters.setFiles(db, review=self)
            self.filters.load(db, review=self)
            self.relevant_files = self.filters.getRelevantFiles()

            cursor = db.cursor()
            cursor.execute(
                "SELECT assignee, file FROM fullreviewuserfiles WHERE review=%s",
                (self.id, ))
            for user_id, file_id in cursor:
                self.relevant_files.setdefault(user_id, set()).add(file_id)

        return self.relevant_files.get(user.id, set())

    def getUserAssociation(self, db, user):
        cursor = db.cursor()

        association = []

        if user in self.owners:
            association.append("owner")

        cursor.execute(
            """SELECT 1
                            FROM reviewchangesets
                            JOIN changesets ON (changesets.id=reviewchangesets.changeset)
                            JOIN commits ON (commits.id=changesets.child)
                            JOIN gitusers ON (gitusers.id=commits.author_gituser)
                            JOIN usergitemails USING (email)
                           WHERE reviewchangesets.review=%s
                             AND usergitemails.uid=%s""", (self.id, user.id))
        if cursor.fetchone():
            association.append("author")

        cursor.execute(
            "SELECT COUNT(*) FROM fullreviewuserfiles WHERE review=%s AND assignee=%s",
            (self.id, user.id))
        if cursor.fetchone()[0] != 0:
            association.append("reviewer")
        elif user not in self.owners:
            cursor.execute(
                "SELECT 1 FROM reviewusers WHERE review=%s AND uid=%s",
                (self.id, user.id))
            if cursor.fetchone():
                association.append("watcher")

        if not association:
            association.append("none")

        return ", ".join(association)

    @staticmethod
    def fromId(db, review_id, branch=None, profiler=None):
        from dbutils import User

        cursor = db.cursor()
        cursor.execute(
            "SELECT type, branch, state, serial, summary, description, applyfilters, applyparentfilters FROM reviews WHERE id=%s",
            [review_id])
        row = cursor.fetchone()
        if not row: raise NoSuchReview(review_id)

        type, branch_id, state, serial, summary, description, applyfilters, applyparentfilters = row

        if profiler: profiler.check("Review.fromId: basic")

        if branch is None:
            from dbutils import Branch
            branch = Branch.fromId(db,
                                   branch_id,
                                   load_review=False,
                                   profiler=profiler)

        cursor.execute("SELECT uid FROM reviewusers WHERE review=%s AND owner",
                       (review_id, ))

        owners = User.fromIds(db, [user_id for (user_id, ) in cursor])

        if profiler: profiler.check("Review.fromId: owners")

        review = Review(review_id, owners, type, branch, state, serial,
                        summary, description, applyfilters, applyparentfilters)
        branch.review = review

        # Reviewers: all users that have at least one review file assigned to them.
        cursor.execute(
            """SELECT DISTINCT uid, assignee IS NOT NULL, type
                            FROM reviewusers
                 LEFT OUTER JOIN fullreviewuserfiles ON (fullreviewuserfiles.review=reviewusers.review AND assignee=uid)
                           WHERE reviewusers.review=%s""", (review_id, ))

        reviewers = []
        watchers = []
        watcher_types = {}

        for user_id, is_reviewer, user_type in cursor.fetchall():
            if is_reviewer:
                reviewers.append(user_id)
            elif user_id not in review.owners:
                watchers.append(user_id)
                watcher_types[user_id] = user_type

        review.reviewers = User.fromIds(db, reviewers)

        for watcher in User.fromIds(db, watchers):
            review.watchers[watcher] = watcher_types[watcher]

        if profiler: profiler.check("Review.fromId: users")

        return review

    @staticmethod
    def fromBranch(db, branch):
        if branch:
            cursor = db.cursor()
            cursor.execute("SELECT id FROM reviews WHERE branch=%s",
                           [branch.id])
            row = cursor.fetchone()
            if not row: return None
            else: return Review.fromId(db, row[0], branch)
        else:
            return None

    @staticmethod
    def fromName(db, repository, name):
        from dbutils import Branch
        return Review.fromBranch(db, Branch.fromName(db, repository, name))

    @staticmethod
    def fromArgument(db, argument):
        try:
            return Review.fromId(db, int(argument))
        except:
            from dbutils import Branch
            branch = Branch.fromName(db, str(argument))
            if not branch: return None
            return Review.fromBranch(db, branch)

    @staticmethod
    def fromAPI(api_review):
        return Review.fromId(api_review.critic.database, api_review.id)
Ejemplo n.º 7
0
def createCommentChain(db, user, review, chain_type, commit_id=None, origin=None, file_id=None, parent_id=None, child_id=None, old_sha1=None, new_sha1=None, offset=None, count=None):
    if chain_type == "issue" and review.state != "open":
        raise OperationFailure(code="reviewclosed",
                               title="Review is closed!",
                               message="You need to reopen the review before you can raise new issues.")

    cursor = db.cursor()

    if file_id is not None and (parent_id == child_id or parent_id is None):
        cursor.execute("""SELECT 1
                            FROM reviewchangesets
                            JOIN fileversions USING (changeset)
                           WHERE reviewchangesets.review=%s
                             AND fileversions.file=%s
                             AND fileversions.old_sha1!='0000000000000000000000000000000000000000'
                             AND fileversions.new_sha1!='0000000000000000000000000000000000000000'""",
                       (review.id, file_id))

        if cursor.fetchone():
            cursor.execute("""SELECT parent, child
                                FROM changesets
                                JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                JOIN fileversions ON (fileversions.changeset=changesets.id)
                               WHERE fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                           (file_id, new_sha1))

            rows = cursor.fetchall()

            if not rows:
                cursor.execute("""SELECT parent, child
                                    FROM changesets
                                    JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                    JOIN fileversions ON (fileversions.changeset=changesets.id)
                                   WHERE fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                               (file_id, new_sha1))

                rows = cursor.fetchall()

            parent = child = None

            for row_parent_id, row_child_id in rows:
                if row_child_id == child_id:
                    parent = gitutils.Commit.fromId(db, review.repository, row_parent_id)
                    child = gitutils.Commit.fromId(db, review.repository, row_child_id)
                    break
                elif row_parent_id == child_id and parent is None:
                    parent = gitutils.Commit.fromId(db, review.repository, row_parent_id)
                    child = gitutils.Commit.fromId(db, review.repository, row_child_id)

            if parent and child:
                url = "/%s/%s..%s?review=%d&file=%d" % (review.repository.name, parent.sha1[:8], child.sha1[:8], review.id, file_id)
                link = ("<p>The link below goes to a diff that can be use to create the comment:</p>" +
                        "<p style='padding-left: 2em'><a href='%s'>%s%s</a></p>") % (url, dbutils.getURLPrefix(db), url)
            else:
                link = ""

            raise OperationFailure(code="notsupported",
                                   title="File changed in review",
                                   message=("<p>Due to limitations in the code used to create comments, " +
                                            "it's only possible to create comments via a diff view if " +
                                            "the commented file has been changed in the review.</p>" +
                                            link),
                                   is_html=True)

        cursor.execute("""INSERT INTO commentchains (review, uid, type, file, first_commit, last_commit)
                               VALUES (%s, %s, %s, %s, %s, %s)
                            RETURNING id""",
                       (review.id, user.id, chain_type, file_id, child_id, child_id))
        chain_id = cursor.fetchone()[0]

        cursor.execute("""INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line)
                               VALUES (%s, %s, %s, %s, %s, %s)""",
                       (chain_id, user.id, child_id, new_sha1, offset, offset + count - 1))
    elif file_id is not None:
        parents_returned = set()

        def getFileParent(new_sha1):
            cursor.execute("""SELECT changesets.id, fileversions.old_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.new_sha1=%s""",
                           [review.id, file_id, new_sha1])
            try:
                changeset_id, old_sha1 = cursor.fetchone()
                if old_sha1 in parents_returned: return None, None
                parents_returned.add(old_sha1)
                return changeset_id, old_sha1
            except:
                return None, None

        children_returned = set()

        def getFileChild(old_sha1):
            cursor.execute("""SELECT changesets.id, fileversions.new_sha1
                                FROM changesets, reviewchangesets, fileversions
                               WHERE reviewchangesets.review=%s
                                 AND reviewchangesets.changeset=changesets.id
                                 AND fileversions.changeset=changesets.id
                                 AND fileversions.file=%s
                                 AND fileversions.old_sha1=%s""",
                           [review.id, file_id, old_sha1])
            try:
                changeset_id, new_sha1 = cursor.fetchone()
                if new_sha1 in children_returned: return None, None
                children_returned.add(new_sha1)
                return changeset_id, new_sha1
            except:
                return None, None

        cursor.execute("""SELECT changesets.id
                            FROM changesets, reviewchangesets, fileversions
                           WHERE reviewchangesets.review=%s
                             AND reviewchangesets.changeset=changesets.id
                             AND changesets.child=%s
                             AND fileversions.changeset=changesets.id
                             AND fileversions.file=%s
                             AND fileversions.old_sha1=%s
                             AND fileversions.new_sha1=%s""",
                       [review.id, child_id, file_id, old_sha1, new_sha1])

        row = cursor.fetchone()

        if not row:
            if origin == "old":
                cursor.execute("""SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.old_sha1=%s""",
                               [review.id, file_id, old_sha1])
            else:
                cursor.execute("""SELECT changesets.id
                                    FROM changesets, reviewchangesets, fileversions
                                   WHERE reviewchangesets.review=%s
                                     AND reviewchangesets.changeset=changesets.id
                                     AND fileversions.changeset=changesets.id
                                     AND fileversions.file=%s
                                     AND fileversions.new_sha1=%s""",
                               [review.id, file_id, new_sha1])

            row = cursor.fetchone()

        primary_changeset_id = row[0]

        sha1s_older = { }
        sha1s_newer = { old_sha1: (primary_changeset_id, new_sha1) }

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileParent(sha1)
            if changeset_id:
                sha1s_older[sha1] = changeset_id, next_sha1
                sha1s_newer[next_sha1] = changeset_id, sha1
                sha1 = next_sha1
            else:
                break

        sha1 = new_sha1
        while True:
            changeset_id, next_sha1 = getFileChild(sha1)
            if changeset_id:
                sha1s_newer[sha1] = changeset_id, next_sha1
                sha1 = next_sha1
            else:
                break

        commentchainlines_values = []
        processed = set()

        def searchOrigin(changeset_id, sha1, search_space, first_line, last_line):
            try:
                while sha1 not in processed:
                    processed.add(sha1)
                    changeset_id, next_sha1 = search_space[sha1]
                    changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))
                    if len(changeset.child.parents) > 1: break
                    verdict, next_first_line, next_last_line = updateCommentChain(first_line, last_line, changeset.files[0].chunks, forward)
                    if verdict == "modified": break
                    sha1 = next_sha1
                    first_line = next_first_line
                    last_line = next_last_line
            except:
                pass
            return changeset_id, sha1, first_line, last_line

        first_line = offset
        last_line = offset + count - 1

        if origin == 'old':
            changeset_id, sha1, first_line, last_line = searchOrigin(primary_changeset_id, old_sha1, sha1s_older, first_line, last_line)
            commit_id = diff.Changeset.fromId(db, review.repository, changeset_id).parent.id
        else:
            changeset_id, sha1, first_line, last_line = searchOrigin(primary_changeset_id, new_sha1, sha1s_older, first_line, last_line)
            commit_id = diff.Changeset.fromId(db, review.repository, changeset_id).child.id

        commentchainlines_values.append((user.id, commit_id, sha1, first_line, last_line))
        processed = set()
        processed.add(sha1)

        while sha1 in sha1s_newer:
            changeset_id, sha1 = sha1s_newer[sha1]

            if sha1 in processed: break
            else: processed.add(sha1)

            changeset = changeset_load.loadChangeset(db, review.repository, changeset_id, filtered_file_ids=set([file_id]))

            if len(changeset.child.parents) != 1:
                chunks = diff.parse.parseDifferences(review.repository, from_commit=changeset.parent, to_commit=changeset.child, selected_path=dbutils.describe_file(db, file_id)).chunks
            else:
                chunks = changeset.files[0].chunks

            verdict, first_line, last_line = updateCommentChain(first_line, last_line, chunks)

            if verdict == "transfer":
                commentchainlines_values.append((user.id, changeset.child.getId(db), sha1, first_line, last_line))
            else:
                break

        cursor.execute("INSERT INTO commentchains (review, uid, type, origin, file, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, origin, file_id, parent_id, child_id])
        chain_id = cursor.fetchone()[0]

        try: cursor.executemany("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", [(chain_id,) + values for values in commentchainlines_values])
        except: raise Exception, repr(commentchainlines_values)
    elif commit_id is not None:
        commit = gitutils.Commit.fromId(db, review.repository, commit_id)

        cursor.execute("INSERT INTO commentchains (review, uid, type, first_commit, last_commit) VALUES (%s, %s, %s, %s, %s) RETURNING id", [review.id, user.id, chain_type, commit_id, commit_id])
        chain_id = cursor.fetchone()[0]

        cursor.execute("INSERT INTO commentchainlines (chain, uid, commit, sha1, first_line, last_line) VALUES (%s, %s, %s, %s, %s, %s)", (chain_id, user.id, commit_id, commit.sha1, offset, offset + count - 1))
    else:
        cursor.execute("INSERT INTO commentchains (review, uid, type) VALUES (%s, %s, %s) RETURNING id", [review.id, user.id, chain_type])
        chain_id = cursor.fetchone()[0]

    commentchainusers = set([user.id] + map(int, review.owners))

    if file_id is not None:
        filters = Filters()
        filters.load(db, review=review)

        for user_id in filters.listUsers(db, file_id):
            commentchainusers.add(user_id)

    cursor.executemany("INSERT INTO commentchainusers (chain, uid) VALUES (%s, %s)", [(chain_id, user_id) for user_id in commentchainusers])

    return chain_id