diff options
Diffstat (limited to 'gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java')
-rw-r--r-- | gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java new file mode 100644 index 0000000000..61e6a97e92 --- /dev/null +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java @@ -0,0 +1,530 @@ +// Copyright (C) 2008 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.reviewdb.client; + +import com.google.gwtorm.client.Column; +import com.google.gwtorm.client.IntKey; +import com.google.gwtorm.client.RowVersion; +import com.google.gwtorm.client.StringKey; + +import java.sql.Timestamp; + +/** + * A change proposed to be merged into a {@link Branch}. + * <p> + * The data graph rooted below a Change can be quite complex: + * + * <pre> + * {@link Change} + * | + * +- {@link ChangeMessage}: "cover letter" or general comment. + * | + * +- {@link PatchSet}: a single variant of this change. + * | + * +- {@link PatchSetApproval}: a +/- vote on the change's current state. + * | + * +- {@link PatchSetAncestor}: parents of this change's commit. + * | + * +- {@link PatchLineComment}: comment about a specific line + * </pre> + * <p> + * <h5>PatchSets</h5> + * <p> + * Every change has at least one PatchSet. A change starts out with one + * PatchSet, the initial proposal put forth by the change owner. This + * {@link Account} is usually also listed as the author and committer in the + * PatchSetInfo. + * <p> + * The {@link PatchSetAncestor} entities are a mirror of the Git commit + * metadata, providing access to the information without needing direct + * accessing Git. These entities are actually legacy artifacts from Gerrit 1.x + * and could be removed, replaced by direct RevCommit access. + * <p> + * Each PatchSet contains zero or more Patch records, detailing the file paths + * impacted by the change (otherwise known as, the file paths the author + * added/deleted/modified). Sometimes a merge commit can contain zero patches, + * if the merge has no conflicts, or has no impact other than to cut off a line + * of development. + * <p> + * Each PatchLineComment is a draft or a published comment about a single line + * of the associated file. These are the inline comment entities created by + * users as they perform a review. + * <p> + * When additional PatchSets appear under a change, these PatchSets reference + * <i>replacement</i> commits; alternative commits that could be made to the + * project instead of the original commit referenced by the first PatchSet. + * <p> + * A change has at most one current PatchSet. The current PatchSet is updated + * when a new replacement PatchSet is uploaded. When a change is submitted, the + * current patch set is what is merged into the destination branch. + * <p> + * <h5>ChangeMessage</h5> + * <p> + * The ChangeMessage entity is a general free-form comment about the whole + * change, rather than PatchLineComment's file and line specific context. The + * ChangeMessage appears at the start of any email generated by Gerrit, and is + * shown on the change overview page, rather than in a file-specific context. + * Users often use this entity to describe general remarks about the overall + * concept proposed by the change. + * <p> + * <h5>PatchSetApproval</h5> + * <p> + * PatchSetApproval entities exist to fill in the <i>cells</i> of the approvals + * table in the web UI. That is, a single PatchSetApproval record's key is the + * tuple {@code (PatchSet,Account,ApprovalCategory)}. Each PatchSetApproval + * carries with it a small score value, typically within the range -2..+2. + * <p> + * If an Account has created only PatchSetApprovals with a score value of 0, the + * Change shows in their dashboard, and they are said to be CC'd (carbon copied) + * on the Change, but are not a direct reviewer. This often happens when an + * account was specified at upload time with the {@code --cc} command line flag, + * or have published comments, but left the approval scores at 0 ("No Score"). + * <p> + * If an Account has one or more PatchSetApprovals with a score != 0, the Change + * shows in their dashboard, and they are said to be an active reviewer. Such + * individuals are highlighted when notice of a replacement patch set is sent, + * or when notice of the change submission occurs. + */ +public final class Change { + public static class Id extends IntKey<com.google.gwtorm.client.Key<?>> { + private static final long serialVersionUID = 1L; + + @Column(id = 1) + protected int id; + + protected Id() { + } + + public Id(final int id) { + this.id = id; + } + + @Override + public int get() { + return id; + } + + @Override + protected void set(int newValue) { + id = newValue; + } + + /** Parse a Change.Id out of a string representation. */ + public static Id parse(final String str) { + final Id r = new Id(); + r.fromString(str); + return r; + } + + public static Id fromRef(final String ref) { + return PatchSet.Id.fromRef(ref).getParentKey(); + } + } + + /** Globally unique identification of this change. */ + public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> { + private static final long serialVersionUID = 1L; + + @Column(id = 1, length = 60) + protected String id; + + protected Key() { + } + + public Key(final String id) { + this.id = id; + } + + @Override + public String get() { + return id; + } + + @Override + protected void set(String newValue) { + id = newValue; + } + + /** Construct a key that is after all keys prefixed by this key. */ + public Key max() { + final StringBuilder revEnd = new StringBuilder(get().length() + 1); + revEnd.append(get()); + revEnd.append('\u9fa5'); + return new Key(revEnd.toString()); + } + + /** Obtain a shorter version of this key string, using a leading prefix. */ + public String abbreviate() { + final String s = get(); + return s.substring(0, Math.min(s.length(), 9)); + } + + /** Parse a Change.Key out of a string representation. */ + public static Key parse(final String str) { + final Key r = new Key(); + r.fromString(str); + return r; + } + } + + /** Minimum database status constant for an open change. */ + private static final char MIN_OPEN = 'a'; + /** Database constant for {@link Status#NEW}. */ + public static final char STATUS_NEW = 'n'; + /** Database constant for {@link Status#SUBMITTED}. */ + public static final char STATUS_SUBMITTED = 's'; + /** Database constant for {@link Status#DRAFT}. */ + public static final char STATUS_DRAFT = 'd'; + /** Maximum database status constant for an open change. */ + private static final char MAX_OPEN = 'z'; + + /** Database constant for {@link Status#MERGED}. */ + public static final char STATUS_MERGED = 'M'; + + /** + * Current state within the basic workflow of the change. + * + * <p> + * Within the database, lower case codes ('a'..'z') indicate a change that is + * still open, and that can be modified/refined further, while upper case + * codes ('A'..'Z') indicate a change that is closed and cannot be further + * modified. + * */ + public static enum Status { + /** + * Change is open and pending review, or review is in progress. + * + * <p> + * This is the default state assigned to a change when it is first created + * in the database. A change stays in the NEW state throughout its review + * cycle, until the change is submitted or abandoned. + * + * <p> + * Changes in the NEW state can be moved to: + * <ul> + * <li>{@link #SUBMITTED} - when the Submit Patch Set action is used; + * <li>{@link #ABANDONED} - when the Abandon action is used. + * </ul> + */ + NEW(STATUS_NEW), + + /** + * Change is open, but has been submitted to the merge queue. + * + * <p> + * A change enters the SUBMITTED state when an authorized user presses the + * "submit" action through the web UI, requesting that Gerrit merge the + * change's current patch set into the destination branch. + * + * <p> + * Typically a change resides in the SUBMITTED for only a brief sub-second + * period while the merge queue fires and the destination branch is updated. + * However, if a dependency commit (a {@link PatchSetAncestor}, directly or + * transitively) is not yet merged into the branch, the change will hang in + * the SUBMITTED state indefinately. + * + * <p> + * Changes in the SUBMITTED state can be moved to: + * <ul> + * <li>{@link #NEW} - when a replacement patch set is supplied, OR when a + * merge conflict is detected; + * <li>{@link #MERGED} - when the change has been successfully merged into + * the destination branch; + * <li>{@link #ABANDONED} - when the Abandon action is used. + * </ul> + */ + SUBMITTED(STATUS_SUBMITTED), + + /** + * Change is a draft change that only consists of draft patchsets. + * + * <p> + * This is a change that is not meant to be submitted or reviewed yet. If + * the uploader publishes the change, it becomes a NEW change. + * Publishing is a one-way action, a change cannot return to DRAFT status. + * Draft changes are only visible to the uploader and those explicitly + * added as reviewers. + * + * <p> + * Changes in the DRAFT state can be moved to: + * <ul> + * <li>{@link #NEW} - when the change is published, it becomes a new change; + * </ul> + */ + DRAFT(STATUS_DRAFT), + + /** + * Change is closed, and submitted to its destination branch. + * + * <p> + * Once a change has been merged, it cannot be further modified by adding a + * replacement patch set. Draft comments however may be published, + * supporting a post-submit review. + */ + MERGED(STATUS_MERGED), + + /** + * Change is closed, but was not submitted to its destination branch. + * + * <p> + * Once a change has been abandoned, it cannot be further modified by adding + * a replacement patch set, and it cannot be merged. Draft comments however + * may be published, permitting reviewers to send constructive feedback. + */ + ABANDONED('A'); + + private final char code; + private final boolean closed; + + private Status(final char c) { + code = c; + closed = !(MIN_OPEN <= c && c <= MAX_OPEN); + } + + public char getCode() { + return code; + } + + public boolean isOpen() { + return !closed; + } + + public boolean isClosed() { + return closed; + } + + public static Status forCode(final char c) { + for (final Status s : Status.values()) { + if (s.code == c) { + return s; + } + } + return null; + } + } + + /** Locally assigned unique identifier of the change */ + @Column(id = 1) + protected Id changeId; + + /** Globally assigned unique identifier of the change */ + @Column(id = 2) + protected Key changeKey; + + /** optimistic locking */ + @Column(id = 3) + @RowVersion + protected int rowVersion; + + /** When this change was first introduced into the database. */ + @Column(id = 4) + protected Timestamp createdOn; + + /** + * When was a meaningful modification last made to this record's data + * <p> + * Note, this update timestamp includes its children. + */ + @Column(id = 5) + protected Timestamp lastUpdatedOn; + + /** A {@link #lastUpdatedOn} ASC,{@link #changeId} ASC for sorting. */ + @Column(id = 6, length = 16) + protected String sortKey; + + @Column(id = 7, name = "owner_account_id") + protected Account.Id owner; + + /** The branch (and project) this change merges into. */ + @Column(id = 8) + protected Branch.NameKey dest; + + /** Is the change currently open? Set to {@link #status}.isOpen(). */ + @Column(id = 9) + protected boolean open; + + /** Current state code; see {@link Status}. */ + @Column(id = 10) + protected char status; + + /** The total number of {@link PatchSet} children in this Change. */ + @Column(id = 11) + protected int nbrPatchSets; + + /** The current patch set. */ + @Column(id = 12) + protected int currentPatchSetId; + + /** Subject from the current patch set. */ + @Column(id = 13) + protected String subject; + + /** Topic name assigned by the user, if any. */ + @Column(id = 14, notNull = false) + protected String topic; + + /** + * Null if the change has never been tested. + * Empty if it has been tested but against a branch that does + * not exist. + */ + @Column(id = 15, notNull = false) + protected RevId lastSha1MergeTested; + + @Column(id = 16) + protected boolean mergeable; + + protected Change() { + } + + public Change(final Change.Key newKey, final Change.Id newId, + final Account.Id ownedBy, final Branch.NameKey forBranch) { + changeKey = newKey; + changeId = newId; + createdOn = new Timestamp(System.currentTimeMillis()); + lastUpdatedOn = createdOn; + owner = ownedBy; + dest = forBranch; + setStatus(Status.NEW); + setLastSha1MergeTested(null); + } + + /** Legacy 32 bit integer identity for a change. */ + public Change.Id getId() { + return changeId; + } + + /** Legacy 32 bit integer identity for a change. */ + public int getChangeId() { + return changeId.get(); + } + + /** The Change-Id tag out of the initial commit, or a natural key. */ + public Change.Key getKey() { + return changeKey; + } + + public void setKey(final Change.Key k) { + changeKey = k; + } + + public Timestamp getCreatedOn() { + return createdOn; + } + + public Timestamp getLastUpdatedOn() { + return lastUpdatedOn; + } + + public void resetLastUpdatedOn() { + lastUpdatedOn = new Timestamp(System.currentTimeMillis()); + } + + public int getNumberOfPatchSets() { + return nbrPatchSets; + } + + public String getSortKey() { + return sortKey; + } + + public void setSortKey(final String newSortKey) { + sortKey = newSortKey; + } + + public Account.Id getOwner() { + return owner; + } + + public Branch.NameKey getDest() { + return dest; + } + + public Project.NameKey getProject() { + return dest.getParentKey(); + } + + public String getSubject() { + return subject; + } + + /** Get the id of the most current {@link PatchSet} in this change. */ + public PatchSet.Id currentPatchSetId() { + if (currentPatchSetId > 0) { + return new PatchSet.Id(changeId, currentPatchSetId); + } + return null; + } + + public void setCurrentPatchSet(final PatchSetInfo ps) { + currentPatchSetId = ps.getKey().get(); + subject = ps.getSubject(); + } + + /** + * Allocate a new PatchSet id within this change. + * <p> + * <b>Note: This makes the change dirty. Call update() after.</b> + */ + public void nextPatchSetId() { + ++nbrPatchSets; + } + + /** + * Reverts to an older PatchSet id within this change. + * <p> + * <b>Note: This makes the change dirty. Call update() after.</b> + */ + public void removeLastPatchSetId() { + --nbrPatchSets; + } + + public PatchSet.Id currPatchSetId() { + return new PatchSet.Id(changeId, nbrPatchSets); + } + + public Status getStatus() { + return Status.forCode(status); + } + + public void setStatus(final Status newStatus) { + open = newStatus.isOpen(); + status = newStatus.getCode(); + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public RevId getLastSha1MergeTested() { + return lastSha1MergeTested; + } + + public void setLastSha1MergeTested(RevId lastSha1MergeTested) { + this.lastSha1MergeTested = lastSha1MergeTested; + } + + public boolean isMergeable() { + return mergeable; + } + + public void setMergeable(boolean mergeable) { + this.mergeable = mergeable; + } +} |