LCOV - code coverage report
Current view: top level - entities - Change.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 213 232 91.8 %
Date: 2022-11-19 15:00:39 Functions: 73 77 94.8 %

          Line data    Source code
       1             : // Copyright (C) 2008 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.entities;
      16             : 
      17             : import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
      18             : 
      19             : import com.google.auto.value.AutoValue;
      20             : import com.google.common.primitives.Ints;
      21             : import com.google.gerrit.common.Nullable;
      22             : import com.google.gerrit.extensions.client.ChangeStatus;
      23             : import com.google.gson.Gson;
      24             : import com.google.gson.TypeAdapter;
      25             : import com.google.gson.annotations.SerializedName;
      26             : import java.time.Instant;
      27             : import java.util.Arrays;
      28             : import java.util.Optional;
      29             : 
      30             : /**
      31             :  * A change proposed to be merged into a branch.
      32             :  *
      33             :  * <p>The data graph rooted below a Change can be quite complex:
      34             :  *
      35             :  * <pre>
      36             :  *   {@link Change}
      37             :  *     |
      38             :  *     +- {@link ChangeMessage}: &quot;cover letter&quot; or general comment.
      39             :  *     |
      40             :  *     +- {@link PatchSet}: a single variant of this change.
      41             :  *          |
      42             :  *          +- {@link PatchSetApproval}: a +/- vote on the change's current state.
      43             :  *          |
      44             :  *          +- {@link HumanComment}: comment about a specific line
      45             :  * </pre>
      46             :  *
      47             :  * <p>
      48             :  *
      49             :  * <h5>PatchSets</h5>
      50             :  *
      51             :  * <p>Every change has at least one PatchSet. A change starts out with one PatchSet, the initial
      52             :  * proposal put forth by the change owner. This {@link Account} is usually also listed as the author
      53             :  * and committer in the PatchSetInfo.
      54             :  *
      55             :  * <p>Each PatchSet contains zero or more Patch records, detailing the file paths impacted by the
      56             :  * change (otherwise known as, the file paths the author added/deleted/modified). Sometimes a merge
      57             :  * commit can contain zero patches, if the merge has no conflicts, or has no impact other than to
      58             :  * cut off a line of development.
      59             :  *
      60             :  * <p>Each Comment is a draft or a published comment about a single line of the associated file.
      61             :  * These are the inline comment entities created by users as they perform a review.
      62             :  *
      63             :  * <p>When additional PatchSets appear under a change, these PatchSets reference <i>replacement</i>
      64             :  * commits; alternative commits that could be made to the project instead of the original commit
      65             :  * referenced by the first PatchSet.
      66             :  *
      67             :  * <p>A change has at most one current PatchSet. The current PatchSet is updated when a new
      68             :  * replacement PatchSet is uploaded. When a change is submitted, the current patch set is what is
      69             :  * merged into the destination branch.
      70             :  *
      71             :  * <p>
      72             :  *
      73             :  * <h5>ChangeMessage</h5>
      74             :  *
      75             :  * <p>The ChangeMessage entity is a general free-form comment about the whole change, rather than
      76             :  * Comment's file and line specific context. The ChangeMessage appears at the start of any email
      77             :  * generated by Gerrit, and is shown on the change overview page, rather than in a file-specific
      78             :  * context. Users often use this entity to describe general remarks about the overall concept
      79             :  * proposed by the change.
      80             :  *
      81             :  * <p>
      82             :  *
      83             :  * <h5>PatchSetApproval</h5>
      84             :  *
      85             :  * <p>PatchSetApproval entities exist to fill in the <i>cells</i> of the approvals table in the web
      86             :  * UI. That is, a single PatchSetApproval record's key is the tuple {@code
      87             :  * (PatchSet,Account,ApprovalCategory)}. Each PatchSetApproval carries with it a small score value,
      88             :  * typically within the range -2..+2.
      89             :  *
      90             :  * <p>If an Account has created only PatchSetApprovals with a score value of 0, the Change shows in
      91             :  * their dashboard, and they are said to be CC'd (carbon copied) on the Change, but are not a direct
      92             :  * reviewer. This often happens when an account was specified at upload time with the {@code --cc}
      93             :  * command line flag, or have published comments, but left the approval scores at 0 ("No Score").
      94             :  *
      95             :  * <p>If an Account has one or more PatchSetApprovals with a score != 0, the Change shows in their
      96             :  * dashboard, and they are said to be an active reviewer. Such individuals are highlighted when
      97             :  * notice of a replacement patch set is sent, or when notice of the change submission occurs.
      98             :  */
      99             : public final class Change {
     100             : 
     101             :   public static Id id(int id) {
     102         109 :     return new AutoValue_Change_Id(id);
     103             :   }
     104             : 
     105             :   /** The numeric change ID */
     106             :   @AutoValue
     107         109 :   public abstract static class Id {
     108             :     /**
     109             :      * Parse a Change.Id out of a string representation.
     110             :      *
     111             :      * @param str the string to parse
     112             :      * @return Optional containing the Change.Id, or {@code Optional.empty()} if str does not
     113             :      *     represent a valid Change.Id.
     114             :      */
     115             :     public static Optional<Id> tryParse(String str) {
     116          11 :       Integer id = Ints.tryParse(str);
     117          11 :       return id != null ? Optional.of(Change.id(id)) : Optional.empty();
     118             :     }
     119             : 
     120             :     @Nullable
     121             :     public static Id fromRef(String ref) {
     122         114 :       if (RefNames.isRefsEdit(ref)) {
     123          21 :         return fromEditRefPart(ref);
     124             :       }
     125         114 :       int cs = startIndex(ref);
     126         114 :       if (cs < 0) {
     127         110 :         return null;
     128             :       }
     129         100 :       int ce = nextNonDigit(ref, cs);
     130         100 :       if (ref.substring(ce).equals(RefNames.META_SUFFIX)
     131         100 :           || ref.substring(ce).equals(RefNames.ROBOT_COMMENTS_SUFFIX)
     132         100 :           || PatchSet.Id.fromRef(ref, ce) >= 0) {
     133         100 :         return Change.id(Integer.parseInt(ref.substring(cs, ce)));
     134             :       }
     135           1 :       return null;
     136             :     }
     137             : 
     138             :     @Nullable
     139             :     public static Id fromAllUsersRef(String ref) {
     140           9 :       if (ref == null) {
     141           1 :         return null;
     142             :       }
     143             :       String prefix;
     144           9 :       if (ref.startsWith(RefNames.REFS_STARRED_CHANGES)) {
     145           5 :         prefix = RefNames.REFS_STARRED_CHANGES;
     146           9 :       } else if (ref.startsWith(RefNames.REFS_DRAFT_COMMENTS)) {
     147           9 :         prefix = RefNames.REFS_DRAFT_COMMENTS;
     148             :       } else {
     149           1 :         return null;
     150             :       }
     151           9 :       int cs = startIndex(ref, prefix);
     152           9 :       if (cs < 0) {
     153           1 :         return null;
     154             :       }
     155           9 :       int ce = nextNonDigit(ref, cs);
     156           9 :       if (ce < ref.length() && ref.charAt(ce) == '/' && isNumeric(ref, ce + 1)) {
     157           9 :         return Change.id(Integer.parseInt(ref.substring(cs, ce)));
     158             :       }
     159           1 :       return null;
     160             :     }
     161             : 
     162             :     private static boolean isNumeric(String s, int off) {
     163           9 :       if (off >= s.length()) {
     164           1 :         return false;
     165             :       }
     166           9 :       for (int i = off; i < s.length(); i++) {
     167           9 :         if (!Character.isDigit(s.charAt(i))) {
     168           1 :           return false;
     169             :         }
     170             :       }
     171           9 :       return true;
     172             :     }
     173             : 
     174             :     @Nullable
     175             :     public static Id fromEditRefPart(String ref) {
     176          28 :       int startChangeId = ref.indexOf(RefNames.EDIT_PREFIX) + RefNames.EDIT_PREFIX.length();
     177          28 :       int endChangeId = nextNonDigit(ref, startChangeId);
     178          28 :       String id = ref.substring(startChangeId, endChangeId);
     179          28 :       if (id != null && !id.isEmpty()) {
     180          28 :         return Change.id(Integer.parseInt(id));
     181             :       }
     182           0 :       return null;
     183             :     }
     184             : 
     185             :     @Nullable
     186             :     public static Id fromRefPart(String ref) {
     187          40 :       Integer id = RefNames.parseShardedRefPart(ref);
     188          40 :       return id != null ? Change.id(id) : null;
     189             :     }
     190             : 
     191             :     static int startIndex(String ref) {
     192         115 :       return startIndex(ref, REFS_CHANGES);
     193             :     }
     194             : 
     195             :     static int startIndex(String ref, String expectedPrefix) {
     196         115 :       if (ref == null || !ref.startsWith(expectedPrefix)) {
     197         110 :         return -1;
     198             :       }
     199             : 
     200             :       // Last 2 digits.
     201         101 :       int ls = expectedPrefix.length();
     202         101 :       int le = nextNonDigit(ref, ls);
     203         101 :       if (le - ls != 2 || le >= ref.length() || ref.charAt(le) != '/') {
     204           1 :         return -1;
     205             :       }
     206             : 
     207             :       // Change ID.
     208         101 :       int cs = le + 1;
     209         101 :       if (cs >= ref.length() || ref.charAt(cs) == '0') {
     210           1 :         return -1;
     211             :       }
     212         101 :       int ce = nextNonDigit(ref, cs);
     213         101 :       if (ce >= ref.length() || ref.charAt(ce) != '/') {
     214           1 :         return -1;
     215             :       }
     216         101 :       switch (ce - cs) {
     217             :         case 0:
     218           1 :           return -1;
     219             :         case 1:
     220         101 :           if (ref.charAt(ls) != '0' || ref.charAt(ls + 1) != ref.charAt(cs)) {
     221           0 :             return -1;
     222             :           }
     223             :           break;
     224             :         default:
     225          64 :           if (ref.charAt(ls) != ref.charAt(ce - 2) || ref.charAt(ls + 1) != ref.charAt(ce - 1)) {
     226           1 :             return -1;
     227             :           }
     228             :           break;
     229             :       }
     230         101 :       return cs;
     231             :     }
     232             : 
     233             :     static int nextNonDigit(String s, int i) {
     234         101 :       while (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9') {
     235         101 :         i++;
     236             :       }
     237         101 :       return i;
     238             :     }
     239             : 
     240             :     abstract int id();
     241             : 
     242             :     public int get() {
     243         107 :       return id();
     244             :     }
     245             : 
     246             :     public String toRefPrefix() {
     247         104 :       return refPrefixBuilder().toString();
     248             :     }
     249             : 
     250             :     StringBuilder refPrefixBuilder() {
     251         104 :       StringBuilder r = new StringBuilder(32).append(REFS_CHANGES);
     252         104 :       int m = get() % 100;
     253         104 :       if (m < 10) {
     254         104 :         r.append('0');
     255             :       }
     256         104 :       return r.append(m).append('/').append(get()).append('/');
     257             :     }
     258             : 
     259             :     @Override
     260             :     public final String toString() {
     261         105 :       return Integer.toString(get());
     262             :     }
     263             :   }
     264             : 
     265             :   public static Key key(String key) {
     266         112 :     return new AutoValue_Change_Key(key);
     267             :   }
     268             : 
     269             :   /**
     270             :    * Globally unique identification of this change. This generally takes the form of a string
     271             :    * "Ixxxxxx...", and is stored in the Change-Id footer of a commit.
     272             :    */
     273             :   @AutoValue
     274         112 :   public abstract static class Key {
     275             :     // TODO(dborowitz): This hardly seems worth it: why would someone pass a URL-encoded change key?
     276             :     // Ideally the standard key() factory method would enforce the format and throw IAE.
     277             :     public static Key parse(String str) {
     278           0 :       return Change.key(KeyUtil.decode(str));
     279             :     }
     280             : 
     281             :     @SerializedName("id")
     282             :     abstract String key();
     283             : 
     284             :     public String get() {
     285         107 :       return key();
     286             :     }
     287             : 
     288             :     /** Construct a key that is after all keys prefixed by this key. */
     289             :     public Key max() {
     290           0 :       final StringBuilder revEnd = new StringBuilder(get().length() + 1);
     291           0 :       revEnd.append(get());
     292           0 :       revEnd.append('\u9fa5');
     293           0 :       return Change.key(revEnd.toString());
     294             :     }
     295             : 
     296             :     /** Obtain a shorter version of this key string, using a leading prefix. */
     297             :     public String abbreviate() {
     298           4 :       final String s = get();
     299           4 :       return s.substring(0, Math.min(s.length(), 9));
     300             :     }
     301             : 
     302             :     @Override
     303             :     public final String toString() {
     304          11 :       return get();
     305             :     }
     306             : 
     307             :     public static TypeAdapter<Key> typeAdapter(Gson gson) {
     308           2 :       return new AutoValue_Change_Key.GsonTypeAdapter(gson);
     309             :     }
     310             :   }
     311             : 
     312             :   /** Minimum database status constant for an open change. */
     313             :   private static final char MIN_OPEN = 'a';
     314             :   /** Database constant for {@link Status#NEW}. */
     315             :   public static final char STATUS_NEW = 'n';
     316             :   /** Maximum database status constant for an open change. */
     317             :   private static final char MAX_OPEN = 'z';
     318             : 
     319             :   /** Database constant for {@link Status#MERGED}. */
     320             :   public static final char STATUS_MERGED = 'M';
     321             : 
     322             :   /** ID number of the first patch set in a change. */
     323             :   public static final int INITIAL_PATCH_SET_ID = 1;
     324             : 
     325             :   /** Change-Id pattern. */
     326             :   public static final String CHANGE_ID_PATTERN = "^[iI][0-9a-f]{4,}.*$";
     327             : 
     328             :   /**
     329             :    * Current state within the basic workflow of the change.
     330             :    *
     331             :    * <p>Within the database, lower case codes ('a'..'z') indicate a change that is still open, and
     332             :    * that can be modified/refined further, while upper case codes ('A'..'Z') indicate a change that
     333             :    * is closed and cannot be further modified.
     334             :    */
     335         153 :   public enum Status {
     336             :     /**
     337             :      * Change is open and pending review, or review is in progress.
     338             :      *
     339             :      * <p>This is the default state assigned to a change when it is first created in the database. A
     340             :      * change stays in the NEW state throughout its review cycle, until the change is submitted or
     341             :      * abandoned.
     342             :      *
     343             :      * <p>Changes in the NEW state can be moved to:
     344             :      *
     345             :      * <ul>
     346             :      *   <li>{@link #MERGED} - when the Submit Patch Set action is used;
     347             :      *   <li>{@link #ABANDONED} - when the Abandon action is used.
     348             :      * </ul>
     349             :      */
     350         153 :     NEW(STATUS_NEW, ChangeStatus.NEW),
     351             : 
     352             :     /**
     353             :      * Change is closed, and submitted to its destination branch.
     354             :      *
     355             :      * <p>Once a change has been merged, it cannot be further modified by adding a replacement patch
     356             :      */
     357         153 :     MERGED(STATUS_MERGED, ChangeStatus.MERGED),
     358             : 
     359             :     /**
     360             :      * Change is closed, but was not submitted to its destination branch.
     361             :      *
     362             :      * <p>Once a change has been abandoned, it cannot be further modified by adding a replacement
     363             :      * patch set, and it cannot be merged. Draft comments however may be published, permitting
     364             :      * reviewers to send constructive feedback.
     365             :      */
     366         153 :     ABANDONED('A', ChangeStatus.ABANDONED);
     367             : 
     368             :     static {
     369         153 :       boolean ok = true;
     370         153 :       if (Status.values().length != ChangeStatus.values().length) {
     371           0 :         ok = false;
     372             :       }
     373         153 :       for (Status s : Status.values()) {
     374         153 :         ok &= s.name().equals(s.changeStatus.name());
     375             :       }
     376         153 :       if (!ok) {
     377           0 :         throw new IllegalStateException(
     378             :             "Mismatched status mapping: "
     379           0 :                 + Arrays.asList(Status.values())
     380             :                 + " != "
     381           0 :                 + Arrays.asList(ChangeStatus.values()));
     382             :       }
     383         153 :     }
     384             : 
     385             :     private final char code;
     386             :     private final boolean closed;
     387             :     private final ChangeStatus changeStatus;
     388             : 
     389         153 :     Status(char c, ChangeStatus cs) {
     390         153 :       code = c;
     391         153 :       closed = !(MIN_OPEN <= c && c <= MAX_OPEN);
     392         153 :       changeStatus = cs;
     393         153 :     }
     394             : 
     395             :     public char getCode() {
     396         106 :       return code;
     397             :     }
     398             : 
     399             :     public boolean isOpen() {
     400         150 :       return !closed;
     401             :     }
     402             : 
     403             :     public boolean isClosed() {
     404         103 :       return closed;
     405             :     }
     406             : 
     407             :     public ChangeStatus asChangeStatus() {
     408         103 :       return changeStatus;
     409             :     }
     410             : 
     411             :     @Nullable
     412             :     public static Status forCode(char c) {
     413         105 :       for (Status s : Status.values()) {
     414         105 :         if (s.code == c) {
     415         105 :           return s;
     416             :         }
     417             :       }
     418             : 
     419           1 :       return null;
     420             :     }
     421             : 
     422             :     @Nullable
     423             :     public static Status forChangeStatus(ChangeStatus cs) {
     424           1 :       for (Status s : Status.values()) {
     425           1 :         if (s.changeStatus == cs) {
     426           1 :           return s;
     427             :         }
     428             :       }
     429           0 :       return null;
     430             :     }
     431             :   }
     432             : 
     433             :   /** Locally assigned unique identifier of the change */
     434             :   private Id changeId;
     435             : 
     436             :   /** Globally assigned unique identifier of the change */
     437             :   private Key changeKey;
     438             : 
     439             :   /** When this change was first introduced into the database. */
     440             :   private Instant createdOn;
     441             : 
     442             :   /**
     443             :    * When was a meaningful modification last made to this record's data
     444             :    *
     445             :    * <p>Note, this update timestamp includes its children.
     446             :    */
     447             :   private Instant lastUpdatedOn;
     448             : 
     449             :   private Account.Id owner;
     450             : 
     451             :   /** The branch (and project) this change merges into. */
     452             :   private BranchNameKey dest;
     453             : 
     454             :   /** Current state code; see {@link Status}. */
     455             :   private char status;
     456             : 
     457             :   /** The current patch set. */
     458             :   private int currentPatchSetId;
     459             : 
     460             :   /** Subject from the current patch set. */
     461             :   private String subject;
     462             : 
     463             :   /** Topic name assigned by the user, if any. */
     464             :   @Nullable private String topic;
     465             : 
     466             :   /**
     467             :    * First line of first patch set's commit message.
     468             :    *
     469             :    * <p>Unlike {@link #subject}, this string does not change if future patch sets change the first
     470             :    * line.
     471             :    */
     472             :   @Nullable private String originalSubject;
     473             : 
     474             :   /**
     475             :    * Unique id for the changes submitted together assigned during merging. Only set if the status is
     476             :    * MERGED.
     477             :    */
     478             :   @Nullable private String submissionId;
     479             : 
     480             :   /** Allows assigning a change to a user. */
     481             :   @Nullable private Account.Id assignee;
     482             : 
     483             :   /** Whether the change is private. */
     484             :   private boolean isPrivate;
     485             : 
     486             :   /** Whether the change is work in progress. */
     487             :   private boolean workInProgress;
     488             : 
     489             :   /** Whether the change has started review. */
     490             :   private boolean reviewStarted;
     491             : 
     492             :   /** References a change that this change reverts. */
     493             :   @Nullable private Id revertOf;
     494             : 
     495             :   /** References the source change and patchset that this change was cherry-picked from. */
     496             :   @Nullable private PatchSet.Id cherryPickOf;
     497             : 
     498           0 :   Change() {}
     499             : 
     500             :   public Change(
     501         106 :       Change.Key newKey, Change.Id newId, Account.Id ownedBy, BranchNameKey forBranch, Instant ts) {
     502         106 :     changeKey = newKey;
     503         106 :     changeId = newId;
     504         106 :     createdOn = ts;
     505         106 :     lastUpdatedOn = createdOn;
     506         106 :     owner = ownedBy;
     507         106 :     dest = forBranch;
     508         106 :     setStatus(Status.NEW);
     509         106 :   }
     510             : 
     511         103 :   public Change(Change other) {
     512         103 :     assignee = other.assignee;
     513         103 :     changeId = other.changeId;
     514         103 :     changeKey = other.changeKey;
     515         103 :     createdOn = other.createdOn;
     516         103 :     lastUpdatedOn = other.lastUpdatedOn;
     517         103 :     owner = other.owner;
     518         103 :     dest = other.dest;
     519         103 :     status = other.status;
     520         103 :     currentPatchSetId = other.currentPatchSetId;
     521         103 :     subject = other.subject;
     522         103 :     originalSubject = other.originalSubject;
     523         103 :     submissionId = other.submissionId;
     524         103 :     topic = other.topic;
     525         103 :     isPrivate = other.isPrivate;
     526         103 :     workInProgress = other.workInProgress;
     527         103 :     reviewStarted = other.reviewStarted;
     528         103 :     revertOf = other.revertOf;
     529         103 :     cherryPickOf = other.cherryPickOf;
     530         103 :   }
     531             : 
     532             :   /** 32 bit integer identity for a change. */
     533             :   public Change.Id getId() {
     534         106 :     return changeId;
     535             :   }
     536             : 
     537             :   /** 32 bit integer identity for a change. */
     538             :   public int getChangeId() {
     539         104 :     return changeId.get();
     540             :   }
     541             : 
     542             :   /** The Change-Id tag out of the initial commit, or a natural key. */
     543             :   public Change.Key getKey() {
     544         105 :     return changeKey;
     545             :   }
     546             : 
     547             :   public void setKey(Change.Key k) {
     548         103 :     changeKey = k;
     549         103 :   }
     550             : 
     551             :   public Account.Id getAssignee() {
     552         104 :     return assignee;
     553             :   }
     554             : 
     555             :   public void setAssignee(Account.Id a) {
     556           8 :     assignee = a;
     557           8 :   }
     558             : 
     559             :   public Instant getCreatedOn() {
     560         105 :     return createdOn;
     561             :   }
     562             : 
     563             :   public void setCreatedOn(Instant ts) {
     564         103 :     createdOn = ts;
     565         103 :   }
     566             : 
     567             :   public Instant getLastUpdatedOn() {
     568         104 :     return lastUpdatedOn;
     569             :   }
     570             : 
     571             :   public void setLastUpdatedOn(Instant now) {
     572         104 :     lastUpdatedOn = now;
     573         104 :   }
     574             : 
     575             :   public Account.Id getOwner() {
     576         104 :     return owner;
     577             :   }
     578             : 
     579             :   public void setOwner(Account.Id owner) {
     580         103 :     this.owner = owner;
     581         103 :   }
     582             : 
     583             :   public BranchNameKey getDest() {
     584         105 :     return dest;
     585             :   }
     586             : 
     587             :   public void setDest(BranchNameKey dest) {
     588         103 :     this.dest = dest;
     589         103 :   }
     590             : 
     591             :   public Project.NameKey getProject() {
     592         104 :     return dest.project();
     593             :   }
     594             : 
     595             :   public String getSubject() {
     596         105 :     return subject;
     597             :   }
     598             : 
     599             :   public String getOriginalSubject() {
     600         104 :     return originalSubject != null ? originalSubject : subject;
     601             :   }
     602             : 
     603             :   public String getOriginalSubjectOrNull() {
     604         104 :     return originalSubject;
     605             :   }
     606             : 
     607             :   /** Get the id of the most current {@link PatchSet} in this change. */
     608             :   @Nullable
     609             :   public PatchSet.Id currentPatchSetId() {
     610         105 :     if (currentPatchSetId > 0) {
     611         105 :       return PatchSet.id(changeId, currentPatchSetId);
     612             :     }
     613           5 :     return null;
     614             :   }
     615             : 
     616             :   public void setCurrentPatchSet(PatchSetInfo ps) {
     617         104 :     if (originalSubject == null && subject != null) {
     618             :       // Change was created before schema upgrade. Use the last subject
     619             :       // associated with this change, as the most recent discussion will
     620             :       // be under that thread in an email client such as GMail.
     621           0 :       originalSubject = subject;
     622             :     }
     623             : 
     624         104 :     currentPatchSetId = ps.getKey().get();
     625         104 :     subject = ps.getSubject();
     626             : 
     627         104 :     if (originalSubject == null) {
     628             :       // Newly created changes remember the first commit's subject.
     629         104 :       originalSubject = subject;
     630             :     }
     631         104 :   }
     632             : 
     633             :   public void setCurrentPatchSet(PatchSet.Id psId, String subject, String originalSubject) {
     634         104 :     if (!psId.changeId().equals(changeId)) {
     635           0 :       throw new IllegalArgumentException("patch set ID " + psId + " is not for change " + changeId);
     636             :     }
     637         104 :     currentPatchSetId = psId.get();
     638         104 :     this.subject = subject;
     639         104 :     this.originalSubject = originalSubject;
     640         104 :   }
     641             : 
     642             :   public void clearCurrentPatchSet() {
     643           0 :     currentPatchSetId = 0;
     644           0 :     subject = null;
     645           0 :     originalSubject = null;
     646           0 :   }
     647             : 
     648             :   public String getSubmissionId() {
     649         104 :     return submissionId;
     650             :   }
     651             : 
     652             :   public void setSubmissionId(String id) {
     653         104 :     this.submissionId = id;
     654         104 :   }
     655             : 
     656             :   public Status getStatus() {
     657         105 :     return Status.forCode(status);
     658             :   }
     659             : 
     660             :   public void setStatus(Status newStatus) {
     661         106 :     status = newStatus.getCode();
     662         106 :   }
     663             : 
     664             :   public boolean isNew() {
     665         104 :     return getStatus().equals(Status.NEW);
     666             :   }
     667             : 
     668             :   public boolean isMerged() {
     669         103 :     return getStatus().equals(Status.MERGED);
     670             :   }
     671             : 
     672             :   public boolean isAbandoned() {
     673         103 :     return getStatus().equals(Status.ABANDONED);
     674             :   }
     675             : 
     676             :   public boolean isClosed() {
     677         103 :     return isAbandoned() || isMerged();
     678             :   }
     679             : 
     680             :   public String getTopic() {
     681         105 :     return topic;
     682             :   }
     683             : 
     684             :   public void setTopic(String topic) {
     685         104 :     this.topic = topic;
     686         104 :   }
     687             : 
     688             :   public boolean isPrivate() {
     689         105 :     return isPrivate;
     690             :   }
     691             : 
     692             :   public void setPrivate(boolean isPrivate) {
     693         104 :     this.isPrivate = isPrivate;
     694         104 :   }
     695             : 
     696             :   public boolean isWorkInProgress() {
     697         105 :     return workInProgress;
     698             :   }
     699             : 
     700             :   public void setWorkInProgress(boolean workInProgress) {
     701         104 :     this.workInProgress = workInProgress;
     702         104 :   }
     703             : 
     704             :   public boolean hasReviewStarted() {
     705         104 :     return reviewStarted;
     706             :   }
     707             : 
     708             :   public void setReviewStarted(boolean reviewStarted) {
     709         104 :     this.reviewStarted = reviewStarted;
     710         104 :   }
     711             : 
     712             :   public void setRevertOf(Id revertOf) {
     713         104 :     this.revertOf = revertOf;
     714         104 :   }
     715             : 
     716             :   public Id getRevertOf() {
     717         104 :     return this.revertOf;
     718             :   }
     719             : 
     720             :   public PatchSet.Id getCherryPickOf() {
     721         104 :     return cherryPickOf;
     722             :   }
     723             : 
     724             :   public void setCherryPickOf(@Nullable PatchSet.Id cherryPickOf) {
     725         103 :     this.cherryPickOf = cherryPickOf;
     726         103 :   }
     727             : 
     728             :   @Override
     729             :   public String toString() {
     730           5 :     return new StringBuilder(getClass().getSimpleName())
     731           5 :         .append('{')
     732           5 :         .append(changeId)
     733           5 :         .append(" (")
     734           5 :         .append(changeKey)
     735           5 :         .append("), ")
     736           5 :         .append("dest=")
     737           5 :         .append(dest)
     738           5 :         .append(", ")
     739           5 :         .append("status=")
     740           5 :         .append(status)
     741           5 :         .append('}')
     742           5 :         .toString();
     743             :   }
     744             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750