Merge "Don't show next arrow when there are not more changes on next page"
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index f7431ca..7c35b12 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -229,7 +229,7 @@
To run one or more specific groups of tests:
----
- bazel test --test_tag_filters=api,git //..
+ bazel test --test_tag_filters=api,git //...
----
The following values are currently supported for the group name:
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index f69b955..e2be6e6 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -2862,6 +2862,8 @@
the signature.
|`tagger`|Only set for annotated tags, if present in the tag.|The tagger as a
link:rest-api-changes.html#git-person-info[GitPersonInfo] entity.
+|`can_delete`|`false` if not set|
+Whether the calling user can delete this tag.
|=========================
[[tag-input]]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 2b5702e..9775c40 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -118,6 +118,12 @@
special case of `reviewer:self` will find changes where the caller
has been added as a reviewer.
+[[cc]]
+cc:'USER'::
++
+Changes that have the given user CC'ed on them. The special case of `cc:self`
+will find changes where the caller has been CC'ed.
+
[[reviewerin]]
reviewerin:'GROUP'::
+
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index db5a300..e6f2a79 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -48,6 +48,7 @@
"//lib/httpcomponents:httpcore",
"//lib/jetty:servlet",
"//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib:jimfs",
"//lib/log:impl_log4j",
"//lib/log:log4j",
],
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 2cc64d8..1e0065f 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
@@ -29,8 +29,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
+import com.google.common.jimfs.Jimfs;
import com.google.common.primitives.Chars;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
import com.google.gerrit.common.Nullable;
@@ -107,10 +107,14 @@
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -121,8 +125,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -997,6 +999,7 @@
protected void grantTagPermissions() throws Exception {
grant(Permission.CREATE, project, R_TAGS + "*");
+ grant(Permission.DELETE, project, R_TAGS + "");
grant(Permission.CREATE_TAG, project, R_TAGS + "*");
grant(Permission.CREATE_SIGNED_TAG, project, R_TAGS + "*");
}
@@ -1033,33 +1036,51 @@
return ca;
}
+ protected BinaryResult submitPreview(String changeId) throws Exception {
+ return gApi.changes().id(changeId).current().submitPreview();
+ }
+
+ protected BinaryResult submitPreview(String changeId, String format) throws Exception {
+ return gApi.changes().id(changeId).current().submitPreview(format);
+ }
+
+ protected Map<Branch.NameKey, ObjectId> fetchFromSubmitPreview(String changeId) throws Exception {
+ try (BinaryResult result = submitPreview(changeId)) {
+ return fetchFromBundles(result);
+ }
+ }
+
/**
* Fetches each bundle into a newly cloned repository, then it applies the bundle, and returns the
* resulting tree id.
*/
- protected Map<Branch.NameKey, RevTree> fetchFromBundles(BinaryResult bundles) throws Exception {
-
+ protected Map<Branch.NameKey, ObjectId> fetchFromBundles(BinaryResult bundles) throws Exception {
assertThat(bundles.getContentType()).isEqualTo("application/x-zip");
- File tempfile = File.createTempFile("test", null);
- bundles.writeTo(new FileOutputStream(tempfile));
-
- Map<Branch.NameKey, RevTree> ret = new HashMap<>();
- try (ZipFile readback = new ZipFile(tempfile); ) {
- for (ZipEntry entry : ImmutableList.copyOf(Iterators.forEnumeration(readback.entries()))) {
- String bundleName = entry.getName();
- InputStream bundleStream = readback.getInputStream(entry);
-
+ FileSystem fs = Jimfs.newFileSystem();
+ Path previewPath = fs.getPath("preview.zip");
+ try (OutputStream out = Files.newOutputStream(previewPath)) {
+ bundles.writeTo(out);
+ }
+ Map<Branch.NameKey, ObjectId> ret = new HashMap<>();
+ try (FileSystem zipFs = FileSystems.newFileSystem(previewPath, null);
+ DirectoryStream<Path> dirStream =
+ Files.newDirectoryStream(Iterables.getOnlyElement(zipFs.getRootDirectories()))) {
+ for (Path p : dirStream) {
+ if (!Files.isRegularFile(p)) {
+ continue;
+ }
+ String bundleName = p.getFileName().toString();
int len = bundleName.length();
assertThat(bundleName).endsWith(".git");
String repoName = bundleName.substring(0, len - 4);
Project.NameKey proj = new Project.NameKey(repoName);
TestRepository<?> localRepo = cloneProject(proj);
- try (TransportBundleStream tbs =
- new TransportBundleStream(
- localRepo.getRepository(), new URIish(bundleName), bundleStream); ) {
-
+ try (InputStream bundleStream = Files.newInputStream(p);
+ TransportBundleStream tbs =
+ new TransportBundleStream(
+ localRepo.getRepository(), new URIish(bundleName), bundleStream)) {
FetchResult fr =
tbs.fetch(
NullProgressMonitor.INSTANCE,
@@ -1069,16 +1090,17 @@
Branch.NameKey n = new Branch.NameKey(proj, branchName);
RevCommit c = localRepo.getRevWalk().parseCommit(r.getObjectId());
- ret.put(n, c.getTree());
+ ret.put(n, c.getTree().copy());
}
}
}
}
+ assertThat(ret).isNotEmpty();
return ret;
}
/** Assert that the given branches have the given tree ids. */
- protected void assertRevTrees(Project.NameKey proj, Map<Branch.NameKey, RevTree> trees)
+ protected void assertTrees(Project.NameKey proj, Map<Branch.NameKey, ObjectId> trees)
throws Exception {
TestRepository<?> localRepo = cloneProject(proj);
GitUtil.fetch(localRepo, "refs/*:refs/*");
diff --git a/gerrit-acceptance-tests/BUILD b/gerrit-acceptance-tests/BUILD
index 3154b1f..4c1a62d 100644
--- a/gerrit-acceptance-tests/BUILD
+++ b/gerrit-acceptance-tests/BUILD
@@ -27,6 +27,7 @@
"//lib:gwtjsonrpc",
"//lib:gwtorm",
"//lib:h2",
+ "//lib:jimfs",
"//lib:jsch",
"//lib:servlet-api-3_1-without-neverlink",
"//lib/bouncycastle:bcpg",
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 0559cb0..8fa80dd 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
@@ -88,6 +88,7 @@
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -328,8 +329,22 @@
gApi.changes().id(r.getChangeId()).revert();
}
+ @FunctionalInterface
+ private interface Rebase {
+ void call(String id) throws RestApiException;
+ }
+
@Test
- public void rebase() throws Exception {
+ public void rebaseViaRevisionApi() throws Exception {
+ testRebase(id -> gApi.changes().id(id).current().rebase());
+ }
+
+ @Test
+ public void rebaseViaChangeApi() throws Exception {
+ testRebase(id -> gApi.changes().id(id).rebase());
+ }
+
+ private void testRebase(Rebase rebase) throws Exception {
// Create two changes both with the same parent
PushOneCommit.Result r = createChange();
testRepo.reset("HEAD~1");
@@ -342,7 +357,7 @@
String changeId = r2.getChangeId();
// Rebase the second change
- gApi.changes().id(changeId).current().rebase();
+ rebase.call(changeId);
// Second change should have 2 patch sets
ChangeInfo c2 = gApi.changes().id(changeId).get();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index cfc5937..5dceff9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.testutil.ConfigSuite;
@@ -31,7 +30,6 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.transport.RefSpec;
import org.junit.Test;
@@ -136,10 +134,7 @@
gApi.changes().id(id2).current().review(ReviewInput.approve());
gApi.changes().id(id3).current().review(ReviewInput.approve());
- Map<Branch.NameKey, RevTree> preview;
- try (BinaryResult request = gApi.changes().id(id1).current().submitPreview()) {
- preview = fetchFromBundles(request);
- }
+ Map<Branch.NameKey, ObjectId> preview = fetchFromSubmitPreview(id1);
gApi.changes().id(id1).current().submit();
ObjectId subRepoId =
subRepo
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 81f136d..6344cbe 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -93,7 +93,6 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.After;
import org.junit.Before;
@@ -150,27 +149,21 @@
public void submitToEmptyRepo() throws Exception {
RevCommit initialHead = getRemoteHead();
PushOneCommit.Result change = createChange();
- Map<Branch.NameKey, RevTree> actual;
- try (BinaryResult request = submitPreview(change.getChangeId())) {
- actual = fetchFromBundles(request);
- }
+ Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
RevCommit headAfterSubmitPreview = getRemoteHead();
assertThat(headAfterSubmitPreview).isEqualTo(initialHead);
assertThat(actual).hasSize(1);
submit(change.getChangeId());
assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit());
- assertRevTrees(project, actual);
+ assertTrees(project, actual);
}
@Test
public void submitSingleChange() throws Exception {
RevCommit initialHead = getRemoteHead();
PushOneCommit.Result change = createChange();
- Map<Branch.NameKey, RevTree> actual;
- try (BinaryResult request = submitPreview(change.getChangeId())) {
- actual = fetchFromBundles(request);
- }
+ Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
RevCommit headAfterSubmit = getRemoteHead();
assertThat(headAfterSubmit).isEqualTo(initialHead);
assertRefUpdatedEvents();
@@ -185,7 +178,7 @@
}
submit(change.getChangeId());
- assertRevTrees(project, actual);
+ assertTrees(project, actual);
}
@Test
@@ -205,57 +198,59 @@
try (BinaryResult request = submitPreview(change4.getChangeId())) {
assertThat(getSubmitType()).isEqualTo(SubmitType.CHERRY_PICK);
- Map<Branch.NameKey, RevTree> s = fetchFromBundles(request);
submit(change4.getChangeId());
- assertRevTrees(project, s);
} catch (RestApiException e) {
switch (getSubmitType()) {
case FAST_FORWARD_ONLY:
- assertThat(e.getMessage()).isEqualTo(
- "Failed to submit 3 changes due to the following problems:\n"
- + "Change "
- + change2.getChange().getId()
- + ": internal error: "
- + "change not processed by merge strategy\n"
- + "Change "
- + change3.getChange().getId()
- + ": internal error: "
- + "change not processed by merge strategy\n"
- + "Change "
- + change4.getChange().getId()
- + ": Project policy "
- + "requires all submissions to be a fast-forward. Please "
- + "rebase the change locally and upload again for review.");
+ assertThat(e.getMessage())
+ .isEqualTo(
+ "Failed to submit 3 changes due to the following problems:\n"
+ + "Change "
+ + change2.getChange().getId()
+ + ": internal error: "
+ + "change not processed by merge strategy\n"
+ + "Change "
+ + change3.getChange().getId()
+ + ": internal error: "
+ + "change not processed by merge strategy\n"
+ + "Change "
+ + change4.getChange().getId()
+ + ": Project policy "
+ + "requires all submissions to be a fast-forward. Please "
+ + "rebase the change locally and upload again for review.");
break;
case REBASE_IF_NECESSARY:
case REBASE_ALWAYS:
String change2hash = change2.getChange().currentPatchSet().getRevision().get();
- assertThat(e.getMessage()).isEqualTo(
- "Cannot rebase "
- + change2hash
- + ": The change could "
- + "not be rebased due to a conflict during merge.");
+ assertThat(e.getMessage())
+ .isEqualTo(
+ "Cannot rebase "
+ + change2hash
+ + ": The change could "
+ + "not be rebased due to a conflict during merge.");
break;
case MERGE_ALWAYS:
case MERGE_IF_NECESSARY:
- assertThat(e.getMessage()).isEqualTo(
- "Failed to submit 3 changes due to the following problems:\n"
- + "Change "
- + change2.getChange().getId()
- + ": Change could not be "
- + "merged due to a path conflict. Please rebase the change "
- + "locally and upload the rebased commit for review.\n"
- + "Change "
- + change3.getChange().getId()
- + ": Change could not be "
- + "merged due to a path conflict. Please rebase the change "
- + "locally and upload the rebased commit for review.\n"
- + "Change "
- + change4.getChange().getId()
- + ": Change could not be "
- + "merged due to a path conflict. Please rebase the change "
- + "locally and upload the rebased commit for review.");
+ assertThat(e.getMessage())
+ .isEqualTo(
+ "Failed to submit 3 changes due to the following problems:\n"
+ + "Change "
+ + change2.getChange().getId()
+ + ": Change could not be "
+ + "merged due to a path conflict. Please rebase the change "
+ + "locally and upload the rebased commit for review.\n"
+ + "Change "
+ + change3.getChange().getId()
+ + ": Change could not be "
+ + "merged due to a path conflict. Please rebase the change "
+ + "locally and upload the rebased commit for review.\n"
+ + "Change "
+ + change4.getChange().getId()
+ + ": Change could not be "
+ + "merged due to a path conflict. Please rebase the change "
+ + "locally and upload the rebased commit for review.");
break;
+ case CHERRY_PICK:
default:
fail("Should not reach here.");
break;
@@ -276,10 +271,7 @@
PushOneCommit.Result change4 = createChange("Change 4", "e", "e");
// change 2 is not approved, but we ignore labels
approve(change3.getChangeId());
- Map<Branch.NameKey, RevTree> actual;
- try (BinaryResult request = submitPreview(change4.getChangeId())) {
- actual = fetchFromBundles(request);
- }
+ Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change4.getChangeId());
Map<String, Map<String, Integer>> expected = new HashMap<>();
expected.put(project.get(), new HashMap<>());
expected.get(project.get()).put("refs/heads/master", 3);
@@ -306,7 +298,7 @@
// now check we actually have the same content:
approve(change2.getChangeId());
submit(change4.getChangeId());
- assertRevTrees(project, actual);
+ assertTrees(project, actual);
}
@Test
@@ -842,14 +834,6 @@
assertMerged(change.changeId);
}
- protected BinaryResult submitPreview(String changeId) throws Exception {
- return gApi.changes().id(changeId).current().submitPreview();
- }
-
- protected BinaryResult submitPreview(String changeId, String format) throws Exception {
- return gApi.changes().id(changeId).current().submitPreview(format);
- }
-
protected void assertSubmittable(String changeId) throws Exception {
assertThat(get(changeId, SUBMITTABLE).submittable)
.named("submit bit on ChangeInfo")
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index 96b982d..04151e9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -41,8 +41,8 @@
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.transport.RefSpec;
import org.junit.Test;
@@ -171,10 +171,7 @@
approve(change3.getChangeId());
// get a preview before submitting:
- Map<Branch.NameKey, RevTree> preview;
- try (BinaryResult request = submitPreview(change1b.getChangeId())) {
- preview = fetchFromBundles(request);
- }
+ Map<Branch.NameKey, ObjectId> preview = fetchFromSubmitPreview(change1b.getChangeId());
submit(change1b.getChangeId());
RevCommit tip1 = getRemoteLog(p1, "master").get(0);
@@ -191,13 +188,13 @@
assertThat(preview).hasSize(3);
assertThat(preview).containsKey(new Branch.NameKey(p1, "refs/heads/master"));
- assertRevTrees(p1, preview);
+ assertTrees(p1, preview);
assertThat(preview).containsKey(new Branch.NameKey(p2, "refs/heads/master"));
- assertRevTrees(p2, preview);
+ assertTrees(p2, preview);
assertThat(preview).containsKey(new Branch.NameKey(p3, "refs/heads/master"));
- assertRevTrees(p3, preview);
+ assertTrees(p3, preview);
} else {
assertThat(tip2.getShortMessage()).isEqualTo(initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(initialHead3.getShortMessage());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
index 03459da..f191681 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -170,11 +170,17 @@
TagInfo result = tag(input.ref).create(input).get();
assertThat(result.ref).isEqualTo(R_TAGS + input.ref);
assertThat(result.revision).isEqualTo(input.revision);
+ assertThat(result.canDelete).isTrue();
input.ref = "refs/tags/v2.0";
result = tag(input.ref).create(input).get();
assertThat(result.ref).isEqualTo(input.ref);
assertThat(result.revision).isEqualTo(input.revision);
+ assertThat(result.canDelete).isTrue();
+
+ setApiUser(user);
+ result = tag(input.ref).get();
+ assertThat(result.canDelete).isFalse();
eventRecorder.assertRefUpdatedEvents(project.get(), result.ref, null, result.revision);
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 8d14326..27fdc18 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -114,6 +114,12 @@
/** Publishes a draft change. */
void publish() throws RestApiException;
+ /** Rebase the current revision of a change using default options. */
+ void rebase() throws RestApiException;
+
+ /** Rebase the current revision of a change. */
+ void rebase(RebaseInput in) throws RestApiException;
+
/** Deletes a change. */
void delete() throws RestApiException;
@@ -316,6 +322,16 @@
}
@Override
+ public void rebase() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void rebase(RebaseInput in) {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void delete() {
throw new NotImplementedException();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java
index 2b57781..ccfea46 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java
@@ -21,13 +21,20 @@
public String message;
public GitPerson tagger;
- public TagInfo(String ref, String revision) {
+ public TagInfo(String ref, String revision, boolean canDelete) {
this.ref = ref;
this.revision = revision;
+ this.canDelete = canDelete;
}
- public TagInfo(String ref, String revision, String object, String message, GitPerson tagger) {
- this(ref, revision);
+ public TagInfo(
+ String ref,
+ String revision,
+ String object,
+ String message,
+ GitPerson tagger,
+ boolean canDelete) {
+ this(ref, revision, canDelete);
this.object = object;
this.message = message;
this.tagger = tagger;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 7d22e9b..b44cd1c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -49,10 +49,18 @@
String branchCreationConfirmationMessage();
+ String tagCreationDialogTitle();
+
+ String tagCreationConfirmationMessage();
+
String branchDeletionDialogTitle();
String branchDeletionConfirmationMessage();
+ String tagDeletionDialogTitle();
+
+ String tagDeletionConfirmationMessage();
+
String newUi();
String notSignedInTitle();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index b4665b2..9aa4388 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -20,9 +20,15 @@
branchCreationDialogTitle = Branch Creation
branchCreationConfirmationMessage = The following branch was successfully created:
+tagCreationDialogTitle = Tag Creation
+tagCreationConfirmationMessage = The following tag was successfully created:
+
branchDeletionDialogTitle = Branch Deletion
branchDeletionConfirmationMessage = Do you really want to delete the following branches?
+tagDeletionDialogTitle = Tag Deletion
+tagDeletionConfirmationMessage = Do you really want to delete the following tags?
+
newUi = New UI
notSignedInTitle = Code Review - Session Expired
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 165d0ca..5c0508e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -40,7 +40,8 @@
"author:",
"committer:",
"from:",
- "assignee:"),
+ "assignee:",
+ "cc:"),
new AccountSuggestOracle() {
@Override
public void onRequestSuggestions(final Request request, final Callback done) {
@@ -152,6 +153,7 @@
suggestions.add("unresolved:");
if (Gerrit.isNoteDbEnabled()) {
+ suggestions.add("cc:");
suggestions.add("hashtag:");
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index b8da3a7..d7fb072 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -27,6 +27,8 @@
String defaultBranchName();
+ String defaultTagName();
+
String defaultRevisionSpec();
String buttonDeleteIncludedGroup();
@@ -171,18 +173,24 @@
String columnBranchRevision();
+ String columnTagName();
+
+ String columnTagRevision();
+
String initialRevision();
String buttonAddBranch();
String buttonDeleteBranch();
+ String buttonAddTag();
+
+ String buttonDeleteTag();
+
String saveHeadButton();
String cancelHeadButton();
- String columnTagName();
-
String groupItemHelp();
String groupListTitle();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 2f13a50..465bcfc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -1,6 +1,7 @@
defaultAccountName = Name or Email
defaultAccountGroupName = Group Name
defaultBranchName = Branch Name
+defaultTagName = Tag Name
defaultRevisionSpec = Revision (Branch or SHA-1)
buttonDeleteIncludedGroup = Delete
@@ -81,12 +82,15 @@
columnBranchName = Branch Name
columnBranchRevision = Revision
+columnTagName = Tag Name
+columnTagRevision = Revision
initialRevision = Initial Revision
buttonAddBranch = Create Branch
+buttonAddTag = Create Tag
buttonDeleteBranch = Delete
+buttonDeleteTag = Delete
saveHeadButton = Save
cancelHeadButton = Cancel
-columnTagName = Tag Name
groupItemHelp = group
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
index e64f569..b89139c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
@@ -16,32 +16,63 @@
import static com.google.gerrit.client.ui.Util.highlight;
+import com.google.gerrit.client.ConfirmationCallback;
+import com.google.gerrit.client.ConfirmationDialog;
+import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.access.AccessMap;
+import com.google.gerrit.client.access.ProjectAccessInfo;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.projects.TagInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.PagingHyperlink;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.globalkey.client.NpTextBox;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public class ProjectTagsScreen extends PaginatedProjectScreen {
- private NpTextBox filterTxt;
- private Query query;
private Hyperlink prev;
private Hyperlink next;
- private TagsTable tagsTable;
+ private TagsTable tagTable;
+ private Button delTag;
+ private Button addTag;
+ private HintTextBox nameTxtBox;
+ private HintTextBox irevTxtBox;
+ private FlowPanel addPanel;
+ private NpTextBox filterTxt;
+ private Query query;
public ProjectTagsScreen(Project.NameKey toShow) {
super(toShow);
@@ -53,30 +84,106 @@
}
@Override
+ protected void onLoad() {
+ super.onLoad();
+ addPanel.setVisible(false);
+ AccessMap.get(
+ getProjectKey(),
+ new GerritCallback<ProjectAccessInfo>() {
+ @Override
+ public void onSuccess(ProjectAccessInfo result) {
+ addPanel.setVisible(result.canAddRefs());
+ }
+ });
+ query = new Query(match).start(start).run();
+ savedPanel = TAGS;
+ }
+
+ private void updateForm() {
+ tagTable.updateDeleteButton();
+ addTag.setEnabled(true);
+ nameTxtBox.setEnabled(true);
+ irevTxtBox.setEnabled(true);
+ }
+
+ @Override
protected void onInitUI() {
super.onInitUI();
initPageHeader();
+
prev = PagingHyperlink.createPrev();
prev.setVisible(false);
next = PagingHyperlink.createNext();
next.setVisible(false);
- tagsTable = new TagsTable();
+ addPanel = new FlowPanel();
+
+ Grid addGrid = new Grid(2, 2);
+ addGrid.setStyleName(Gerrit.RESOURCES.css().addBranch());
+ int texBoxLength = 50;
+
+ nameTxtBox = new HintTextBox();
+ nameTxtBox.setVisibleLength(texBoxLength);
+ nameTxtBox.setHintText(AdminConstants.I.defaultTagName());
+ nameTxtBox.addKeyPressHandler(
+ new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ doAddNewTag();
+ }
+ }
+ });
+ addGrid.setText(0, 0, AdminConstants.I.columnTagName() + ":");
+ addGrid.setWidget(0, 1, nameTxtBox);
+
+ irevTxtBox = new HintTextBox();
+ irevTxtBox.setVisibleLength(texBoxLength);
+ irevTxtBox.setHintText(AdminConstants.I.defaultRevisionSpec());
+ irevTxtBox.addKeyPressHandler(
+ new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ doAddNewTag();
+ }
+ }
+ });
+ addGrid.setText(1, 0, AdminConstants.I.initialRevision() + ":");
+ addGrid.setWidget(1, 1, irevTxtBox);
+
+ addTag = new Button(AdminConstants.I.buttonAddTag());
+ addTag.addClickHandler(
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ doAddNewTag();
+ }
+ });
+ addPanel.add(addGrid);
+ addPanel.add(addTag);
+
+ tagTable = new TagsTable();
+
+ delTag = new Button(AdminConstants.I.buttonDeleteTag());
+ delTag.setStyleName(Gerrit.RESOURCES.css().branchTableDeleteButton());
+ delTag.addClickHandler(
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ tagTable.deleteChecked();
+ }
+ });
HorizontalPanel buttons = new HorizontalPanel();
buttons.setStyleName(Gerrit.RESOURCES.css().branchTablePrevNextLinks());
+ buttons.add(delTag);
buttons.add(prev);
buttons.add(next);
- add(tagsTable);
+ add(tagTable);
add(buttons);
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- query = new Query(match).start(start).run();
- savedPanel = TAGS;
+ add(addPanel);
}
private void initPageHeader() {
@@ -95,8 +202,10 @@
Query q = new Query(filterTxt.getValue());
if (match.equals(q.qMatch)) {
q.start(start);
- } else if (query == null) {
- q.run();
+ } else {
+ if (query == null) {
+ q.run();
+ }
query = q;
}
}
@@ -105,16 +214,184 @@
add(hp);
}
+ private void doAddNewTag() {
+ String tagName = nameTxtBox.getText().trim();
+ if (tagName.isEmpty()) {
+ nameTxtBox.setFocus(true);
+ return;
+ }
+
+ String rev = irevTxtBox.getText().trim();
+ if (rev.isEmpty()) {
+ irevTxtBox.setText("HEAD");
+ Scheduler.get()
+ .scheduleDeferred(
+ new ScheduledCommand() {
+ @Override
+ public void execute() {
+ irevTxtBox.selectAll();
+ irevTxtBox.setFocus(true);
+ }
+ });
+ return;
+ }
+
+ addTag.setEnabled(false);
+ ProjectApi.createTag(
+ getProjectKey(),
+ tagName,
+ rev,
+ new GerritCallback<TagInfo>() {
+ @Override
+ public void onSuccess(TagInfo tag) {
+ showAddedTag(tag);
+ nameTxtBox.setText("");
+ irevTxtBox.setText("");
+ query = new Query(match).start(start).run();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ addTag.setEnabled(true);
+ selectAllAndFocus(nameTxtBox);
+ new ErrorDialog(caught.getMessage()).center();
+ }
+ });
+ }
+
+ void showAddedTag(TagInfo tag) {
+ SafeHtmlBuilder b = new SafeHtmlBuilder();
+ b.openElement("b");
+ b.append(Gerrit.C.tagCreationConfirmationMessage());
+ b.closeElement("b");
+
+ b.openElement("p");
+ b.append(tag.ref());
+ b.closeElement("p");
+
+ ConfirmationDialog confirmationDialog =
+ new ConfirmationDialog(
+ Gerrit.C.tagCreationDialogTitle(),
+ b.toSafeHtml(),
+ new ConfirmationCallback() {
+ @Override
+ public void onOk() {
+ //do nothing
+ }
+ });
+ confirmationDialog.center();
+ confirmationDialog.setCancelVisible(false);
+ }
+
+ private static void selectAllAndFocus(TextBox textBox) {
+ textBox.selectAll();
+ textBox.setFocus(true);
+ }
+
private class TagsTable extends NavigationTable<TagInfo> {
+ private ValueChangeHandler<Boolean> updateDeleteHandler;
+ boolean canDelete;
TagsTable() {
table.setWidth("");
- table.setText(0, 1, AdminConstants.I.columnTagName());
- table.setText(0, 2, AdminConstants.I.columnBranchRevision());
+ table.setText(0, 2, AdminConstants.I.columnTagName());
+ table.setText(0, 3, AdminConstants.I.columnTagRevision());
FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
+
+ updateDeleteHandler =
+ new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event) {
+ updateDeleteButton();
+ }
+ };
+ }
+
+ Set<String> getCheckedRefs() {
+ Set<String> refs = new HashSet<>();
+ for (int row = 1; row < table.getRowCount(); row++) {
+ TagInfo k = getRowItem(row);
+ if (k != null
+ && table.getWidget(row, 1) instanceof CheckBox
+ && ((CheckBox) table.getWidget(row, 1)).getValue()) {
+ refs.add(k.ref());
+ }
+ }
+ return refs;
+ }
+
+ void setChecked(Set<String> refs) {
+ for (int row = 1; row < table.getRowCount(); row++) {
+ TagInfo k = getRowItem(row);
+ if (k != null && refs.contains(k.ref()) && table.getWidget(row, 1) instanceof CheckBox) {
+ ((CheckBox) table.getWidget(row, 1)).setValue(true);
+ }
+ }
+ }
+
+ void deleteChecked() {
+ final Set<String> refs = getCheckedRefs();
+
+ SafeHtmlBuilder b = new SafeHtmlBuilder();
+ b.openElement("b");
+ b.append(Gerrit.C.tagDeletionConfirmationMessage());
+ b.closeElement("b");
+
+ b.openElement("p");
+ boolean first = true;
+ for (String ref : refs) {
+ if (!first) {
+ b.append(",").br();
+ }
+ b.append(ref);
+ first = false;
+ }
+ b.closeElement("p");
+
+ if (refs.isEmpty()) {
+ updateDeleteButton();
+ return;
+ }
+
+ delTag.setEnabled(false);
+ ConfirmationDialog confirmationDialog =
+ new ConfirmationDialog(
+ Gerrit.C.tagDeletionDialogTitle(),
+ b.toSafeHtml(),
+ new ConfirmationCallback() {
+ @Override
+ public void onOk() {
+ deleteTags(refs);
+ }
+
+ @Override
+ public void onCancel() {
+ tagTable.updateDeleteButton();
+ }
+ });
+ confirmationDialog.center();
+ }
+
+ private void deleteTags(final Set<String> tags) {
+ ProjectApi.deleteTags(
+ getProjectKey(),
+ tags,
+ new GerritCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ query = new Query(match).start(start).run();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ query = new Query(match).start(start).run();
+ super.onFailure(caught);
+ }
+ });
}
void display(List<TagInfo> tags) {
@@ -122,6 +399,8 @@
}
void displaySubset(List<TagInfo> tags, int fromIndex, int toIndex) {
+ canDelete = false;
+
while (1 < table.getRowCount()) {
table.removeRow(table.getRowCount() - 1);
}
@@ -135,22 +414,52 @@
}
void populate(int row, TagInfo k) {
- table.setWidget(row, 1, new InlineHTML(highlight(k.getShortName(), match)));
+ if (k.canDelete()) {
+ CheckBox sel = new CheckBox();
+ sel.addValueChangeHandler(updateDeleteHandler);
+ table.setWidget(row, 1, sel);
+ canDelete = true;
+ } else {
+ table.setText(row, 1, "");
+ }
+
+ table.setWidget(row, 2, new InlineHTML(highlight(k.getShortName(), match)));
if (k.revision() != null) {
- table.setText(row, 2, k.revision());
+ table.setText(row, 3, k.revision());
} else {
- table.setText(row, 2, "");
+ table.setText(row, 3, "");
}
FlexCellFormatter fmt = table.getFlexCellFormatter();
+ String iconCellStyle = Gerrit.RESOURCES.css().iconCell();
String dataCellStyle = Gerrit.RESOURCES.css().dataCell();
- fmt.addStyleName(row, 1, dataCellStyle);
+ fmt.addStyleName(row, 1, iconCellStyle);
fmt.addStyleName(row, 2, dataCellStyle);
+ fmt.addStyleName(row, 3, dataCellStyle);
setRowItem(row, k);
}
+ boolean hasTagCanDelete() {
+ return canDelete;
+ }
+
+ void updateDeleteButton() {
+ boolean on = false;
+ for (int row = 1; row < table.getRowCount(); row++) {
+ Widget w = table.getWidget(row, 1);
+ if (w != null && w instanceof CheckBox) {
+ CheckBox sel = (CheckBox) w;
+ if (sel.getValue()) {
+ on = true;
+ break;
+ }
+ }
+ }
+ delTag.setEnabled(on);
+ }
+
@Override
protected void onOpenRow(int row) {
if (row > 0) {
@@ -216,10 +525,10 @@
ProjectTagsScreen.this.start = qStart;
if (result.length() <= pageSize) {
- tagsTable.display(Natives.asList(result));
+ tagTable.display(Natives.asList(result));
next.setVisible(false);
} else {
- tagsTable.displaySubset(Natives.asList(result), 0, result.length() - 1);
+ tagTable.displaySubset(Natives.asList(result), 0, result.length() - 1);
setupNavigationLink(next, qMatch, qStart + pageSize);
}
if (qStart > 0) {
@@ -228,6 +537,11 @@
prev.setVisible(false);
}
+ delTag.setVisible(tagTable.hasTagCanDelete());
+ Set<String> checkedRefs = tagTable.getCheckedRefs();
+ tagTable.setChecked(checkedRefs);
+ updateForm();
+
if (!isCurrentView()) {
display();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index d1343df..4be877e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -60,6 +60,14 @@
return call;
}
+ /** Create a new tag */
+ public static void createTag(
+ Project.NameKey name, String ref, String revision, AsyncCallback<TagInfo> cb) {
+ TagInput input = TagInput.create();
+ input.setRevision(revision);
+ project(name).view("tags").id(ref).ifNoneMatch().put(input, cb);
+ }
+
/** Retrieve all visible tags of the project */
public static void getTags(Project.NameKey name, AsyncCallback<JsArray<TagInfo>> cb) {
project(name).view("tags").get(cb);
@@ -74,6 +82,20 @@
getRestApi(name, "tags", limit, start, match).get(cb);
}
+ /** Delete tags. One call is fired to the server to delete all the tags. */
+ public static void deleteTags(
+ Project.NameKey name, Set<String> refs, AsyncCallback<VoidResult> cb) {
+ if (refs.size() == 1) {
+ project(name).view("tags").id(refs.iterator().next()).delete(cb);
+ } else {
+ DeleteTagsInput d = DeleteTagsInput.create();
+ for (String ref : refs) {
+ d.addTag(ref);
+ }
+ project(name).view("tags:delete").post(d, cb);
+ }
+ }
+
/** Create a new branch */
public static void createBranch(
Project.NameKey name, String ref, String revision, AsyncCallback<BranchInfo> cb) {
@@ -325,6 +347,16 @@
public final native void put(String n, ConfigParameterValue v) /*-{ this[n] = v; }-*/;
}
+ private static class TagInput extends JavaScriptObject {
+ static TagInput create() {
+ return (TagInput) createObject();
+ }
+
+ protected TagInput() {}
+
+ final native void setRevision(String r) /*-{ if(r)this.revision=r; }-*/;
+ }
+
private static class BranchInput extends JavaScriptObject {
static BranchInput create() {
return (BranchInput) createObject();
@@ -355,6 +387,20 @@
final native void setRef(String r) /*-{ if(r)this.ref=r; }-*/;
}
+ private static class DeleteTagsInput extends JavaScriptObject {
+ static DeleteTagsInput create() {
+ DeleteTagsInput d = createObject().cast();
+ d.init();
+ return d;
+ }
+
+ protected DeleteTagsInput() {}
+
+ final native void init() /*-{ this.tags = []; }-*/;
+
+ final native void addTag(String b) /*-{ this.tags.push(b); }-*/;
+ }
+
private static class DeleteBranchesInput extends JavaScriptObject {
static DeleteBranchesInput create() {
DeleteBranchesInput d = createObject().cast();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
index 1657b25..24487ae 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
@@ -15,6 +15,7 @@
package com.google.gerrit.client.projects;
public class TagInfo extends RefInfo {
+ public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
// TODO(dpursehouse) add extra tag-related fields (message, tagger, etc)
protected TagInfo() {}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 9a294c9..ebed0f9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -117,7 +117,11 @@
getClass().getSimpleName() + " is not an IdentifiedUser");
}
- /** Return account ID if {@link #isIdentifiedUser} is true. */
+ /**
+ * Return account ID if {@link #isIdentifiedUser} is true.
+ *
+ * @throws UnsupportedOperationException if the user is not logged in.
+ */
public Account.Id getAccountId() {
throw new UnsupportedOperationException(
getClass().getSimpleName() + " is not an IdentifiedUser");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 52353ca..5c398ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.api.changes;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AssigneeInput;
@@ -24,6 +25,7 @@
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.api.changes.MoveInput;
+import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.api.changes.RestoreInput;
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.api.changes.ReviewerApi;
@@ -63,6 +65,7 @@
import com.google.gerrit.server.change.PublishDraftPatchSet;
import com.google.gerrit.server.change.PutAssignee;
import com.google.gerrit.server.change.PutTopic;
+import com.google.gerrit.server.change.Rebase;
import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.Revert;
import com.google.gerrit.server.change.Reviewers;
@@ -99,6 +102,7 @@
private final CreateMergePatchSet updateByMerge;
private final Provider<SubmittedTogether> submittedTogether;
private final PublishDraftPatchSet.CurrentRevision publishDraftChange;
+ private final Rebase.CurrentRevision rebase;
private final DeleteChange deleteChange;
private final GetTopic getTopic;
private final PutTopic putTopic;
@@ -133,6 +137,7 @@
CreateMergePatchSet updateByMerge,
Provider<SubmittedTogether> submittedTogether,
PublishDraftPatchSet.CurrentRevision publishDraftChange,
+ Rebase.CurrentRevision rebase,
DeleteChange deleteChange,
GetTopic getTopic,
PutTopic putTopic,
@@ -165,6 +170,7 @@
this.updateByMerge = updateByMerge;
this.submittedTogether = submittedTogether;
this.publishDraftChange = publishDraftChange;
+ this.rebase = rebase;
this.deleteChange = deleteChange;
this.getTopic = getTopic;
this.putTopic = putTopic;
@@ -326,6 +332,20 @@
}
@Override
+ public void rebase() throws RestApiException {
+ rebase(new RebaseInput());
+ }
+
+ @Override
+ public void rebase(RebaseInput in) throws RestApiException {
+ try {
+ rebase.apply(change, in);
+ } catch (EmailException | OrmException | UpdateException | RestApiException | IOException e) {
+ throw new RestApiException("Cannot rebase change", e);
+ }
+ }
+
+ @Override
public void delete() throws RestApiException {
try {
deleteChange.apply(change, null);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
index 3fc9789..90c2daf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
@@ -111,11 +111,13 @@
IdentifiedUser caller = control.getUser().asIdentifiedUser();
Change change = rsrc.getChange();
- final MergeOp op = mergeOpProvider.get();
+ @SuppressWarnings("resource") // Returned BinaryResult takes ownership and handles closing.
+ MergeOp op = mergeOpProvider.get();
try {
op.merge(db, change, caller, false, new SubmitInput(), true);
BinaryResult bin = new SubmitPreviewResult(op, f, maxBundleSize);
- bin.disableGzip().setContentType(f.getMimeType())
+ bin.disableGzip()
+ .setContentType(f.getMimeType())
.setAttachmentName("submit-preview-" + change.getChangeId() + "." + format);
return bin;
} catch (OrmException | RestApiException | RuntimeException e) {
@@ -138,8 +140,7 @@
@Override
public void writeTo(OutputStream out) throws IOException {
- try (ArchiveOutputStream aos = archiveFormat
- .createArchiveOutputStream(out)) {
+ try (ArchiveOutputStream aos = archiveFormat.createArchiveOutputStream(out)) {
MergeOpRepoManager orm = mergeOp.getMergeOpRepoManager();
for (Project.NameKey p : mergeOp.getAllProjects()) {
OpenRepo or = orm.getRepo(p);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index 046712d..608a56f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -29,6 +29,7 @@
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RebaseUtil.Base;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -204,18 +205,19 @@
}
public static class CurrentRevision implements RestModifyView<ChangeResource, RebaseInput> {
+ private final PatchSetUtil psUtil;
private final Rebase rebase;
@Inject
- CurrentRevision(Rebase rebase) {
+ CurrentRevision(PatchSetUtil psUtil, Rebase rebase) {
+ this.psUtil = psUtil;
this.rebase = rebase;
}
@Override
public ChangeInfo apply(ChangeResource rsrc, RebaseInput input)
- throws EmailException, OrmException, UpdateException, RestApiException, IOException,
- NoSuchChangeException {
- PatchSet ps = rebase.dbProvider.get().patchSets().get(rsrc.getChange().currentPatchSetId());
+ throws EmailException, OrmException, UpdateException, RestApiException, IOException {
+ PatchSet ps = psUtil.current(rebase.dbProvider.get(), rsrc.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
} else if (!rsrc.getControl().isPatchVisible(ps, rebase.dbProvider.get())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java
index ed38c47..f674d17 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java
@@ -134,7 +134,7 @@
result.getObjectId(),
identifiedUser.get().getAccount());
try (RevWalk w = new RevWalk(repo)) {
- return ListTags.createTagInfo(result, w);
+ return ListTags.createTagInfo(result, w, refControl);
}
}
} catch (InvalidRevisionException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
index 9312e49..7f1ee60 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
@@ -121,10 +121,11 @@
try (Repository repo = getRepository(resource.getNameKey());
RevWalk rw = new RevWalk(repo)) {
+ ProjectControl control = resource.getControl();
Map<String, Ref> all =
- visibleTags(resource.getControl(), repo, repo.getRefDatabase().getRefs(Constants.R_TAGS));
+ visibleTags(control, repo, repo.getRefDatabase().getRefs(Constants.R_TAGS));
for (Ref ref : all.values()) {
- tags.add(createTagInfo(ref, rw));
+ tags.add(createTagInfo(ref, rw, control.controlForRef(ref.getName())));
}
}
@@ -154,16 +155,16 @@
tagName = Constants.R_TAGS + tagName;
}
Ref ref = repo.getRefDatabase().exactRef(tagName);
+ ProjectControl control = resource.getControl();
if (ref != null
- && !visibleTags(resource.getControl(), repo, ImmutableMap.of(ref.getName(), ref))
- .isEmpty()) {
- return createTagInfo(ref, rw);
+ && !visibleTags(control, repo, ImmutableMap.of(ref.getName(), ref)).isEmpty()) {
+ return createTagInfo(ref, rw, control.controlForRef(ref.getName()));
}
}
throw new ResourceNotFoundException(id);
}
- public static TagInfo createTagInfo(Ref ref, RevWalk rw)
+ public static TagInfo createTagInfo(Ref ref, RevWalk rw, RefControl control)
throws MissingObjectException, IOException {
RevObject object = rw.parseAny(ref.getObjectId());
if (object instanceof RevTag) {
@@ -175,10 +176,11 @@
tag.getName(),
tag.getObject().getName(),
tag.getFullMessage().trim(),
- tagger != null ? CommonConverters.toGitPerson(tag.getTaggerIdent()) : null);
+ tagger != null ? CommonConverters.toGitPerson(tag.getTaggerIdent()) : null,
+ control.canDelete());
}
// Lightweight tag
- return new TagInfo(ref.getName(), ref.getObjectId().getName());
+ return new TagInfo(ref.getName(), ref.getObjectId().getName(), control.canDelete());
}
private Repository getRepository(Project.NameKey project)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
index 1009ce6..8a57c73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
@@ -25,6 +25,7 @@
import static com.google.gerrit.server.query.QueryParser.OR;
import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
+import com.google.common.base.Strings;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -181,6 +182,9 @@
* an invalid value being passed to a recognized operator.
*/
public Predicate<T> parse(final String query) throws QueryParseException {
+ if (Strings.isNullOrEmpty(query)) {
+ throw new QueryParseException("query is empty");
+ }
return toPredicate(QueryParser.parse(query));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index aa220e1..39968f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN;
import static com.google.gerrit.server.query.change.ChangeData.asChanges;
+import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import com.google.common.annotations.VisibleForTesting;
@@ -61,6 +62,7 @@
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeIndexRewriter;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ListChildProjects;
@@ -173,34 +175,35 @@
@VisibleForTesting
public static class Arguments {
- final Provider<ReviewDb> db;
- final Provider<InternalChangeQuery> queryProvider;
- final ChangeIndexRewriter rewriter;
- final DynamicMap<ChangeOperatorFactory> opFactories;
- final DynamicMap<ChangeHasOperandFactory> hasOperands;
- final IdentifiedUser.GenericFactory userFactory;
- final CapabilityControl.Factory capabilityControlFactory;
- final ChangeControl.GenericFactory changeControlGenericFactory;
- final ChangeNotes.Factory notesFactory;
- final ChangeData.Factory changeDataFactory;
- final FieldDef.FillArgs fillArgs;
- final CommentsUtil commentsUtil;
+ final AccountCache accountCache;
final AccountResolver accountResolver;
- final GroupBackend groupBackend;
final AllProjectsName allProjectsName;
final AllUsersName allUsersName;
- final PatchListCache patchListCache;
- final GitRepositoryManager repoManager;
- final ProjectCache projectCache;
- final Provider<ListChildProjects> listChildProjects;
- final SubmitDryRun submitDryRun;
- final ConflictsCache conflictsCache;
- final TrackingFooters trackingFooters;
+ final CapabilityControl.Factory capabilityControlFactory;
+ final ChangeControl.GenericFactory changeControlGenericFactory;
+ final ChangeData.Factory changeDataFactory;
final ChangeIndex index;
+ final ChangeIndexRewriter rewriter;
+ final ChangeNotes.Factory notesFactory;
+ final CommentsUtil commentsUtil;
+ final ConflictsCache conflictsCache;
+ final DynamicMap<ChangeHasOperandFactory> hasOperands;
+ final DynamicMap<ChangeOperatorFactory> opFactories;
+ final FieldDef.FillArgs fillArgs;
+ final GitRepositoryManager repoManager;
+ final GroupBackend groupBackend;
+ final IdentifiedUser.GenericFactory userFactory;
final IndexConfig indexConfig;
+ final NotesMigration notesMigration;
+ final PatchListCache patchListCache;
+ final ProjectCache projectCache;
+ final Provider<InternalChangeQuery> queryProvider;
+ final Provider<ListChildProjects> listChildProjects;
final Provider<ListMembers> listMembers;
+ final Provider<ReviewDb> db;
final StarredChangesUtil starredChangesUtil;
- final AccountCache accountCache;
+ final SubmitDryRun submitDryRun;
+ final TrackingFooters trackingFooters;
final boolean allowsDrafts;
private final Provider<CurrentUser> self;
@@ -237,7 +240,8 @@
Provider<ListMembers> listMembers,
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
- @GerritServerConfig Config cfg) {
+ @GerritServerConfig Config cfg,
+ NotesMigration notesMigration) {
this(
db,
queryProvider,
@@ -268,7 +272,8 @@
listMembers,
starredChangesUtil,
accountCache,
- cfg == null ? true : cfg.getBoolean("change", "allowDrafts", true));
+ cfg == null ? true : cfg.getBoolean("change", "allowDrafts", true),
+ notesMigration);
}
private Arguments(
@@ -301,7 +306,8 @@
Provider<ListMembers> listMembers,
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
- boolean allowsDrafts) {
+ boolean allowsDrafts,
+ NotesMigration notesMigration) {
this.db = db;
this.queryProvider = queryProvider;
this.rewriter = rewriter;
@@ -332,6 +338,7 @@
this.accountCache = accountCache;
this.allowsDrafts = allowsDrafts;
this.hasOperands = hasOperands;
+ this.notesMigration = notesMigration;
}
Arguments asUser(CurrentUser otherUser) {
@@ -365,7 +372,8 @@
listMembers,
starredChangesUtil,
accountCache,
- allowsDrafts);
+ allowsDrafts,
+ notesMigration);
}
Arguments asUser(Account.Id otherId) {
@@ -553,7 +561,11 @@
}
if ("reviewer".equalsIgnoreCase(value)) {
- return ReviewerPredicate.create(args, self());
+ return ReviewerPredicate.reviewer(args, self());
+ }
+
+ if ("cc".equalsIgnoreCase(value)) {
+ return ReviewerPredicate.cc(args, self());
}
if ("mergeable".equalsIgnoreCase(value)) {
@@ -925,12 +937,17 @@
@Operator
public Predicate<ChangeData> reviewer(String who) throws QueryParseException, OrmException {
- Set<Account.Id> m = parseAccount(who);
- List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
- for (Account.Id id : m) {
- p.add(ReviewerPredicate.create(args, id));
- }
- return Predicate.or(p);
+ return Predicate.or(
+ parseAccount(who)
+ .stream()
+ .map(id -> ReviewerPredicate.reviewer(args, id))
+ .collect(toList()));
+ }
+
+ @Operator
+ public Predicate<ChangeData> cc(String who) throws QueryParseException, OrmException {
+ return Predicate.or(
+ parseAccount(who).stream().map(id -> ReviewerPredicate.cc(args, id)).collect(toList()));
}
@Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index 4a11d28..6ce02fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.query.change;
+import static java.util.stream.Collectors.toList;
+
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.change.ChangeField;
@@ -21,32 +23,50 @@
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.stream.Stream;
class ReviewerPredicate extends ChangeIndexPredicate {
- static Predicate<ChangeData> create(Arguments args, Account.Id id) {
- List<Predicate<ChangeData>> and = new ArrayList<>(2);
- ReviewerStateInternal[] states = ReviewerStateInternal.values();
- List<Predicate<ChangeData>> or = new ArrayList<>(states.length - 1);
- for (ReviewerStateInternal state : states) {
- if (state != ReviewerStateInternal.REMOVED) {
- or.add(new ReviewerPredicate(state, id));
- }
+ static Predicate<ChangeData> reviewer(Arguments args, Account.Id id) {
+ Predicate<ChangeData> p;
+ if (args.notesMigration.readChanges()) {
+ // With NoteDb, Reviewer/CC are clearly distinct states, so only choose reviewer.
+ p = new ReviewerPredicate(ReviewerStateInternal.REVIEWER, id);
+ } else {
+ // Without NoteDb, Reviewer/CC are a bit unpredictable; maintain the old behavior of matching
+ // any reviewer state.
+ p = anyReviewerState(id);
}
- and.add(Predicate.or(or));
+ return create(args, p);
+ }
- // TODO(dborowitz): This really belongs much higher up e.g. QueryProcessor.
+ static Predicate<ChangeData> cc(Arguments args, Account.Id id) {
+ // As noted above, CC is nebulous without NoteDb, but it certainly doesn't make sense to return
+ // Reviewers for cc:foo. Most likely this will just not match anything, but let the index sort
+ // it out.
+ return create(args, new ReviewerPredicate(ReviewerStateInternal.CC, id));
+ }
+
+ private static Predicate<ChangeData> anyReviewerState(Account.Id id) {
+ return Predicate.or(
+ Stream.of(ReviewerStateInternal.values())
+ .filter(s -> s != ReviewerStateInternal.REMOVED)
+ .map(s -> new ReviewerPredicate(s, id))
+ .collect(toList()));
+ }
+
+ private static Predicate<ChangeData> create(Arguments args, Predicate<ChangeData> p) {
if (!args.allowsDrafts) {
- and.add(Predicate.not(new ChangeStatusPredicate(Change.Status.DRAFT)));
+ // TODO(dborowitz): This really belongs much higher up e.g. QueryProcessor. Also, why are we
+ // even doing this?
+ return Predicate.and(p, Predicate.not(new ChangeStatusPredicate(Change.Status.DRAFT)));
}
- return Predicate.and(and);
+ return p;
}
private final ReviewerStateInternal state;
private final Account.Id id;
- ReviewerPredicate(ReviewerStateInternal state, Account.Id id) {
+ private ReviewerPredicate(ReviewerStateInternal state, Account.Id id) {
super(ChangeField.REVIEWER, ChangeField.getReviewerFieldValue(state, id));
this.state = state;
this.id = id;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index 3652ec7..6fda100 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -28,7 +28,7 @@
new ChangeQueryBuilder.Arguments(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, indexes, null, null, null, null, null, null,
- null, null));
+ null, null, null));
}
@Operator
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 1b9fc61..6bde106 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -34,6 +34,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.Changes.QueryRequest;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
@@ -43,6 +44,7 @@
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.api.groups.GroupInput;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
@@ -1480,17 +1482,30 @@
}
@Test
- public void reviewer() throws Exception {
+ public void reviewerAndCc() throws Exception {
+ Account.Id user1 = createAccount("user1");
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChange(repo));
Change change2 = insert(repo, newChange(repo));
insert(repo, newChange(repo));
- gApi.changes().id(change1.getId().get()).current().review(ReviewInput.approve());
- gApi.changes().id(change2.getId().get()).current().review(ReviewInput.approve());
+ AddReviewerInput rin = new AddReviewerInput();
+ rin.reviewer = user1.toString();
+ rin.state = ReviewerState.REVIEWER;
+ gApi.changes().id(change1.getId().get()).addReviewer(rin);
- Account.Id id = user.getAccountId();
- assertQuery("reviewer:" + id, change2, change1);
+ rin = new AddReviewerInput();
+ rin.reviewer = user1.toString();
+ rin.state = ReviewerState.CC;
+ gApi.changes().id(change2.getId().get()).addReviewer(rin);
+
+ if (notesMigration.readChanges()) {
+ assertQuery("reviewer:" + user1, change1);
+ assertQuery("cc:" + user1, change2);
+ } else {
+ assertQuery("reviewer:" + user1, change2, change1);
+ assertQuery("cc:" + user1);
+ }
}
@Test
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index 2dc439b..7789c65 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -49,11 +49,7 @@
private static final Logger log = LoggerFactory.getLogger(Receive.class);
@Inject private AsyncReceiveCommits.Factory factory;
-
@Inject private IdentifiedUser currentUser;
-
- @Inject private IdentifiedUser.GenericFactory identifiedUserFactory;
-
@Inject private SshSession session;
private final Set<Account.Id> reviewerId = new HashSet<>();
@@ -92,9 +88,6 @@
throw die(r.getMessage());
}
- verifyProjectVisible("reviewer", reviewerId);
- verifyProjectVisible("CC", ccId);
-
receive.init();
receive.addReviewers(reviewerId);
receive.addExtraCC(ccId);
@@ -172,14 +165,4 @@
throw new Failure(128, "fatal: Unpack error, check server log", detail);
}
}
-
- private void verifyProjectVisible(final String type, final Set<Account.Id> who)
- throws UnloggedFailure {
- for (final Account.Id id : who) {
- final IdentifiedUser user = identifiedUserFactory.create(id);
- if (!projectControl.forUser(user).isVisible()) {
- throw die(type + " " + user.getAccount().getFullName() + " cannot access the project");
- }
- }
- }
}
diff --git a/plugins/hooks b/plugins/hooks
index 7156fc2..a329746 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 7156fc2b350307fd8212591e29eb6b4662f9d17d
+Subproject commit a32974634954aa904acc00644bb0beb58b1ea934
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 5314b09..7875450 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -391,7 +391,8 @@
on-change="_handlePatchChange">
<template is="dom-repeat" items="[[_allPatchSets]]"
as="patchNum">
- <option value$="[[patchNum.num]]">
+ <option value$="[[patchNum.num]]"
+ disabled$="[[_computePatchSetDisabled(patchNum.num, _patchRange.basePatchNum)]]">
[[patchNum.num]]
/
[[_computeLatestPatchNum(_allPatchSets)]]
@@ -429,7 +430,7 @@
<gr-file-list id="fileList"
change="[[_change]]"
change-num="[[_changeNum]]"
- patch-range="[[_patchRange]]"
+ patch-range="{{_patchRange}}"
comments="[[_comments]]"
drafts="[[_diffDrafts]]"
revisions="[[_change.revisions]]"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 7cfaf76..8eeac09 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -639,6 +639,18 @@
return 'patchInfo--oldPatchSet';
},
+ /**
+ * Determines if a patch number should be disabled based on value of the
+ * basePatchNum from gr-file-list.
+ * @param {Number} patchNum Patch number available in dropdown
+ * @param {Number|String} basePatchNum Base patch number from file list
+ * @return {Boolean}
+ */
+ _computePatchSetDisabled: function(patchNum, basePatchNum) {
+ basePatchNum = basePatchNum === 'PARENT' ? 0 : basePatchNum;
+ return parseInt(patchNum, 10) <= parseInt(basePatchNum, 10);
+ },
+
_computeAllPatchSets: function(change) {
var patchNums = [];
for (var commit in change.revisions) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index a416aa2..8180dfc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -151,6 +151,19 @@
'Add a patch set description');
});
+ test('_computePatchSetDisabled', function() {
+ var basePatchNum = 'PARENT';
+ var patchNum = 1;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ false);
+ basePatchNum = 1;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ true);
+ patchNum = 2;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ false);
+ });
+
test('_prepareCommitMsgForLinkify', function() {
var commitMessage = 'R=test@google.com';
var result = element._prepareCommitMsgForLinkify(commitMessage);
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 9cee168..760d6c7 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -114,6 +114,9 @@
margin-right: .5em;
white-space: nowrap;
}
+ .labelMessage {
+ color: #666;
+ }
iron-selector {
display: inline-flex;
}
@@ -236,12 +239,12 @@
config="[[projectConfig.commentlinks]]"></gr-formatted-text>
</section>
<section class="labelsContainer">
- <template is="dom-repeat"
- items="[[_labels]]" as="label">
+ <template is="dom-repeat" items="[[_labels]]" as="label">
<div class="labelContainer">
<span class="labelName">[[label.name]]</span>
<iron-selector data-label$="[[label.name]]"
- selected="[[_computeIndexOfLabelValue(change.labels, permittedLabels, label)]]">
+ selected="[[_computeIndexOfLabelValue(change.labels, permittedLabels, label)]]"
+ hidden$="[[!_computeAnyPermittedLabelValues(permittedLabels, label.name)]]">
<template is="dom-repeat"
items="[[_computePermittedLabelValues(permittedLabels, label.name)]]"
as="value">
@@ -249,6 +252,10 @@
title$="[[_computeLabelValueTitle(change.labels, label.name, value)]]">[[value]]</gr-button>
</template>
</iron-selector>
+ <span class="labelMessage"
+ hidden$="[[_computeAnyPermittedLabelValues(permittedLabels, label.name)]]">
+ You don't have permission to edit this label.
+ </span>
</div>
</template>
</section>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index f929888..f56d85d 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -425,6 +425,10 @@
return permittedLabels[label];
},
+ _computeAnyPermittedLabelValues: function(permittedLabels, label) {
+ return permittedLabels.hasOwnProperty(label);
+ },
+
_changeUpdated: function(changeRecord, owner, serverConfig) {
this._rebuildReviewerArrays(changeRecord.base, owner, serverConfig);
},
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index e9fdbd1..a225b12 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -22,6 +22,8 @@
'author',
'branch',
'bug',
+ 'cc',
+ 'cc:self',
'change',
'comment',
'commentby',
@@ -237,6 +239,7 @@
return this._fetchProjects(predicate, expression);
case 'author':
+ case 'cc':
case 'commentby':
case 'committer':
case 'from':
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index 8863aa6..8ef9ad6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -83,19 +83,11 @@
.action {
margin-right: .5em;
}
- .danger {
- display: flex;
- flex: 1;
- justify-content: flex-end;
- }
.editMessage {
display: none;
margin: .5em 0;
width: 100%;
}
- .danger .action {
- margin-right: 0;
- }
.container:not(.draft) .actions .hideOnPublished {
display: none;
}
@@ -118,7 +110,8 @@
.editing .quote,
.editing .ack,
.editing .done,
- .editing .edit {
+ .editing .edit,
+ .editing .unresolved {
display: none;
}
.editing .editMessage {
@@ -171,8 +164,12 @@
#container.collapsed iron-autogrow-textarea {
display: none;
}
- .resolve {
- margin: auto;
+ .resolve,
+ .unresolved {
+ align-items: center;
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
}
.resolve label {
color: #333;
@@ -236,6 +233,8 @@
disabled$="[[_computeSaveDisabled(_messageText)]]">Save</gr-button>
<gr-button class="action cancel hideOnPublished"
on-tap="_handleCancel" hidden>Cancel</gr-button>
+ <gr-button class="action discard hideOnPublished"
+ on-tap="_handleDiscard">Discard</gr-button>
<div class="action resolve hideOnPublished">
<label>
<input type="checkbox"
@@ -244,9 +243,8 @@
Resolved
</label>
</div>
- <div class="danger">
- <gr-button class="action discard hideOnPublished"
- on-tap="_handleDiscard">Discard</gr-button>
+ <div class="action unresolved hideOnPublished" hidden$="[[resolved]]">
+ Unresolved
</div>
</div>
<div class="actions robotActions" hidden$="[[!_showRobotActions]]">
diff --git a/polygerrit-ui/app/run_test.sh b/polygerrit-ui/app/run_test.sh
old mode 100644
new mode 100755