Merge "Fix the running of plugin acceptance tests in eclipse such as CookbookIT"
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 70a695e..0590337 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -128,6 +128,12 @@
or invalid value) and votes that are not permitted for the user are
silently ignored.
+--strict-labels::
+ Require ability to vote on all specified labels before reviewing change.
+ If the vote is invalid (invalid label or invalid name), the vote is not
+ permitted for the user, or the vote is on an outdated or closed patch set,
+ return an error instead of silently discarding the vote.
+
== ACCESS
Any user who has configured an SSH key.
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index b53da4b..d68dc8a 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -48,6 +48,13 @@
Prolog based submit type computes a submit type for each change. The computed
submit type is shown on the change screen for each change.
+When submitting changes in a batch using "Submit including ancestors" or "Submit
+whole topic", submit type rules may not be used to mix submit types on a single
+branch, and trying to submit such a batch will fail. This avoids potentially
+confusing behavior and spurious submit failures. It is recommended to only use
+submit type rules to change submit types for an entire branch, which avoids this
+situation.
+
== Prolog Language
This document is not a complete Prolog tutorial.
link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a
@@ -999,37 +1006,13 @@
submit_type(fast_forward_only) :-
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
!.
-submit_type(T) :- gerrit:project_default_submit_type(T)
+submit_type(T) :- gerrit:project_default_submit_type(T).
----
The first `submit_type` predicate defines the `Fast Forward Only` submit type
for `+refs/heads/stable.*+` branches. The second `submit_type` predicate returns
the project's default submit type.
-=== Example 3: Don't require `Fast Forward Only` if only documentation was changed
-Like in the previous example we want the `Fast Forward Only` submit type for the
-`+refs/heads/stable*+` branches. However, if only documentation was changed
-(only `+*.txt+` files), then we allow project's default submit type for such
-changes.
-
-`rules.pl`
-[source,prolog]
-----
-submit_type(fast_forward_only) :-
- gerrit:commit_delta('(?<!\.txt)$'),
- gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
- !.
-submit_type(T) :- gerrit:project_default_submit_type(T)
-----
-
-The `gerrit:commit_delta('(?<!\.txt)$')` succeeds if the change contains a file
-whose name doesn't end with `.txt` The rest of this rule is same like in the
-previous example.
-
-If all file names in the change end with `.txt`, then the
-`gerrit:commit_delta('(?<!\.txt)$')` will fail as no file name will match this
-regular expression.
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/ReleaseNotes/ReleaseNotes-2.13.txt b/ReleaseNotes/ReleaseNotes-2.13.txt
index 0f08b56..aba6131 100644
--- a/ReleaseNotes/ReleaseNotes-2.13.txt
+++ b/ReleaseNotes/ReleaseNotes-2.13.txt
@@ -66,6 +66,13 @@
* TODO add more
+Changes
+~~~~~~~
+
+In order to avoid potentially confusing behavior, when submitting changes in a
+batch, submit type rules may not be used to mix submit types on a single branch,
+and trying to submit such a batch will fail.
+
Bug Fixes
---------
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 3cea4a2..4414593 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -552,11 +552,8 @@
protected void saveProjectConfig(Project.NameKey p, ProjectConfig cfg)
throws Exception {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(p)) {
cfg.commit(md);
- } finally {
- md.close();
}
projectCache.evict(cfg.getProject());
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 617315c..ec429c4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -470,9 +470,7 @@
// notedb and this record stays even when all votes of that user have been
// deleted, hence there is no dummy 0 approval left when a vote is
// deleted.
- // TODO(dborowitz): Support modifying other users' labels in notedb
- // format.
- //assertThat(m).isEmpty();
+ assertThat(m).isEmpty();
} else {
// When notedb is disabled there is a dummy 0 approval on the change so
// that the user is still returned as CC when all votes of that user have
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
new file mode 100644
index 0000000..7fc4719
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
@@ -0,0 +1,264 @@
+// 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.
+
+package com.google.gerrit.acceptance.api.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.extensions.client.SubmitType.CHERRY_PICK;
+import static com.google.gerrit.extensions.client.SubmitType.FAST_FORWARD_ONLY;
+import static com.google.gerrit.extensions.client.SubmitType.MERGE_ALWAYS;
+import static com.google.gerrit.extensions.client.SubmitType.MERGE_IF_NECESSARY;
+import static com.google.gerrit.extensions.client.SubmitType.REBASE_IF_NECESSARY;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.IntegrationException;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.VersionedMetaData;
+import com.google.gerrit.testutil.ConfigSuite;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@NoHttpd
+public class SubmitTypeRuleIT extends AbstractDaemonTest {
+ @ConfigSuite.Default
+ public static Config submitWholeTopicEnabled() {
+ return submitWholeTopicEnabledConfig();
+ }
+
+ private class RulesPl extends VersionedMetaData {
+ private static final String FILENAME = "rules.pl";
+
+ private String rule;
+
+ @Override
+ protected String getRefName() {
+ return RefNames.REFS_CONFIG;
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ rule = readUTF8(FILENAME);
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit)
+ throws IOException, ConfigInvalidException {
+ TestSubmitRuleInput in = new TestSubmitRuleInput();
+ in.rule = rule;
+ try {
+ gApi.changes().id(testChangeId.get()).current().testSubmitType(in);
+ } catch (RestApiException e) {
+ throw new ConfigInvalidException("Invalid submit type rule", e);
+ }
+
+ saveUTF8(FILENAME, rule);
+ return true;
+ }
+ }
+
+ private AtomicInteger fileCounter;
+ private Change.Id testChangeId;
+
+ @Before
+ public void setUp() throws Exception {
+ fileCounter = new AtomicInteger();
+ gApi.projects().name(project.get()).branch("test")
+ .create(new BranchInput());
+ testChangeId = createChange("test", "test change").getChange().getId();
+ }
+
+ private void setRulesPl(String rule) throws Exception {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
+ RulesPl r = new RulesPl();
+ r.load(md);
+ r.rule = rule;
+ r.commit(md);
+ }
+ }
+
+ private static final String SUBMIT_TYPE_FROM_SUBJECT =
+ "submit_type(fast_forward_only) :-"
+ + "gerrit:commit_message(M),"
+ + "regex_matches('.*FAST_FORWARD_ONLY.*', M),"
+ + "!.\n"
+ + "submit_type(merge_if_necessary) :-"
+ + "gerrit:commit_message(M),"
+ + "regex_matches('.*MERGE_IF_NECESSARY.*', M),"
+ + "!.\n"
+ + "submit_type(rebase_if_necessary) :-"
+ + "gerrit:commit_message(M),"
+ + "regex_matches('.*REBASE_IF_NECESSARY.*', M),"
+ + "!.\n"
+ + "submit_type(merge_always) :-"
+ + "gerrit:commit_message(M),"
+ + "regex_matches('.*MERGE_ALWAYS.*', M),"
+ + "!.\n"
+ + "submit_type(cherry_pick) :-"
+ + "gerrit:commit_message(M),"
+ + "regex_matches('.*CHERRY_PICK.*', M),"
+ + "!.\n"
+ + "submit_type(T) :- gerrit:project_default_submit_type(T).";
+
+ private PushOneCommit.Result createChange(String dest, String subject)
+ throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo,
+ subject, "file" + fileCounter.incrementAndGet(),
+ PushOneCommit.FILE_CONTENT);
+ PushOneCommit.Result r = push.to("refs/for/" + dest);
+ r.assertOkStatus();
+ return r;
+ }
+
+ @Test
+ public void unconditionalCherryPick() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertSubmitType(MERGE_IF_NECESSARY, r.getChangeId());
+ setRulesPl("submit_type(cherry_pick).");
+ assertSubmitType(CHERRY_PICK, r.getChangeId());
+ }
+
+ @Test
+ public void submitTypeFromSubject() throws Exception {
+ PushOneCommit.Result r1 = createChange("master", "Default 1");
+ PushOneCommit.Result r2 = createChange("master", "FAST_FORWARD_ONLY 2");
+ PushOneCommit.Result r3 = createChange("master", "MERGE_IF_NECESSARY 3");
+ PushOneCommit.Result r4 = createChange("master", "REBASE_IF_NECESSARY 4");
+ PushOneCommit.Result r5 = createChange("master", "MERGE_ALWAYS 5");
+ PushOneCommit.Result r6 = createChange("master", "CHERRY_PICK 6");
+
+ assertSubmitType(MERGE_IF_NECESSARY, r1.getChangeId());
+ assertSubmitType(MERGE_IF_NECESSARY, r2.getChangeId());
+ assertSubmitType(MERGE_IF_NECESSARY, r3.getChangeId());
+ assertSubmitType(MERGE_IF_NECESSARY, r4.getChangeId());
+ assertSubmitType(MERGE_IF_NECESSARY, r5.getChangeId());
+ assertSubmitType(MERGE_IF_NECESSARY, r6.getChangeId());
+
+ setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
+
+ assertSubmitType(MERGE_IF_NECESSARY, r1.getChangeId());
+ assertSubmitType(FAST_FORWARD_ONLY, r2.getChangeId());
+ assertSubmitType(MERGE_IF_NECESSARY, r3.getChangeId());
+ assertSubmitType(REBASE_IF_NECESSARY, r4.getChangeId());
+ assertSubmitType(MERGE_ALWAYS, r5.getChangeId());
+ assertSubmitType(CHERRY_PICK, r6.getChangeId());
+ }
+
+ @Test
+ public void submitTypeIsUsedForSubmit() throws Exception {
+ setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
+
+ PushOneCommit.Result r = createChange("master", "CHERRY_PICK 1");
+
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).current().submit();
+
+ List<RevCommit> log = log("master", 1);
+ assertThat(log.get(0).getShortMessage()).isEqualTo("CHERRY_PICK 1");
+ assertThat(log.get(0).name()).isNotEqualTo(r.getCommit().name());
+ assertThat(log.get(0).getFullMessage())
+ .contains("Change-Id: " + r.getChangeId());
+ assertThat(log.get(0).getFullMessage()).contains("Reviewed-on: ");
+ }
+
+ @Test
+ public void mixingSubmitTypesAcrossBranchesSucceeds() throws Exception {
+ setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
+
+ PushOneCommit.Result r1 = createChange("master", "MERGE_IF_NECESSARY 1");
+
+ RevCommit initialCommit = r1.getCommit().getParent(0);
+ BranchInput bin = new BranchInput();
+ bin.revision = initialCommit.name();
+ gApi.projects().name(project.get()).branch("branch").create(bin);
+
+ testRepo.reset(initialCommit);
+ PushOneCommit.Result r2 = createChange("branch", "MERGE_ALWAYS 1");
+
+ gApi.changes().id(r1.getChangeId()).topic(name("topic"));
+ gApi.changes().id(r1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r2.getChangeId()).topic(name("topic"));
+ gApi.changes().id(r2.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r2.getChangeId()).current().submit();
+
+ assertThat(log("master", 1).get(0).name()).isEqualTo(r1.getCommit().name());
+
+ List<RevCommit> branchLog = log("branch", 1);
+ assertThat(branchLog.get(0).getParents()).hasLength(2);
+ assertThat(branchLog.get(0).getParent(1).name())
+ .isEqualTo(r2.getCommit().name());
+ }
+
+ @Test
+ public void mixingSubmitTypesOnOneBranchFails() throws Exception {
+ setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
+
+ PushOneCommit.Result r1 = createChange("master", "CHERRY_PICK 1");
+ PushOneCommit.Result r2 = createChange("master", "MERGE_IF_NECESSARY 2");
+
+ gApi.changes().id(r1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r2.getChangeId()).current().review(ReviewInput.approve());
+
+ try {
+ gApi.changes().id(r2.getChangeId()).current().submit();
+ fail("Expected ResourceConflictException");
+ } catch (ResourceConflictException e) {
+ assertThat(e).hasMessage("Merge Conflict");
+ Throwable t = e.getCause();
+ assertThat(t).isInstanceOf(IntegrationException.class);
+ assertThat(t.getMessage()).isEqualTo(
+ "Change " + r1.getChange().getId() + " has submit type CHERRY_PICK, "
+ + "but previously chose submit type MERGE_IF_NECESSARY from change "
+ + r2.getChange().getId() + " in the same batch");
+ }
+ }
+
+ private List<RevCommit> log(String commitish, int n) throws Exception {
+ try (Repository repo = repoManager.openRepository(project);
+ Git git = new Git(repo)) {
+ ObjectId id = repo.resolve(commitish);
+ assertThat(id).isNotNull();
+ return ImmutableList.copyOf(git.log().add(id).setMaxCount(n).call());
+ }
+ }
+
+ private void assertSubmitType(SubmitType expected, String id)
+ throws Exception {
+ assertThat(gApi.changes().id(id).current().submitType())
+ .isEqualTo(expected);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 73a02a5..d104a41 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -83,11 +83,8 @@
}
private void saveProjectConfig(ProjectConfig cfg) throws Exception {
- MetaDataUpdate md = metaDataUpdateFactory.create(project);
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
cfg.commit(md);
- } finally {
- md.close();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
index f7e11fb..534afcb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
@@ -321,11 +321,8 @@
}
private void saveProjectConfig(ProjectConfig cfg) throws Exception {
- MetaDataUpdate md = metaDataUpdateFactory.create(project);
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
cfg.commit(md);
- } finally {
- md.close();
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 44c2ba4..6ff7889 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -14,10 +14,12 @@
package com.google.gerrit.extensions.api.changes;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.common.MergeableInfo;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -28,6 +30,7 @@
public interface RevisionApi {
void delete() throws RestApiException;
+
void review(ReviewInput in) throws RestApiException;
void submit() throws RestApiException;
@@ -65,6 +68,9 @@
Map<String, ActionInfo> actions() throws RestApiException;
+ SubmitType submitType() throws RestApiException;
+ SubmitType testSubmitType(TestSubmitRuleInput in) throws RestApiException;
+
/**
* A default implementation which allows source compatibility
* when adding new methods to the interface.
@@ -194,5 +200,16 @@
public Map<String, ActionInfo> actions() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public SubmitType submitType() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public SubmitType testSubmitType(TestSubmitRuleInput in)
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TestSubmitRuleInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TestSubmitRuleInput.java
new file mode 100644
index 0000000..96a1626
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TestSubmitRuleInput.java
@@ -0,0 +1,27 @@
+// 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.
+
+package com.google.gerrit.extensions.common;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class TestSubmitRuleInput {
+ public enum Filters {
+ RUN, SKIP
+ }
+
+ @DefaultInput
+ public String rule;
+ public Filters filters;
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index aee238d..ed2a4f9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -100,8 +100,7 @@
// state, force a cache flush now.
//
ProjectConfig config;
- MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
config = ProjectConfig.read(md);
if (config.updateGroupNames(groupBackend)) {
@@ -115,8 +114,6 @@
projectCache.evict(config.getProject());
pc = open();
}
- } finally {
- md.close();
}
final RefControl metaConfigControl = pc.controlForRef(RefNames.REFS_CONFIG);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index ba4f012..f8d3e70 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -92,13 +92,7 @@
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
- final MetaDataUpdate md;
- try {
- md = metaDataUpdateFactory.create(projectName);
- } catch (RepositoryNotFoundException notFound) {
- throw new NoSuchProjectException(projectName);
- }
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
ProjectConfig config = ProjectConfig.read(md, base);
Set<String> toDelete = scanSectionNames(config);
@@ -163,8 +157,8 @@
return updateProjectConfig(projectControl, config, md,
parentProjectUpdate);
- } finally {
- md.close();
+ } catch (RepositoryNotFoundException notFound) {
+ throw new NoSuchProjectException(projectName);
}
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
index bb69533bf..ac43363 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
@@ -243,6 +243,9 @@
} else if (isGenAvailableNowForCurrentSearcher()) {
set(null);
return true;
+ } else if (!reopenThread.isAlive()) {
+ setException(new IllegalStateException("NRT thread is dead"));
+ return true;
}
return false;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 2815099..5108315 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -333,6 +333,11 @@
modules.add(SchemaVersionCheck.module());
modules.add(new DropWizardMetricMaker.RestModule());
modules.add(new LogFileCompressor.Module());
+
+ // Index module shutdown must happen before work queue shutdown, otherwise
+ // work queue can get stuck waiting on index futures that will never return.
+ modules.add(createIndexModule());
+
modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
@@ -351,7 +356,6 @@
modules.add(new PluginRestApiModule());
modules.add(new RestCacheAdminModule());
modules.add(new GpgModule(config));
- modules.add(createIndexModule());
if (MoreObjects.firstNonNull(httpd, true)) {
modules.add(new CanonicalWebUrlModule() {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
index 93a3814..04546aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -93,40 +93,32 @@
throws ResourceNotFoundException, ResourceConflictException, IOException {
Map<String, ProjectAccessInfo> access = Maps.newTreeMap();
for (String p: projects) {
+ // Load the current configuration from the repository, ensuring it's the most
+ // recent version available. If it differs from what was in the project
+ // state, force a cache flush now.
+ //
Project.NameKey projectName = new Project.NameKey(p);
- ProjectControl pc = open(projectName);
- ProjectConfig config;
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
+ ProjectControl pc = open(projectName);
+ ProjectConfig config = ProjectConfig.read(md);
- try {
- // Load the current configuration from the repository, ensuring it's the most
- // recent version available. If it differs from what was in the project
- // state, force a cache flush now.
- //
- MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
- try {
- config = ProjectConfig.read(md);
-
- if (config.updateGroupNames(groupBackend)) {
- md.setMessage("Update group names\n");
- config.commit(md);
- projectCache.evict(config.getProject());
- pc = open(projectName);
- } else if (config.getRevision() != null
- && !config.getRevision().equals(
- pc.getProjectState().getConfig().getRevision())) {
- projectCache.evict(config.getProject());
- pc = open(projectName);
- }
- } catch (ConfigInvalidException e) {
- throw new ResourceConflictException(e.getMessage());
- } finally {
- md.close();
+ if (config.updateGroupNames(groupBackend)) {
+ md.setMessage("Update group names\n");
+ config.commit(md);
+ projectCache.evict(config.getProject());
+ pc = open(projectName);
+ } else if (config.getRevision() != null
+ && !config.getRevision().equals(
+ pc.getProjectState().getConfig().getRevision())) {
+ projectCache.evict(config.getProject());
+ pc = open(projectName);
}
+ access.put(p, new ProjectAccessInfo(pc, config));
+ } catch (ConfigInvalidException e) {
+ throw new ResourceConflictException(e.getMessage());
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(p);
}
-
- access.put(p, new ProjectAccessInfo(pc, config));
}
return access;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java
index 35d7c70..c585f97 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.account;
+import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_EXTERNAL;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GERRIT;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_MAILTO;
-import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_EXTERNAL;
import com.google.gerrit.reviewdb.client.AccountExternalId;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index e14791c..9617d94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -77,10 +77,8 @@
private DiffPreferencesInfo writeToGit(DiffPreferencesInfo in,
Account.Id userId) throws RepositoryNotFoundException, IOException,
ConfigInvalidException {
- MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName);
-
DiffPreferencesInfo out = new DiffPreferencesInfo();
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
VersionedAccountPreferences prefs = VersionedAccountPreferences.forUser(
userId);
prefs.load(md);
@@ -90,8 +88,6 @@
prefs.commit(md);
loadSection(prefs.getConfig(), UserConfigSections.DIFF, null, out,
DiffPreferencesInfo.defaults(), null);
- } finally {
- md.close();
}
return out;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
index eabe31d..d51e24a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
@@ -71,18 +71,15 @@
}
Account.Id accountId = rsrc.getUser().getAccountId();
- MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName);
VersionedAccountPreferences prefs;
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
prefs = VersionedAccountPreferences.forUser(accountId);
prefs.load(md);
storeSection(prefs.getConfig(), UserConfigSections.EDIT, null,
readFromGit(accountId, gitMgr, allUsersName, in),
EditPreferencesInfo.defaults());
prefs.commit(md);
- } finally {
- md.close();
}
return Response.none();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index 7042076..51c54ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -115,9 +115,8 @@
Account.Id accountId = rsrc.getUser().getAccountId();
AccountGeneralPreferences p;
VersionedAccountPreferences versionedPrefs;
- MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName);
db.get().accounts().beginTransaction(accountId);
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
Account a = db.get().accounts().get(accountId);
if (a == null) {
throw new ResourceNotFoundException();
@@ -184,7 +183,6 @@
return new GetPreferences.PreferenceInfo(
p, versionedPrefs, md.getRepository());
} finally {
- md.close();
db.get().rollback();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 0ca43f5..eb16ffb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -27,10 +27,12 @@
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.common.MergeableInfo;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -54,6 +56,7 @@
import com.google.gerrit.server.change.Reviewed;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit;
+import com.google.gerrit.server.change.TestSubmitType;
import com.google.gerrit.server.git.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -94,6 +97,8 @@
private final Comments comments;
private final CommentApiImpl.Factory commentFactory;
private final GetRevisionActions revisionActions;
+ private final Provider<TestSubmitType> testSubmitType;
+ private final TestSubmitType.Get getSubmitType;
@Inject
RevisionApiImpl(Changes changes,
@@ -119,6 +124,8 @@
Comments comments,
CommentApiImpl.Factory commentFactory,
GetRevisionActions revisionActions,
+ Provider<TestSubmitType> testSubmitType,
+ TestSubmitType.Get getSubmitType,
@Assisted RevisionResource r) {
this.changes = changes;
this.cherryPick = cherryPick;
@@ -143,6 +150,8 @@
this.comments = comments;
this.commentFactory = commentFactory;
this.revisionActions = revisionActions;
+ this.testSubmitType = testSubmitType;
+ this.getSubmitType = getSubmitType;
this.revision = r;
}
@@ -375,4 +384,23 @@
public Map<String, ActionInfo> actions() throws RestApiException {
return revisionActions.apply(revision).value();
}
+
+ @Override
+ public SubmitType submitType() throws RestApiException {
+ try {
+ return getSubmitType.apply(revision);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot get submit type", e);
+ }
+ }
+
+ @Override
+ public SubmitType testSubmitType(TestSubmitRuleInput in)
+ throws RestApiException {
+ try {
+ return testSubmitType.get().apply(revision, in);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot test submit type", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
index cf08a81..0d685df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -112,9 +112,7 @@
psa = a;
a.setValue((short)0);
ctx.getChangeUpdate().setPatchSetId(psId);
- // TODO(dborowitz): Support modifying other users' labels in notedb
- // format.
- ctx.getChangeUpdate().removeApproval(label);
+ ctx.getChangeUpdate().removeApprovalFor(a.getAccountId(), label);
break;
}
} else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index 95a701e..3eecea3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -19,13 +19,13 @@
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput.Filters;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.account.AccountLoader;
-import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
@@ -37,17 +37,8 @@
import java.util.List;
import java.util.Map;
-public class TestSubmitRule implements RestModifyView<RevisionResource, Input> {
- public enum Filters {
- RUN, SKIP
- }
-
- public static class Input {
- @DefaultInput
- public String rule;
- public Filters filters;
- }
-
+public class TestSubmitRule
+ implements RestModifyView<RevisionResource, TestSubmitRuleInput> {
private final Provider<ReviewDb> db;
private final ChangeData.Factory changeDataFactory;
private final RulesCache rules;
@@ -68,10 +59,10 @@
}
@Override
- public List<Record> apply(RevisionResource rsrc, Input input)
+ public List<Record> apply(RevisionResource rsrc, TestSubmitRuleInput input)
throws AuthException, OrmException {
if (input == null) {
- input = new Input();
+ input = new TestSubmitRuleInput();
}
if (input.rule != null && !rules.isProjectRulesEnabled()) {
throw new AuthException("project rules are disabled");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
index f6016b5..4855012 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
@@ -17,14 +17,14 @@
import com.google.common.base.MoreObjects;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput.Filters;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.RulesCache;
-import com.google.gerrit.server.change.TestSubmitRule.Filters;
-import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
@@ -33,7 +33,8 @@
import org.kohsuke.args4j.Option;
-public class TestSubmitType implements RestModifyView<RevisionResource, Input> {
+public class TestSubmitType
+ implements RestModifyView<RevisionResource, TestSubmitRuleInput> {
private final Provider<ReviewDb> db;
private final ChangeData.Factory changeDataFactory;
private final RulesCache rules;
@@ -51,10 +52,10 @@
}
@Override
- public SubmitType apply(RevisionResource rsrc, Input input)
+ public SubmitType apply(RevisionResource rsrc, TestSubmitRuleInput input)
throws AuthException, BadRequestException, OrmException {
if (input == null) {
- input = new Input();
+ input = new TestSubmitRuleInput();
}
if (input.rule != null && !rules.isProjectRulesEnabled()) {
throw new AuthException("project rules are disabled");
@@ -71,13 +72,13 @@
if (rec.status != SubmitTypeRecord.Status.OK) {
throw new BadRequestException(String.format(
"rule %s produced invalid result: %s",
- evaluator.getSubmitRule(), rec));
+ evaluator.getSubmitRuleName(), rec));
}
return rec.type;
}
- static class Get implements RestReadView<RevisionResource> {
+ public static class Get implements RestReadView<RevisionResource> {
private final TestSubmitType test;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
index d6693ae..07d22b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
@@ -60,15 +60,12 @@
}
VersionedAccountPreferences p;
- MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName);
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
p = VersionedAccountPreferences.forDefault();
p.load(md);
com.google.gerrit.server.account.SetPreferences.storeMyMenus(p, i.my);
p.commit(md);
return new PreferenceInfo(null, p, md.getRepository());
- } finally {
- md.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
index 66417fb..da64332 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
@@ -48,9 +48,6 @@
REVISION_GONE(""),
/** */
- NO_SUBMIT_TYPE(""),
-
- /** */
MANUAL_RECURSIVE_MERGE("The change requires a local merge to resolve.\n"
+ "\n"
+ "Please merge (or rebase) the change locally and upload the resolution for review."),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 61689a1..7857fb9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -16,15 +16,14 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
+import com.google.auto.value.AutoValue;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
@@ -392,8 +391,7 @@
throws IntegrationException, NoSuchChangeException,
ResourceConflictException {
logDebug("Beginning merge attempt on {}", cs);
- Map<Branch.NameKey, ListMultimap<SubmitType, ChangeData>> toSubmit =
- new HashMap<>();
+ Map<Branch.NameKey, BranchBatch> toSubmit = new HashMap<>();
logDebug("Perform the merges");
try {
Multimap<Project.NameKey, Branch.NameKey> br = cs.branchesByProject();
@@ -402,22 +400,15 @@
openRepository(project);
for (Branch.NameKey branch : br.get(project)) {
setDestProject(branch);
-
- ListMultimap<SubmitType, ChangeData> submitting =
- validateChangeList(cbb.get(branch));
+ BranchBatch submitting = validateChangeList(cbb.get(branch));
toSubmit.put(branch, submitting);
- Set<SubmitType> submitTypes = new HashSet<>(submitting.keySet());
- for (SubmitType submitType : submitTypes) {
- SubmitStrategy strategy = createStrategy(branch, submitType,
- getBranchTip(branch), caller);
-
- MergeTip mergeTip = preMerge(strategy, submitting.get(submitType),
- getBranchTip(branch));
- mergeTips.put(branch, mergeTip);
- updateChangeStatus(submitting.get(submitType), branch,
- true, caller);
- }
+ SubmitStrategy strategy = createStrategy(
+ branch, submitting.submitType(), getBranchTip(branch), caller);
+ MergeTip mergeTip = preMerge(strategy, submitting.changes(),
+ getBranchTip(branch));
+ mergeTips.put(branch, mergeTip);
+ updateChangeStatus(submitting.changes(), branch, true, caller);
inserter.flush();
}
closeRepository();
@@ -431,12 +422,9 @@
pendingRefUpdates.remove(branch);
setDestProject(branch);
- ListMultimap<SubmitType, ChangeData> submitting = toSubmit.get(branch);
- for (SubmitType submitType : submitting.keySet()) {
- updateChangeStatus(submitting.get(submitType), branch,
- false, caller);
- updateSubmoduleSubscriptions(subOp, branch, getBranchTip(branch));
- }
+ BranchBatch submitting = toSubmit.get(branch);
+ updateChangeStatus(submitting.changes(), branch, false, caller);
+ updateSubmoduleSubscriptions(subOp, branch, getBranchTip(branch));
if (update != null) {
fireRefUpdated(branch, update);
}
@@ -583,30 +571,26 @@
return alreadyAccepted;
}
- private ListMultimap<SubmitType, ChangeData> validateChangeList(
+ @AutoValue
+ static abstract class BranchBatch {
+ abstract SubmitType submitType();
+ abstract List<ChangeData> changes();
+ }
+
+ private BranchBatch validateChangeList(
Collection<ChangeData> submitted) throws IntegrationException {
logDebug("Validating {} changes", submitted.size());
- ListMultimap<SubmitType, ChangeData> toSubmit = ArrayListMultimap.create();
+ List<ChangeData> toSubmit = new ArrayList<>(submitted.size());
+ Multimap<ObjectId, PatchSet.Id> revisions = getRevisions(submitted);
- Map<String, Ref> allRefs;
- try {
- allRefs = repo.getRefDatabase().getRefs(ALL);
- } catch (IOException e) {
- throw new IntegrationException(e.getMessage(), e);
- }
-
- Set<ObjectId> tips = new HashSet<>();
- for (Ref r : allRefs.values()) {
- tips.add(r.getObjectId());
- }
-
+ SubmitType submitType = null;
+ ChangeData choseSubmitTypeFrom = null;
for (ChangeData cd : submitted) {
ChangeControl ctl;
Change chg;
try {
ctl = cd.changeControl();
- // Reload change in case index was stale.
- chg = cd.reloadChange();
+ chg = cd.change();
} catch (OrmException e) {
throw new IntegrationException("Failed to validate changes", e);
}
@@ -645,18 +629,13 @@
continue;
}
- if (!tips.contains(id)) {
- // TODO Technically the proper way to do this test is to use a
- // RevWalk on "$id --not --all" and test for an empty set. But
- // that is way slower than looking for a ref directly pointing
- // at the desired tip. We should always have a ref available.
- //
+ if (!revisions.containsEntry(id, ps.getId())) {
// TODO this is actually an error, the branch is gone but we
// want to merge the issue. We can't safely do that if the
// tip is not reachable.
//
logError("Revision " + idstr + " of patch set " + ps.getId()
- + " is not contained in any ref");
+ + " does not match " + ps.getId().toRefName());
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
continue;
}
@@ -686,20 +665,52 @@
continue;
}
- SubmitType submitType;
- submitType = getSubmitType(commit.getControl(), ps);
- if (submitType == null) {
+ SubmitType st = getSubmitType(commit.getControl(), ps);
+ if (st == null) {
logError("No submit type for revision " + idstr + " of patch set "
+ ps.getId());
- commit.setStatusCode(CommitMergeStatus.NO_SUBMIT_TYPE);
- continue;
+ throw new IntegrationException(
+ "No submit type for change " + cd.getId());
}
-
+ if (submitType == null) {
+ submitType = st;
+ choseSubmitTypeFrom = cd;
+ } else if (st != submitType) {
+ throw new IntegrationException(String.format(
+ "Change %s has submit type %s, but previously chose submit type %s "
+ + "from change %s in the same batch",
+ cd.getId(), st, submitType, choseSubmitTypeFrom.getId()));
+ }
commit.add(canMergeFlag);
- toSubmit.put(submitType, cd);
+ toSubmit.add(cd);
}
logDebug("Submitting on this run: {}", toSubmit);
- return toSubmit;
+ return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit);
+ }
+
+ private Multimap<ObjectId, PatchSet.Id> getRevisions(
+ Collection<ChangeData> cds) throws IntegrationException {
+ try {
+ List<String> refNames = new ArrayList<>(cds.size());
+ for (ChangeData cd : cds) {
+ // Reload change in case index was stale.
+ cd.reloadChange();
+ Change c = cd.change();
+ if (c != null) {
+ refNames.add(c.currentPatchSetId().toRefName());
+ }
+ }
+ Multimap<ObjectId, PatchSet.Id> revisions =
+ HashMultimap.create(cds.size(), 1);
+ for (Map.Entry<String, Ref> e : repo.getRefDatabase().exactRef(
+ refNames.toArray(new String[refNames.size()])).entrySet()) {
+ revisions.put(
+ e.getValue().getObjectId(), PatchSet.Id.fromRef(e.getKey()));
+ }
+ return revisions;
+ } catch (IOException | OrmException e) {
+ throw new IntegrationException("Failed to validate changes", e);
+ }
}
private SubmitType getSubmitType(ChangeControl ctl, PatchSet ps) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index 840b167..540d479 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -34,7 +34,7 @@
import java.io.IOException;
/** Helps with the updating of a {@link VersionedMetaData}. */
-public class MetaDataUpdate {
+public class MetaDataUpdate implements AutoCloseable {
public static class User {
private final InternalFactory factory;
private final GitRepositoryManager mgr;
@@ -89,6 +89,33 @@
* multiple commits to a single metadata ref, see
* {@link VersionedMetaData#openUpdate(MetaDataUpdate)}.
*
+ * Important: Create a new MetaDataUpdate instance for each update:
+ * <pre>
+ * <code>
+ * try (Repository repo = repoMgr.openRepository(allUsersName);
+ * RevWalk rw = new RevWalk(repo) {
+ * BatchRefUpdate batchUpdate = repo.getRefDatabase().newBatchUpdate();
+ * // WRONG: create the MetaDataUpdate instance here and reuse it for
+ * // all updates in the loop
+ * for{@code (Map.Entry<Account.Id, DiffPreferencesInfo> e : diffPrefsFromDb)} {
+ * // CORRECT: create a new MetaDataUpdate instance for each update
+ * try (MetaDataUpdate md =
+ * metaDataUpdateFactory.create(allUsersName, batchUpdate)) {
+ * md.setMessage("Import diff preferences from reviewdb\n");
+ * VersionedAccountPreferences vPrefs =
+ * VersionedAccountPreferences.forUser(e.getKey());
+ * storeSection(vPrefs.getConfig(), UserConfigSections.DIFF, null,
+ * e.getValue(), DiffPreferencesInfo.defaults());
+ * vPrefs.commit(md);
+ * } catch (ConfigInvalidException e) {
+ * // TODO handle exception
+ * }
+ * }
+ * batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
+ * }
+ * </code>
+ * </pre>
+ *
* @param name project name.
* @param repository GIT respository
* @param user user for the update.
@@ -191,6 +218,7 @@
}
/** Close the cached Repository handle. */
+ @Override
public void close() {
getRepository().close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
index 06314db..00c9a7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
@@ -83,13 +83,8 @@
continue;
}
- try {
- MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
- try {
- rename(md);
- } finally {
- md.close();
- }
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
+ rename(md);
} catch (RepositoryNotFoundException noProject) {
continue;
} catch (ConfigInvalidException | IOException err) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 9d759ed..3bca5d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -27,6 +27,7 @@
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
@@ -38,6 +39,7 @@
import com.google.common.collect.Table;
import com.google.common.collect.Tables;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -85,8 +87,9 @@
private final ObjectId tip;
private final RevWalk walk;
private final Repository repo;
- private final Map<PatchSet.Id,
- Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
+ private final Map<PatchSet.Id, Table<Account.Id, String, PatchSetApproval>>
+ approvals;
+ private final Map<PatchSet.Id, Multimap<Account.Id, String>> removedApprovals;
private final List<ChangeMessage> allChangeMessages;
private final Multimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet;
@@ -99,6 +102,7 @@
this.repo =
repoManager.openMetadataRepository(ChangeNotes.getProjectName(change));
approvals = Maps.newHashMap();
+ removedApprovals = Maps.newHashMap();
reviewers = Maps.newLinkedHashMap();
allPastReviewers = Lists.newArrayList();
submitRecords = Lists.newArrayListWithExpectedSize(1);
@@ -126,9 +130,8 @@
buildApprovals() {
Multimap<PatchSet.Id, PatchSetApproval> result =
ArrayListMultimap.create(approvals.keySet().size(), 3);
- for (Table<?, ?, Optional<PatchSetApproval>> curr
- : approvals.values()) {
- for (PatchSetApproval psa : Optional.presentInstances(curr.values())) {
+ for (Table<?, ?, PatchSetApproval> curr : approvals.values()) {
+ for (PatchSetApproval psa : curr.values()) {
result.put(psa.getPatchSetId(), psa);
}
}
@@ -305,46 +308,99 @@
private void parseApproval(PatchSet.Id psId, Account.Id accountId,
RevCommit commit, String line) throws ConfigInvalidException {
- Table<Account.Id, String, Optional<PatchSetApproval>> curr =
- approvals.get(psId);
- if (curr == null) {
- curr = Tables.newCustomTable(
- Maps.<Account.Id, Map<String, Optional<PatchSetApproval>>>
+ if (line.startsWith("-")) {
+ parseRemoveApproval(psId, accountId, line);
+ } else {
+ parseAddApproval(psId, accountId, commit, line);
+ }
+ }
+
+ private void parseAddApproval(PatchSet.Id psId, Account.Id accountId,
+ RevCommit commit, String line) throws ConfigInvalidException {
+ LabelVote l;
+ try {
+ l = LabelVote.parseWithEquals(line);
+ } catch (IllegalArgumentException e) {
+ ConfigInvalidException pe =
+ parseException("invalid %s: %s", FOOTER_LABEL, line);
+ pe.initCause(e);
+ throw pe;
+ }
+ if (isApprovalRemoved(psId, accountId, l.label())) {
+ return;
+ }
+
+ Table<Account.Id, String, PatchSetApproval> curr = approvals.get(psId);
+ if (curr != null) {
+ if (curr.contains(accountId, l.label())) {
+ return;
+ }
+ } else {
+ curr = newApprovalsTable();
+ approvals.put(psId, curr);
+ }
+ curr.put(accountId, l.label(), new PatchSetApproval(
+ new PatchSetApproval.Key(
+ psId,
+ accountId,
+ new LabelId(l.label())),
+ l.value(),
+ new Timestamp(commit.getCommitterIdent().getWhen().getTime())));
+ }
+
+ private static Table<Account.Id, String, PatchSetApproval>
+ newApprovalsTable() {
+ return Tables.newCustomTable(
+ Maps.<Account.Id, Map<String, PatchSetApproval>>
newHashMapWithExpectedSize(2),
- new Supplier<Map<String, Optional<PatchSetApproval>>>() {
+ new Supplier<Map<String, PatchSetApproval>>() {
@Override
- public Map<String, Optional<PatchSetApproval>> get() {
+ public Map<String, PatchSetApproval> get() {
return Maps.newLinkedHashMap();
}
});
- approvals.put(psId, curr);
+ }
+
+ private void parseRemoveApproval(PatchSet.Id psId, Account.Id accountId,
+ String line) throws ConfigInvalidException {
+ Multimap<Account.Id, String> curr = removedApprovals.get(psId);
+ if (curr == null) {
+ curr = HashMultimap.create(1, 1);
+ removedApprovals.put(psId, curr);
+ }
+ String label;
+ Account.Id removedAccountId;
+ line = line.substring(1);
+ int s = line.indexOf(' ');
+ if (s > 0) {
+ label = line.substring(0, s);
+ PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
+ checkFooter(ident != null, FOOTER_LABEL, line);
+ removedAccountId = parseIdent(ident);
+ } else {
+ label = line;
+ removedAccountId = accountId;
}
- if (line.startsWith("-")) {
- String label = line.substring(1);
- if (!curr.contains(accountId, label)) {
- curr.put(accountId, label, Optional.<PatchSetApproval> absent());
- }
- } else {
- LabelVote l;
- try {
- l = LabelVote.parseWithEquals(line);
- } catch (IllegalArgumentException e) {
- ConfigInvalidException pe =
- parseException("invalid %s: %s", FOOTER_LABEL, line);
- pe.initCause(e);
- throw pe;
- }
- if (!curr.contains(accountId, l.label())) {
- curr.put(accountId, l.label(), Optional.of(new PatchSetApproval(
- new PatchSetApproval.Key(
- psId,
- accountId,
- new LabelId(l.label())),
- l.value(),
- new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
- }
+ Table<Account.Id, String, PatchSetApproval> added = approvals.get(psId);
+ if (added != null && added.contains(accountId, label)) {
+ return;
}
+
+ try {
+ curr.put(removedAccountId, LabelType.checkNameInternal(label));
+ } catch (IllegalArgumentException e) {
+ ConfigInvalidException pe =
+ parseException("invalid %s: %s", FOOTER_LABEL, line);
+ pe.initCause(e);
+ throw pe;
+ }
+ }
+
+ private boolean isApprovalRemoved(PatchSet.Id psId, Account.Id accountId,
+ String label) {
+ Multimap<Account.Id, String> curr = removedApprovals.get(psId);
+ return curr != null && curr.containsEntry(accountId, label);
}
private void parseSubmitRecords(List<String> lines)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 7c011b6..f1843bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -25,11 +25,12 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -87,8 +88,9 @@
}
private final AccountCache accountCache;
- private final Map<String, Optional<Short>> approvals;
+ private final Map<String, Short> approvals;
private final Map<Account.Id, ReviewerStateInternal> reviewers;
+ private final Multimap<Account.Id, String> removedApprovals;
private Change.Status status;
private String subject;
private List<SubmitRecord> submitRecords;
@@ -163,6 +165,8 @@
this.commentsUtil = commentsUtil;
this.approvals = Maps.newTreeMap(labelNameComparator);
this.reviewers = Maps.newLinkedHashMap();
+ this.removedApprovals =
+ MultimapBuilder.linkedHashKeys(1).arrayListValues(1).build();
this.comments = Lists.newArrayList();
}
@@ -177,11 +181,15 @@
}
public void putApproval(String label, short value) {
- approvals.put(label, Optional.of(value));
+ approvals.put(label, value);
}
public void removeApproval(String label) {
- approvals.put(label, Optional.<Short> absent());
+ removeApprovalFor(getUser().getAccountId(), label);
+ }
+
+ public void removeApprovalFor(Account.Id reviewer, String label) {
+ removedApprovals.put(reviewer, label);
}
public void merge(Iterable<SubmitRecord> submitRecords) {
@@ -434,20 +442,22 @@
}
for (Map.Entry<Account.Id, ReviewerStateInternal> e : reviewers.entrySet()) {
- Account account = accountCache.get(e.getKey()).getAccount();
- PersonIdent ident = newIdent(account, when);
- addFooter(msg, e.getValue().getFooterKey())
- .append(ident.getName())
- .append(" <").append(ident.getEmailAddress()).append(">\n");
+ addFooter(msg, e.getValue().getFooterKey());
+ addIdent(msg, e.getKey()).append('\n');
}
- for (Map.Entry<String, Optional<Short>> e : approvals.entrySet()) {
- if (!e.getValue().isPresent()) {
- addFooter(msg, FOOTER_LABEL, '-', e.getKey());
- } else {
- addFooter(msg, FOOTER_LABEL, LabelVote.create(
- e.getKey(), e.getValue().get()).formatWithEquals());
+ for (Map.Entry<String, Short> e : approvals.entrySet()) {
+ addFooter(msg, FOOTER_LABEL, LabelVote.create(
+ e.getKey(), e.getValue()).formatWithEquals());
+ }
+
+ for (Map.Entry<Account.Id, String> e : removedApprovals.entries()) {
+ addFooter(msg, FOOTER_LABEL).append('-').append(e.getValue());
+ Account.Id id = e.getKey();
+ if (!id.equals(ctl.getUser().getAccountId())) {
+ addIdent(msg.append(' '), id);
}
+ msg.append('\n');
}
if (submitRecords != null) {
@@ -486,6 +496,7 @@
private boolean isEmpty() {
return approvals.isEmpty()
+ && removedApprovals.isEmpty()
&& changeMessage == null
&& comments.isEmpty()
&& reviewers.isEmpty()
@@ -509,6 +520,14 @@
sb.append('\n');
}
+ private StringBuilder addIdent(StringBuilder sb, Account.Id accountId) {
+ Account account = accountCache.get(accountId).getAccount();
+ PersonIdent ident = newIdent(account, when);
+ sb.append(ident.getName()).append(" <").append(ident.getEmailAddress())
+ .append('>');
+ return sb;
+ }
+
private static String sanitizeFooter(String value) {
return value.replace('\n', ' ').replace('\0', ' ');
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index c4b686c..c460fd0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -284,9 +284,7 @@
}
private void createProjectConfig(CreateProjectArgs args) throws IOException, ConfigInvalidException {
- MetaDataUpdate md =
- metaDataUpdateFactory.create(args.getProject());
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(args.getProject())) {
ProjectConfig config = ProjectConfig.read(md);
config.load(md);
@@ -321,8 +319,6 @@
md.setMessage("Created project\n");
config.commit(md);
- } finally {
- md.close();
}
projectCache.onCreateProject(args.getProject());
repoManager.setProjectDescription(args.getProject(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 5f921b1..fd7539f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -139,15 +139,7 @@
throw new BadRequestException("config is required");
}
- final MetaDataUpdate md;
- try {
- md = metaDataUpdateFactory.get().create(projectName);
- } catch (RepositoryNotFoundException notFound) {
- throw new ResourceNotFoundException(projectName.get());
- } catch (IOException e) {
- throw new ResourceNotFoundException(projectName.get(), e);
- }
- try {
+ try (MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName)) {
ProjectConfig projectConfig = ProjectConfig.read(md);
Project p = projectConfig.getProject();
@@ -226,12 +218,12 @@
return new ConfigInfo(serverEnableSignedPush,
state.controlFor(user.get()), config, pluginConfigEntries,
cfgFactory, allProjects, views);
+ } catch (RepositoryNotFoundException notFound) {
+ throw new ResourceNotFoundException(projectName.get());
} catch (ConfigInvalidException err) {
throw new ResourceConflictException("Cannot read project " + projectName, err);
} catch (IOException err) {
throw new ResourceConflictException("Cannot update project " + projectName, err);
- } finally {
- md.close();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
index 2d407bd..7cf426d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
@@ -76,42 +76,37 @@
throw new AuthException("not project owner");
}
- try {
- MetaDataUpdate md = updateFactory.create(resource.getNameKey());
- try {
- ProjectConfig config = ProjectConfig.read(md);
- Project project = config.getProject();
- project.setDescription(Strings.emptyToNull(input.description));
+ try (MetaDataUpdate md = updateFactory.create(resource.getNameKey())) {
+ ProjectConfig config = ProjectConfig.read(md);
+ Project project = config.getProject();
+ project.setDescription(Strings.emptyToNull(input.description));
- String msg = MoreObjects.firstNonNull(
- Strings.emptyToNull(input.commitMessage),
- "Updated description.\n");
- if (!msg.endsWith("\n")) {
- msg += "\n";
- }
- md.setAuthor(user);
- md.setMessage(msg);
- ObjectId baseRev = config.getRevision();
- ObjectId commitRev = config.commit(md);
- // Only fire hook if project was actually changed.
- if (!Objects.equals(baseRev, commitRev)) {
- gitRefUpdated.fire(resource.getNameKey(), RefNames.REFS_CONFIG,
- baseRev, commitRev);
- hooks.doRefUpdatedHook(
- new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG),
- baseRev, commitRev, user.getAccount());
- }
- cache.evict(ctl.getProject());
- gitMgr.setProjectDescription(
- resource.getNameKey(),
- project.getDescription());
-
- return Strings.isNullOrEmpty(project.getDescription())
- ? Response.<String>none()
- : Response.ok(project.getDescription());
- } finally {
- md.close();
+ String msg = MoreObjects.firstNonNull(
+ Strings.emptyToNull(input.commitMessage),
+ "Updated description.\n");
+ if (!msg.endsWith("\n")) {
+ msg += "\n";
}
+ md.setAuthor(user);
+ md.setMessage(msg);
+ ObjectId baseRev = config.getRevision();
+ ObjectId commitRev = config.commit(md);
+ // Only fire hook if project was actually changed.
+ if (!Objects.equals(baseRev, commitRev)) {
+ gitRefUpdated.fire(resource.getNameKey(), RefNames.REFS_CONFIG,
+ baseRev, commitRev);
+ hooks.doRefUpdatedHook(
+ new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG),
+ baseRev, commitRev, user.getAccount());
+ }
+ cache.evict(ctl.getProject());
+ gitMgr.setProjectDescription(
+ resource.getNameKey(),
+ project.getDescription());
+
+ return Strings.isNullOrEmpty(project.getDescription())
+ ? Response.<String>none()
+ : Response.ok(project.getDescription());
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(resource.getName());
} catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
index ac01de5..641c3a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
@@ -84,39 +84,34 @@
}
}
- try {
- MetaDataUpdate md = updateFactory.create(ctl.getProject().getNameKey());
- try {
- ProjectConfig config = ProjectConfig.read(md);
- Project project = config.getProject();
- if (inherited) {
- project.setDefaultDashboard(input.id);
- } else {
- project.setLocalDefaultDashboard(input.id);
- }
-
- String msg = MoreObjects.firstNonNull(
- Strings.emptyToNull(input.commitMessage),
- input.id == null
- ? "Removed default dashboard.\n"
- : String.format("Changed default dashboard to %s.\n", input.id));
- if (!msg.endsWith("\n")) {
- msg += "\n";
- }
- md.setAuthor(ctl.getUser().asIdentifiedUser());
- md.setMessage(msg);
- config.commit(md);
- cache.evict(ctl.getProject());
-
- if (target != null) {
- DashboardInfo info = get.get().apply(target);
- info.isDefault = true;
- return Response.ok(info);
- }
- return Response.none();
- } finally {
- md.close();
+ try (MetaDataUpdate md = updateFactory.create(ctl.getProject().getNameKey())) {
+ ProjectConfig config = ProjectConfig.read(md);
+ Project project = config.getProject();
+ if (inherited) {
+ project.setDefaultDashboard(input.id);
+ } else {
+ project.setLocalDefaultDashboard(input.id);
}
+
+ String msg = MoreObjects.firstNonNull(
+ Strings.emptyToNull(input.commitMessage),
+ input.id == null
+ ? "Removed default dashboard.\n"
+ : String.format("Changed default dashboard to %s.\n", input.id));
+ if (!msg.endsWith("\n")) {
+ msg += "\n";
+ }
+ md.setAuthor(ctl.getUser().asIdentifiedUser());
+ md.setMessage(msg);
+ config.commit(md);
+ cache.evict(ctl.getProject());
+
+ if (target != null) {
+ DashboardInfo info = get.get().apply(target);
+ info.isDefault = true;
+ return Response.ok(info);
+ }
+ return Response.none();
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(ctl.getProject().getName());
} catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 15c977f..8113c01 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -71,32 +71,27 @@
ResourceNotFoundException, UnprocessableEntityException, IOException {
ProjectControl ctl = rsrc.getControl();
validateParentUpdate(ctl, input.parent, checkIfAdmin);
- try {
- MetaDataUpdate md = updateFactory.create(rsrc.getNameKey());
- try {
- ProjectConfig config = ProjectConfig.read(md);
- Project project = config.getProject();
- project.setParentName(Strings.emptyToNull(input.parent));
+ try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
+ ProjectConfig config = ProjectConfig.read(md);
+ Project project = config.getProject();
+ project.setParentName(Strings.emptyToNull(input.parent));
- String msg = Strings.emptyToNull(input.commitMessage);
- if (msg == null) {
- msg = String.format(
- "Changed parent to %s.\n",
- MoreObjects.firstNonNull(project.getParentName(),
- allProjects.get()));
- } else if (!msg.endsWith("\n")) {
- msg += "\n";
- }
- md.setAuthor(ctl.getUser().asIdentifiedUser());
- md.setMessage(msg);
- config.commit(md);
- cache.evict(ctl.getProject());
-
- Project.NameKey parentName = project.getParent(allProjects);
- return parentName != null ? parentName.get() : "";
- } finally {
- md.close();
+ String msg = Strings.emptyToNull(input.commitMessage);
+ if (msg == null) {
+ msg = String.format(
+ "Changed parent to %s.\n",
+ MoreObjects.firstNonNull(project.getParentName(),
+ allProjects.get()));
+ } else if (!msg.endsWith("\n")) {
+ msg += "\n";
}
+ md.setAuthor(ctl.getUser().asIdentifiedUser());
+ md.setMessage(msg);
+ config.commit(md);
+ cache.evict(ctl.getProject());
+
+ Project.NameKey parentName = project.getParent(allProjects);
+ return parentName != null ? parentName.get() : "";
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(rsrc.getName());
} catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 2b35cef..e827d3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -231,7 +231,8 @@
// required for this change to be submittable. Each label will indicate
// whether or not that is actually possible given the permissions.
return ruleError(String.format("Submit rule '%s' for change %s of %s has "
- + "no solution.", getSubmitRule(), cd.getId(), getProjectName()));
+ + "no solution.", getSubmitRuleName(), cd.getId(),
+ getProjectName()));
}
return resultsToSubmitRecord(getSubmitRule(), results);
@@ -410,13 +411,13 @@
if (results.isEmpty()) {
// Should never occur for a well written rule
- return typeError("Submit rule '" + getSubmitRule() + "' for change "
+ return typeError("Submit rule '" + getSubmitRuleName() + "' for change "
+ cd.getId() + " of " + getProjectName() + " has no solution.");
}
Term typeTerm = results.get(0);
if (!(typeTerm instanceof SymbolTerm)) {
- return typeError("Submit rule '" + getSubmitRule() + "' for change "
+ return typeError("Submit rule '" + getSubmitRuleName() + "' for change "
+ cd.getId() + " of " + getProjectName()
+ " did not return a symbol.");
}
@@ -609,6 +610,10 @@
return submitRule;
}
+ public String getSubmitRuleName() {
+ return submitRule != null ? submitRule.toString() : "<unknown rule>";
+ }
+
private void initPatchSet() throws OrmException {
if (patchSet == null) {
patchSet = cd.currentPatchSet();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_115.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_115.java
index 920160b..30479b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_115.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_115.java
@@ -131,18 +131,18 @@
try (Repository git = mgr.openRepository(allUsersName);
RevWalk rw = new RevWalk(git)) {
BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
- MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED,
- allUsersName, git, bru);
- md.getCommitBuilder().setAuthor(serverUser);
- md.getCommitBuilder().setCommitter(serverUser);
-
for (Map.Entry<Account.Id, DiffPreferencesInfo> e : imports.entrySet()) {
- VersionedAccountPreferences p =
- VersionedAccountPreferences.forUser(e.getKey());
- p.load(md);
- storeSection(p.getConfig(), UserConfigSections.DIFF, null,
- e.getValue(), DiffPreferencesInfo.defaults());
- p.commit(md);
+ try(MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED,
+ allUsersName, git, bru)) {
+ md.getCommitBuilder().setAuthor(serverUser);
+ md.getCommitBuilder().setCommitter(serverUser);
+ VersionedAccountPreferences p =
+ VersionedAccountPreferences.forUser(e.getKey());
+ p.load(md);
+ storeSection(p.getConfig(), UserConfigSections.DIFF, null,
+ e.getValue(), DiffPreferencesInfo.defaults());
+ p.commit(md);
+ }
}
bru.execute(rw, NullProgressMonitor.INSTANCE);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
index 0f3fee4..5af78461 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -112,6 +112,11 @@
+ "Label: Label2=1\n"
+ "Label: Label3=0\n"
+ "Label: Label4=-1\n");
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: -Label1\n"
+ + "Label: -Label1 Other Account <2@gerrit>\n");
assertParseFails("Update change\n"
+ "\n"
+ "Patch-Set: 1\n"
@@ -124,6 +129,18 @@
+ "\n"
+ "Patch-Set: 1\n"
+ "Label: X+Y\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1 Other Account <2@gerrit>\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: -Label!1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: -Label!1 Other Account <2@gerrit>\n");
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index c68c283..bfb16c1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -197,6 +197,41 @@
}
@Test
+ public void removeOtherUsersApprovals() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.putApproval("Not-For-Long", (short) 1);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ PatchSetApproval psa = Iterables.getOnlyElement(
+ notes.getApprovals().get(c.currentPatchSetId()));
+ assertThat(psa.getAccountId()).isEqualTo(otherUserId);
+ assertThat(psa.getLabel()).isEqualTo("Not-For-Long");
+ assertThat(psa.getValue()).isEqualTo((short) 1);
+
+ update = newUpdate(c, changeOwner);
+ update.removeApprovalFor(otherUserId, "Not-For-Long");
+ update.commit();
+
+ notes = newNotes(c);
+ assertThat(notes.getApprovals()).isEmpty();
+
+ // Add back approval on same label.
+ update = newUpdate(c, otherUser);
+ update.putApproval("Not-For-Long", (short) 2);
+ update.commit();
+
+ notes = newNotes(c);
+ psa = Iterables.getOnlyElement(
+ notes.getApprovals().get(c.currentPatchSetId()));
+ assertThat(psa.getAccountId()).isEqualTo(otherUserId);
+ assertThat(psa.getLabel()).isEqualTo("Not-For-Long");
+ assertThat(psa.getValue()).isEqualTo((short) 2);
+
+ }
+
+ @Test
public void multipleReviewers() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index 6ed70a2..2fb91f6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -145,17 +145,12 @@
continue;
}
- try {
- MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
- try {
- ProjectConfig config = ProjectConfig.read(md);
- config.getProject().setParentName(newParentKey);
- md.setMessage("Inherit access from "
- + (newParentKey != null ? newParentKey.get() : allProjectsName.get()) + "\n");
- config.commit(md);
- } finally {
- md.close();
- }
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(nameKey)) {
+ ProjectConfig config = ProjectConfig.read(md);
+ config.getProject().setParentName(newParentKey);
+ md.setMessage("Inherit access from "
+ + (newParentKey != null ? newParentKey.get() : allProjectsName.get()) + "\n");
+ config.commit(md);
} catch (RepositoryNotFoundException notFound) {
err.append("error: Project ").append(name).append(" not found\n");
} catch (IOException | ConfigInvalidException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
index 5eda57c..301bc0e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput.Filters;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -21,8 +23,6 @@
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Revisions;
-import com.google.gerrit.server.change.TestSubmitRule.Filters;
-import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -34,7 +34,7 @@
import java.nio.ByteBuffer;
abstract class BaseTestPrologCommand extends SshCommand {
- private Input input = new Input();
+ private TestSubmitRuleInput input = new TestSubmitRuleInput();
@Inject
private ChangesCollection changes;
@@ -55,7 +55,7 @@
input.filters = no ? Filters.SKIP : Filters.RUN;
}
- protected abstract RestModifyView<RevisionResource, Input> createView();
+ protected abstract RestModifyView<RevisionResource, TestSubmitRuleInput> createView();
@Override
protected final void run() throws UnloggedFailure {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 4570085..54e07aa3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -122,6 +122,10 @@
@Option(name = "--json", aliases = "-j", usage = "read review input json from stdin")
private boolean json;
+ @Option(name = "--strict-labels", usage = "Strictly check if the labels "
+ + "specified can be applied to the given patch set(s)")
+ private boolean strictLabels;
+
@Option(name = "--label", aliases = "-l", usage = "custom label(s) to assign", metaVar = "LABEL=VALUE")
void addLabel(final String token) {
LabelVote v = LabelVote.parseWithEquals(token);
@@ -272,7 +276,7 @@
review.notify = notify;
review.labels = Maps.newTreeMap();
review.drafts = ReviewInput.DraftHandling.PUBLISH;
- review.strictLabels = false;
+ review.strictLabels = strictLabels;
for (ApproveOption ao : optionList) {
Short v = ao.value();
if (v != null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index ab1353b..589fbf0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -124,41 +124,36 @@
String name = ctlProject.getName();
final StringBuilder err = new StringBuilder();
- try {
- MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
- try {
- ProjectConfig config = ProjectConfig.read(md);
- Project project = config.getProject();
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(nameKey)) {
+ ProjectConfig config = ProjectConfig.read(md);
+ Project project = config.getProject();
- if (requireChangeID != null) {
- project.setRequireChangeID(requireChangeID);
- }
- if (submitType != null) {
- project.setSubmitType(submitType);
- }
- if (contentMerge != null) {
- project.setUseContentMerge(contentMerge);
- }
- if (contributorAgreements != null) {
- project.setUseContributorAgreements(contributorAgreements);
- }
- if (signedOffBy != null) {
- project.setUseSignedOffBy(signedOffBy);
- }
- if (projectDescription != null) {
- project.setDescription(projectDescription);
- }
- if (state != null) {
- project.setState(state);
- }
- if (maxObjectSizeLimit != null) {
- project.setMaxObjectSizeLimit(maxObjectSizeLimit);
- }
- md.setMessage("Project settings updated");
- config.commit(md);
- } finally {
- md.close();
+ if (requireChangeID != null) {
+ project.setRequireChangeID(requireChangeID);
}
+ if (submitType != null) {
+ project.setSubmitType(submitType);
+ }
+ if (contentMerge != null) {
+ project.setUseContentMerge(contentMerge);
+ }
+ if (contributorAgreements != null) {
+ project.setUseContributorAgreements(contributorAgreements);
+ }
+ if (signedOffBy != null) {
+ project.setUseSignedOffBy(signedOffBy);
+ }
+ if (projectDescription != null) {
+ project.setDescription(projectDescription);
+ }
+ if (state != null) {
+ project.setState(state);
+ }
+ if (maxObjectSizeLimit != null) {
+ project.setMaxObjectSizeLimit(maxObjectSizeLimit);
+ }
+ md.setMessage("Project settings updated");
+ config.commit(md);
} catch (RepositoryNotFoundException notFound) {
err.append("error: Project ").append(name).append(" not found\n");
} catch (IOException | ConfigInvalidException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java
index b957a7a..9c4a680 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java
@@ -14,10 +14,10 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.TestSubmitRule;
-import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -29,7 +29,7 @@
private Provider<TestSubmitRule> view;
@Override
- protected RestModifyView<RevisionResource, Input> createView() {
+ protected RestModifyView<RevisionResource, TestSubmitRuleInput> createView() {
return view.get();
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java
index 2e7f0df..34d4bdc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java
@@ -15,9 +15,9 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.server.change.TestSubmitType;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.inject.Inject;
@@ -29,7 +29,7 @@
private Provider<TestSubmitType> view;
@Override
- protected RestModifyView<RevisionResource, Input> createView() {
+ protected RestModifyView<RevisionResource, TestSubmitRuleInput> createView() {
return view.get();
}
}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index a2947af..48f7767 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -292,7 +292,6 @@
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(new DropWizardMetricMaker.RestModule());
- modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
modules.add(new DiffExecutorModule());
@@ -306,13 +305,12 @@
modules.add(new PluginRestApiModule());
modules.add(new RestCacheAdminModule());
modules.add(new GpgModule(config));
- switch (indexType) {
- case LUCENE:
- modules.add(new LuceneIndexModule());
- break;
- default:
- throw new IllegalStateException("unsupported index.type = " + indexType);
- }
+
+ // Index module shutdown must happen before work queue shutdown, otherwise
+ // work queue can get stuck waiting on index futures that will never return.
+ modules.add(createIndexModule());
+
+ modules.add(new WorkQueue.Module());
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
@@ -332,6 +330,15 @@
return cfgInjector.createChildInjector(modules);
}
+ private Module createIndexModule() {
+ switch (indexType) {
+ case LUCENE:
+ return new LuceneIndexModule();
+ default:
+ throw new IllegalStateException("unsupported index.type = " + indexType);
+ }
+ }
+
private void initIndexType() {
indexType = IndexModule.getIndexType(cfgInjector);
}
diff --git a/polygerrit-ui/app/elements/gr-ajax.html b/polygerrit-ui/app/elements/gr-ajax.html
index 9671b37..97f3528 100644
--- a/polygerrit-ui/app/elements/gr-ajax.html
+++ b/polygerrit-ui/app/elements/gr-ajax.html
@@ -79,6 +79,13 @@
},
},
+ ready: function() {
+ // Used for debugging which element a request came from.
+ var headers = this.$.xhr.headers;
+ headers['x-requesting-element-id'] = this.id || 'gr-ajax (no id)';
+ this.$.xhr.headers = headers;
+ },
+
generateRequest: function() {
return this.$.xhr.generateRequest();
},
diff --git a/polygerrit-ui/app/elements/gr-change-view.html b/polygerrit-ui/app/elements/gr-change-view.html
index 8bef101..35153d4 100644
--- a/polygerrit-ui/app/elements/gr-change-view.html
+++ b/polygerrit-ui/app/elements/gr-change-view.html
@@ -38,6 +38,7 @@
height: 4.1em;
}
.header {
+ align-items: center;
background-color: var(--view-background-color);
display: flex;
max-width: var(--max-constrained-width);
@@ -57,6 +58,15 @@
overflow: hidden;
text-overflow: ellipsis;
}
+ .patchSelectLabel {
+ margin-left: var(--default-horizontal-margin);
+ }
+ .header select {
+ margin-left: .5em;
+ }
+ .header gr-reply-dropdown {
+ margin-left: var(--default-horizontal-margin);
+ }
section {
margin: 10px 0;
padding: 10px var(--default-horizontal-margin);
@@ -87,26 +97,39 @@
}
</style>
<gr-ajax id="detailXHR"
- url="[[_computeDetailPath(changeNum)]]"
+ url="[[_computeDetailPath(_changeNum)]]"
params="[[_computeDetailQueryParams()]]"
- last-response="{{change}}"
+ last-response="{{_change}}"
loading="{{_loading}}"></gr-ajax>
<gr-ajax id="commentsXHR"
- url="[[_computeCommentsPath(changeNum)]]"
- last-response="{{comments}}"></gr-ajax>
+ url="[[_computeCommentsPath(_changeNum)]]"
+ last-response="{{_comments}}"></gr-ajax>
+ <gr-ajax id="commitInfoXHR"
+ url="[[_computeCommitInfoPath(_changeNum, _patchNum)]]"
+ last-response="{{_commitInfo}}"></gr-ajax>
<div class="container loading" hidden$="{{!_loading}}">Loading...</div>
<div class="container" hidden$="{{_loading}}">
<div class="headerContainer">
<div class="header">
<h2>
- <a href$="[[_computeChangePath(change._number)]]">[[change._number]]</a><span>:</span>
- <span>[[change.subject]]</span>
+ <a href$="[[_computeChangePath(_change._number)]]">[[_change._number]]</a><span>:</span>
+ <span>[[_change.subject]]</span>
</h2>
+ <label class="patchSelectLabel" for="patchSetSelect">Patch set</label>
+ <select id="patchSetSelect" on-change="_handlePatchChange">
+ <template is="dom-repeat" items="{{_allPatchSets}}" as="patchNumber">
+ <option value$="[[patchNumber]]" selected$="[[_computePatchIndexIsSelected(index, _patchNum)]]">
+ <span>[[patchNumber]]</span>
+ /
+ <span>[[_computeLatestPatchNum(_change)]]</span>
+ </option>
+ </template>
+ </select>
<gr-reply-dropdown id="replyDropdown"
- change-num="[[changeNum]]"
- patch-num="[[_computePatchNum(change.current_revision)]]"
- labels="[[change.labels]]"
- permitted-labels="[[change.permitted_labels]]"
+ change-num="[[_changeNum]]"
+ patch-num="[[_patchNum]]"
+ labels="[[_change.labels]]"
+ permitted-labels="[[_change.permitted_labels]]"
on-send="_handleReplySent"
hidden$="[[!_loggedIn]]">Reply</gr-reply-dropdown>
</div>
@@ -115,13 +138,13 @@
<table>
<tr>
<td class="changeInfo-label">Owner</td>
- <td>[[change.owner.name]]</td>
+ <td>[[_change.owner.name]]</td>
</tr>
<tr>
<td class="changeInfo-label">Reviewers</td>
<td>
<template is="dom-repeat"
- items="[[_computeReviewers(change.labels, change.owner)]]"
+ items="[[_computeReviewers(_change.labels, _change.owner)]]"
as="reviewer">
<div>[[reviewer.name]]</div>
</template>
@@ -129,15 +152,15 @@
</tr>
<tr>
<td class="changeInfo-label">Project</td>
- <td>[[change.project]]</td>
+ <td>[[_change.project]]</td>
</tr>
<tr>
<td class="changeInfo-label">Branch</td>
- <td>[[change.branch]]</td>
+ <td>[[_change.branch]]</td>
</tr>
<tr>
<td class="changeInfo-label">Topic</td>
- <td>[[change.topic]]</td>
+ <td>[[_change.topic]]</td>
</tr>
<tr>
<td class="changeInfo-label">Strategy</td>
@@ -147,21 +170,20 @@
<td class="changeInfo-label">Updated</td>
<td>
<gr-date-formatter
- date-str="[[change.updated]]"></gr-date-formatter>
+ date-str="[[_change.updated]]"></gr-date-formatter>
</td>
</tr>
</table>
</section>
- <section class="summary">[[_computeCurrentRevisionMessage(change)]]</section>
+ <section class="summary">[[_commitInfo.message]]</section>
<gr-file-list id="fileList"
- change-num="[[changeNum]]"
- patch-num="[[_computePatchNum(change.current_revision)]]"
- revision="[[change.current_revision]]"
- comments="[[comments]]"></gr-file-list>
+ change-num="[[_changeNum]]"
+ patch-num="[[_patchNum]]"
+ comments="[[_comments]]"></gr-file-list>
<gr-messages-list
- change-num="[[changeNum]]"
- messages="[[change.messages]]"
- comments="[[comments]]"></gr-messages-list>
+ change-num="[[_changeNum]]"
+ messages="[[_change.messages]]"
+ comments="[[_comments]]"></gr-messages-list>
</div>
</template>
<script>
@@ -189,9 +211,19 @@
type: Object,
observer: '_paramsChanged',
},
- changeNum: Number,
- comments: Object,
+ _comments: Object,
+ _change: {
+ type: Object,
+ observer: '_changeChanged',
+ },
+ _commitInfo: Object,
+ _changeNum: String,
+ _patchNum: String,
+ _allPatchSets: {
+ type: Array,
+ computed: '_computeAllPatchSets(_change)',
+ },
_loggedIn: {
type: Boolean,
value: false,
@@ -240,20 +272,38 @@
el.classList.toggle('pinned', window.scrollY >= top);
},
+ _handlePatchChange: function(e) {
+ var patchNum = e.target.value;
+ var currentPatchNum =
+ this._change.revisions[this._change.current_revision]._number
+ if (patchNum == currentPatchNum) {
+ page.show(this._computeChangePath(this._changeNum));
+ return;
+ }
+ page.show(this._computeChangePath(this._changeNum) + '/' + patchNum);
+ },
+
_handleReplySent: function(e) {
this._reload();
},
_paramsChanged: function(value) {
- this.changeNum = value.changeNum;
- if (!this.changeNum) {
- this.change = null;
- this.comments = null;
+ this._changeNum = value.changeNum;
+ this._patchNum = value.patchNum;
+ if (!this._changeNum) {
+ this._change = null;
+ this._comments = null;
return;
}
this._reload();
},
+ _changeChanged: function(change) {
+ if (!change) { return; }
+ this._patchNum = this._patchNum ||
+ change.revisions[change.current_revision]._number;
+ },
+
_computeChangePath: function(changeNum) {
return '/c/' + changeNum;
},
@@ -262,32 +312,22 @@
return '/changes/' + changeNum + '/detail';
},
- _computeCommitInfoPath: function(changeNum, commitHash) {
- return '/changes/' + changeNum + '/revisions/' + commitHash + '/commit';
+ _computeCommitInfoPath: function(changeNum, patchNum) {
+ return Changes.baseURL(changeNum, patchNum) + '/commit?links';
},
_computeCommentsPath: function(changeNum) {
return '/changes/' + changeNum + '/comments';
},
- _computePatchNum: function(revision) {
- return this.change && this.change.revisions[revision]._number;
- },
-
_computeDetailQueryParams: function() {
var options = Changes.listChangesOptionsToHex(
- Changes.ListChangesOption.CURRENT_REVISION,
- Changes.ListChangesOption.CURRENT_COMMIT,
+ Changes.ListChangesOption.ALL_REVISIONS,
Changes.ListChangesOption.CHANGE_ACTIONS
);
return { O: options };
},
- _computeCurrentRevisionMessage: function(change) {
- return change &&
- change.revisions[change.current_revision].commit.message;
- },
-
_computeReviewers: function(labels, owner) {
var reviewers =
(labels['Code-Review'] && labels['Code-Review'].all) || [];
@@ -297,6 +337,22 @@
});
},
+ _computeLatestPatchNum: function(change) {
+ return change.revisions[change.current_revision]._number;
+ },
+
+ _computeAllPatchSets: function(change) {
+ var patchNums = [];
+ for (var rev in change.revisions) {
+ patchNums.push(change.revisions[rev]._number);
+ }
+ return patchNums.sort();
+ },
+
+ _computePatchIndexIsSelected: function(index, patchNum) {
+ return this._allPatchSets[index] == patchNum;
+ },
+
_handleKey: function(e) {
if (util.shouldSupressKeyboardShortcut(e)) { return; }
e.preventDefault();
@@ -311,9 +367,19 @@
},
_reload: function() {
- this.$.detailXHR.generateRequest();
+ var detailCompletes = this.$.detailXHR.generateRequest().completes;
this.$.commentsXHR.generateRequest();
- this.$.fileList.reload();
+ var reloadCommitInfoAndFileList = function() {
+ this.$.commitInfoXHR.generateRequest();
+ this.$.fileList.reload();
+ }.bind(this);
+
+ if (this._patchNum) {
+ reloadCommitInfoAndFileList();
+ } else {
+ // The patch number is reliant on the change detail request.
+ detailCompletes.then(reloadCommitInfoAndFileList);
+ }
},
});
diff --git a/polygerrit-ui/app/elements/gr-diff-side.html b/polygerrit-ui/app/elements/gr-diff-side.html
index 6e150d6..0120d3b 100644
--- a/polygerrit-ui/app/elements/gr-diff-side.html
+++ b/polygerrit-ui/app/elements/gr-diff-side.html
@@ -60,6 +60,11 @@
/* Line feed */
content: '\A';
}
+ .tab:before {
+ color: #C62828;
+ /* >> character */
+ content: '\00BB';
+ }
.filler {
background: #eee;
}
@@ -80,6 +85,8 @@
SEMICOLON: ';'.charCodeAt(0),
};
+ var TAB_REGEX = /\t/g;
+
Polymer({
is: 'gr-diff-side',
@@ -201,6 +208,7 @@
if (numLines > 1) {
html = this._addNewLines(code.content, html, numLines);
}
+ html = this._addTabIndicators(code.content, html);
// If the html is equivalent to the text then it didn't get highlighted
// or escaped. Use textContent which is faster than innerHTML.
@@ -300,6 +308,11 @@
return result;
},
+ _addTabIndicators: function(content, html) {
+ return html.replace(TAB_REGEX,
+ '<span class="style-scope gr-diff-side tab">\t</span>');
+ },
+
_createElement: function(tagName, className) {
var el = document.createElement(tagName);
// When Shady DOM is being used, these classes are added to account for
diff --git a/polygerrit-ui/app/elements/gr-diff-view.html b/polygerrit-ui/app/elements/gr-diff-view.html
index 4945a67..9829aeb 100644
--- a/polygerrit-ui/app/elements/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/gr-diff-view.html
@@ -55,6 +55,7 @@
change-num="[[_changeNum]]"
patch-range="[[_patchRange]]"
path="[[_path]]"
+ available-patches="[[_computeAvailablePatches(_change.revisions)]]"
on-render="_handleDiffRender">
</gr-diff>
</template>
@@ -156,6 +157,14 @@
}
},
+ _computeAvailablePatches: function(revisions) {
+ var patchNums = [];
+ for (var rev in revisions) {
+ patchNums.push(revisions[rev]._number);
+ }
+ return patchNums.sort(function(a, b) { return a - b; });
+ },
+
_computeChangePath: function(changeNum) {
return '/c/' + changeNum;
},
diff --git a/polygerrit-ui/app/elements/gr-diff.html b/polygerrit-ui/app/elements/gr-diff.html
index 23d14b5..ebc67b9 100644
--- a/polygerrit-ui/app/elements/gr-diff.html
+++ b/polygerrit-ui/app/elements/gr-diff.html
@@ -17,12 +17,16 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="gr-ajax.html">
<link rel="import" href="gr-diff-side.html">
+<link rel="import" href="gr-patch-range-select.html">
<link rel="import" href="gr-request.html">
<dom-module id="gr-diff">
<template>
<style>
- :host {
+ gr-patch-range-select {
+ margin: 0 var(--default-horizontal-margin) .75em;
+ }
+ .diffContainer {
border-bottom: 1px solid #eee;
border-top: 1px solid #eee;
font-family: 'Source Code Pro', monospace;
@@ -51,14 +55,22 @@
url="[[_computeDraftsPath(changeNum, patchRange.basePatchNum)]]"></gr-ajax>
<gr-ajax id="draftsXHR"
url="[[_computeDraftsPath(changeNum, patchRange.patchNum)]]"></gr-ajax>
- <gr-diff-side id="leftDiff"
- content="{{_diff.leftSide}}"
- width="[[sideWidth]]"
- can-comment="[[_loggedIn]]"></gr-diff-side>
- <gr-diff-side id="rightDiff"
- content="{{_diff.rightSide}}"
- width="[[sideWidth]]"
- can-comment="[[_loggedIn]]"></gr-diff-side>
+
+ <gr-patch-range-select
+ path="[[path]]"
+ change-num="[[changeNum]]"
+ patch-range="[[patchRange]]"
+ available-patches="[[availablePatches]]"></gr-patch-range-select>
+ <div class="diffContainer">
+ <gr-diff-side id="leftDiff"
+ content="{{_diff.leftSide}}"
+ width="[[sideWidth]]"
+ can-comment="[[_loggedIn]]"></gr-diff-side>
+ <gr-diff-side id="rightDiff"
+ content="{{_diff.rightSide}}"
+ width="[[sideWidth]]"
+ can-comment="[[_loggedIn]]"></gr-diff-side>
+ </div>
</template>
<script>
(function() {
@@ -78,6 +90,7 @@
type: Boolean,
value: false,
},
+ availablePatches: Array,
changeNum: String,
/*
* A single object to encompass basePatchNum and patchNum is used
@@ -190,7 +203,7 @@
}.bind(this)));
if (basePatchNum != 'PARENT') {
- draftsPromise = this.$baseDraftsXHR.generateRequest().completes;
+ draftsPromise = this.$.baseDraftsXHR.generateRequest().completes;
promises.push(draftsPromise.then(function(req) {
this._baseDrafts =
(req.response[this.path] || []).filter(withoutParent);
diff --git a/polygerrit-ui/app/elements/gr-file-list.html b/polygerrit-ui/app/elements/gr-file-list.html
index dcc96ac..de04f55 100644
--- a/polygerrit-ui/app/elements/gr-file-list.html
+++ b/polygerrit-ui/app/elements/gr-file-list.html
@@ -49,10 +49,10 @@
}
</style>
<gr-ajax id="filesXHR"
- url="[[_computeFilesURL(changeNum, revision)]]"
+ url="[[_computeFilesURL(changeNum, patchNum)]]"
on-response="_handleResponse"></gr-ajax>
<gr-ajax id="draftsXHR"
- url="[[_computeDraftsURL(changeNum, revision)]]"
+ url="[[_computeDraftsURL(changeNum, patchNum)]]"
last-response="{{_drafts}}"></gr-ajax>
</gr-ajax>
<div class="tableContainer">
@@ -73,8 +73,8 @@
href$="[[_computeDiffURL(changeNum, patchNum, file.__path)]]">[[file.__path]]</a>
</td>
<td>
- <span class="drafts">[[_computeDraftsString(_drafts, file.__path)]]</span>
- <span class="comments">[[_computeCommentsString(comments, file.__path)]]</span>
+ <span class="drafts">[[_computeDraftsString(_drafts, patchNum, file.__path)]]</span>
+ <span class="comments">[[_computeCommentsString(comments, patchNum, file.__path)]]</span>
</td>
<td>
+<span>[[file.lines_inserted]]</span> lines,
@@ -93,46 +93,46 @@
is: 'gr-file-list',
properties: {
- patchNum: Number,
- changeNum: Number,
- revision: String,
+ patchNum: String,
+ changeNum: String,
comments: Object,
_drafts: Object,
},
- observers: [
- '_changeNumOrRevisionChanged(changeNum, revision)',
- ],
-
reload: function() {
- if (!!this.changeNum && !!this.revision) {
+ if (!!this.changeNum && !!this.patchNum) {
this.$.filesXHR.generateRequest();
- this.$.draftsXHR.generateRequest();
+ app.accountReady.then(function() {
+ if (!app.loggedIn) { return; }
+ this.$.draftsXHR.generateRequest();
+ }.bind(this));
}
},
- _changeNumOrRevisionChanged: function(changeNum, revision) {
- this.reload();
+ _computeFilesURL: function(changeNum, patchNum) {
+ return Changes.baseURL(changeNum, patchNum) + '/files';
},
- _computeFilesURL: function(changeNum, revision) {
- return Changes.baseURL(changeNum, revision) + '/files';
- },
-
- _computeCommentsString: function(comments, path) {
- var num = (comments[path] || []).length;
+ _computeCommentsString: function(comments, patchNum, path) {
+ var patchComments = (comments[path] || []).filter(function(c) {
+ return c.patch_set == patchNum;
+ });
+ var num = patchComments.length;
if (num == 0) { return ''; }
if (num == 1) { return '1 comment'; }
if (num > 1) { return num + ' comments'; };
},
- _computeDraftsURL: function(changeNum, revision) {
- return Changes.baseURL(changeNum, revision) + '/drafts';
+ _computeDraftsURL: function(changeNum, patchNum) {
+ return Changes.baseURL(changeNum, patchNum) + '/drafts';
},
- _computeDraftsString: function(drafts, path) {
- var num = (drafts[path] || []).length;
+ _computeDraftsString: function(drafts, patchNum, path) {
+ var patchDrafts = (drafts[path] || []).filter(function(d) {
+ return d.patch_set == patchNum;
+ });
+ var num = patchDrafts.length;
if (num == 0) { return ''; }
if (num == 1) { return '1 draft'; }
if (num > 1) { return num + ' drafts'; };
diff --git a/polygerrit-ui/app/elements/gr-patch-range-select.html b/polygerrit-ui/app/elements/gr-patch-range-select.html
new file mode 100644
index 0000000..a2a5dc4
--- /dev/null
+++ b/polygerrit-ui/app/elements/gr-patch-range-select.html
@@ -0,0 +1,95 @@
+<!--
+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">
+
+<dom-module id="gr-patch-range-select">
+ <template>
+ <style>
+ :host {
+ display: block;
+ }
+ .patchRange {
+ display: inline-block;
+ }
+ </style>
+ Patch set:
+ <span class="patchRange">
+ <select id="leftPatchSelect" on-change="_handlePatchChange">
+ <option value="PARENT"
+ selected$="[[_computeLeftSelected('PARENT', patchRange)]]">Base</option>
+ <template is="dom-repeat" items="{{availablePatches}}" as="patchNum">
+ <option value$="[[patchNum]]"
+ selected$="[[_computeLeftSelected(patchNum, patchRange)]]"
+ disabled$="[[_computeLeftDisabled(patchNum, patchRange)]]">[[patchNum]]</option>
+ </template>
+ </select>
+ </span>
+ →
+ <span class="patchRange">
+ <select id="rightPatchSelect" on-change="_handlePatchChange">
+ <template is="dom-repeat" items="{{availablePatches}}" as="patchNum">
+ <option value$="[[patchNum]]"
+ selected$="[[_computeRightSelected(patchNum, patchRange)]]"
+ disabled$="[[_computeRightDisabled(patchNum, patchRange)]]">[[patchNum]]</option>
+ </template>
+ </select>
+ </span>
+ </template>
+ <script>
+ (function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-patch-range-select',
+
+ properties: {
+ availablePatches: Array,
+ changeNum: String,
+ patchRange: Object,
+ path: String,
+ },
+
+ _handlePatchChange: function(e) {
+ var leftPatch = this.$.leftPatchSelect.value;
+ var rightPatch = this.$.rightPatchSelect.value;
+ var rangeStr = rightPatch;
+ if (leftPatch != 'PARENT') {
+ rangeStr = leftPatch + '..' + rangeStr;
+ }
+ page.show('/c/' + this.changeNum + '/' + rangeStr + '/' + this.path);
+ },
+
+ _computeLeftSelected: function(patchNum, patchRange) {
+ return patchNum == patchRange.basePatchNum;
+ },
+
+ _computeRightSelected: function(patchNum, patchRange) {
+ return patchNum == patchRange.patchNum;
+ },
+
+ _computeLeftDisabled: function(patchNum, patchRange) {
+ return parseInt(patchNum, 10) >= parseInt(patchRange.patchNum, 10);
+ },
+
+ _computeRightDisabled: function(patchNum, patchRange) {
+ if (patchRange.basePatchNum == 'PARENT') { return false; }
+ return parseInt(patchNum, 10) <= parseInt(patchRange.basePatchNum, 10);
+ },
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/scripts/app.js b/polygerrit-ui/app/scripts/app.js
index 3ceb90d..2ab2c5c 100644
--- a/polygerrit-ui/app/scripts/app.js
+++ b/polygerrit-ui/app/scripts/app.js
@@ -61,7 +61,7 @@
page.redirect('/c/' + ctx.params[0]);
});
- page('/c/:changeNum', function(data) {
+ page('/c/:changeNum/:patchNum?', function(data) {
app.route = 'gr-change-view';
app.params = data.params;
});
diff --git a/polygerrit-ui/app/test/gr-change-view-test.html b/polygerrit-ui/app/test/gr-change-view-test.html
index 4cf1d4c..8671560 100644
--- a/polygerrit-ui/app/test/gr-change-view-test.html
+++ b/polygerrit-ui/app/test/gr-change-view-test.html
@@ -37,9 +37,25 @@
<script>
suite('gr-change-view tests', function() {
var element;
+ var server;
setup(function() {
element = fixture('basic');
+ server = sinon.fakeServer.create();
+ // Eat any requests made by elements in this suite.
+ server.respondWith(
+ 'GET',
+ /\/changes\/(.*)/,
+ [
+ 200,
+ { 'Content-Type': 'application/json' },
+ ')]}\'\n{}',
+ ]
+ );
+ });
+
+ teardown(function() {
+ server.restore();
});
test('keyboard shortcuts', function() {
@@ -57,5 +73,49 @@
assert.isFalse(dropdownEl.opened);
});
+ test('patch num change', function(done) {
+ element._changeNum = '42';
+ element._patchNum = 2;
+ element._change = {
+ revisions: {
+ rev2: { _number: 2 },
+ rev1: { _number: 1 },
+ rev3: { _number: 3 },
+ },
+ current_revision: 'rev3',
+ };
+ flushAsynchronousOperations();
+ var selectEl = element.$$('.header select');
+ assert.ok(selectEl);
+ var optionEls =
+ Polymer.dom(element.root).querySelectorAll('.header option');
+ assert.equal(optionEls.length, 3);
+ assert.isFalse(
+ element.$$('.header option[value="1"]').hasAttribute('selected'));
+ assert.isTrue(
+ element.$$('.header option[value="2"]').hasAttribute('selected'));
+ assert.isFalse(
+ element.$$('.header option[value="3"]').hasAttribute('selected'));
+
+ var showStub = sinon.stub(page, 'show');
+
+ var numEvents = 0;
+ selectEl.addEventListener('change', function(e) {
+ numEvents++;
+ if (numEvents == 1) {
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+ selectEl.value = '3';
+ element.fire('change', {}, {node: selectEl});
+ } else if (numEvents == 2) {
+ assert(showStub.lastCall.calledWithExactly('/c/42'),
+ 'Should navigate to /c/42');
+ showStub.restore();
+ done();
+ }
+ });
+ selectEl.value = '1';
+ element.fire('change', {}, {node: selectEl});
+ });
});
</script>
diff --git a/polygerrit-ui/app/test/gr-file-list-test.html b/polygerrit-ui/app/test/gr-file-list-test.html
new file mode 100644
index 0000000..f474f15
--- /dev/null
+++ b/polygerrit-ui/app/test/gr-file-list-test.html
@@ -0,0 +1,126 @@
+<!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-file-list</title>
+
+<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="../../bower_components/page/page.js"></script>
+<script src="../scripts/changes.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-file-list.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-file-list></gr-file-list>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-file-list tests', function() {
+ var element;
+ var server;
+
+ setup(function() {
+ element = fixture('basic');
+ server = sinon.fakeServer.create();
+ server.respondWith(
+ 'GET',
+ '/changes/42/revisions/1/files',
+ [
+ 200,
+ { 'Content-Type': 'application/json' },
+ ')]}\'\n' +
+ JSON.stringify({
+ '/COMMIT_MSG': {
+ status: 'A',
+ lines_inserted: 9,
+ size_delta: 317,
+ size: 317
+ },
+ 'myfile.txt': {
+ lines_inserted: 35,
+ size_delta: 1146,
+ size: 1167
+ }
+ }),
+ ]
+ );
+ server.respondWith(
+ 'GET',
+ '/changes/42/revisions/2/files',
+ [
+ 200,
+ { 'Content-Type': 'application/json' },
+ ')]}\'\n' +
+ JSON.stringify({
+ '/COMMIT_MSG': {
+ status: 'A',
+ lines_inserted: 9,
+ size_delta: 317,
+ size: 317
+ },
+ 'myfile.txt': {
+ lines_inserted: 35,
+ size_delta: 1146,
+ size: 1167
+ },
+ 'file_added_in_rev2.txt': {
+ lines_inserted: 98,
+ size_delta: 234,
+ size: 136
+ }
+ }),
+ ]
+ );
+ });
+
+ teardown(function() {
+ server.restore();
+ });
+
+ test('requests', function(done) {
+ element.changeNum = '42';
+ element.patchNum = '1';
+ element.reload();
+ server.respond();
+
+ element.async(function() {
+ var filenames = element.files.map(function(f) {
+ return f.__path;
+ });
+ assert.deepEqual(filenames, ['/COMMIT_MSG', 'myfile.txt']);
+
+ element.patchNum = '2';
+ element.reload();
+ server.respond();
+ element.async(function() {
+ filenames = element.files.map(function(f) {
+ return f.__path;
+ });
+ assert.deepEqual(filenames,
+ ['/COMMIT_MSG', 'file_added_in_rev2.txt', 'myfile.txt']);
+ done();
+ }, 1);
+ }, 1)
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/gr-patch-range-select-test.html b/polygerrit-ui/app/test/gr-patch-range-select-test.html
new file mode 100644
index 0000000..c2b19e7
--- /dev/null
+++ b/polygerrit-ui/app/test/gr-patch-range-select-test.html
@@ -0,0 +1,93 @@
+<!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-patch-range-select</title>
+
+<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="../../bower_components/page/page.js"></script>
+
+<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../elements/gr-patch-range-select.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-patch-range-select auto></gr-patch-range-select>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-patch-range-select tests', function() {
+ var element;
+
+ setup(function() {
+ element = fixture('basic');
+ });
+
+ test('enabled/disabled options', function() {
+ var patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: '3',
+ };
+ ['1', '2', '3'].forEach(function(patchNum) {
+ assert.isFalse(element._computeRightDisabled(patchNum, patchRange));
+ });
+ ['PARENT', '1', '2'].forEach(function(patchNum) {
+ assert.isFalse(element._computeLeftDisabled(patchNum, patchRange));
+ });
+ assert.isTrue(element._computeLeftDisabled('3', patchRange));
+
+ patchRange.basePatchNum = '2';
+ assert.isTrue(element._computeLeftDisabled('3', patchRange));
+ assert.isTrue(element._computeRightDisabled('1', patchRange));
+ assert.isTrue(element._computeRightDisabled('2', patchRange));
+ assert.isFalse(element._computeRightDisabled('3', patchRange));
+ });
+
+ test('navigation', function(done) {
+ var showStub = sinon.stub(page, 'show');
+ var leftSelectEl = element.$.leftPatchSelect;
+ var rightSelectEl = element.$.rightPatchSelect;
+ element.changeNum = '42';
+ element.path = 'path/to/file.txt';
+ element.availablePatches = ['1', '2', '3'];
+ flushAsynchronousOperations();
+
+ var numEvents = 0;
+ leftSelectEl.addEventListener('change', function(e) {
+ numEvents++;
+ if (numEvents == 1) {
+ assert(showStub.lastCall.calledWithExactly(
+ '/c/42/3/path/to/file.txt'),
+ 'Should navigate to /c/42/3/path/to/file.txt');
+ leftSelectEl.value = '1';
+ element.fire('change', {}, {node: leftSelectEl});
+ } else if (numEvents == 2) {
+ assert(showStub.lastCall.calledWithExactly(
+ '/c/42/1..3/path/to/file.txt'),
+ 'Should navigate to /c/42/1..3/path/to/file.txt');
+ showStub.restore();
+ done();
+ }
+ });
+ leftSelectEl.value = 'PARENT';
+ rightSelectEl.value = '3';
+ element.fire('change', {}, {node: leftSelectEl});
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 42cd24f..1ba06b0 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -33,6 +33,8 @@
'gr-diff-side-test.html',
'gr-diff-test.html',
'gr-diff-view-test.html',
+ 'gr-file-list-test.html',
+ 'gr-patch-range-select-test.html',
'gr-reply-dropdown-test.html',
'gr-search-bar-test.html',
].forEach(function(file) {