Merge "Added note about stable project dashboard URLs to docs"
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index 7c7dc4a..605fe51 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -19,7 +19,6 @@
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gwt.http.client.URL;
import com.google.gwtorm.client.KeyUtil;
public class PageLinks {
@@ -78,14 +77,13 @@
return "/q/" + KeyUtil.encode(query) + "," + page;
}
- public static String toProjectDashboard(Project.NameKey projectName,
- String dashboardId) {
- return PROJECTS + projectName.get() + DASHBOARDS + dashboardId;
+ public static String toProjectDashboard(Project.NameKey name, String id) {
+ return PROJECTS + name.get() + DASHBOARDS + id;
}
public static String projectQuery(Project.NameKey proj) {
return op("project", proj.get());
-}
+ }
public static String projectQuery(Project.NameKey proj, Status status) {
return status(status) + " " + op("project", proj.get());
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestCollection.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestCollection.java
index e874a52..56ff555 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestCollection.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestCollection.java
@@ -67,8 +67,9 @@
*
* @return view to list the collection.
* @throws ResourceNotFoundException if the collection cannot be listed.
+ * @throws AuthException if the collection requires authentication.
*/
- RestView<P> list() throws ResourceNotFoundException;
+ RestView<P> list() throws ResourceNotFoundException, AuthException;
/**
* Parse a path component into a resource handle.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 7f589aa..dd9e481 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -88,6 +88,7 @@
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Window;
+import com.google.gwtjsonrpc.client.RemoteJsonException;
import com.google.gwtorm.client.KeyUtil;
public class Dispatcher {
@@ -401,20 +402,36 @@
rest = rest.substring(c);
if (matchPrefix(DASHBOARDS, rest)) {
final String dashboardId = skip(rest);
+ GerritCallback<DashboardInfo> cb = new GerritCallback<DashboardInfo>() {
+ @Override
+ public void onSuccess(DashboardInfo result) {
+ if (matchPrefix("/dashboard/", result.url())) {
+ String rest = skip(result.url());
+ Gerrit.display(token, new CustomDashboardScreen(rest.substring(1)));
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ if ("default".equals(dashboardId)
+ && caught instanceof RemoteJsonException
+ && ((RemoteJsonException) caught).getCode() == 404) {
+ Gerrit.display(PageLinks.toChangeQuery(
+ PageLinks.projectQuery(new Project.NameKey(project))));
+ } else {
+ super.onFailure(caught);
+ }
+ }
+ };
+ if ("default".equals(dashboardId)) {
+ DashboardList.getDefault(new Project.NameKey(project), cb);
+ return;
+ }
c = dashboardId.indexOf(":");
if (0 <= c) {
final String ref = URL.decodePathSegment(dashboardId.substring(0, c));
final String path = URL.decodePathSegment(dashboardId.substring(c + 1));
- DashboardList.get(new Project.NameKey(project), ref + ":" + path,
- new GerritCallback<DashboardInfo>() {
- @Override
- public void onSuccess(DashboardInfo result) {
- if (matchPrefix("/dashboard/", result.url())) {
- String rest = skip(result.url());
- Gerrit.display(token, new CustomDashboardScreen(rest.substring(1)));
- }
- }
- });
+ DashboardList.get(new Project.NameKey(project), ref + ":" + path, cb);
return;
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index a2fcb50..b250446 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -98,14 +98,8 @@
private Widget createSearchLink(final ProjectInfo projectInfo) {
Image image = new Image(Gerrit.RESOURCES.queryProjectLink());
- InlineHyperlink h;
- if (projectInfo.defaultDashboard() != null) {
- h = new InlineHyperlink(" ", PageLinks.toProjectDashboard(
- projectInfo.name_key(), projectInfo.defaultDashboard()));
- } else {
- h = new InlineHyperlink(" ", PageLinks.toChangeQuery(PageLinks
- .projectQuery(projectInfo.name_key())));
- }
+ InlineHyperlink h = new InlineHyperlink(" ",
+ PageLinks.toProjectDashboard(projectInfo.name_key(), "default"));
h.setTitle(Util.C.projectListQueryLink());
DOM.insertBefore(h.getElement(), image.getElement(),
DOM.getFirstChild(h.getElement()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java
index 229791b..02ee2b1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java
@@ -29,7 +29,7 @@
.get(callback);
}
- public static void defaultDashboard(Project.NameKey project,
+ public static void getDefault(Project.NameKey project,
AsyncCallback<DashboardInfo> callback) {
new RestApi(base(project) + "default")
.addParameterTrue("inherited")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
index 5df41a2..80c1feb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
@@ -27,7 +27,6 @@
public final native String name() /*-{ return this.name; }-*/;
public final native String description() /*-{ return this.description; }-*/;
- public final native String defaultDashboard() /*-{ return this.default_dashboard; }-*/;
@Override
public final String getDisplayString() {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
index f28d597..24f7bba 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
@@ -138,6 +138,10 @@
return lineNbr;
}
+ public void setLine(int line) {
+ lineNbr = line;
+ }
+
public Account.Id getAuthor() {
return author;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index 1485b65..aa688e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -94,7 +94,12 @@
}
return Collections.emptyList();
} else if (k.project == null && k.branch == null && k.changeId != null) {
- return db.get().changes().byKey(new Change.Key(k.changeId)).toList();
+ Change.Key id = new Change.Key(k.changeId);
+ if (id.get().length() == 41) {
+ return db.get().changes().byKey(id).toList();
+ } else {
+ return db.get().changes().byKeyRange(id, id.max()).toList();
+ }
}
return db.get().changes().byBranchKey(
k.branch(),
@@ -109,7 +114,7 @@
ParsedId(String id) throws ResourceNotFoundException,
UnsupportedEncodingException {
- if (id.matches("^[0-9]+$")) {
+ if (id.matches("^[1-9][0-9]*$")) {
legacyId = Change.Id.parse(id);
return;
}
@@ -117,7 +122,7 @@
int t2 = id.lastIndexOf('~');
int t1 = id.lastIndexOf('~', t2 - 1);
if (t1 < 0 || t2 < 0) {
- if (!id.matches("^I[0-9a-z]{40}$")) {
+ if (!id.matches("^I[0-9a-z]{4,40}$")) {
throw new ResourceNotFoundException(id);
}
changeId = id;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
new file mode 100644
index 0000000..01e7e3b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.PutDraft.Input;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+class CreateDraft implements RestModifyView<RevisionResource, Input> {
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ CreateDraft(Provider<ReviewDb> db) {
+ this.db = db;
+ }
+
+ @Override
+ public Class<Input> inputType() {
+ return Input.class;
+ }
+
+ @Override
+ public Object apply(RevisionResource rsrc, Input in) throws AuthException,
+ BadRequestException, ResourceConflictException, Exception {
+ if (Strings.isNullOrEmpty(in.path)) {
+ throw new BadRequestException("path must be non-empty");
+ } else if (in.message == null || in.message.trim().isEmpty()) {
+ throw new BadRequestException("message must be non-empty");
+ } else if (in.line != null && in.line <= 0) {
+ throw new BadRequestException("line must be > 0");
+ }
+
+ PatchLineComment c = new PatchLineComment(
+ new PatchLineComment.Key(
+ new Patch.Key(rsrc.getPatchSet().getId(), in.path),
+ ChangeUtil.messageUUID(db.get())),
+ in.line != null ? in.line : 0,
+ rsrc.getAuthorId(),
+ null);
+ c.setStatus(Status.DRAFT);
+ c.setSide(in.side == GetDraft.Side.PARENT ? (short) 0 : (short) 1);
+ c.setMessage(in.message.trim());
+ db.get().patchComments().insert(Collections.singleton(c));
+ return new GetDraft.Comment(c);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
new file mode 100644
index 0000000..af9b846
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.DeleteDraft.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+class DeleteDraft implements RestModifyView<DraftResource, Input> {
+ static class Input {
+ }
+
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ DeleteDraft(Provider<ReviewDb> db) {
+ this.db = db;
+ }
+
+ @Override
+ public Class<Input> inputType() {
+ return Input.class;
+ }
+
+ @Override
+ public Object apply(DraftResource rsrc, Input input) throws OrmException {
+ db.get().patchComments().delete(Collections.singleton(rsrc.getComment()));
+ return new Object();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftResource.java
new file mode 100644
index 0000000..bcd8902
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftResource.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.TypeLiteral;
+
+public class DraftResource implements RestResource {
+ public static final TypeLiteral<RestView<DraftResource>> DRAFT_KIND =
+ new TypeLiteral<RestView<DraftResource>>() {};
+
+ private final RevisionResource rev;
+ private final PatchLineComment comment;
+
+ DraftResource(RevisionResource rev, PatchLineComment c) {
+ this.rev = rev;
+ this.comment = c;
+ }
+
+ public ChangeControl getControl() {
+ return rev.getControl();
+ }
+
+ public Change getChange() {
+ return getControl().getChange();
+ }
+
+ public PatchSet getPatchSet() {
+ return rev.getPatchSet();
+ }
+
+ PatchLineComment getComment() {
+ return comment;
+ }
+
+ String getId() {
+ return comment.getKey().get();
+ }
+
+ Account.Id getAuthorId() {
+ return ((IdentifiedUser) getControl().getCurrentUser()).getAccountId();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java
new file mode 100644
index 0000000..83959a0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+class Drafts implements ChildCollection<RevisionResource, DraftResource> {
+ private final DynamicMap<RestView<DraftResource>> views;
+ private final Provider<CurrentUser> user;
+ private final Provider<ListDrafts> list;
+ private final Provider<ReviewDb> dbProvider;
+
+ @Inject
+ Drafts(DynamicMap<RestView<DraftResource>> views,
+ Provider<CurrentUser> user,
+ Provider<ListDrafts> list,
+ Provider<ReviewDb> dbProvider) {
+ this.views = views;
+ this.user = user;
+ this.list = list;
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public DynamicMap<RestView<DraftResource>> views() {
+ return views;
+ }
+
+ @Override
+ public RestView<RevisionResource> list() throws AuthException {
+ checkIdentifiedUser();
+ return list.get();
+ }
+
+ @Override
+ public DraftResource parse(RevisionResource rev, String id)
+ throws ResourceNotFoundException, UnsupportedEncodingException,
+ OrmException, AuthException {
+ checkIdentifiedUser();
+ String uuid = URLDecoder.decode(id, "UTF-8");
+ for (PatchLineComment c : dbProvider.get().patchComments()
+ .draftByPatchSetAuthor(
+ rev.getPatchSet().getId(),
+ rev.getAuthorId())) {
+ if (uuid.equals(c.getKey().get())) {
+ return new DraftResource(rev, c);
+ }
+ }
+ throw new ResourceNotFoundException(id);
+ }
+
+ private void checkIdentifiedUser() throws AuthException {
+ if (!(user.get() instanceof IdentifiedUser)) {
+ throw new AuthException("drafts only available to authenticated users");
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java
new file mode 100644
index 0000000..fbfe1d5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.sql.Timestamp;
+
+class GetDraft implements RestReadView<DraftResource> {
+ @Override
+ public Object apply(DraftResource rsrc) throws AuthException,
+ BadRequestException, ResourceConflictException, Exception {
+ return new Comment(rsrc.getComment());
+ }
+
+ static enum Side {
+ PARENT, REVISION;
+ }
+
+ static class Comment {
+ final String kind = "gerritcodereview#comment";
+ String id;
+ String path;
+ Side side;
+ Integer line;
+ String message;
+ Timestamp updated;
+
+ Comment(PatchLineComment c) {
+ try {
+ id = URLEncoder.encode(c.getKey().get(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported", e);
+ }
+ path = c.getKey().getParentKey().getFileName();
+ if (c.getSide() == 0) {
+ side = Side.PARENT;
+ }
+ if (c.getLine() > 0) {
+ line = c.getLine();
+ }
+ message = Strings.emptyToNull(c.getMessage());
+ updated = c.getWrittenOn();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
new file mode 100644
index 0000000..208f271
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.base.Objects.firstNonNull;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.GetDraft.Comment;
+import com.google.gerrit.server.change.GetDraft.Side;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+class ListDrafts implements RestReadView<RevisionResource> {
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ ListDrafts(Provider<ReviewDb> db) {
+ this.db = db;
+ }
+
+ @Override
+ public Object apply(RevisionResource rsrc) throws AuthException,
+ BadRequestException, ResourceConflictException, Exception {
+ Map<String, List<Comment>> out = Maps.newTreeMap();
+ for (PatchLineComment c : db.get().patchComments()
+ .draftByPatchSetAuthor(
+ rsrc.getPatchSet().getId(),
+ rsrc.getAuthorId())) {
+ Comment o = new Comment(c);
+ List<Comment> list = out.get(o.path);
+ if (list == null) {
+ list = Lists.newArrayList();
+ out.put(o.path, list);
+ }
+ list.add(o);
+ }
+ for (List<Comment> list : out.values()) {
+ Collections.sort(list, new Comparator<Comment>() {
+ @Override
+ public int compare(Comment a, Comment b) {
+ int c = firstNonNull(a.side, Side.REVISION).ordinal()
+ - firstNonNull(b.side, Side.REVISION).ordinal();
+ if (c == 0) {
+ c = firstNonNull(a.line, 0) - firstNonNull(b.line, 0);
+ }
+ if (c == 0) {
+ c = a.id.compareTo(b.id);
+ }
+ return c;
+ }
+ });
+ }
+ return out;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index f0e7e46..279e925 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.change;
import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
+import static com.google.gerrit.server.change.DraftResource.DRAFT_KIND;
import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
@@ -26,6 +27,7 @@
@Override
protected void configure() {
DynamicMap.mapOf(binder(), CHANGE_KIND);
+ DynamicMap.mapOf(binder(), DRAFT_KIND);
DynamicMap.mapOf(binder(), REVIEWER_KIND);
DynamicMap.mapOf(binder(), REVISION_KIND);
@@ -41,6 +43,12 @@
child(CHANGE_KIND, "revisions").to(Revisions.class);
post(REVISION_KIND, "review").to(PostReview.class);
+ child(REVISION_KIND, "drafts").to(Drafts.class);
+ put(REVISION_KIND, "drafts").to(CreateDraft.class);
+ get(DRAFT_KIND).to(GetDraft.class);
+ put(DRAFT_KIND).to(PutDraft.class);
+ delete(DRAFT_KIND).to(DeleteDraft.class);
+
install(new FactoryModule() {
@Override
protected void configure() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 59fe465..5011f31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -26,7 +26,6 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.ApprovalCategoryValue;
@@ -82,17 +81,17 @@
DELETE, PUBLISH, KEEP;
}
- static enum Side {
- PARENT, REVISION;
- }
-
static class Comment {
String id;
- Side side;
+ GetDraft.Side side;
int line;
String message;
}
+ static class Output {
+ Map<String, Short> labels;
+ }
+
private final ReviewDb db;
private final ApprovalTypes approvalTypes;
private final EmailReviewComments.Factory email;
@@ -124,8 +123,7 @@
@Override
public Object apply(RevisionResource revision, Input input)
- throws AuthException, BadRequestException, ResourceConflictException,
- Exception {
+ throws AuthException, BadRequestException, OrmException {
if (input.labels != null) {
checkLabels(revision, input.strictLabels, input.labels);
}
@@ -161,7 +159,10 @@
message,
comments).sendAsync();
fireCommentAddedHook(revision);
- return input;
+
+ Output output = new Output();
+ output.labels = input.labels;
+ return output;
}
private void checkLabels(RevisionResource revision, boolean strict,
@@ -190,7 +191,7 @@
continue;
}
- if (!at.getValuesAsList().contains(ent.getValue())) {
+ if (at.getValue(ent.getValue()) == null) {
if (strict) {
throw new BadRequestException(String.format(
"label \"%s\": %d is not a valid value",
@@ -273,7 +274,7 @@
}
e.setStatus(PatchLineComment.Status.PUBLISHED);
e.setWrittenOn(timestamp);
- e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
+ e.setSide(c.side == GetDraft.Side.PARENT ? (short) 0 : (short) 1);
e.setMessage(c.message);
(create ? ins : upd).add(e);
}
@@ -422,7 +423,7 @@
}
@Deprecated
- private void fireCommentAddedHook(RevisionResource rsrc) throws OrmException {
+ private void fireCommentAddedHook(RevisionResource rsrc) {
IdentifiedUser user = (IdentifiedUser) rsrc.getControl().getCurrentUser();
try {
hooks.doCommentAddedHook(change,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
new file mode 100644
index 0000000..38b4da1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
@@ -0,0 +1,103 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.GetDraft.Side;
+import com.google.gerrit.server.change.PutDraft.Input;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.Timestamp;
+import java.util.Collections;
+
+class PutDraft implements RestModifyView<DraftResource, Input> {
+ static class Input {
+ String kind;
+ String id;
+ String path;
+ Side side;
+ Integer line;
+ Timestamp updated; // Accepted but ignored.
+
+ @DefaultInput
+ String message;
+ }
+
+ private final Provider<ReviewDb> db;
+ private final Provider<DeleteDraft> delete;
+
+ @Inject
+ PutDraft(Provider<ReviewDb> db, Provider<DeleteDraft> delete) {
+ this.db = db;
+ this.delete = delete;
+ }
+
+ @Override
+ public Class<Input> inputType() {
+ return Input.class;
+ }
+
+ @Override
+ public Object apply(DraftResource rsrc, Input in)
+ throws AuthException, BadRequestException, ResourceConflictException,
+ Exception {
+ if (in == null || in.message == null || in.message.trim().isEmpty()) {
+ return delete.get().apply(rsrc, null);
+ } else if (in.kind != null && !"gerritcodereview#comment".equals(in.kind)) {
+ throw new BadRequestException("expected kind gerritcodereview#comment");
+ } else if (in.line != null && in.line < 0) {
+ throw new BadRequestException("line must be >= 0");
+ }
+
+ PatchLineComment c = rsrc.getComment();
+ if (in.path != null
+ && !in.path.equals(c.getKey().getParentKey().getFileName())) {
+ // Updating the path alters the primary key, which isn't possible.
+ // Delete then recreate the comment instead of an update.
+ db.get().patchComments().delete(Collections.singleton(c));
+ c = update(new PatchLineComment(
+ new PatchLineComment.Key(
+ new Patch.Key(rsrc.getPatchSet().getId(), in.path),
+ c.getKey().get()),
+ c.getLine(),
+ rsrc.getAuthorId(),
+ c.getParentUuid()), in);
+ db.get().patchComments().insert(Collections.singleton(c));
+ } else {
+ db.get().patchComments().update(Collections.singleton(update(c, in)));
+ }
+ return new GetDraft.Comment(c);
+ }
+
+ private PatchLineComment update(PatchLineComment e, Input in) {
+ if (in.side != null) {
+ e.setSide(in.side == GetDraft.Side.PARENT ? (short) 0 : (short) 1);
+ }
+ if (in.line != null) {
+ e.setLine(in.line);
+ }
+ e.setMessage(in.message.trim());
+ e.updated();
+ return e;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index 1bfb006..970230b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -26,6 +27,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.util.Collections;
import java.util.List;
class Revisions implements ChildCollection<ChangeResource, RevisionResource> {
@@ -52,36 +54,57 @@
@Override
public RevisionResource parse(ChangeResource change, String id)
throws ResourceNotFoundException, Exception {
- if (id.matches("^[1-9][0-9]{0,4}$")) {
+ List<PatchSet> match = Lists.newArrayListWithExpectedSize(2);
+ for (PatchSet ps : find(change, id)) {
+ Change.Id changeId = ps.getId().getParentKey();
+ if (changeId.equals(change.getChange().getId())
+ && change.getControl().isPatchVisible(ps, dbProvider.get())) {
+ match.add(ps);
+ }
+ }
+ if (match.size() != 1) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new RevisionResource(change, match.get(0));
+ }
+
+ private List<PatchSet> find(ChangeResource change, String id)
+ throws OrmException {
+ ReviewDb db = dbProvider.get();
+
+ if (id.length() < 6 && id.matches("^[1-9][0-9]{0,4}$")) {
+ // Legacy patch set number syntax.
PatchSet ps = dbProvider.get().patchSets().get(new PatchSet.Id(
change.getChange().getId(),
Integer.parseInt(id)));
- if (ps != null
- && change.getControl().isPatchVisible(ps, dbProvider.get())) {
- return new RevisionResource(change, ps);
+ if (ps != null) {
+ return Collections.singletonList(ps);
}
- throw new ResourceNotFoundException(id);
- }
-
- for (PatchSet ps : find(id)) {
- Change.Id changeId = ps.getId().getParentKey();
- if (changeId.equals(change.getChange().getId())) {
- if (change.getControl().isPatchVisible(ps, dbProvider.get())) {
- return new RevisionResource(change, ps);
- }
- break;
+ return Collections.emptyList();
+ } else if (id.length() < 4 || id.length() > RevId.LEN) {
+ // Require a minimum of 4 digits.
+ // Impossibly long identifier will never match.
+ return Collections.emptyList();
+ } else if (id.length() >= 8) {
+ // Commit names are rather unique. Query for the commit and later
+ // match to the change. This is most likely going to identify 1 or
+ // at most 2 patch sets to consider, which is smaller than looking
+ // for all patch sets in the change.
+ RevId revid = new RevId(id);
+ if (revid.isComplete()) {
+ return db.patchSets().byRevision(revid).toList();
+ } else {
+ return db.patchSets().byRevisionRange(revid, revid.max()).toList();
}
- }
- throw new ResourceNotFoundException(id);
- }
-
- private List<PatchSet> find(String id) throws OrmException {
- ReviewDb db = dbProvider.get();
- RevId revid = new RevId(id);
- if (revid.isComplete()) {
- return db.patchSets().byRevision(revid).toList();
} else {
- return db.patchSets().byRevisionRange(revid, revid.max()).toList();
+ // Chance of collision rises; look at all patch sets on the change.
+ List<PatchSet> out = Lists.newArrayList();
+ for (PatchSet ps : db.patchSets().byChange(change.getChange().getId())) {
+ if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) {
+ out.add(ps);
+ }
+ }
+ return out;
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index e352598..2db3a26 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -17,6 +17,8 @@
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -41,6 +43,7 @@
import java.util.Map;
/** List the installed plugins. */
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
public class ListPlugins implements RestReadView<TopLevelResource> {
private final PluginLoader pluginLoader;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index 083b696..4710698 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -163,7 +163,7 @@
return query.replace("${project}", project);
}
- public static String defaultOf(Project proj) {
+ private static String defaultOf(Project proj) {
final String defaultId = Objects.firstNonNull(
proj.getLocalDefaultDashboard(),
Strings.nullToEmpty(proj.getDefaultDashboard()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index 0547185..b88a445c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -284,8 +284,6 @@
if (showDescription) {
info.description = Strings.emptyToNull(e.getProject().getDescription());
}
- info.defaultDashboard =
- Strings.emptyToNull(DashboardsCollection.defaultOf(e.getProject()));
try {
if (showBranch != null) {
@@ -453,7 +451,6 @@
String id;
String parent;
String description;
- String defaultDashboard;
Map<String, String> branches;
void setName(String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 67e5739..93f6f4c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -54,7 +54,7 @@
private List<String> queries;
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "Maximum number of results to return")
- void setLimit(int limit) {
+ public void setLimit(int limit) {
imp.setLimit(limit);
}
@@ -69,7 +69,7 @@
}
@Option(name = "-P", metaVar = "SORTKEY", usage = "Previous changes before SORTKEY")
- void setSortKeyAfter(String key) {
+ public void setSortKeyAfter(String key) {
// Querying for the prior page of changes requires sortkey_after predicate.
// Changes are shown most recent->least recent. The previous page of
// results contains changes that were updated after the given key.
@@ -78,7 +78,7 @@
}
@Option(name = "-N", metaVar = "SORTKEY", usage = "Next changes after SORTKEY")
- void setSortKeyBefore(String key) {
+ public void setSortKeyBefore(String key) {
// Querying for the next page of changes requires sortkey_before predicate.
// Changes are shown most recent->least recent. The next page contains
// changes that were updated before the given key.
@@ -98,6 +98,13 @@
json.setChangeControlFactory(cf);
}
+ public void addQuery(String query) {
+ if (queries == null) {
+ queries = Lists.newArrayList();
+ }
+ queries.add(query);
+ }
+
@Override
public Object apply(TopLevelResource rsrc)
throws BadRequestException, AuthException, OrmException {