Merge "Remove required access permission to get account username"
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index 4c75ad4..4746fee 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -218,6 +218,10 @@
this.force = force;
}
+ public void noParents() {
+ commitBuilder.noParents();
+ }
+
public class Result {
private final String ref;
private final PushResult result;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index 72fcd03..c390c67 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
@@ -84,4 +85,37 @@
"and upload the rebased commit for review.");
assertThat(getRemoteHead()).isEqualTo(oldHead);
}
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void submitMultipleCommitsToEmptyRepoAsFastForward() throws Exception {
+ PushOneCommit.Result change1 = createChange();
+ PushOneCommit.Result change2 = createChange();
+ approve(change1.getChangeId());
+ submit(change2.getChangeId());
+ assertThat(getRemoteHead().getId()).isEqualTo(change2.getCommitId());
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void submitMultipleCommitsToEmptyRepoWithOneMerge() throws Exception {
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+ PushOneCommit.Result change1 = pushFactory.create(
+ db, admin.getIdent(), testRepo, "Change 1", "a", "a")
+ .to("refs/for/master/" + name("topic"));
+
+ PushOneCommit push2 = pushFactory.create(
+ db, admin.getIdent(), testRepo, "Change 2", "b", "b");
+ push2.noParents();
+ PushOneCommit.Result change2 = push2.to("refs/for/master/" + name("topic"));
+ change2.assertOkStatus();
+
+ approve(change1.getChangeId());
+ submit(change2.getChangeId());
+
+ RevCommit head = getRemoteHead();
+ assertThat(head.getParents()).hasLength(2);
+ assertThat(head.getParent(0)).isEqualTo(change1.getCommit());
+ assertThat(head.getParent(1)).isEqualTo(change2.getCommit());
+ }
}
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
index 4b2cb03..79a97a9 100644
--- a/gerrit-gwtexpui/BUCK
+++ b/gerrit-gwtexpui/BUCK
@@ -7,7 +7,7 @@
resources = [
SRC + 'clippy/client/clippy.css',
SRC + 'clippy/client/clippy.swf',
- SRC + 'clippy/client/clipboard-16.png',
+ SRC + 'clippy/client/page_white_copy.png',
SRC + 'clippy/client/CopyableLabelText.properties',
],
provided_deps = ['//lib/gwt:user'],
@@ -15,7 +15,7 @@
':SafeHtml',
':UserAgent',
'//lib:LICENSE-clippy',
- '//lib:LICENSE-drifty',
+ '//lib:LICENSE-silk_icons',
],
visibility = ['PUBLIC'],
)
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java
index dd3cc18..f3435cc 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java
@@ -30,6 +30,6 @@
@DoNotEmbed
DataResource swf();
- @Source("clipboard-16.png")
+ @Source("page_white_copy.png")
ImageResource clipboard();
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clipboard-16.png b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clipboard-16.png
deleted file mode 100644
index 9c6e10a..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clipboard-16.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/page_white_copy.png b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/page_white_copy.png
new file mode 100644
index 0000000..a9f31a2
--- /dev/null
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/page_white_copy.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
index 85b350d..fa4be77 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
@@ -73,6 +73,7 @@
Modes.I.soy(),
Modes.I.sql(),
Modes.I.stex(),
+ Modes.I.swift(),
Modes.I.velocity(),
Modes.I.verilog(),
Modes.I.vhdl(),
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
index 42990b8..4faaccf 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
@@ -55,6 +55,7 @@
@Source("scheme.js") @DoNotEmbed DataResource scheme();
@Source("shell.js") @DoNotEmbed DataResource shell();
@Source("smalltalk.js") @DoNotEmbed DataResource smalltalk();
+ @Source("swift.js") @DoNotEmbed DataResource swift();
@Source("soy.js") @DoNotEmbed DataResource soy();
@Source("sql.js") @DoNotEmbed DataResource sql();
@Source("stex.js") @DoNotEmbed DataResource stex();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChangeOp.java
index 41b9736..c9d65f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChangeOp.java
@@ -104,7 +104,7 @@
prefix = prefix.substring(0, prefix.length() - 1);
for (Ref ref
: ctx.getRepository().getRefDatabase().getRefs(prefix).values()) {
- ctx.getBatchRefUpdate().addCommand(
+ ctx.addRefUpdate(
new ReceiveCommand(
ref.getObjectId(), ObjectId.zeroId(), ref.getName()));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
index 7edf483..72b6bf2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -122,7 +122,7 @@
deleteChangeOp.updateRepo(ctx);
return;
}
- ctx.getBatchRefUpdate().addCommand(
+ ctx.addRefUpdate(
new ReceiveCommand(
ObjectId.fromString(patchSet.getRevision().get()),
ObjectId.zeroId(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 8628316..cb2c2bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -44,15 +44,14 @@
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.ChangeModifiedException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gerrit.server.ssh.SshInfo;
-import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -210,8 +209,8 @@
ChangeControl ctl = ctx.getChangeControl();
change = ctx.getChange();
- Change.Id id = change.getId();
- final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
+ ChangeUpdate update = ctx.getChangeUpdate();
+
if (!change.getStatus().isOpen() && !allowClosed) {
throw new InvalidChangeOperationException(String.format(
"Change %s is closed", change.getId()));
@@ -223,6 +222,8 @@
patchSet.setRevision(new RevId(commit.name()));
patchSet.setDraft(draft);
+ update.setPatchSetId(patchSet.getId());
+
if (groups != null) {
patchSet.setGroups(groups);
} else {
@@ -242,30 +243,12 @@
}
patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
- // TODO(dborowitz): Throw ResourceConflictException instead of using
- // AtomicUpdate.
- change = db.changes().atomicUpdate(id, new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isClosed() && !allowClosed) {
- return null;
- }
- if (!change.currentPatchSetId().equals(currentPatchSetId)) {
- return null;
- }
- if (change.getStatus() != Change.Status.DRAFT && !allowClosed) {
- change.setStatus(Change.Status.NEW);
- }
- change.setCurrentPatchSet(patchSetInfo);
- ChangeUtil.updated(change);
- return change;
- }
- });
- if (change == null) {
- throw new ChangeModifiedException(String.format(
- "Change %s was modified", id));
+ if (change.getStatus() != Change.Status.DRAFT && !allowClosed) {
+ change.setStatus(Change.Status.NEW);
}
-
+ change.setCurrentPatchSet(patchSetInfo);
+ ChangeUtil.updated(change);
+ db.changes().update(Collections.singleton(change));
approvalCopier.copy(db, ctl, patchSet);
if (changeMessage != null) {
cmUtil.addChangeMessage(db, ctx.getChangeUpdate(), changeMessage);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index 7a8cb9ae..991f6c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -146,7 +146,7 @@
return BatchUpdate.this.getObjectInserter();
}
- public BatchRefUpdate getBatchRefUpdate() throws IOException {
+ private BatchRefUpdate getBatchRefUpdate() throws IOException {
initRepository();
if (batchRefUpdate == null) {
batchRefUpdate = repo.getRefDatabase().newBatchUpdate();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
index 0da24c8..3f0e833 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
@@ -14,9 +14,16 @@
package com.google.gerrit.server.git.strategy;
+import static com.google.gerrit.server.git.strategy.MarkCleanMergesOp.anyChangeId;
+
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MergeTip;
+import com.google.gerrit.server.git.UpdateException;
import java.util.Collection;
import java.util.List;
@@ -27,24 +34,46 @@
}
@Override
- public MergeTip run(final CodeReviewCommit branchTip,
- final Collection<CodeReviewCommit> toMerge) throws IntegrationException {
+ public MergeTip run(CodeReviewCommit branchTip,
+ Collection<CodeReviewCommit> toMerge) throws IntegrationException {
+ List<CodeReviewCommit> sorted =
+ args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
- List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(
- args.mergeSorter, toMerge);
- final CodeReviewCommit newMergeTipCommit =
- args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
- mergeTip.moveTipTo(newMergeTipCommit, newMergeTipCommit);
+ try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
+ CodeReviewCommit newTipCommit =
+ args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
+ if (!newTipCommit.equals(branchTip)) {
+ u.addOp(newTipCommit.change().getId(),
+ new FastForwardOp(mergeTip, newTipCommit));
+ }
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit n = sorted.remove(0);
+ u.addOp(n.change().getId(), new NotFastForwardOp(n));
+ }
+ u.addOp(anyChangeId(toMerge), new MarkCleanMergesOp(args, mergeTip));
- while (!sorted.isEmpty()) {
- final CodeReviewCommit n = sorted.remove(0);
- n.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
+ u.execute();
+ } catch (RestApiException | UpdateException e) {
+ if (e.getCause() instanceof IntegrationException) {
+ throw new IntegrationException(e.getCause().getMessage(), e);
+ }
+ throw new IntegrationException(
+ "Cannot fast-forward into " + args.destBranch);
+ }
+ return mergeTip;
+ }
+
+ private static class NotFastForwardOp extends BatchUpdate.Op {
+ private final CodeReviewCommit toMerge;
+
+ private NotFastForwardOp(CodeReviewCommit toMerge) {
+ this.toMerge = toMerge;
}
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
- newMergeTipCommit, args.alreadyAccepted);
-
- return mergeTip;
+ @Override
+ public void updateRepo(RepoContext ctx) {
+ toMerge.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
+ }
}
static boolean dryRun(SubmitDryRun.Arguments args,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
new file mode 100644
index 0000000..19d5080
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2016 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.git.strategy;
+
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.IntegrationException;
+import com.google.gerrit.server.git.MergeTip;
+
+import java.io.IOException;
+
+class FastForwardOp extends BatchUpdate.Op {
+ private final MergeTip mergeTip;
+ private final CodeReviewCommit toMerge;
+
+ FastForwardOp(MergeTip mergeTip,
+ CodeReviewCommit toMerge) {
+ this.mergeTip = mergeTip;
+ this.toMerge = toMerge;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx)
+ throws IntegrationException, IOException {
+ mergeTip.moveTipTo(toMerge, toMerge);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MarkCleanMergesOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MarkCleanMergesOp.java
new file mode 100644
index 0000000..9a7b9b5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MarkCleanMergesOp.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2016 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.git.strategy;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.IntegrationException;
+import com.google.gerrit.server.git.MergeTip;
+
+class MarkCleanMergesOp extends BatchUpdate.Op {
+ static Change.Id anyChangeId(Iterable<CodeReviewCommit> commits) {
+ for (CodeReviewCommit c : commits) {
+ if (c.change() != null) {
+ return c.change().getId();
+ }
+ }
+ throw new IllegalArgumentException(
+ "no CodeReviewCommits have changes: " + commits);
+ }
+ private final SubmitStrategy.Arguments args;
+ private final MergeTip mergeTip;
+
+ MarkCleanMergesOp(SubmitStrategy.Arguments args,
+ MergeTip mergeTip) {
+ this.args = args;
+ this.mergeTip = mergeTip;
+ }
+
+ @Override
+ public void postUpdate(Context ctx) throws IntegrationException {
+ // TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk.
+ // When hoisting BatchUpdate into MergeOp, we will need to teach
+ // BatchUpdate how to produce CodeReviewRevWalks.
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ mergeTip.getCurrentTip(), args.alreadyAccepted);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
index 94fc588..329aef0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.git.strategy;
+import static com.google.gerrit.server.git.strategy.MarkCleanMergesOp.anyChangeId;
+
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MergeTip;
-
-import org.eclipse.jgit.lib.PersonIdent;
+import com.google.gerrit.server.git.UpdateException;
import java.util.Collection;
import java.util.List;
@@ -31,30 +35,30 @@
@Override
public MergeTip run(CodeReviewCommit branchTip,
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
- List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
- MergeTip mergeTip;
- if (branchTip == null) {
- // The branch is unborn. Take a fast-forward resolution to
- // create the branch.
- mergeTip = new MergeTip(sorted.get(0), toMerge);
- sorted.remove(0);
- } else {
- mergeTip = new MergeTip(branchTip, toMerge);
- }
- while (!sorted.isEmpty()) {
- CodeReviewCommit mergedFrom = sorted.remove(0);
- PersonIdent caller = args.caller.newCommitterIdent(
- args.serverIdent.getWhen(), args.serverIdent.getTimeZone());
- CodeReviewCommit newTip =
- args.mergeUtil.mergeOneCommit(caller, args.serverIdent,
- args.repo, args.rw, args.inserter, args.canMergeFlag,
- args.destBranch, mergeTip.getCurrentTip(), mergedFrom);
- mergeTip.moveTipTo(newTip, mergedFrom);
- }
+ List<CodeReviewCommit> sorted =
+ args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
+ MergeTip mergeTip = new MergeTip(branchTip, toMerge);
+ try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
+ if (branchTip == null) {
+ // The branch is unborn. Take a fast-forward resolution to
+ // create the branch.
+ CodeReviewCommit first = sorted.remove(0);
+ u.addOp(first.change().getId(), new FastForwardOp(mergeTip, first));
+ }
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit n = sorted.remove(0);
+ u.addOp(n.change().getId(), new MergeOneOp(args, mergeTip, n));
+ }
+ u.addOp(anyChangeId(toMerge), new MarkCleanMergesOp(args, mergeTip));
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
- mergeTip.getCurrentTip(), args.alreadyAccepted);
-
+ u.execute();
+ } catch (RestApiException | UpdateException e) {
+ if (e.getCause() instanceof IntegrationException) {
+ throw new IntegrationException(e.getCause().getMessage(), e);
+ }
+ throw new IntegrationException(
+ "Cannot merge into " + args.destBranch);
+ }
return mergeTip;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index ce9dd5b..40ab2c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.git.strategy;
+import static com.google.gerrit.server.git.strategy.MarkCleanMergesOp.anyChangeId;
+
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MergeTip;
-
-import org.eclipse.jgit.lib.PersonIdent;
+import com.google.gerrit.server.git.UpdateException;
import java.util.Collection;
import java.util.List;
@@ -33,33 +37,37 @@
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
List<CodeReviewCommit> sorted =
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
- MergeTip mergeTip;
- if (branchTip == null) {
- // The branch is unborn. Take a fast-forward resolution to
- // create the branch.
- mergeTip = new MergeTip(sorted.get(0), toMerge);
- branchTip = sorted.remove(0);
- } else {
- mergeTip = new MergeTip(branchTip, toMerge);
- branchTip =
- args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
- }
- mergeTip.moveTipTo(branchTip, branchTip);
+ MergeTip mergeTip = new MergeTip(branchTip, toMerge);
+ try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
+ // Start with the first fast-forward. This may create the branch if it did
+ // not exist.
+ CodeReviewCommit firstFastForward;
+ if (branchTip == null) {
+ firstFastForward = sorted.remove(0);
+ } else {
+ firstFastForward =
+ args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
+ }
+ if (!firstFastForward.equals(branchTip)) {
+ u.addOp(firstFastForward.change().getId(),
+ new FastForwardOp(mergeTip, firstFastForward));
+ }
- // For every other commit do a pair-wise merge.
- while (!sorted.isEmpty()) {
- CodeReviewCommit mergedFrom = sorted.remove(0);
- PersonIdent caller = args.caller.newCommitterIdent(
- args.serverIdent.getWhen(), args.serverIdent.getTimeZone());
- branchTip =
- args.mergeUtil.mergeOneCommit(caller, args.serverIdent,
- args.repo, args.rw, args.inserter, args.canMergeFlag,
- args.destBranch, branchTip, mergedFrom);
- mergeTip.moveTipTo(branchTip, mergedFrom);
- }
+ // For every other commit do a pair-wise merge.
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit n = sorted.remove(0);
+ u.addOp(n.change().getId(), new MergeOneOp(args, mergeTip, n));
+ }
+ u.addOp(anyChangeId(toMerge), new MarkCleanMergesOp(args, mergeTip));
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, branchTip,
- args.alreadyAccepted);
+ u.execute();
+ } catch (RestApiException | UpdateException e) {
+ if (e.getCause() instanceof IntegrationException) {
+ throw new IntegrationException(e.getCause().getMessage(), e);
+ }
+ throw new IntegrationException(
+ "Cannot merge into " + args.destBranch);
+ }
return mergeTip;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
new file mode 100644
index 0000000..834a6c0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2016 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.git.strategy;
+
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.IntegrationException;
+import com.google.gerrit.server.git.MergeTip;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.io.IOException;
+
+class MergeOneOp extends BatchUpdate.Op {
+ private final SubmitStrategy.Arguments args;
+ private final MergeTip mergeTip;
+ private final CodeReviewCommit toMerge;
+
+ MergeOneOp(SubmitStrategy.Arguments args, MergeTip mergeTip,
+ CodeReviewCommit toMerge) {
+ this.args = args;
+ this.mergeTip = mergeTip;
+ this.toMerge = toMerge;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx)
+ throws IntegrationException, IOException {
+ PersonIdent caller = ctx.getUser().asIdentifiedUser().newCommitterIdent(
+ ctx.getWhen(), ctx.getTimeZone());
+ if (mergeTip.getCurrentTip() == null) {
+ throw new IllegalStateException("cannot merge commit " + toMerge.name()
+ + " onto a null tip; expected at least one fast-forward prior to"
+ + " this operation");
+ }
+ // TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk.
+ // When hoisting BatchUpdate into MergeOp, we will need to teach
+ // BatchUpdate how to produce CodeReviewRevWalks.
+ CodeReviewCommit merged =
+ args.mergeUtil.mergeOneCommit(caller, args.serverIdent,
+ ctx.getRepository(), args.rw, ctx.getInserter(), args.canMergeFlag,
+ args.destBranch, mergeTip.getCurrentTip(), toMerge);
+ mergeTip.moveTipTo(merged, toMerge);
+ }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
index 912e4b0..e3fb157 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -46,6 +46,7 @@
soy = text/x-soy
st = text/x-stsrc
stex = text/x-stex
+swift = text/x-swift
v = text/x-verilog
vert = x-shader/x-vertex
vh = text/x-verilog
diff --git a/lib/BUCK b/lib/BUCK
index a0924aa..8a2d22b 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -10,7 +10,6 @@
define_license(name = 'clippy')
define_license(name = 'codemirror')
define_license(name = 'diffy')
-define_license(name = 'drifty')
define_license(name = 'freebie_application_icon_set')
define_license(name = 'h2')
define_license(name = 'jgit')
diff --git a/lib/LICENSE-drifty b/lib/LICENSE-drifty
deleted file mode 100644
index 18ab118..0000000
--- a/lib/LICENSE-drifty
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2014 Drifty (http://drifty.com/)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
index abb6d92..9677058 100644
--- a/lib/codemirror/cm.defs
+++ b/lib/codemirror/cm.defs
@@ -78,6 +78,7 @@
'soy',
'sql',
'stex',
+ 'swift',
'tcl',
'velocity',
'verilog',
diff --git a/lib/js.defs b/lib/js.defs
index 9508c30..570ed1e 100644
--- a/lib/js.defs
+++ b/lib/js.defs
@@ -165,5 +165,5 @@
'--js', '%s.js' % name,
'&&', 'zip', '$OUT', '%s.html' % name, '%s.js' % name,
]),
- out = '%s.vulcanized.zip',
+ out = '%s.vulcanized.zip' % name,
)
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index c3c520b..e53b1a5 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -83,6 +83,9 @@
display: flex;
margin-left: var(--default-horizontal-margin);
}
+ .feedback {
+ color: #b71c1c;
+ }
</style>
<gr-ajax auto url="/accounts/self/detail" last-response="{{account}}"></gr-ajax>
<gr-ajax auto url="/config/server/info" last-response="{{config}}"></gr-ajax>
@@ -102,7 +105,8 @@
</header>
<main>
<template is="dom-if" if="{{_showChangeListView}}" restamp="true">
- <gr-change-list-view params="[[params]]"></gr-change-list-view>
+ <gr-change-list-view params="[[params]]"
+ logged-in="[[_computeLoggedIn(account)]]"></gr-change-list-view>
</template>
<template is="dom-if" if="{{_showDashboardView}}" restamp="true">
<gr-dashboard-view params="[[params]]"></gr-dashboard-view>
@@ -117,18 +121,20 @@
</main>
<footer role="contentinfo">
Powered by <a href="https://www.gerritcodereview.com/" target="_blank">Gerrit Code Review</a>
- (<span>[[version]]</span>)
+ ([[version]])
<span hidden$="[[!config.gerrit.report_bug_url]]">
|
<a href$="[[config.gerrit.report_bug_url]]" target="_blank">
<span hidden$="[[!config.gerrit.report_bug_text]]">
[[config.gerrit.report_bug_text]]
</span>
- <span hidden$="[[config.gerrit.report_bug_text]]">
- Report Bug
- </span>
+ <span hidden$="[[config.gerrit.report_bug_text]]">Report Bug</span>
</a>
</span>
+ |
+ <a class="feedback" href="http://goo.gl/forms/ETHmIH2Kga" target="_blank">
+ PolyGerrit Feedback
+ </a>
</footer>
</template>
<script>
@@ -219,6 +225,10 @@
window.location.pathname + window.location.hash));
},
+ _computeLoggedIn: function(account) { // argument used for binding update only
+ return this.loggedIn;
+ },
+
});
})();
</script>
diff --git a/polygerrit-ui/app/elements/gr-change-actions.html b/polygerrit-ui/app/elements/gr-change-actions.html
index c37a931..7cdab50 100644
--- a/polygerrit-ui/app/elements/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/gr-change-actions.html
@@ -24,6 +24,9 @@
:host {
display: block;
}
+ button:before {
+ content: attr(data-label);
+ }
button {
background-color: #448aff;
border: none;
@@ -33,7 +36,15 @@
font: inherit;
padding: .5em .75em;
}
+ button[loading] {
+ cursor: wait;
+ opacity: .5;
+ }
+ button[loading]:before {
+ content: attr(data-loading-label);
+ }
button[disabled] {
+ background-color: #555961;
opacity: .5;
}
</style>
@@ -44,10 +55,11 @@
<div>
<template is="dom-repeat" items="[[_computeActionValues(_actions)]]" as="action">
<button title$="[[action.title]]"
- hidden$="[[!action.enabled]]"
+ disabled$="[[!action.enabled]]"
data-action-key$="[[action.__key]]"
- disabled$="[[_loading]]"
- on-tap="_handleActionTap">[[action.label]]</button>
+ data-label$="[[action.label]]"
+ data-loading-label$="[[_computeLoadingLabel(action.__key)]]"
+ on-tap="_handleActionTap"></button>
</template>
</div>
</template>
@@ -73,7 +85,7 @@
},
_loading: {
type: Boolean,
- value: false,
+ value: true,
},
},
@@ -102,6 +114,12 @@
return result;
},
+ _computeLoadingLabel: function(action) {
+ return {
+ 'submit': 'Submitting...',
+ }[action];
+ },
+
_handleActionTap: function(e) {
e.preventDefault();
var el = Polymer.dom(e).rootTarget;
@@ -112,12 +130,20 @@
},
_submitChange: function(endpoint, action) {
+ var buttonEl = this.$$('[data-action-key="' + action.__key + '"]');
+ buttonEl.setAttribute('loading', true);
+ buttonEl.disabled = true;
+
this._send(action.method, {}, endpoint).then(
function() {
this.fire('reload-change', null, {bubbles: false});
+ buttonEl.setAttribute('loading', false);
+ buttonEl.disabled = false;
}.bind(this)).catch(function(err) {
alert('Oops. Something went wrong. Check the console and bug the ' +
'PolyGerrit team for assistance.');
+ buttonEl.setAttribute('loading', false);
+ buttonEl.disabled = false;
throw err;
});
},
diff --git a/polygerrit-ui/app/elements/gr-change-list-item.html b/polygerrit-ui/app/elements/gr-change-list-item.html
index e2241919..9c77a06 100644
--- a/polygerrit-ui/app/elements/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/gr-change-list-item.html
@@ -17,6 +17,7 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../styles/gr-change-list-styles.html">
<link rel="import" href="gr-account-link.html">
+<link rel="import" href="gr-change-star.html">
<link rel="import" href="gr-date-formatter.html">
<dom-module id="gr-change-list-item">
@@ -60,6 +61,9 @@
<span class="cell keyboard">
<span class="positionIndicator">▶</span>
</span>
+ <span class="cell star" hidden$="[[!showStar]]">
+ <gr-change-star change="{{change}}"></gr-change-star>
+ </span>
<a class="cell subject" href$="[[changeURL]]">[[change.subject]]</a>
<span class="cell status">[[_computeChangeStatusString(change)]]</span>
<span class="cell owner">
@@ -73,8 +77,9 @@
<span class="u-red"><span>-</span>[[change.deletions]]</span>
</span>
<span title="Code-Review"
- class$="[[_computeCodeReviewClass(change.labels.Code_Review)]]">[[_computeCodeReviewLabel(change.labels.Code_Review)]]</span>
- <span class="cell verified u-green" title="Verified">[[_computeVerifiedLabel(change.labels.Verified)]]</span>
+ class$="[[_computeLabelClass(change.labels.Code_Review)]]">[[_computeLabelValue(change.labels.Code_Review)]]</span>
+ <span title="Verified"
+ class$="[[_computeLabelClass(change.labels.Verified)]]">[[_computeLabelValue(change.labels.Verified)]]</span>
</template>
<script>
(function() {
@@ -93,6 +98,10 @@
type: String,
computed: '_computeChangeURL(change._number)',
},
+ showStar: {
+ type: Boolean,
+ value: false,
+ },
},
_computeChangeURL: function(changeNum) {
@@ -116,48 +125,47 @@
return '';
},
- _computeCodeReviewClass: function(codeReview) {
+ _computeLabelClass: function(label) {
// Mimic a Set.
var classes = {
'cell': true,
- 'codeReview': true,
+ 'label': true,
};
- if (codeReview) {
- if (codeReview.approved) {
+ if (label) {
+ if (label.approved) {
classes['u-green'] = true;
}
- if (codeReview.value == 1) {
+ if (label.value == 1) {
classes['u-monospace'] = true;
classes['u-green'] = true;
- } else if (codeReview.value == -1) {
+ } else if (label.value == -1) {
classes['u-monospace'] = true;
classes['u-red'] = true;
}
+ if (label.rejected) {
+ classes['u-red'] = true;
+ }
}
return Object.keys(classes).sort().join(' ');
},
- _computeCodeReviewLabel: function(codeReview) {
- if (!codeReview) { return ''; }
- if (codeReview.approved) {
+ _computeLabelValue: function(label) {
+ if (!label) { return ''; }
+ if (label.approved) {
return '✓';
}
- if (codeReview.value > 0) {
- return '+' + codeReview.value;
+ if (label.rejected) {
+ return '✕';
}
- if (codeReview.value < 0) {
- return codeReview.value;
+ if (label.value > 0) {
+ return '+' + label.value;
+ }
+ if (label.value < 0) {
+ return label.value;
}
return '';
},
- _computeVerifiedLabel: function(verified) {
- if (verified && verified.approved) {
- return '✓';
- }
- return ''
- },
-
_computeProjectURL: function(project) {
return '/projects/' + project + ',dashboards/default';
},
diff --git a/polygerrit-ui/app/elements/gr-change-list-view.html b/polygerrit-ui/app/elements/gr-change-list-view.html
index 5381552..aef69c5 100644
--- a/polygerrit-ui/app/elements/gr-change-list-view.html
+++ b/polygerrit-ui/app/elements/gr-change-list-view.html
@@ -56,9 +56,10 @@
params="[[_computeQueryParams(query, offset)]]"
last-response="{{_changes}}"
loading="{{_loading}}"></gr-ajax>
- <div class="loading" hidden$="{{!_loading}}">Loading...</div>
- <div hidden$="{{_loading}}">
- <gr-change-list changes="{{_changes}}"></gr-change-list>
+ <div class="loading" hidden$="[[!_loading]]">Loading...</div>
+ <div hidden$="[[_loading]]">
+ <gr-change-list changes="{{_changes}}"
+ show-star="[[loggedIn]]"></gr-change-list>
<nav>
<a href$="[[_computeNavLink(query, offset, -1)]]"
hidden$="[[_hidePrevArrow(offset)]]">← Prev</a>
@@ -86,6 +87,14 @@
},
/**
+ * True when user is logged in.
+ */
+ loggedIn: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
* Change objects loaded from the server.
*/
_changes: Array,
diff --git a/polygerrit-ui/app/elements/gr-change-list.html b/polygerrit-ui/app/elements/gr-change-list.html
index 45a87fa..fd2b520 100644
--- a/polygerrit-ui/app/elements/gr-change-list.html
+++ b/polygerrit-ui/app/elements/gr-change-list.html
@@ -42,6 +42,7 @@
<style include="gr-change-list-styles"></style>
<div class="headerRow">
<span class="topHeader keyboard"></span> <!-- keyboard position indicator -->
+ <span class="topHeader star" hidden$="[[!showStar]]"></span>
<span class="topHeader subject">Subject</span>
<span class="topHeader status">Status</span>
<span class="topHeader owner">Owner</span>
@@ -49,15 +50,15 @@
<span class="topHeader branch">Branch</span>
<span class="topHeader updated">Updated</span>
<span class="topHeader size">Size</span>
- <span class="topHeader codeReview" title="Code-Review">CR</span>
- <span class="topHeader verified" title="Verified">V</span>
+ <span class="topHeader label" title="Code-Review">CR</span>
+ <span class="topHeader label" title="Verified">V</span>
</div>
<template is="dom-repeat" items="{{groups}}" as="changeGroup" index-as="groupIndex">
<template is="dom-if" if="[[_groupTitle(groupIndex)]]">
<div class="groupHeader">[[_groupTitle(groupIndex)]]</div>
</template>
<template is="dom-repeat" items="[[changeGroup]]" as="change">
- <gr-change-list-item change="[[change]]"></gr-change-list-item>
+ <gr-change-list-item change="[[change]]" show-star="[[showStar]]"></gr-change-list-item>
</template>
</template>
</template>
@@ -100,6 +101,10 @@
value: 0,
observer: '_selectedIndexChanged',
},
+ showStar: {
+ type: Boolean,
+ value: false,
+ },
_boundKeyHandler: {
type: Function,
value: function() { return this._handleKey.bind(this); },
diff --git a/polygerrit-ui/app/elements/gr-change-star.html b/polygerrit-ui/app/elements/gr-change-star.html
new file mode 100644
index 0000000..9311aff
--- /dev/null
+++ b/polygerrit-ui/app/elements/gr-change-star.html
@@ -0,0 +1,88 @@
+<!--
+Copyright (C) 2015 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.
+-->
+
+<link rel="import" href="../bower_components/polymer/polymer.html">
+<link rel="import" href="gr-request.html">
+
+<dom-module id="gr-change-star">
+ <template>
+ <style>
+ :host {
+ display: inline;
+ }
+ .starButton {
+ font-size: .95em;
+ margin-right: .25em;
+ cursor: pointer;
+ background-color: transparent;
+ border-color: transparent;
+ }
+ .star {
+ color: #fbc02d;
+ }
+ .unstar {
+ color: #666;
+ }
+ </style>
+ <button class="starButton" on-tap="_handleStarTap">
+ <span class="star" hidden$="[[!change.starred]]">★</span>
+ <span class="unstar" hidden$="[[change.starred]]">☆</span>
+ </button>
+ </template>
+ <script>
+ (function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-change-star',
+
+ properties: {
+ change: {
+ type: Object,
+ notify: true,
+ },
+
+ _xhrPromise: Object, // Used for testing.
+ },
+
+ _handleStarTap: function() {
+ var method = this.change.starred ? 'DELETE' : 'PUT';
+ this.set('change.starred', !this.change.starred);
+ this._send(method, this._restEndpoint()).catch(function(err) {
+ this.set('change.starred', !this.change.starred);
+ alert('Change couldn’t be starred. Check the console and contact ' +
+ 'the PolyGerrit team for assistance.');
+ throw err;
+ }.bind(this));
+ },
+
+ _send: function(method, url) {
+ var xhr = document.createElement('gr-request');
+ this._xhrPromise = xhr.send({
+ method: method,
+ url: url,
+ });
+ return this._xhrPromise;
+ },
+
+ _restEndpoint: function() {
+ return '/accounts/self/starred.changes/' + this.change._number;
+ },
+
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-change-view.html b/polygerrit-ui/app/elements/gr-change-view.html
index be63e59..6175803 100644
--- a/polygerrit-ui/app/elements/gr-change-view.html
+++ b/polygerrit-ui/app/elements/gr-change-view.html
@@ -18,6 +18,7 @@
<link rel="import" href="gr-account-link.html">
<link rel="import" href="gr-ajax.html">
<link rel="import" href="gr-change-actions.html">
+<link rel="import" href="gr-change-star.html">
<link rel="import" href="gr-date-formatter.html">
<link rel="import" href="gr-file-list.html">
<link rel="import" href="gr-linked-text.html">
@@ -149,6 +150,7 @@
<div class="headerContainer">
<div class="header">
<span class="header-title">
+ <gr-change-star change="{{_change}}" hidden$="[[!_loggedIn]]"></gr-change-star>
<a href$="[[_computeChangePath(_change._number)]]">[[_change._number]]</a><span>:</span>
<span>[[_change.subject]]</span>
<span class="changeStatus">[[_computeChangeStatus(_change.status)]]</span>
@@ -290,7 +292,10 @@
_headerContainerEl: Object,
_headerEl: Object,
_projectConfig: Object,
- _scrollHandler: Function,
+ _boundScrollHandler: {
+ type: Function,
+ value: function() { return this._handleBodyScroll.bind(this); },
+ },
_boundKeyHandler: {
type: Function,
value: function() { return this._handleKey.bind(this); },
@@ -301,16 +306,15 @@
app.accountReady.then(function() {
this._loggedIn = app.loggedIn;
}.bind(this));
- this._scrollHandler = this._handleBodyScroll.bind(this);
},
attached: function() {
- window.addEventListener('scroll', this._scrollHandler);
+ window.addEventListener('scroll', this._boundScrollHandler);
document.body.addEventListener('keydown', this._boundKeyHandler);
},
detached: function() {
- window.removeEventListener('scroll', this._scrollHandler);
+ window.removeEventListener('scroll', this._boundScrollHandler);
document.body.removeEventListener('keydown', this._boundKeyHandler);
},
@@ -333,6 +337,12 @@
el.classList.toggle('pinned', window.scrollY >= top);
},
+ _resetHeaderEl: function() {
+ var el = this._headerEl || this.$$('.header');
+ this._headerEl = el;
+ el.classList.remove('pinned');
+ },
+
_handlePatchChange: function(e) {
var patchNum = e.target.value;
var currentPatchNum =
@@ -443,12 +453,14 @@
_handleKey: function(e) {
if (util.shouldSupressKeyboardShortcut(e)) { return; }
- e.preventDefault();
+
switch(e.keyCode) {
case 65: // 'a'
+ e.preventDefault();
this.$.replyDropdown.open();
break;
case 85: // 'u'
+ e.preventDefault();
page.show('/');
break;
}
@@ -469,6 +481,7 @@
// The patch number is reliant on the change detail request.
detailCompletes.then(reloadPatchNumDependentResources);
}
+ this._resetHeaderEl();
},
});
diff --git a/polygerrit-ui/app/elements/gr-dashboard-view.html b/polygerrit-ui/app/elements/gr-dashboard-view.html
index 88997f6..d04b9f6 100644
--- a/polygerrit-ui/app/elements/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/gr-dashboard-view.html
@@ -34,7 +34,7 @@
url="/changes/"
params="[[_computeQueryParams()]]"
last-response="{{_results}}"></gr-ajax>
- <gr-change-list groups="{{_results}}"
+ <gr-change-list groups="{{_results}}" show-star
group-titles="[[_groupTitles]]"></gr-change-list>
</template>
<script>
diff --git a/polygerrit-ui/app/elements/gr-diff-view.html b/polygerrit-ui/app/elements/gr-diff-view.html
index a60460f..88ad3e7 100644
--- a/polygerrit-ui/app/elements/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/gr-diff-view.html
@@ -117,7 +117,6 @@
</div>
</h3>
<gr-diff id="diff"
- auto
change-num="[[_changeNum]]"
prefs="{{prefs}}"
patch-range="[[_patchRange]]"
@@ -157,7 +156,7 @@
_boundKeyHandler: {
type: Function,
value: function() { return this._handleKey.bind(this); },
- }
+ },
},
attached: function() {
@@ -220,6 +219,8 @@
if (!this._patchRange.patchNum) {
return;
}
+
+ this.$.diff.reload();
},
_computeDiffURL: function(changeNum, patchRange, path) {
diff --git a/polygerrit-ui/app/elements/gr-diff.html b/polygerrit-ui/app/elements/gr-diff.html
index 730e937..a4b516e 100644
--- a/polygerrit-ui/app/elements/gr-diff.html
+++ b/polygerrit-ui/app/elements/gr-diff.html
@@ -23,6 +23,10 @@
<dom-module id="gr-diff">
<template>
<style>
+ .loading {
+ padding: 0 var(--default-horizontal-margin) 1em;
+ color: #666;
+ }
.header {
display: flex;
margin: 0 var(--default-horizontal-margin) .75em;
@@ -52,7 +56,8 @@
<gr-ajax id="diffXHR"
url="[[_computeDiffPath(changeNum, patchRange.patchNum, path)]]"
params="[[_computeDiffQueryParams(patchRange.basePatchNum)]]"
- last-response="{{_diffResponse}}"></gr-ajax>
+ last-response="{{_diffResponse}}"
+ loading="{{_loading}}"></gr-ajax>
<gr-ajax id="baseCommentsXHR"
url="[[_computeCommentsPath(changeNum, patchRange.basePatchNum)]]"></gr-ajax>
<gr-ajax id="commentsXHR"
@@ -61,50 +66,52 @@
url="[[_computeDraftsPath(changeNum, patchRange.basePatchNum)]]"></gr-ajax>
<gr-ajax id="draftsXHR"
url="[[_computeDraftsPath(changeNum, patchRange.patchNum)]]"></gr-ajax>
-
- <div class="header">
- <gr-patch-range-select
- path="[[path]]"
- change-num="[[changeNum]]"
- patch-range="[[patchRange]]"
- available-patches="[[availablePatches]]"></gr-patch-range-select>
- <div class="contextControl" hidden$="[[!prefs.context]]" hidden>
- Context:
- <select id="contextSelect" on-change="_handleContextSelectChange">
- <option value="3">3 lines</option>
- <option value="10">10 lines</option>
- <option value="25">25 lines</option>
- <option value="50">50 lines</option>
- <option value="75">75 lines</option>
- <option value="100">100 lines</option>
- <option value="-1">Whole file</option>
- </select>
+ <div class="loading" hidden$="[[!_loading]]">Loading...</div>
+ <div hidden$="[[_loading]]" hidden>
+ <div class="header">
+ <gr-patch-range-select
+ path="[[path]]"
+ change-num="[[changeNum]]"
+ patch-range="[[patchRange]]"
+ available-patches="[[availablePatches]]"></gr-patch-range-select>
+ <div class="contextControl" hidden$="[[!prefs.context]]" hidden>
+ Context:
+ <select id="contextSelect" on-change="_handleContextSelectChange">
+ <option value="3">3 lines</option>
+ <option value="10">10 lines</option>
+ <option value="25">25 lines</option>
+ <option value="50">50 lines</option>
+ <option value="75">75 lines</option>
+ <option value="100">100 lines</option>
+ <option value="-1">Whole file</option>
+ </select>
+ </div>
</div>
- </div>
- <div class="diffContainer">
- <gr-diff-side id="leftDiff"
- change-num="[[changeNum]]"
- patch-num="[[patchRange.basePatchNum]]"
- path="[[path]]"
- content="{{_diff.leftSide}}"
- width="[[sideWidth]]"
- can-comment="[[_loggedIn]]"
- on-expand-context="_handleExpandContext"
- on-thread-height-change="_handleThreadHeightChange"
- on-add-draft="_handleAddDraft"
- on-remove-thread="_handleRemoveThread"></gr-diff-side>
- <gr-diff-side id="rightDiff"
- change-num="[[changeNum]]"
- patch-num="[[patchRange.patchNum]]"
- path="[[path]]"
- content="{{_diff.rightSide}}"
- width="[[sideWidth]]"
- can-comment="[[_loggedIn]]"
- on-expand-context="_handleExpandContext"
- on-thread-height-change="_handleThreadHeightChange"
- on-add-draft="_handleAddDraft"
- on-remove-thread="_handleRemoveThread"></gr-diff-side>
+ <div class="diffContainer">
+ <gr-diff-side id="leftDiff"
+ change-num="[[changeNum]]"
+ patch-num="[[patchRange.basePatchNum]]"
+ path="[[path]]"
+ content="{{_diff.leftSide}}"
+ width="[[sideWidth]]"
+ can-comment="[[_loggedIn]]"
+ on-expand-context="_handleExpandContext"
+ on-thread-height-change="_handleThreadHeightChange"
+ on-add-draft="_handleAddDraft"
+ on-remove-thread="_handleRemoveThread"></gr-diff-side>
+ <gr-diff-side id="rightDiff"
+ change-num="[[changeNum]]"
+ patch-num="[[patchRange.patchNum]]"
+ path="[[path]]"
+ content="{{_diff.rightSide}}"
+ width="[[sideWidth]]"
+ can-comment="[[_loggedIn]]"
+ on-expand-context="_handleExpandContext"
+ on-thread-height-change="_handleThreadHeightChange"
+ on-add-draft="_handleAddDraft"
+ on-remove-thread="_handleRemoveThread"></gr-diff-side>
+ </div>
</div>
</template>
<script>
@@ -121,10 +128,6 @@
*/
properties: {
- auto: {
- type: Boolean,
- value: false,
- },
availablePatches: Array,
changeNum: String,
/*
@@ -182,13 +185,16 @@
type: Boolean,
value: false,
},
+ _loading: {
+ type: Boolean,
+ value: true,
+ },
_diffRequestsPromise: Object, // Used for testing.
_diffPreferencesPromise: Object, // Used for testing.
},
observers: [
'_prefsChanged(prefs.*)',
- '_diffOptionsChanged(changeNum, patchRange, path)',
],
ready: function() {
@@ -202,28 +208,14 @@
this.$.rightDiff.scrollToLine(lineNum);
},
- _prefsChanged: function(changeRecord) {
- var prefs = changeRecord.base;
-
- this.$.contextSelect.value = prefs.context;
-
- if (this._initialRenderComplete) {
- this._render();
- }
-
- this._resolvePrefsReady(prefs);
- },
-
- _diffOptionsChanged: function(changeNum, patchRange, path) {
- if (!this.auto) { return; }
-
+ reload: function(changeNum, patchRange, path) {
var promises = [
this._prefsReady,
this.$.diffXHR.generateRequest().completes
];
- var basePatchNum = patchRange.basePatchNum;
- var patchNum = patchRange.patchNum;
+ var basePatchNum = this.patchRange.basePatchNum;
+ var patchNum = this.patchRange.patchNum;
app.accountReady.then(function() {
promises.push(this._getCommentsAndDrafts(basePatchNum, app.loggedIn));
@@ -237,6 +229,18 @@
}.bind(this));
},
+ _prefsChanged: function(changeRecord) {
+ var prefs = changeRecord.base;
+
+ this.$.contextSelect.value = prefs.context;
+
+ if (this._initialRenderComplete) {
+ this._render();
+ }
+
+ this._resolvePrefsReady(prefs);
+ },
+
_render: function() {
this._groupCommentsAndDrafts();
this._processContent();
diff --git a/polygerrit-ui/app/elements/gr-reply-dropdown.html b/polygerrit-ui/app/elements/gr-reply-dropdown.html
index d136d43..522018bf 100644
--- a/polygerrit-ui/app/elements/gr-reply-dropdown.html
+++ b/polygerrit-ui/app/elements/gr-reply-dropdown.html
@@ -35,6 +35,7 @@
iron-dropdown {
background-color: #fff;
box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
+ max-width: 40em;
}
button {
background-color: #f1f2f3;
diff --git a/polygerrit-ui/app/styles/gr-change-list-styles.html b/polygerrit-ui/app/styles/gr-change-list-styles.html
index 9b44d31..5a96b6c 100644
--- a/polygerrit-ui/app/styles/gr-change-list-styles.html
+++ b/polygerrit-ui/app/styles/gr-change-list-styles.html
@@ -16,7 +16,8 @@
<dom-module id="gr-change-list-styles">
<template>
<style>
- .keyboard {
+ .keyboard,
+ .star {
width: 2em;
}
.subject {
@@ -50,14 +51,10 @@
width: 9em;
text-align: right;
}
- .codeReview {
+ .label {
width: 2.6em;
text-align: center;
}
- .verified {
- width: 2em;
- text-align: center;
- }
</style>
</template>
</dom-module>
diff --git a/polygerrit-ui/app/test/gr-change-star-test.html b/polygerrit-ui/app/test/gr-change-star-test.html
new file mode 100644
index 0000000..25eacf4
--- /dev/null
+++ b/polygerrit-ui/app/test/gr-change-star-test.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2015 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-change-star</title>
+
+<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="../../bower_components/page/page.js"></script>
+<script src="../scripts/fake-app.js"></script>
+<script src="../scripts/util.js"></script>
+
+<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../elements/gr-change-star.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-change-star></gr-change-star>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-change-star tests', function() {
+ var element;
+ var server;
+
+ setup(function() {
+ element = fixture('basic');
+ element.change = {
+ _number: 2,
+ starred: true,
+ };
+
+ server = sinon.fakeServer.create();
+ server.respondWith(
+ 'PUT',
+ '/accounts/self/starred.changes/2',
+ [
+ 204,
+ { 'Content-Type': 'application/json' },
+ ''
+ ]
+ );
+
+ server.respondWith(
+ 'DELETE',
+ '/accounts/self/starred.changes/2',
+ [
+ 204,
+ { 'Content-Type': 'application/json' },
+ ''
+ ]
+ );
+ });
+
+ teardown(function() {
+ server.restore();
+ });
+
+ function isVisible(el) {
+ assert.ok(el);
+ return getComputedStyle(el).getPropertyValue('display') != 'none';
+ }
+
+ test('star visibility states', function() {
+ element.set('change.starred', true);
+ assert.isTrue(isVisible(element.$$('.star')), 'star is visible');
+ assert.isFalse(isVisible(element.$$('.unstar')), 'unstar is not visible');
+
+ element.set('change.starred', false);
+ assert.isTrue(isVisible(element.$$('.unstar')), 'unstar is visible');
+ assert.isFalse(isVisible(element.$$('.star')), 'star is not visible');
+ });
+
+ test('starring', function(done) {
+ element.set('change.starred', false);
+ MockInteractions.tap(element.$$('button'));
+
+ server.respond();
+
+ element._xhrPromise.then(function(req) {
+ assert.equal(req.status, 204);
+ assert.equal(req.url, '/accounts/self/starred.changes/2');
+ assert.equal(element.change.starred, true);
+ done();
+ });
+ });
+
+ test('unstarring', function(done) {
+ element.set('change.starred', true);
+ MockInteractions.tap(element.$$('button'));
+
+ server.respond();
+
+ element._xhrPromise.then(function(req) {
+ assert.equal(req.status, 204);
+ assert.equal(req.url, '/accounts/self/starred.changes/2');
+ assert.equal(element.change.starred, false);
+ done();
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/gr-diff-test.html b/polygerrit-ui/app/test/gr-diff-test.html
index 6ed4a8f..e201662 100644
--- a/polygerrit-ui/app/test/gr-diff-test.html
+++ b/polygerrit-ui/app/test/gr-diff-test.html
@@ -29,7 +29,7 @@
<test-fixture id="basic">
<template>
- <gr-diff auto></gr-diff>
+ <gr-diff></gr-diff>
</template>
</test-fixture>
@@ -211,6 +211,7 @@
patchNum: 1,
};
+ element.reload();
server.respond();
element._diffRequestsPromise.then(function() {
@@ -228,6 +229,7 @@
patchNum: 2,
};
+ element.reload();
server.respond();
element._diffRequestsPromise.then(function() {
@@ -247,6 +249,7 @@
patchNum: 2,
};
+ element.reload();
server.respond();
// Allow events to fire and the threads to render.
diff --git a/polygerrit-ui/app/test/gr-diff-view-test.html b/polygerrit-ui/app/test/gr-diff-view-test.html
index 99fae59b..fd25ef5 100644
--- a/polygerrit-ui/app/test/gr-diff-view-test.html
+++ b/polygerrit-ui/app/test/gr-diff-view-test.html
@@ -45,37 +45,6 @@
element.$.diff.auto = false;
});
- // https://github.com/PolymerElements/iron-test-helpers/issues/33
- function keyboardEventFor(type, keyIdentifier) {
- var event = new CustomEvent(type, {
- bubbles: true,
- cancelable: true
- });
-
- event.keyIdentifier = keyIdentifier;
-
- return event;
- }
-
- function keyEventOn(target, type, keyIdentifier) {
- target.dispatchEvent(keyboardEventFor(type, keyIdentifier));
- }
-
- function keyDownOn(target, keyIdentifier) {
- keyEventOn(target, 'keydown', keyIdentifier);
- }
-
- function keyUpOn(target, keyIdentifier) {
- keyEventOn(target, 'keyup', keyIdentifier);
- }
-
- function pressAndReleaseKeyIdentifierOn(target, keyIdentifier) {
- keyDownOn(target, keyIdentifier);
- Polymer.Base.async(function() {
- keyUpOn(target, keyIdentifier);
- }, 1);
- }
-
test('keyboard shortcuts', function() {
element._changeNum = '42';
element._patchRange = {
@@ -89,22 +58,22 @@
assert(showStub.lastCall.calledWithExactly('/c/42'),
'Should navigate to /c/42');
- pressAndReleaseKeyIdentifierOn(element, '\U+005D'); // ']'
+ MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
assert(showStub.lastCall.calledWithExactly('/c/42/10/wheatley.md'),
'Should navigate to /c/42/10/wheatley.md');
element._path = 'wheatley.md';
- pressAndReleaseKeyIdentifierOn(element, '\U+005B'); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
assert(showStub.lastCall.calledWithExactly('/c/42/10/glados.txt'),
'Should navigate to /c/42/10/glados.txt');
element._path = 'glados.txt';
- pressAndReleaseKeyIdentifierOn(element, '\U+005B'); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
assert(showStub.lastCall.calledWithExactly('/c/42/10/chell.go'),
'Should navigate to /c/42/10/chell.go');
element._path = 'chell.go';
- pressAndReleaseKeyIdentifierOn(element, '\U+005B'); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
assert(showStub.lastCall.calledWithExactly('/c/42'),
'Should navigate to /c/42');
@@ -126,22 +95,22 @@
assert(showStub.lastCall.calledWithExactly('/c/42'),
'Should navigate to /c/42');
- pressAndReleaseKeyIdentifierOn(element, '\U+005D'); // ']'
+ MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/wheatley.md'),
'Should navigate to /c/42/5..10/wheatley.md');
element._path = 'wheatley.md';
- pressAndReleaseKeyIdentifierOn(element, '\U+005B'); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/glados.txt'),
'Should navigate to /c/42/5..10/glados.txt');
element._path = 'glados.txt';
- pressAndReleaseKeyIdentifierOn(element, '\U+005B'); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/chell.go'),
'Should navigate to /c/42/5..10/chell.go');
element._path = 'chell.go';
- pressAndReleaseKeyIdentifierOn(element, '\U+005B'); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
assert(showStub.lastCall.calledWithExactly('/c/42'),
'Should navigate to /c/42');
});
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index e415b2e..eff75db 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -29,6 +29,7 @@
'gr-change-actions-test.html',
'gr-change-list-item-test.html',
'gr-change-list-test.html',
+ 'gr-change-star-test.html',
'gr-change-view-test.html',
'gr-date-formatter-test.html',
'gr-diff-comment-test.html',