Merge changes from topic 'revert-of'
* changes: Add revertOf to ChangeIndex Add revertOf to ChangeInfo and populate it when /revert is called Add revertOf to ReviewDb and NoteDb
This commit is contained in:
@@ -5729,6 +5729,8 @@ When present, change is marked as private.
|
|||||||
When present, change is marked as Work In Progress.
|
When present, change is marked as Work In Progress.
|
||||||
|`has_review_started` |optional, not set if `false`|
|
|`has_review_started` |optional, not set if `false`|
|
||||||
When present, change has been marked Ready at some point in time.
|
When present, change has been marked Ready at some point in time.
|
||||||
|
|`revert_of` |optional|
|
||||||
|
The numeric Change-Id of the change that this change reverts.
|
||||||
|==================================
|
|==================================
|
||||||
|
|
||||||
[[change-input]]
|
[[change-input]]
|
||||||
|
|||||||
@@ -129,6 +129,11 @@ cc:'USER'::
|
|||||||
Changes that have the given user CC'ed on them. The special case of `cc:self`
|
Changes that have the given user CC'ed on them. The special case of `cc:self`
|
||||||
will find changes where the caller has been CC'ed.
|
will find changes where the caller has been CC'ed.
|
||||||
|
|
||||||
|
[[revertof]]
|
||||||
|
revertof:'ID'::
|
||||||
|
+
|
||||||
|
Changes that revert the change specified by the numeric 'ID'.
|
||||||
|
|
||||||
[[reviewerin]]
|
[[reviewerin]]
|
||||||
reviewerin:'GROUP'::
|
reviewerin:'GROUP'::
|
||||||
+
|
+
|
||||||
|
|||||||
@@ -617,6 +617,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
assertThat(revertChange.messages).hasSize(1);
|
assertThat(revertChange.messages).hasSize(1);
|
||||||
assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1.");
|
assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1.");
|
||||||
|
assertThat(revertChange.revertOf).isEqualTo(gApi.changes().id(r.getChangeId()).get()._number);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class ChangeInfo {
|
|||||||
public Boolean isPrivate;
|
public Boolean isPrivate;
|
||||||
public Boolean workInProgress;
|
public Boolean workInProgress;
|
||||||
public Boolean hasReviewStarted;
|
public Boolean hasReviewStarted;
|
||||||
|
public Integer revertOf;
|
||||||
|
|
||||||
public int _number;
|
public int _number;
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
|
|||||||
|
|
||||||
suggestions.add("unresolved:");
|
suggestions.add("unresolved:");
|
||||||
|
|
||||||
|
suggestions.add("revertof:");
|
||||||
|
|
||||||
if (Gerrit.isNoteDbEnabled()) {
|
if (Gerrit.isNoteDbEnabled()) {
|
||||||
suggestions.add("cc:");
|
suggestions.add("cc:");
|
||||||
suggestions.add("hashtag:");
|
suggestions.add("hashtag:");
|
||||||
|
|||||||
@@ -524,6 +524,10 @@ public final class Change {
|
|||||||
@Column(id = 22)
|
@Column(id = 22)
|
||||||
protected boolean reviewStarted;
|
protected boolean reviewStarted;
|
||||||
|
|
||||||
|
/** References a change that this change reverts. */
|
||||||
|
@Column(id = 23, notNull = false)
|
||||||
|
protected Id revertOf;
|
||||||
|
|
||||||
/** @see com.google.gerrit.server.notedb.NoteDbChangeState */
|
/** @see com.google.gerrit.server.notedb.NoteDbChangeState */
|
||||||
@Column(id = 101, notNull = false, length = Integer.MAX_VALUE)
|
@Column(id = 101, notNull = false, length = Integer.MAX_VALUE)
|
||||||
protected String noteDbState;
|
protected String noteDbState;
|
||||||
@@ -564,6 +568,7 @@ public final class Change {
|
|||||||
workInProgress = other.workInProgress;
|
workInProgress = other.workInProgress;
|
||||||
reviewStarted = other.reviewStarted;
|
reviewStarted = other.reviewStarted;
|
||||||
noteDbState = other.noteDbState;
|
noteDbState = other.noteDbState;
|
||||||
|
revertOf = other.revertOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Legacy 32 bit integer identity for a change. */
|
/** Legacy 32 bit integer identity for a change. */
|
||||||
@@ -733,6 +738,14 @@ public final class Change {
|
|||||||
this.reviewStarted = reviewStarted;
|
this.reviewStarted = reviewStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRevertOf(Id revertOf) {
|
||||||
|
this.revertOf = revertOf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Id getRevertOf() {
|
||||||
|
return this.revertOf;
|
||||||
|
}
|
||||||
|
|
||||||
public String getNoteDbState() {
|
public String getNoteDbState() {
|
||||||
return noteDbState;
|
return noteDbState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
private boolean fireRevisionCreated;
|
private boolean fireRevisionCreated;
|
||||||
private boolean sendMail;
|
private boolean sendMail;
|
||||||
private boolean updateRef;
|
private boolean updateRef;
|
||||||
|
private Change.Id revertOf;
|
||||||
|
|
||||||
// Fields set during the insertion process.
|
// Fields set during the insertion process.
|
||||||
private ReceiveCommand cmd;
|
private ReceiveCommand cmd;
|
||||||
@@ -198,6 +199,7 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
change.setPrivate(isPrivate);
|
change.setPrivate(isPrivate);
|
||||||
change.setWorkInProgress(workInProgress);
|
change.setWorkInProgress(workInProgress);
|
||||||
change.setReviewStarted(!workInProgress);
|
change.setReviewStarted(!workInProgress);
|
||||||
|
change.setRevertOf(revertOf);
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,6 +321,11 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChangeInserter setRevertOf(Change.Id revertOf) {
|
||||||
|
this.revertOf = revertOf;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPushCertificate(String cert) {
|
public void setPushCertificate(String cert) {
|
||||||
pushCert = cert;
|
pushCert = cert;
|
||||||
}
|
}
|
||||||
@@ -390,6 +397,9 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
update.setPsDescription(patchSetDescription);
|
update.setPsDescription(patchSetDescription);
|
||||||
update.setPrivate(isPrivate);
|
update.setPrivate(isPrivate);
|
||||||
update.setWorkInProgress(workInProgress);
|
update.setWorkInProgress(workInProgress);
|
||||||
|
if (revertOf != null) {
|
||||||
|
update.setRevertOf(revertOf.get());
|
||||||
|
}
|
||||||
|
|
||||||
boolean draft = status == Change.Status.DRAFT;
|
boolean draft = status == Change.Status.DRAFT;
|
||||||
List<String> newGroups = groups;
|
List<String> newGroups = groups;
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ public class ChangeJson {
|
|||||||
out.submitted = getSubmittedOn(cd);
|
out.submitted = getSubmittedOn(cd);
|
||||||
out.plugins =
|
out.plugins =
|
||||||
pluginDefinedAttributesFactory != null ? pluginDefinedAttributesFactory.create(cd) : null;
|
pluginDefinedAttributesFactory != null ? pluginDefinedAttributesFactory.create(cd) : null;
|
||||||
|
out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
|
||||||
|
|
||||||
if (out.labels != null && has(DETAILED_LABELS)) {
|
if (out.labels != null && has(DETAILED_LABELS)) {
|
||||||
// If limited to specific patch sets but not the current patch set, don't
|
// If limited to specific patch sets but not the current patch set, don't
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
|
|||||||
Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC));
|
Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC));
|
||||||
ccs.remove(user.getAccountId());
|
ccs.remove(user.getAccountId());
|
||||||
ins.setExtraCC(ccs);
|
ins.setExtraCC(ccs);
|
||||||
|
ins.setRevertOf(changeIdToRevert);
|
||||||
|
|
||||||
try (BatchUpdate bu = updateFactory.create(db.get(), project, user, now)) {
|
try (BatchUpdate bu = updateFactory.create(db.get(), project, user, now)) {
|
||||||
bu.setRepository(git, revWalk, oi);
|
bu.setRepository(git, revWalk, oi);
|
||||||
|
|||||||
@@ -207,6 +207,11 @@ public class ChangeField {
|
|||||||
.stored()
|
.stored()
|
||||||
.buildRepeatable(cd -> getReviewerByEmailFieldValues(cd.pendingReviewersByEmail()));
|
.buildRepeatable(cd -> getReviewerByEmailFieldValues(cd.pendingReviewersByEmail()));
|
||||||
|
|
||||||
|
/** References a change that this change reverts. */
|
||||||
|
public static final FieldDef<ChangeData, Integer> REVERT_OF =
|
||||||
|
integer(ChangeQueryBuilder.FIELD_REVERTOF)
|
||||||
|
.build(cd -> cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null);
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static List<String> getReviewerFieldValues(ReviewerSet reviewers) {
|
static List<String> getReviewerFieldValues(ReviewerSet reviewers) {
|
||||||
List<String> r = new ArrayList<>(reviewers.asTable().size() * 2);
|
List<String> r = new ArrayList<>(reviewers.asTable().size() * 2);
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
static final Schema<ChangeData> V43 =
|
static final Schema<ChangeData> V43 =
|
||||||
schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER);
|
schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
static final Schema<ChangeData> V44 =
|
static final Schema<ChangeData> V44 =
|
||||||
schema(
|
schema(
|
||||||
V43,
|
V43,
|
||||||
@@ -85,6 +86,8 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
ChangeField.PENDING_REVIEWER,
|
ChangeField.PENDING_REVIEWER,
|
||||||
ChangeField.PENDING_REVIEWER_BY_EMAIL);
|
ChangeField.PENDING_REVIEWER_BY_EMAIL);
|
||||||
|
|
||||||
|
static final Schema<ChangeData> V45 = schema(V44, ChangeField.REVERT_OF);
|
||||||
|
|
||||||
public static final String NAME = "changes";
|
public static final String NAME = "changes";
|
||||||
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
|
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public abstract class AbstractChangeUpdate {
|
|||||||
|
|
||||||
protected PatchSet.Id psId;
|
protected PatchSet.Id psId;
|
||||||
private ObjectId result;
|
private ObjectId result;
|
||||||
|
protected boolean rootOnly;
|
||||||
|
|
||||||
protected AbstractChangeUpdate(
|
protected AbstractChangeUpdate(
|
||||||
Config cfg,
|
Config cfg,
|
||||||
@@ -190,6 +191,11 @@ public abstract class AbstractChangeUpdate {
|
|||||||
/** Whether no updates have been done. */
|
/** Whether no updates have been done. */
|
||||||
public abstract boolean isEmpty();
|
public abstract boolean isEmpty();
|
||||||
|
|
||||||
|
/** Wether this update can only be a root commit. */
|
||||||
|
public boolean isRootOnly() {
|
||||||
|
return rootOnly;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the NameKey for the project where the update will be stored, which is not necessarily
|
* @return the NameKey for the project where the update will be stored, which is not necessarily
|
||||||
* the same as the change's project.
|
* the same as the change's project.
|
||||||
|
|||||||
@@ -233,7 +233,8 @@ public class ChangeBundle {
|
|||||||
// last time this file was updated.
|
// last time this file was updated.
|
||||||
checkColumns(Change.Id.class, 1);
|
checkColumns(Change.Id.class, 1);
|
||||||
|
|
||||||
checkColumns(Change.class, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 19, 20, 21, 22, 101);
|
checkColumns(
|
||||||
|
Change.class, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 101);
|
||||||
checkColumns(ChangeMessage.Key.class, 1, 2);
|
checkColumns(ChangeMessage.Key.class, 1, 2);
|
||||||
checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7);
|
checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7);
|
||||||
checkColumns(PatchSet.Id.class, 1, 2);
|
checkColumns(PatchSet.Id.class, 1, 2);
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ public class ChangeNoteUtil {
|
|||||||
public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
|
public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
|
||||||
public static final FooterKey FOOTER_TAG = new FooterKey("Tag");
|
public static final FooterKey FOOTER_TAG = new FooterKey("Tag");
|
||||||
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
|
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
|
||||||
|
public static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
|
||||||
|
|
||||||
private static final String AUTHOR = "Author";
|
private static final String AUTHOR = "Author";
|
||||||
private static final String BASE_PATCH_SET = "Base-for-patch-set";
|
private static final String BASE_PATCH_SET = "Base-for-patch-set";
|
||||||
|
|||||||
@@ -623,6 +623,10 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
return state.isWorkInProgress();
|
return state.isWorkInProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Change.Id getRevertOf() {
|
||||||
|
return state.revertOf();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasReviewStarted() {
|
public boolean hasReviewStarted() {
|
||||||
return state.hasReviewStarted();
|
return state.hasReviewStarted();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET_DE
|
|||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
|
||||||
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REVERT_OF;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
|
||||||
@@ -169,6 +170,7 @@ class ChangeNotesParser {
|
|||||||
private Boolean hasReviewStarted;
|
private Boolean hasReviewStarted;
|
||||||
private ReviewerSet pendingReviewers;
|
private ReviewerSet pendingReviewers;
|
||||||
private ReviewerByEmailSet pendingReviewersByEmail;
|
private ReviewerByEmailSet pendingReviewersByEmail;
|
||||||
|
private Change.Id revertOf;
|
||||||
|
|
||||||
ChangeNotesParser(
|
ChangeNotesParser(
|
||||||
Change.Id changeId,
|
Change.Id changeId,
|
||||||
@@ -267,7 +269,8 @@ class ChangeNotesParser {
|
|||||||
readOnlyUntil,
|
readOnlyUntil,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
workInProgress,
|
workInProgress,
|
||||||
hasReviewStarted);
|
hasReviewStarted,
|
||||||
|
revertOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PatchSet.Id buildCurrentPatchSetId() {
|
private PatchSet.Id buildCurrentPatchSetId() {
|
||||||
@@ -415,6 +418,10 @@ class ChangeNotesParser {
|
|||||||
parseIsPrivate(commit);
|
parseIsPrivate(commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (revertOf == null) {
|
||||||
|
revertOf = parseRevertOf(commit);
|
||||||
|
}
|
||||||
|
|
||||||
previousWorkInProgressFooter = null;
|
previousWorkInProgressFooter = null;
|
||||||
parseWorkInProgress(commit);
|
parseWorkInProgress(commit);
|
||||||
|
|
||||||
@@ -1022,6 +1029,18 @@ class ChangeNotesParser {
|
|||||||
throw invalidFooter(FOOTER_WORK_IN_PROGRESS, raw);
|
throw invalidFooter(FOOTER_WORK_IN_PROGRESS, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Change.Id parseRevertOf(ChangeNotesCommit commit) throws ConfigInvalidException {
|
||||||
|
String footer = parseOneFooter(commit, FOOTER_REVERT_OF);
|
||||||
|
if (footer == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Integer revertOf = Ints.tryParse(footer);
|
||||||
|
if (revertOf == null) {
|
||||||
|
throw invalidFooter(FOOTER_REVERT_OF, footer);
|
||||||
|
}
|
||||||
|
return new Change.Id(revertOf);
|
||||||
|
}
|
||||||
|
|
||||||
private void pruneReviewers() {
|
private void pruneReviewers() {
|
||||||
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
|
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
|
||||||
reviewers.cellSet().iterator();
|
reviewers.cellSet().iterator();
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ public abstract class ChangeNotesState {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
true);
|
true,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ChangeNotesState create(
|
static ChangeNotesState create(
|
||||||
@@ -113,7 +114,8 @@ public abstract class ChangeNotesState {
|
|||||||
@Nullable Timestamp readOnlyUntil,
|
@Nullable Timestamp readOnlyUntil,
|
||||||
@Nullable Boolean isPrivate,
|
@Nullable Boolean isPrivate,
|
||||||
@Nullable Boolean workInProgress,
|
@Nullable Boolean workInProgress,
|
||||||
boolean hasReviewStarted) {
|
boolean hasReviewStarted,
|
||||||
|
@Nullable Change.Id revertOf) {
|
||||||
if (hashtags == null) {
|
if (hashtags == null) {
|
||||||
hashtags = ImmutableSet.of();
|
hashtags = ImmutableSet.of();
|
||||||
}
|
}
|
||||||
@@ -135,7 +137,8 @@ public abstract class ChangeNotesState {
|
|||||||
status,
|
status,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
workInProgress,
|
workInProgress,
|
||||||
hasReviewStarted),
|
hasReviewStarted,
|
||||||
|
revertOf),
|
||||||
ImmutableSet.copyOf(pastAssignees),
|
ImmutableSet.copyOf(pastAssignees),
|
||||||
ImmutableSet.copyOf(hashtags),
|
ImmutableSet.copyOf(hashtags),
|
||||||
ImmutableList.copyOf(patchSets.entrySet()),
|
ImmutableList.copyOf(patchSets.entrySet()),
|
||||||
@@ -153,7 +156,8 @@ public abstract class ChangeNotesState {
|
|||||||
readOnlyUntil,
|
readOnlyUntil,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
workInProgress,
|
workInProgress,
|
||||||
hasReviewStarted);
|
hasReviewStarted,
|
||||||
|
revertOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,6 +209,9 @@ public abstract class ChangeNotesState {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
abstract Boolean hasReviewStarted();
|
abstract Boolean hasReviewStarted();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
abstract Change.Id revertOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only null if NoteDb is disabled.
|
// Only null if NoteDb is disabled.
|
||||||
@@ -258,6 +265,9 @@ public abstract class ChangeNotesState {
|
|||||||
@Nullable
|
@Nullable
|
||||||
abstract Boolean hasReviewStarted();
|
abstract Boolean hasReviewStarted();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
abstract Change.Id revertOf();
|
||||||
|
|
||||||
Change newChange(Project.NameKey project) {
|
Change newChange(Project.NameKey project) {
|
||||||
ChangeColumns c = checkNotNull(columns(), "columns are required");
|
ChangeColumns c = checkNotNull(columns(), "columns are required");
|
||||||
Change change =
|
Change change =
|
||||||
@@ -318,6 +328,7 @@ public abstract class ChangeNotesState {
|
|||||||
change.setPrivate(c.isPrivate() == null ? false : c.isPrivate());
|
change.setPrivate(c.isPrivate() == null ? false : c.isPrivate());
|
||||||
change.setWorkInProgress(c.isWorkInProgress() == null ? false : c.isWorkInProgress());
|
change.setWorkInProgress(c.isWorkInProgress() == null ? false : c.isWorkInProgress());
|
||||||
change.setReviewStarted(c.hasReviewStarted() == null ? false : c.hasReviewStarted());
|
change.setReviewStarted(c.hasReviewStarted() == null ? false : c.hasReviewStarted());
|
||||||
|
change.setRevertOf(c.revertOf());
|
||||||
|
|
||||||
if (!patchSets().isEmpty()) {
|
if (!patchSets().isEmpty()) {
|
||||||
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
|
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET_DE
|
|||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
|
||||||
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REVERT_OF;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
|
||||||
@@ -156,6 +157,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
private Timestamp readOnlyUntil;
|
private Timestamp readOnlyUntil;
|
||||||
private Boolean isPrivate;
|
private Boolean isPrivate;
|
||||||
private Boolean workInProgress;
|
private Boolean workInProgress;
|
||||||
|
private Integer revertOf;
|
||||||
|
|
||||||
private ChangeDraftUpdate draftUpdate;
|
private ChangeDraftUpdate draftUpdate;
|
||||||
private RobotCommentUpdate robotCommentUpdate;
|
private RobotCommentUpdate robotCommentUpdate;
|
||||||
@@ -512,6 +514,13 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRevertOf(int revertOf) {
|
||||||
|
int ownId = getChange().getId().get();
|
||||||
|
checkArgument(ownId != revertOf, "A change cannot revert itself");
|
||||||
|
this.revertOf = revertOf;
|
||||||
|
rootOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return the tree id for the updated tree */
|
/** @return the tree id for the updated tree */
|
||||||
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
|
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
|
||||||
throws ConfigInvalidException, OrmException, IOException {
|
throws ConfigInvalidException, OrmException, IOException {
|
||||||
@@ -755,6 +764,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
addFooter(msg, FOOTER_WORK_IN_PROGRESS, workInProgress);
|
addFooter(msg, FOOTER_WORK_IN_PROGRESS, workInProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (revertOf != null) {
|
||||||
|
addFooter(msg, FOOTER_REVERT_OF, revertOf);
|
||||||
|
}
|
||||||
|
|
||||||
cb.setMessage(msg.toString());
|
cb.setMessage(msg.toString());
|
||||||
try {
|
try {
|
||||||
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
|
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
|
||||||
@@ -804,7 +817,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
&& !currentPatchSet
|
&& !currentPatchSet
|
||||||
&& readOnlyUntil == null
|
&& readOnlyUntil == null
|
||||||
&& isPrivate == null
|
&& isPrivate == null
|
||||||
&& workInProgress == null;
|
&& workInProgress == null
|
||||||
|
&& revertOf == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeDraftUpdate getDraftUpdate() {
|
ChangeDraftUpdate getDraftUpdate() {
|
||||||
|
|||||||
@@ -688,6 +688,9 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
|
|
||||||
ObjectId curr = old;
|
ObjectId curr = old;
|
||||||
for (U u : updates) {
|
for (U u : updates) {
|
||||||
|
if (u.isRootOnly() && !old.equals(ObjectId.zeroId())) {
|
||||||
|
throw new OrmException("Given ChangeUpdate is only allowed on initial commit");
|
||||||
|
}
|
||||||
ObjectId next = u.apply(or.rw, or.tempIns, curr);
|
ObjectId next = u.apply(or.rw, or.tempIns, curr);
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -618,6 +618,9 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
|
|||||||
update.setChangeId(change.getKey().get());
|
update.setChangeId(change.getKey().get());
|
||||||
update.setBranch(change.getDest().get());
|
update.setBranch(change.getDest().get());
|
||||||
update.setSubject(change.getOriginalSubject());
|
update.setSubject(change.getOriginalSubject());
|
||||||
|
if (change.getRevertOf() != null) {
|
||||||
|
update.setRevertOf(change.getRevertOf().get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ public class ChangeData {
|
|||||||
private PersonIdent author;
|
private PersonIdent author;
|
||||||
private PersonIdent committer;
|
private PersonIdent committer;
|
||||||
private Integer unresolvedCommentCount;
|
private Integer unresolvedCommentCount;
|
||||||
|
private Change.Id revertOf;
|
||||||
|
|
||||||
private ImmutableList<byte[]> refStates;
|
private ImmutableList<byte[]> refStates;
|
||||||
private ImmutableList<byte[]> refStatePatterns;
|
private ImmutableList<byte[]> refStatePatterns;
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
|||||||
public static final String FIELD_VISIBLETO = "visibleto";
|
public static final String FIELD_VISIBLETO = "visibleto";
|
||||||
public static final String FIELD_WATCHEDBY = "watchedby";
|
public static final String FIELD_WATCHEDBY = "watchedby";
|
||||||
public static final String FIELD_WIP = "wip";
|
public static final String FIELD_WIP = "wip";
|
||||||
|
public static final String FIELD_REVERTOF = "revertof";
|
||||||
|
|
||||||
public static final String ARG_ID_USER = "user";
|
public static final String ARG_ID_USER = "user";
|
||||||
public static final String ARG_ID_GROUP = "group";
|
public static final String ARG_ID_GROUP = "group";
|
||||||
@@ -1179,6 +1180,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
|||||||
return new IsUnresolvedPredicate(value);
|
return new IsUnresolvedPredicate(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operator
|
||||||
|
public Predicate<ChangeData> revertof(String value) throws QueryParseException {
|
||||||
|
if (args.getSchema().hasField(ChangeField.REVERT_OF)) {
|
||||||
|
return new RevertOfPredicate(value);
|
||||||
|
}
|
||||||
|
throw new QueryParseException("'revertof' operator is not supported by change index version");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
|
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
|
||||||
if (query.startsWith("refs/")) {
|
if (query.startsWith("refs/")) {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (C) 2017 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.server.query.change;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.index.change.ChangeField;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
|
||||||
|
public class RevertOfPredicate extends ChangeIndexPredicate {
|
||||||
|
public RevertOfPredicate(String revertOf) {
|
||||||
|
super(ChangeField.REVERT_OF, revertOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean match(ChangeData cd) throws OrmException {
|
||||||
|
if (cd.change().getRevertOf() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return cd.change().getRevertOf().toString().equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCost() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
/** A version of the database schema. */
|
/** A version of the database schema. */
|
||||||
public abstract class SchemaVersion {
|
public abstract class SchemaVersion {
|
||||||
/** The current schema version. */
|
/** The current schema version. */
|
||||||
public static final Class<Schema_155> C = Schema_155.class;
|
public static final Class<Schema_156> C = Schema_156.class;
|
||||||
|
|
||||||
public static int getBinaryVersion() {
|
public static int getBinaryVersion() {
|
||||||
return guessVersion(C);
|
return guessVersion(C);
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (C) 2017 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.server.schema;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
/** Add revertOf field to change. */
|
||||||
|
public class Schema_156 extends SchemaVersion {
|
||||||
|
@Inject
|
||||||
|
Schema_156(Provider<Schema_155> prior) {
|
||||||
|
super(prior);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3494,6 +3494,43 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty();
|
assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void revertOfIsNullByDefault() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getRevertOf()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRevertOfPersistsValue() throws Exception {
|
||||||
|
Change changeToRevert = newChange();
|
||||||
|
Change c = TestChanges.newChange(project, changeOwner.getAccountId());
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.setChangeId(c.getKey().get());
|
||||||
|
update.setRevertOf(changeToRevert.getId().get());
|
||||||
|
update.commit();
|
||||||
|
assertThat(newNotes(c).getRevertOf()).isEqualTo(changeToRevert.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRevertOfToCurrentChangeFails() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
exception.expect(IllegalArgumentException.class);
|
||||||
|
exception.expectMessage("A change cannot revert itself");
|
||||||
|
update.setRevertOf(c.getId().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRevertOfOnChildCommitFails() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
exception.expect(OrmException.class);
|
||||||
|
exception.expectMessage("Given ChangeUpdate is only allowed on initial commit");
|
||||||
|
update.setRevertOf(newChange().getId().get());
|
||||||
|
update.commit();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean testJson() {
|
private boolean testJson() {
|
||||||
return noteUtil.getWriteJson();
|
return noteUtil.getWriteJson();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Streams;
|
||||||
import com.google.common.truth.ThrowableSubject;
|
import com.google.common.truth.ThrowableSubject;
|
||||||
import com.google.gerrit.common.Nullable;
|
import com.google.gerrit.common.Nullable;
|
||||||
import com.google.gerrit.common.TimeUtil;
|
import com.google.gerrit.common.TimeUtil;
|
||||||
@@ -51,6 +52,7 @@ import com.google.gerrit.extensions.client.InheritableBoolean;
|
|||||||
import com.google.gerrit.extensions.client.ReviewerState;
|
import com.google.gerrit.extensions.client.ReviewerState;
|
||||||
import com.google.gerrit.extensions.common.AccountInfo;
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeInput;
|
||||||
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
@@ -2207,6 +2209,31 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
assertQuery("us");
|
assertQuery("us");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void revertOf() throws Exception {
|
||||||
|
if (getSchemaVersion() < 45) {
|
||||||
|
assertMissingField(ChangeField.REVERT_OF);
|
||||||
|
assertFailingQuery(
|
||||||
|
"revertof:1", "'revertof' operator is not supported by change index version");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestRepository<Repo> repo = createProject("repo");
|
||||||
|
// Create two commits and revert second commit (initial commit can't be reverted)
|
||||||
|
Change initial = insert(repo, newChange(repo));
|
||||||
|
gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
|
||||||
|
gApi.changes().id(initial.getChangeId()).current().submit();
|
||||||
|
|
||||||
|
ChangeInfo changeToRevert =
|
||||||
|
gApi.changes().create(new ChangeInput("repo", "master", "commit to revert")).get();
|
||||||
|
gApi.changes().id(changeToRevert.id).current().review(ReviewInput.approve());
|
||||||
|
gApi.changes().id(changeToRevert.id).current().submit();
|
||||||
|
|
||||||
|
ChangeInfo changeThatReverts = gApi.changes().id(changeToRevert.id).revert().get();
|
||||||
|
assertQueryByIds(
|
||||||
|
"revertof:" + changeToRevert._number, new Change.Id(changeThatReverts._number));
|
||||||
|
}
|
||||||
|
|
||||||
protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
|
protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
|
||||||
return newChange(repo, null, null, null, null, false);
|
return newChange(repo, null, null, null, null, false);
|
||||||
}
|
}
|
||||||
@@ -2341,36 +2368,47 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
return assertQuery(newQuery(query), changes);
|
return assertQuery(newQuery(query), changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<ChangeInfo> assertQueryByIds(Object query, Change.Id... changes) throws Exception {
|
||||||
|
return assertQueryByIds(newQuery(query), changes);
|
||||||
|
}
|
||||||
|
|
||||||
protected List<ChangeInfo> assertQuery(QueryRequest query, Change... changes) throws Exception {
|
protected List<ChangeInfo> assertQuery(QueryRequest query, Change... changes) throws Exception {
|
||||||
|
return assertQueryByIds(
|
||||||
|
query, Arrays.stream(changes).map(Change::getId).toArray(Change.Id[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<ChangeInfo> assertQueryByIds(QueryRequest query, Change.Id... changes)
|
||||||
|
throws Exception {
|
||||||
List<ChangeInfo> result = query.get();
|
List<ChangeInfo> result = query.get();
|
||||||
Iterable<Integer> ids = ids(result);
|
Iterable<Change.Id> ids = ids(result);
|
||||||
assertThat(ids)
|
assertThat(ids)
|
||||||
.named(format(query, ids, changes))
|
.named(format(query, ids, changes))
|
||||||
.containsExactlyElementsIn(ids(changes))
|
.containsExactlyElementsIn(Arrays.asList(changes))
|
||||||
.inOrder();
|
.inOrder();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String format(QueryRequest query, Iterable<Integer> actualIds, Change... expectedChanges)
|
private String format(
|
||||||
|
QueryRequest query, Iterable<Change.Id> actualIds, Change.Id... expectedChanges)
|
||||||
throws RestApiException {
|
throws RestApiException {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
b.append("query '").append(query.getQuery()).append("' with expected changes ");
|
b.append("query '").append(query.getQuery()).append("' with expected changes ");
|
||||||
b.append(format(Arrays.stream(expectedChanges).map(Change::getChangeId).iterator()));
|
b.append(format(Arrays.asList(expectedChanges)));
|
||||||
b.append(" and result ");
|
b.append(" and result ");
|
||||||
b.append(format(actualIds));
|
b.append(format(actualIds));
|
||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String format(Iterable<Integer> changeIds) throws RestApiException {
|
private String format(Iterable<Change.Id> changeIds) throws RestApiException {
|
||||||
return format(changeIds.iterator());
|
return format(changeIds.iterator());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String format(Iterator<Integer> changeIds) throws RestApiException {
|
private String format(Iterator<Change.Id> changeIds) throws RestApiException {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
b.append("[");
|
b.append("[");
|
||||||
while (changeIds.hasNext()) {
|
while (changeIds.hasNext()) {
|
||||||
int id = changeIds.next();
|
Change.Id id = changeIds.next();
|
||||||
ChangeInfo c = gApi.changes().id(id).get();
|
ChangeInfo c = gApi.changes().id(id.get()).get();
|
||||||
b.append("{")
|
b.append("{")
|
||||||
.append(id)
|
.append(id)
|
||||||
.append(" (")
|
.append(" (")
|
||||||
@@ -2393,12 +2431,12 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Iterable<Integer> ids(Change... changes) {
|
protected static Iterable<Change.Id> ids(Change... changes) {
|
||||||
return FluentIterable.from(Arrays.asList(changes)).transform(in -> in.getId().get());
|
return Arrays.stream(changes).map(c -> c.getId()).collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Iterable<Integer> ids(Iterable<ChangeInfo> changes) {
|
protected static Iterable<Change.Id> ids(Iterable<ChangeInfo> changes) {
|
||||||
return FluentIterable.from(changes).transform(in -> in._number);
|
return Streams.stream(changes).map(c -> new Change.Id(c._number)).collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static long lastUpdatedMs(Change c) {
|
protected static long lastUpdatedMs(Change c) {
|
||||||
|
|||||||
Reference in New Issue
Block a user