Merge "Document how to get pretty-printed JSON for REST calls"
diff --git a/java/com/google/gerrit/server/patch/ComparisonType.java b/java/com/google/gerrit/server/patch/ComparisonType.java
index 260c507..eca2658 100644
--- a/java/com/google/gerrit/server/patch/ComparisonType.java
+++ b/java/com/google/gerrit/server/patch/ComparisonType.java
@@ -16,34 +16,40 @@
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
-import static java.util.Objects.requireNonNull;
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.server.cache.proto.Cache.FileDiffOutputProto;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Optional;
-public class ComparisonType {
+/** Relation between the old and new commits used in the diff. */
+@AutoValue
+public abstract class ComparisonType {
- /** 1-based parent */
- private final Integer parentNum;
+ /**
+ * 1-based parent. Available if the old commit is the parent of the new commit and old commit is
+ * not the auto-merge.
+ */
+ abstract Optional<Integer> parentNum();
- private final boolean autoMerge;
+ abstract boolean autoMerge();
public static ComparisonType againstOtherPatchSet() {
- return new ComparisonType(null, false);
+ return new AutoValue_ComparisonType(Optional.empty(), false);
}
public static ComparisonType againstParent(int parentNum) {
- return new ComparisonType(parentNum, false);
+ return new AutoValue_ComparisonType(Optional.of(parentNum), false);
}
public static ComparisonType againstAutoMerge() {
- return new ComparisonType(null, true);
+ return new AutoValue_ComparisonType(Optional.empty(), true);
}
- private ComparisonType(Integer parentNum, boolean autoMerge) {
- this.parentNum = parentNum;
- this.autoMerge = autoMerge;
+ private static ComparisonType create(Optional<Integer> parent, boolean automerge) {
+ return new AutoValue_ComparisonType(parent, automerge);
}
public boolean isAgainstParentOrAutoMerge() {
@@ -51,27 +57,43 @@
}
public boolean isAgainstParent() {
- return parentNum != null;
+ return parentNum().isPresent();
}
public boolean isAgainstAutoMerge() {
- return autoMerge;
+ return autoMerge();
}
- public int getParentNum() {
- requireNonNull(parentNum);
- return parentNum;
+ public Optional<Integer> getParentNum() {
+ return parentNum();
}
void writeTo(OutputStream out) throws IOException {
- writeVarInt32(out, parentNum != null ? parentNum : 0);
- writeVarInt32(out, autoMerge ? 1 : 0);
+ writeVarInt32(out, isAgainstParent() ? parentNum().get() : 0);
+ writeVarInt32(out, autoMerge() ? 1 : 0);
}
static ComparisonType readFrom(InputStream in) throws IOException {
int p = readVarInt32(in);
- Integer parentNum = p > 0 ? p : null;
+ Optional<Integer> parentNum = p > 0 ? Optional.of(p) : Optional.empty();
boolean autoMerge = readVarInt32(in) != 0;
- return new ComparisonType(parentNum, autoMerge);
+ return create(parentNum, autoMerge);
+ }
+
+ public FileDiffOutputProto.ComparisonType toProto() {
+ FileDiffOutputProto.ComparisonType.Builder builder =
+ FileDiffOutputProto.ComparisonType.newBuilder().setAutoMerge(autoMerge());
+ if (parentNum().isPresent()) {
+ builder.setParentNum(parentNum().get());
+ }
+ return builder.build();
+ }
+
+ public static ComparisonType fromProto(FileDiffOutputProto.ComparisonType proto) {
+ Optional<Integer> parentNum = Optional.empty();
+ if (proto.hasField(FileDiffOutputProto.ComparisonType.getDescriptor().findFieldByNumber(1))) {
+ parentNum = Optional.of(proto.getParentNum());
+ }
+ return create(parentNum, proto.getAutoMerge());
}
}
diff --git a/java/com/google/gerrit/server/patch/DiffOperations.java b/java/com/google/gerrit/server/patch/DiffOperations.java
index 8b90531..93aefff 100644
--- a/java/com/google/gerrit/server/patch/DiffOperations.java
+++ b/java/com/google/gerrit/server/patch/DiffOperations.java
@@ -74,8 +74,9 @@
/**
* Returns the diff for a single file between a patchset commit against its parent or the
* auto-merge commit. For deleted files, the {@code fileName} parameter should contain the old
- * name of the file. This method will return {@link FileDiffOutput#empty(String)} if the requested
- * file identified by {@code fileName} has unchanged content or does not exist at both commits.
+ * name of the file. This method will return {@link FileDiffOutput#empty(String, ObjectId,
+ * ObjectId)} if the requested file identified by {@code fileName} has unchanged content or does
+ * not exist at both commits.
*
* @param project a project name representing a git repository.
* @param newCommit 20 bytes SHA-1 of the new commit used in the diff.
@@ -98,8 +99,8 @@
/**
* Returns the diff for a single file between two patchset commits. For deleted files, the {@code
* fileName} parameter should contain the old name of the file. This method will return {@link
- * FileDiffOutput#empty(String)} if the requested file identified by {@code fileName} has
- * unchanged content or does not exist at both commits.
+ * FileDiffOutput#empty(String, ObjectId, ObjectId)} if the requested file identified by {@code
+ * fileName} has unchanged content or does not exist at both commits.
*
* @param project a project name representing a git repository.
* @param oldCommit 20 bytes SHA-1 of the old commit used in the diff.
diff --git a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
index 16bd135..efb64bc 100644
--- a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
+++ b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
@@ -125,7 +125,9 @@
FileDiffCacheKey key =
createFileDiffCacheKey(project, diffParams.baseCommit(), newCommit, fileName, whitespace);
Map<String, FileDiffOutput> result = getModifiedFilesForKeys(ImmutableList.of(key));
- return result.containsKey(fileName) ? result.get(fileName) : FileDiffOutput.empty(fileName);
+ return result.containsKey(fileName)
+ ? result.get(fileName)
+ : FileDiffOutput.empty(fileName, key.oldCommit(), key.newCommit());
} catch (IOException e) {
throw new DiffNotAvailableException(
"Failed to evaluate the parent/base commit for commit " + newCommit, e);
@@ -143,7 +145,9 @@
FileDiffCacheKey key =
createFileDiffCacheKey(project, oldCommit, newCommit, fileName, whitespace);
Map<String, FileDiffOutput> result = getModifiedFilesForKeys(ImmutableList.of(key));
- return result.containsKey(fileName) ? result.get(fileName) : FileDiffOutput.empty(fileName);
+ return result.containsKey(fileName)
+ ? result.get(fileName)
+ : FileDiffOutput.empty(fileName, oldCommit, newCommit);
}
private Map<String, FileDiffOutput> getModifiedFiles(
diff --git a/java/com/google/gerrit/server/patch/MagicFile.java b/java/com/google/gerrit/server/patch/MagicFile.java
index aa6b11f..e42dd8c 100644
--- a/java/com/google/gerrit/server/patch/MagicFile.java
+++ b/java/com/google/gerrit/server/patch/MagicFile.java
@@ -93,7 +93,7 @@
}
default:
int uninterestingParent =
- comparisonType.isAgainstParent() ? comparisonType.getParentNum() : 1;
+ comparisonType.isAgainstParent() ? comparisonType.getParentNum().get() : 1;
b.append("Merge List:\n\n");
for (RevCommit commit : MergeListBuilder.build(rw, c, uninterestingParent)) {
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index 63f311b..1bb407d 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -93,6 +93,7 @@
persist(DIFF, FileDiffCacheKey.class, FileDiffOutput.class)
.maximumWeight(10 << 20)
.weigher(FileDiffWeigher.class)
+ .version(2)
.keySerializer(FileDiffCacheKey.Serializer.INSTANCE)
.valueSerializer(FileDiffOutput.Serializer.INSTANCE)
.loader(FileDiffLoader.class);
@@ -219,19 +220,16 @@
RawTextComparator cmp = comparatorFor(key.whitespace());
ComparisonType comparisonType =
getComparisonType(rw, reader, key.oldCommit(), key.newCommit());
- RevCommit aCommit =
- comparisonType.isAgainstParentOrAutoMerge()
- ? null
- : DiffUtil.getRevCommit(rw, key.oldCommit());
+ RevCommit aCommit = DiffUtil.getRevCommit(rw, key.oldCommit());
RevCommit bCommit = DiffUtil.getRevCommit(rw, key.newCommit());
return magicPath == MagicPath.COMMIT
- ? createCommitEntry(reader, aCommit, bCommit, cmp, key.diffAlgorithm())
+ ? createCommitEntry(reader, aCommit, bCommit, comparisonType, cmp, key.diffAlgorithm())
: createMergeListEntry(
reader, aCommit, bCommit, comparisonType, cmp, key.diffAlgorithm());
} catch (IOException e) {
logger.atWarning().log("Failed to compute commit entry for key %s", key);
}
- return FileDiffOutput.empty(key.newFilePath());
+ return FileDiffOutput.empty(key.newFilePath(), key.oldCommit(), key.newCommit());
}
private static RawTextComparator comparatorFor(Whitespace ws) {
@@ -255,13 +253,24 @@
ObjectReader reader,
RevCommit oldCommit,
RevCommit newCommit,
+ ComparisonType comparisonType,
RawTextComparator rawTextComparator,
GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
throws IOException {
- Text aText = oldCommit != null ? Text.forCommit(reader, oldCommit) : Text.EMPTY;
+ Text aText =
+ comparisonType.isAgainstParentOrAutoMerge()
+ ? Text.EMPTY
+ : Text.forCommit(reader, oldCommit);
Text bText = Text.forCommit(reader, newCommit);
return createMagicFileDiffOutput(
- rawTextComparator, oldCommit, aText, bText, Patch.COMMIT_MSG, diffAlgorithm);
+ oldCommit,
+ newCommit,
+ comparisonType,
+ rawTextComparator,
+ aText,
+ bText,
+ Patch.COMMIT_MSG,
+ diffAlgorithm);
}
private FileDiffOutput createMergeListEntry(
@@ -273,20 +282,31 @@
GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
throws IOException {
Text aText =
- oldCommit != null ? Text.forMergeList(comparisonType, reader, oldCommit) : Text.EMPTY;
+ comparisonType.isAgainstParentOrAutoMerge()
+ ? Text.EMPTY
+ : Text.forMergeList(comparisonType, reader, oldCommit);
Text bText = Text.forMergeList(comparisonType, reader, newCommit);
return createMagicFileDiffOutput(
- rawTextComparator, oldCommit, aText, bText, Patch.MERGE_LIST, diffAlgorithm);
+ oldCommit,
+ newCommit,
+ comparisonType,
+ rawTextComparator,
+ aText,
+ bText,
+ Patch.MERGE_LIST,
+ diffAlgorithm);
}
private static FileDiffOutput createMagicFileDiffOutput(
+ ObjectId oldCommit,
+ ObjectId newCommit,
+ ComparisonType comparisonType,
RawTextComparator rawTextComparator,
- RevCommit aCommit,
Text aText,
Text bText,
String fileName,
GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm) {
- byte[] rawHdr = getRawHeader(aCommit != null, fileName);
+ byte[] rawHdr = getRawHeader(!comparisonType.isAgainstParentOrAutoMerge(), fileName);
byte[] aContent = aText.getContent();
byte[] bContent = bText.getContent();
long size = bContent.length;
@@ -298,6 +318,9 @@
FileHeader fileHeader = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
Patch.ChangeType changeType = FileHeaderUtil.getChangeType(fileHeader);
return FileDiffOutput.builder()
+ .oldCommitId(oldCommit)
+ .newCommitId(newCommit)
+ .comparisonType(comparisonType)
.oldPath(FileHeaderUtil.getOldPath(fileHeader))
.newPath(FileHeaderUtil.getNewPath(fileHeader))
.changeType(changeType)
@@ -370,8 +393,13 @@
mainGitDiff.newPath().get())
: 0;
+ ObjectId oldCommit = augmentedKey.key().oldCommit();
+ ObjectId newCommit = augmentedKey.key().newCommit();
FileDiffOutput fileDiff =
FileDiffOutput.builder()
+ .oldCommitId(oldCommit)
+ .newCommitId(newCommit)
+ .comparisonType(getComparisonType(rw, reader, oldCommit, newCommit))
.changeType(mainGitDiff.changeType())
.patchType(mainGitDiff.patchType())
.oldPath(mainGitDiff.oldPath())
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
index 3348033..e7f47ef 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
@@ -24,16 +24,28 @@
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.cache.proto.Cache.FileDiffOutputProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
+import com.google.gerrit.server.patch.ComparisonType;
import com.google.protobuf.Descriptors.FieldDescriptor;
import java.io.Serializable;
import java.util.Optional;
import java.util.stream.Collectors;
+import org.eclipse.jgit.lib.ObjectId;
/** File diff for a single file path. Produced as output of the {@link FileDiffCache}. */
@AutoValue
public abstract class FileDiffOutput implements Serializable {
private static final long serialVersionUID = 1L;
+ /** The 20 bytes SHA-1 object ID of the old git commit used in the diff. */
+ public abstract ObjectId oldCommitId();
+
+ /** The 20 bytes SHA-1 object ID of the new git commit used in the diff. */
+ public abstract ObjectId newCommitId();
+
+ /** Comparison type of old and new commits: against another patchset, parent or auto-merge. */
+ public abstract ComparisonType comparisonType();
+
/**
* The file path at the old commit. Returns an empty Optional if {@link #changeType()} is equal to
* {@link ChangeType#ADDED}.
@@ -95,8 +107,11 @@
}
/** Returns an entity representing an unchanged file between two commits. */
- public static FileDiffOutput empty(String filePath) {
+ public static FileDiffOutput empty(String filePath, ObjectId oldCommitId, ObjectId newCommitId) {
return builder()
+ .oldCommitId(oldCommitId)
+ .newCommitId(newCommitId)
+ .comparisonType(ComparisonType.againstOtherPatchSet()) // not important
.oldPath(Optional.empty())
.newPath(Optional.of(filePath))
.changeType(ChangeType.MODIFIED)
@@ -124,6 +139,8 @@
if (newPath().isPresent()) {
result += stringSize(newPath().get());
}
+ result += 20 + 20; // old and new commit IDs
+ result += 4; // comparison type
result += 4; // changeType
if (patchType().isPresent()) {
result += 4;
@@ -140,6 +157,12 @@
@AutoValue.Builder
public abstract static class Builder {
+ public abstract Builder oldCommitId(ObjectId value);
+
+ public abstract Builder newCommitId(ObjectId value);
+
+ public abstract Builder comparisonType(ComparisonType value);
+
public abstract Builder oldPath(Optional<String> value);
public abstract Builder newPath(Optional<String> value);
@@ -173,8 +196,12 @@
@Override
public byte[] serialize(FileDiffOutput fileDiff) {
+ ObjectIdConverter idConverter = ObjectIdConverter.create();
FileDiffOutputProto.Builder builder =
FileDiffOutputProto.newBuilder()
+ .setOldCommit(idConverter.toByteString(fileDiff.oldCommitId().toObjectId()))
+ .setNewCommit(idConverter.toByteString(fileDiff.newCommitId().toObjectId()))
+ .setComparisonType(fileDiff.comparisonType().toProto())
.setSize(fileDiff.size())
.setSizeDelta(fileDiff.sizeDelta())
.addAllHeaderLines(fileDiff.headerLines())
@@ -212,9 +239,13 @@
@Override
public FileDiffOutput deserialize(byte[] in) {
+ ObjectIdConverter idConverter = ObjectIdConverter.create();
FileDiffOutputProto proto = Protos.parseUnchecked(FileDiffOutputProto.parser(), in);
FileDiffOutput.Builder builder = FileDiffOutput.builder();
builder
+ .oldCommitId(idConverter.fromByteString(proto.getOldCommit()))
+ .newCommitId(idConverter.fromByteString(proto.getNewCommit()))
+ .comparisonType(ComparisonType.fromProto(proto.getComparisonType()))
.size(proto.getSize())
.sizeDelta(proto.getSizeDelta())
.headerLines(proto.getHeaderLinesList().stream().collect(ImmutableList.toImmutableList()))
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
index 44ea55a..17fd959 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
@@ -19,10 +19,12 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.entities.Patch.PatchType;
+import com.google.gerrit.server.patch.ComparisonType;
import com.google.gerrit.server.patch.filediff.Edit;
import com.google.gerrit.server.patch.filediff.FileDiffOutput;
import com.google.gerrit.server.patch.filediff.TaggedEdit;
import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class FileDiffOutputSerializerTest {
@@ -35,6 +37,9 @@
FileDiffOutput fileDiff =
FileDiffOutput.builder()
+ .oldCommitId(ObjectId.fromString("dd4d2a1498870ca5fe415b33f65d052d69d9eaf5"))
+ .newCommitId(ObjectId.fromString("0cfaab3f2ba76f71798da0a2651f41be8d45f842"))
+ .comparisonType(ComparisonType.againstOtherPatchSet())
.oldPath(Optional.of("old_file_path.txt"))
.newPath(Optional.empty())
.changeType(ChangeType.DELETED)
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index 225e3e9..ebcabbf 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -38,6 +38,7 @@
import {AutocompleteSuggestion} from '../../shared/gr-autocomplete/gr-autocomplete';
import {HttpMethod, ChangeStatus} from '../../../constants/constants';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {fireEvent} from '../../../utils/event-util';
const SUGGESTIONS_LIMIT = 15;
const CHANGE_SUBJECT_LIMIT = 50;
@@ -264,10 +265,12 @@
_handlecherryPickSingleChangeClicked() {
this._cherryPickType = CherryPickType.SINGLE_CHANGE;
+ fireEvent(this, 'iron-resize');
}
_handlecherryPickTopicClicked() {
this._cherryPickType = CherryPickType.TOPIC;
+ fireEvent(this, 'iron-resize');
}
@observe('changeStatus', 'commitNum', 'commitMessage')
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts
index 4de395c..6c0099e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts
@@ -46,11 +46,12 @@
}
.cherryPickTopicLayout {
display: flex;
+ align-items: center;
+ margin-bottom: var(--spacing-m);
}
.cherryPickSingleChange,
.cherryPickTopic {
margin-left: var(--spacing-m);
- margin-bottom: var(--spacing-m);
}
.cherry-pick-topic-message {
margin-bottom: var(--spacing-m);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index e5386f4..9376a03 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -28,18 +28,23 @@
allActions$,
allResults$,
allRuns$,
+ checksPatchsetNumber$,
someProvidersAreLoading$,
} from '../../services/checks/checks-model';
import './gr-checks-runs';
import './gr-checks-results';
import {sharedStyles} from '../../styles/shared-styles';
-import {changeNum$, currentPatchNum$} from '../../services/change/change-model';
-import {NumericChangeId, PatchSetNum} from '../../types/common';
+import {changeNum$, latestPatchNum$} from '../../services/change/change-model';
+import {NumericChangeId, PatchSetNumber} from '../../types/common';
import {
ActionTriggeredEvent,
fireActionTriggered,
} from '../../services/checks/checks-util';
-import {checkRequiredProperty} from '../../utils/common-util';
+import {
+ assertIsDefined,
+ check,
+ checkRequiredProperty,
+} from '../../utils/common-util';
import {RunSelectedEvent} from './gr-checks-runs';
import {ChecksTabState} from '../../types/events';
import {fireAlert} from '../../utils/event-util';
@@ -64,7 +69,10 @@
tabState?: ChecksTabState;
@property()
- currentPatchNum: PatchSetNum | undefined = undefined;
+ checksPatchsetNumber: PatchSetNumber | undefined = undefined;
+
+ @property()
+ latestPatchsetNumber: PatchSetNumber | undefined = undefined;
@property()
changeNum: NumericChangeId | undefined = undefined;
@@ -82,7 +90,8 @@
this.subscribe('runs', allRuns$);
this.subscribe('actions', allActions$);
this.subscribe('results', allResults$);
- this.subscribe('currentPatchNum', currentPatchNum$);
+ this.subscribe('checksPatchsetNumber', checksPatchsetNumber$);
+ this.subscribe('latestPatchsetNumber', latestPatchNum$);
this.subscribe('changeNum', changeNum$);
this.subscribe('someProvidersAreLoading', someProvidersAreLoading$);
@@ -124,7 +133,6 @@
}
render() {
- const ps = `Patchset ${this.currentPatchNum} (Latest)`;
const filteredRuns = this.runs.filter(
r =>
this.selectedRuns.length === 0 ||
@@ -134,13 +142,9 @@
<div class="header">
<div class="left">
<gr-dropdown-list
- value="${ps}"
- .items="${[
- {
- value: `${ps}`,
- text: `${ps}`,
- },
- ]}"
+ value="${this.checksPatchsetNumber}"
+ .items="${this.createPatchsetDropdownItems()}"
+ @value-change="${this.onPatchsetSelected}"
></gr-dropdown-list>
<span ?hidden="${!this.someProvidersAreLoading}">Loading...</span>
</div>
@@ -163,6 +167,24 @@
`;
}
+ private onPatchsetSelected(e: CustomEvent<{value: string}>) {
+ const patchset = Number(e.detail.value);
+ check(!isNaN(patchset), 'selected patchset must be a number');
+ this.checksService.setPatchset(patchset as PatchSetNumber);
+ }
+
+ private createPatchsetDropdownItems() {
+ return Array.from(Array(this.latestPatchsetNumber), (_, i) => {
+ assertIsDefined(this.latestPatchsetNumber, 'latestPatchsetNumber');
+ const index = this.latestPatchsetNumber - i;
+ const postfix = index === this.latestPatchsetNumber ? ' (latest)' : '';
+ return {
+ value: `${index}`,
+ text: `Patchset ${index}${postfix}`,
+ };
+ });
+ }
+
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has('tabState')) {
@@ -181,10 +203,10 @@
handleActionTriggered(action: Action, run?: CheckRun) {
if (!this.changeNum) return;
- if (!this.currentPatchNum) return;
+ if (!this.checksPatchsetNumber) return;
const promise = action.callback(
this.changeNum,
- this.currentPatchNum as number,
+ this.checksPatchsetNumber,
run?.attempt,
run?.externalId,
run?.checkName,
diff --git a/polygerrit-ui/app/services/change/change-model.ts b/polygerrit-ui/app/services/change/change-model.ts
index b2bdcfe..e7472de 100644
--- a/polygerrit-ui/app/services/change/change-model.ts
+++ b/polygerrit-ui/app/services/change/change-model.ts
@@ -46,10 +46,18 @@
// Must only be used by the change service or whatever is in control of this
// model.
export function updateState(change?: ParsedChangeInfo) {
- privateState$.next({
- ...privateState$.getValue(),
- change,
- });
+ const current = privateState$.getValue();
+ // We want to make it easy for subscribers to react to change changes, so we
+ // are explicitly emitting and additional `undefined` when the change number
+ // changes. So if you are subscribed to the latestPatchsetNumber for example,
+ // then you can rely on emissions even if the old and the new change have the
+ // same latestPatchsetNumber.
+ if (change !== undefined && current.change !== undefined) {
+ if (change._number !== current.change._number) {
+ privateState$.next({...current, change: undefined});
+ }
+ }
+ privateState$.next({...current, change});
}
/**
@@ -91,9 +99,6 @@
*
* Note that this selector can emit a patchNum without the change being
* available!
- *
- * TODO: It would be good to assert/enforce somehow that currentPatchNum$ cannot
- * emit 'PARENT'.
*/
export const currentPatchNum$: Observable<
PatchSetNum | undefined
diff --git a/polygerrit-ui/app/services/change/change-service.ts b/polygerrit-ui/app/services/change/change-service.ts
index 4524813..c292fb5 100644
--- a/polygerrit-ui/app/services/change/change-service.ts
+++ b/polygerrit-ui/app/services/change/change-service.ts
@@ -16,21 +16,16 @@
*/
import {routerChangeNum$} from '../router/router-model';
import {updateState} from './change-model';
-import {tap} from 'rxjs/operators';
import {ParsedChangeInfo} from '../../types/types';
export class ChangeService {
- // TODO: In the future we will want to make restApiService.getChangeDetail()
- // calls from a switchMap() here. For now just make sure to invalidate the
- // change when no changeNum is set.
- private routerChangeNumEffect = routerChangeNum$.pipe(
- tap(changeNum => {
- if (!changeNum) updateState(undefined);
- })
- );
-
constructor() {
- this.routerChangeNumEffect.subscribe();
+ // TODO: In the future we will want to make restApiService.getChangeDetail()
+ // calls from a switchMap() here. For now just make sure to invalidate the
+ // change when no changeNum is set.
+ routerChangeNum$.subscribe(changeNum => {
+ if (!changeNum) updateState(undefined);
+ });
}
/**
diff --git a/polygerrit-ui/app/services/checks/checks-model.ts b/polygerrit-ui/app/services/checks/checks-model.ts
index d8dc688..1c5b862 100644
--- a/polygerrit-ui/app/services/checks/checks-model.ts
+++ b/polygerrit-ui/app/services/checks/checks-model.ts
@@ -26,6 +26,7 @@
RunStatus,
} from '../../api/checks';
import {distinctUntilChanged, map} from 'rxjs/operators';
+import {PatchSetNumber} from '../../types/common';
// This is a convenience type for working with results, because when working
// with a bunch of results you will typically also want to know about the run
@@ -41,29 +42,44 @@
}
interface ChecksState {
- [name: string]: ChecksProviderState;
+ patchsetNumber?: PatchSetNumber;
+ providerNameToState: {
+ [name: string]: ChecksProviderState;
+ };
}
-const initialState: ChecksState = {};
+const initialState: ChecksState = {
+ providerNameToState: {},
+};
const privateState$ = new BehaviorSubject(initialState);
// Re-exporting as Observable so that you can only subscribe, but not emit.
export const checksState$: Observable<ChecksState> = privateState$;
-export const aPluginHasRegistered$ = checksState$.pipe(
+export const checksPatchsetNumber$ = checksState$.pipe(
+ map(state => state.patchsetNumber),
+ distinctUntilChanged()
+);
+
+export const checksProviderState$ = checksState$.pipe(
+ map(state => state.providerNameToState),
+ distinctUntilChanged()
+);
+
+export const aPluginHasRegistered$ = checksProviderState$.pipe(
map(state => Object.keys(state).length > 0),
distinctUntilChanged()
);
-export const someProvidersAreLoading$ = checksState$.pipe(
+export const someProvidersAreLoading$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).some(providerState => providerState.loading);
}),
distinctUntilChanged()
);
-export const allActions$ = checksState$.pipe(
+export const allActions$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).reduce(
(allActions: Action[], providerState: ChecksProviderState) => [
@@ -75,7 +91,7 @@
})
);
-export const allRuns$ = checksState$.pipe(
+export const allRuns$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).reduce(
(allRuns: CheckRun[], providerState: ChecksProviderState) => [
@@ -87,7 +103,7 @@
})
);
-export const checkToPluginMap$ = checksState$.pipe(
+export const checkToPluginMap$ = checksProviderState$.pipe(
map(state => {
const map = new Map<string, string>();
for (const [pluginName, providerState] of Object.entries(state)) {
@@ -99,7 +115,7 @@
})
);
-export const allResults$ = checksState$.pipe(
+export const allResults$ = checksProviderState$.pipe(
map(state => {
return Object.values(state)
.reduce(
@@ -124,7 +140,8 @@
config?: ChecksApiConfig
) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
pluginName,
loading: false,
config,
@@ -200,8 +217,9 @@
export function updateStateSetLoading(pluginName: string) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
- ...nextState[pluginName],
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
+ ...nextState.providerNameToState[pluginName],
loading: true,
};
privateState$.next(nextState);
@@ -213,11 +231,18 @@
actions: Action[] = []
) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
- ...nextState[pluginName],
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
+ ...nextState.providerNameToState[pluginName],
loading: false,
runs: [...runs],
actions: [...actions],
};
privateState$.next(nextState);
}
+
+export function updateStateSetPatchset(patchsetNumber?: PatchSetNumber) {
+ const nextState = {...privateState$.getValue()};
+ nextState.patchsetNumber = patchsetNumber;
+ privateState$.next(nextState);
+}
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index d2a4775..fd1f810 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -16,9 +16,9 @@
*/
import {
+ filter,
switchMap,
takeWhile,
- tap,
throttleTime,
withLatestFrom,
} from 'rxjs/operators';
@@ -29,12 +29,14 @@
FetchResponse,
ResponseCode,
} from '../../api/checks';
-import {change$, currentPatchNum$} from '../change/change-model';
+import {change$, changeNum$, latestPatchNum$} from '../change/change-model';
import {
updateStateSetLoading,
checkToPluginMap$,
updateStateSetProvider,
updateStateSetResults,
+ checksPatchsetNumber$,
+ updateStateSetPatchset,
} from './checks-model';
import {
BehaviorSubject,
@@ -43,19 +45,33 @@
Observable,
of,
Subject,
+ timer,
} from 'rxjs';
+import {PatchSetNumber} from '../../types/common';
export class ChecksService {
private readonly providers: {[name: string]: ChecksProvider} = {};
private readonly reloadSubjects: {[name: string]: Subject<void>} = {};
- private changeAndPatchNum$ = change$.pipe(withLatestFrom(currentPatchNum$));
-
private checkToPluginMap = new Map<string, string>();
+ private readonly documentVisibilityChange$ = new BehaviorSubject(undefined);
+
constructor() {
- checkToPluginMap$.subscribe(x => (this.checkToPluginMap = x));
+ checkToPluginMap$.subscribe(map => {
+ this.checkToPluginMap = map;
+ });
+ latestPatchNum$.subscribe(num => {
+ updateStateSetPatchset(num);
+ });
+ document.addEventListener('visibilitychange', () => {
+ this.documentVisibilityChange$.next(undefined);
+ });
+ }
+
+ setPatchset(num: PatchSetNumber) {
+ updateStateSetPatchset(num);
}
reload(pluginName: string) {
@@ -80,39 +96,52 @@
this.providers[pluginName] = provider;
this.reloadSubjects[pluginName] = new BehaviorSubject<void>(undefined);
updateStateSetProvider(pluginName, config);
- // Both, changed numbers and and announceUpdate request should trigger.
+ const pollIntervalMs = (config?.fetchPollingIntervalSeconds ?? 60) * 1000;
+ // Various events should trigger fetching checks from the provider:
+ // 1. Change number and patchset number changes.
+ // 2. Specific reload requests.
+ // 3. Regular polling starting with an initial fetch right now.
+ // 4. A hidden Gerrit tab becoming visible.
combineLatest([
- this.changeAndPatchNum$,
+ changeNum$,
+ checksPatchsetNumber$,
this.reloadSubjects[pluginName].pipe(throttleTime(1000)),
+ timer(0, pollIntervalMs),
+ this.documentVisibilityChange$,
])
.pipe(
takeWhile(_ => !!this.providers[pluginName]),
+ filter(_ => document.visibilityState !== 'hidden'),
+ withLatestFrom(change$),
switchMap(
- ([[change, patchNum], _]): Observable<FetchResponse> => {
- if (!change || !patchNum || typeof patchNum !== 'number') {
+ ([[changeNum, patchNum], change]): Observable<FetchResponse> => {
+ if (
+ !change ||
+ !changeNum ||
+ !patchNum ||
+ typeof patchNum !== 'number'
+ ) {
return of({
responseCode: ResponseCode.OK,
runs: [],
});
}
const data: ChangeData = {
- changeNumber: change._number,
+ changeNumber: changeNum,
patchsetNumber: patchNum,
repo: change.project,
};
updateStateSetLoading(pluginName);
return from(this.providers[pluginName].fetch(data));
}
- ),
- tap(response => {
- updateStateSetResults(
- pluginName,
- response.runs ?? [],
- response.actions
- );
- })
+ )
)
- .subscribe(() => {});
- this.reload(pluginName);
+ .subscribe(response => {
+ updateStateSetResults(
+ pluginName,
+ response.runs ?? [],
+ response.actions
+ );
+ });
}
}
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 711b11b..1bfabf8 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -76,6 +76,7 @@
export type ParsedJSON = BrandType<unknown, '_parsedJSON'>;
export type PatchSetNum = BrandType<'PARENT' | 'edit' | number, '_patchSet'>;
+export type PatchSetNumber = BrandType<number, '_patchSet'>;
export const EditPatchSetNum = 'edit' as PatchSetNum;
// TODO(TS): This is not correct, it is better to have a separate ApiPatchSetNum
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index 40e3eef..af56798 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -3,11 +3,12 @@
ChangeInfo,
PatchSetNum,
EditPatchSetNum,
- BrandType,
ParentPatchSetNum,
+ PatchSetNumber,
} from '../types/common';
import {RestApiService} from '../services/gr-rest-api/gr-rest-api';
import {EditRevisionInfo, ParsedChangeInfo} from '../types/types';
+import {check} from './common-util';
/**
* @license
@@ -82,9 +83,7 @@
return patchset as PatchSetNum;
}
-export function isNumber(
- psn: PatchSetNum
-): psn is BrandType<number, '_patchSet'> {
+export function isNumber(psn: PatchSetNum): psn is PatchSetNumber {
return typeof psn === 'number';
}
@@ -250,14 +249,16 @@
export function computeLatestPatchNum(
allPatchSets?: PatchSet[]
-): PatchSetNum | undefined {
+): PatchSetNumber | undefined {
if (!allPatchSets || !allPatchSets.length) {
return undefined;
}
- if (allPatchSets[0].num === EditPatchSetNum) {
- return allPatchSets[1].num;
+ let latest = allPatchSets[0].num;
+ if (latest === EditPatchSetNum) {
+ latest = allPatchSets[1].num;
}
- return allPatchSets[0].num;
+ check(isNumber(latest), 'Latest patchset cannot be EDIT or PARENT.');
+ return latest;
}
export function computePredecessor(
diff --git a/proto/cache.proto b/proto/cache.proto
index 4fd037d..a610e49 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -617,7 +617,7 @@
// Serialized form of
// com.google.gerrit.server.patch.filediff.FileDiffOutput
-// Next ID: 9
+// Next ID: 12
message FileDiffOutputProto {
// Next ID: 5
message Edit {
@@ -632,6 +632,11 @@
Edit edit = 1;
bool due_to_rebase = 2;
}
+ // Next ID: 3
+ message ComparisonType {
+ int32 parent_num = 1;
+ bool auto_merge = 2;
+ }
string old_path = 1;
string new_path = 2;
string change_type = 3;
@@ -640,4 +645,7 @@
int64 size = 6;
int64 size_delta = 7;
repeated TaggedEdit edits = 8;
+ bytes old_commit = 9;
+ bytes new_commit = 10;
+ ComparisonType comparison_type = 11;
}