Merge "Fix inclusion of LF in IntralineLoader" into stable-2.8
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 814472a..6d00dfa 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2507,6 +2507,14 @@
[[sshd]] Section sshd
~~~~~~~~~~~~~~~~~~~~~
+[[sshd.backend]]sshd.backend::
++
+Starting from version 0.9.0 Apache SSHD project added support for NIO2
+IoSession. To use the new NIO2 session the `backend` option must be set
+to `NIO2`.
++
+By default, `MINA`.
+
[[sshd.listenAddress]]sshd.listenAddress::
+
Specifies the local addresses the internal SSHD should listen
@@ -2545,20 +2553,13 @@
+
By default, sshd.listenAddress.
-[[sshd.reuseAddress]]sshd.reuseAddress::
-+
-If true, permits the daemon to bind to the port even if the port
-is already in use. If false, the daemon ensures the port is not
-in use before starting. Busy sites may need to set this to true
-to permit fast restarts.
-+
-By default, true.
-
[[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
+
If true, enables TCP keepalive messages to the other side, so
the daemon can terminate connections if the peer disappears.
+
+Only effective when `sshd.backend` is set to `MINA`.
++
By default, true.
[[sshd.threads]]sshd.threads::
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 02bb549..70431c6 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -921,6 +921,87 @@
}
----
+[[get-starred-changes]]
+Get Starred Changes
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/starred.changes'
+
+Gets the changes starred by the identified user account. This
+URL endpoint is functionally identical to the changes query
+`GET /changes/?q=is:starred`. The result is a list of
+link:rest-api-changes.html#change-info[ChangeInfo] entities.
+
+.Request
+----
+ GET /a/accounts/self/starred.changes
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ [
+ {
+ "kind": "gerritcodereview#change",
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "_sortkey": "0023412400000f7d",
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ }
+ ]
+----
+
+[[star-change]]
+Star Change
+~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
+
+Star a change. Starred changes are returned for the search query
+`is:starred` or `starredby:USER` and automatically notify the user
+whenever updates are made to the change.
+
+.Request
+----
+ PUT /a/accounts/self/starred.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[unstar-change]]
+Unstar Change
+~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes#change-id[\{change-id\}]'
+
+Unstar a change. Removes the starred flag, stopping notifications.
+
+.Request
+----
+ DELETE /a/accounts/self/starred.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
[[ids]]
IDs
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
index 27ed297..d183cca 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -278,6 +278,15 @@
* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#get-username[
Get account username]
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#get-starred-changes[
+Get starred changes]
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#star-change[
+Star change]
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#unstar-change[
+Unstar change]
+
Changes
^^^^^^^
@@ -741,5 +750,7 @@
* Update guava to 15.0
* Update H2 to 1.3.173
* Update bouncycastle to 1.44
+* Update Apache Mina to 2.0.7
+* Update Apache SSHD to 0.9.0.201311081
* asciidoctor 0.1.4 is now required to build the documentation
* jsr305 library was removed
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
index d813501..1fca451 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
@@ -2,7 +2,11 @@
acceptance_tests(
srcs = glob(['*IT.java']),
- deps = [':util'],
+ deps = [
+ ':util',
+ '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+ '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change:util',
+ ],
)
java_library(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
new file mode 100644
index 0000000..b5ae7de
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.account;
+
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.git.PushOneCommit.Result;
+import com.google.gerrit.acceptance.rest.change.ChangeInfo;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class StarredChangesIT extends AbstractDaemonTest {
+
+ @Inject
+ private AccountCreator accounts;
+
+ @Inject
+ private SchemaFactory<ReviewDb> reviewDbProvider;
+
+ private TestAccount admin;
+
+ private RestSession session;
+ private Git git;
+ private ReviewDb db;
+
+ @Before
+ public void setUp() throws Exception {
+ admin = accounts.admin();
+ session = new RestSession(server, admin);
+ initSsh(admin);
+ Project.NameKey project = new Project.NameKey("p");
+ SshSession sshSession = new SshSession(server, admin);
+ createProject(sshSession, project.get());
+ git = cloneProject(sshSession.getUrl() + "/" + project.get());
+ sshSession.close();
+ db = reviewDbProvider.open();
+ }
+
+ @After
+ public void cleanup() {
+ db.close();
+ }
+
+ @Test
+ public void starredChangeState() throws GitAPIException, IOException,
+ OrmException {
+ Result c1 = createChange();
+ Result c2 = createChange();
+ assertNull(getChange(c1.getChangeId()).starred);
+ assertNull(getChange(c2.getChangeId()).starred);
+ starChange(true, c1.getPatchSetId().getParentKey());
+ starChange(true, c2.getPatchSetId().getParentKey());
+ assertTrue(getChange(c1.getChangeId()).starred);
+ assertTrue(getChange(c2.getChangeId()).starred);
+ starChange(false, c1.getPatchSetId().getParentKey());
+ starChange(false, c2.getPatchSetId().getParentKey());
+ assertNull(getChange(c1.getChangeId()).starred);
+ assertNull(getChange(c2.getChangeId()).starred);
+ }
+
+ private ChangeInfo getChange(String changeId) throws IOException {
+ RestResponse r = session.get("/changes/?q=" + changeId);
+ List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
+ new TypeToken<List<ChangeInfo>>() {}.getType());
+ return c.get(0);
+ }
+
+ private void starChange(boolean on, Change.Id id) throws IOException {
+ String url = "/accounts/self/starred.changes/" + id.get();
+ if (on) {
+ RestResponse r = session.put(url);
+ assertEquals(204, r.getStatusCode());
+ } else {
+ RestResponse r = session.delete(url);
+ assertEquals(204, r.getStatusCode());
+ }
+ }
+
+ private Result createChange() throws GitAPIException, IOException {
+ PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+ return push.to(git, "refs/for/master");
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index b9c2d08..20b1033 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -38,4 +38,5 @@
'//lib:guava',
'//gerrit-reviewdb:server',
],
+ visibility = ['//gerrit-acceptance-tests/...'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
index fe8737e..8b431f0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
@@ -24,4 +24,5 @@
String branch;
List<ChangeMessageInfo> messages;
Change.Status status;
+ public Boolean starred;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
deleted file mode 100644
index 0c466497..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2008 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.common.data;
-
-import com.google.gerrit.common.audit.Audit;
-import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.RemoteJsonService;
-import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtjsonrpc.common.RpcImpl.Version;
-
-@RpcImpl(version = Version.V2_0)
-public interface ChangeListService extends RemoteJsonService {
- /**
- * Add and/or remove changes from the set of starred changes of the caller.
- *
- * @param req the add and remove cluster.
- */
- @Audit
- @SignInRequired
- void toggleStars(ToggleStarRequest req, AsyncCallback<VoidResult> callback);
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
index 7d2084a..b097bd8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
@@ -15,19 +15,23 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.common.data.ToggleStarRequest;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.google.gwtexpui.globalkey.client.KeyCommand;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.web.bindery.event.shared.Event;
import com.google.web.bindery.event.shared.HandlerRegistration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
/** Supports the star icon displayed on changes and tracking the status. */
public class StarredChanges {
private static final Event.Type<ChangeStarHandler> TYPE =
@@ -105,57 +109,52 @@
public static void toggleStar(
final Change.Id changeId,
final boolean newValue) {
- if (next == null) {
- next = new ToggleStarRequest();
- }
- next.toggle(changeId, newValue);
+ pending.put(changeId, newValue);
fireChangeStarEvent(changeId, newValue);
if (!busy) {
- start();
+ startRequest();
}
}
- private static ToggleStarRequest next;
private static boolean busy;
+ private static final Map<Change.Id, Boolean> pending =
+ new LinkedHashMap<Change.Id, Boolean>(4);
- private static void start() {
- final ToggleStarRequest req = next;
- next = null;
+ private static void startRequest() {
busy = true;
- Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
+ final Change.Id id = pending.keySet().iterator().next();
+ final boolean starred = pending.remove(id);
+ RestApi call = AccountApi.self().view("starred.changes").id(id.get());
+ AsyncCallback<JavaScriptObject> cb = new AsyncCallback<JavaScriptObject>() {
@Override
- public void onSuccess(VoidResult result) {
- if (next != null) {
- start();
- } else {
+ public void onSuccess(JavaScriptObject none) {
+ if (pending.isEmpty()) {
busy = false;
+ } else {
+ startRequest();
}
}
@Override
public void onFailure(Throwable caught) {
- rollback(req);
- if (next != null) {
- rollback(next);
- next = null;
+ if (!starred && RestApi.isStatus(caught, 404)) {
+ onSuccess(null);
+ return;
}
- busy = false;
- super.onFailure(caught);
- }
- });
- }
- private static void rollback(ToggleStarRequest req) {
- if (req.getAddSet() != null) {
- for (Change.Id id : req.getAddSet()) {
- fireChangeStarEvent(id, false);
+ fireChangeStarEvent(id, !starred);
+ for (Map.Entry<Change.Id, Boolean> e : pending.entrySet()) {
+ fireChangeStarEvent(e.getKey(), !e.getValue());
+ }
+ pending.clear();
+ busy = false;
}
- }
- if (req.getRemoveSet() != null) {
- for (Change.Id id : req.getRemoveSet()) {
- fireChangeStarEvent(id, true);
- }
+ };
+ if (starred) {
+ call.put(cb);
+ } else {
+ call.delete(cb);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index 590ad87..76dfd58 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -15,7 +15,6 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.common.data.ChangeDetailService;
-import com.google.gerrit.common.data.ChangeListService;
import com.google.gerrit.common.data.ChangeManageService;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
@@ -27,7 +26,6 @@
public static final ChangeResources R = GWT.create(ChangeResources.class);
public static final ChangeDetailService DETAIL_SVC;
- public static final ChangeListService LIST_SVC;
public static final ChangeManageService MANAGE_SVC;
private static final int SUBJECT_MAX_LENGTH = 80;
@@ -38,9 +36,6 @@
DETAIL_SVC = GWT.create(ChangeDetailService.class);
JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
- LIST_SVC = GWT.create(ChangeListService.class);
- JsonUtil.bind(LIST_SVC, "rpc/ChangeListService");
-
MANAGE_SVC = GWT.create(ChangeManageService.class);
JsonUtil.bind(MANAGE_SVC, "rpc/ChangeManageService");
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
deleted file mode 100644
index 0b54db1..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2008 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.httpd.rpc;
-
-import com.google.gerrit.common.data.ChangeListService;
-import com.google.gerrit.common.data.ToggleStarRequest;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.StarredChange;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class ChangeListServiceImpl extends BaseServiceImplementation implements
- ChangeListService {
- private final Provider<CurrentUser> currentUser;
-
- @Inject
- ChangeListServiceImpl(final Provider<ReviewDb> schema,
- final Provider<CurrentUser> currentUser) {
- super(schema, currentUser);
- this.currentUser = currentUser;
- }
-
- public void toggleStars(final ToggleStarRequest req,
- final AsyncCallback<VoidResult> callback) {
- run(callback, new Action<VoidResult>() {
- public VoidResult run(final ReviewDb db) throws OrmException {
- final Account.Id me = getAccountId();
- final Set<Change.Id> existing = currentUser.get().getStarredChanges();
- List<StarredChange> add = new ArrayList<StarredChange>();
- List<StarredChange.Key> remove = new ArrayList<StarredChange.Key>();
-
- if (req.getAddSet() != null) {
- for (final Change.Id id : req.getAddSet()) {
- if (!existing.contains(id)) {
- add.add(new StarredChange(new StarredChange.Key(me, id)));
- }
- }
- }
-
- if (req.getRemoveSet() != null) {
- for (final Change.Id id : req.getRemoveSet()) {
- remove.add(new StarredChange.Key(me, id));
- }
- }
-
- db.starredChanges().insert(add);
- db.starredChanges().deleteKeys(remove);
- return VoidResult.INSTANCE;
- }
- });
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
index 7de332a..08e1582 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
@@ -27,7 +27,6 @@
@Override
protected void configureServlets() {
- rpc(ChangeListServiceImpl.class);
rpc(SuggestServiceImpl.class);
rpc(SystemInfoServiceImpl.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
index 629bd15..106c033 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
@@ -17,7 +17,9 @@
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeResource;
import com.google.inject.TypeLiteral;
public class AccountResource implements RestResource {
@@ -33,6 +35,9 @@
public static final TypeLiteral<RestView<SshKey>> SSH_KEY_KIND =
new TypeLiteral<RestView<SshKey>>() {};
+ public static final TypeLiteral<RestView<StarredChange>> STARRED_CHANGE_KIND =
+ new TypeLiteral<RestView<StarredChange>>() {};
+
private final IdentifiedUser user;
public AccountResource(IdentifiedUser user) {
@@ -90,4 +95,17 @@
return sshKey;
}
}
+
+ public static class StarredChange extends AccountResource {
+ private final ChangeResource change;
+
+ public StarredChange(IdentifiedUser user, ChangeResource change) {
+ super(user);
+ this.change = change;
+ }
+
+ public Change getChange() {
+ return change.getChange();
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 79a0089..11f2e91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.account.AccountResource.CAPABILITY_KIND;
import static com.google.gerrit.server.account.AccountResource.EMAIL_KIND;
import static com.google.gerrit.server.account.AccountResource.SSH_KEY_KIND;
+import static com.google.gerrit.server.account.AccountResource.STARRED_CHANGE_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -30,9 +31,10 @@
bind(Capabilities.class);
DynamicMap.mapOf(binder(), ACCOUNT_KIND);
+ DynamicMap.mapOf(binder(), CAPABILITY_KIND);
DynamicMap.mapOf(binder(), EMAIL_KIND);
DynamicMap.mapOf(binder(), SSH_KEY_KIND);
- DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+ DynamicMap.mapOf(binder(), STARRED_CHANGE_KIND);
put(ACCOUNT_KIND).to(PutAccount.class);
get(ACCOUNT_KIND).to(GetAccount.class);
@@ -65,6 +67,11 @@
put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
+ child(ACCOUNT_KIND, "starred.changes").to(StarredChanges.class);
+ put(STARRED_CHANGE_KIND).to(StarredChanges.Put.class);
+ delete(STARRED_CHANGE_KIND).to(StarredChanges.Delete.class);
+ bind(StarredChanges.Create.class);
+
install(new FactoryModuleBuilder().build(CreateAccount.Factory.class));
install(new FactoryModuleBuilder().build(CreateEmail.Factory.class));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
new file mode 100644
index 0000000..0e335d0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
@@ -0,0 +1,199 @@
+// Copyright (C) 2013 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.account;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.StarredChange;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.query.change.QueryChanges;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+
+class StarredChanges implements
+ ChildCollection<AccountResource, AccountResource.StarredChange>,
+ AcceptsCreate<AccountResource> {
+ private static final Logger log = LoggerFactory.getLogger(StarredChanges.class);
+
+ private final ChangesCollection changes;
+ private final DynamicMap<RestView<AccountResource.StarredChange>> views;
+ private final Provider<Create> createProvider;
+
+ @Inject
+ StarredChanges(ChangesCollection changes,
+ DynamicMap<RestView<AccountResource.StarredChange>> views,
+ Provider<Create> createProvider) {
+ this.changes = changes;
+ this.views = views;
+ this.createProvider = createProvider;
+ }
+
+ @Override
+ public AccountResource.StarredChange parse(AccountResource parent, IdString id)
+ throws ResourceNotFoundException, OrmException, UnsupportedEncodingException {
+ IdentifiedUser user = parent.getUser();
+ try {
+ user.asyncStarredChanges();
+
+ ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
+ if (user.getStarredChanges().contains(change.getChange().getId())) {
+ return new AccountResource.StarredChange(user, change);
+ }
+ throw new ResourceNotFoundException(id);
+ } finally {
+ user.abortStarredChanges();
+ }
+ }
+
+ @Override
+ public DynamicMap<RestView<AccountResource.StarredChange>> views() {
+ return views;
+ }
+
+ @Override
+ public RestView<AccountResource> list() throws ResourceNotFoundException {
+ return new RestReadView<AccountResource>() {
+ @Override
+ public Object apply(AccountResource self) throws BadRequestException,
+ AuthException, OrmException {
+ QueryChanges query = changes.list();
+ query.addQuery("starredby:" + self.getUser().getAccountId().get());
+ return query.apply(TopLevelResource.INSTANCE);
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public RestModifyView<AccountResource, EmptyInput> create(
+ AccountResource parent, IdString id) throws UnprocessableEntityException{
+ try {
+ return createProvider.get()
+ .setChange(changes.parse(TopLevelResource.INSTANCE, id));
+ } catch (ResourceNotFoundException e) {
+ throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
+ } catch (UnsupportedEncodingException e) {
+ log.error("cannot resolve change", e);
+ throw new UnprocessableEntityException("internal server error");
+ } catch (OrmException e) {
+ log.error("cannot resolve change", e);
+ throw new UnprocessableEntityException("internal server error");
+ }
+ }
+
+ static class Create implements RestModifyView<AccountResource, EmptyInput> {
+ private final Provider<CurrentUser> self;
+ private final Provider<ReviewDb> dbProvider;
+ private ChangeResource change;
+
+ @Inject
+ Create(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
+ this.self = self;
+ this.dbProvider = dbProvider;
+ }
+
+ Create setChange(ChangeResource change) {
+ this.change = change;
+ return this;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource rsrc, EmptyInput in)
+ throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed to add starred change");
+ }
+ try {
+ dbProvider.get().starredChanges().insert(Collections.singleton(
+ new StarredChange(new StarredChange.Key(
+ rsrc.getUser().getAccountId(),
+ change.getChange().getId()))));
+ } catch (OrmDuplicateKeyException e) {
+ return Response.none();
+ }
+ return Response.none();
+ }
+ }
+
+ static class Put implements
+ RestModifyView<AccountResource.StarredChange, EmptyInput> {
+ private final Provider<CurrentUser> self;
+
+ @Inject
+ Put(Provider<CurrentUser> self) {
+ this.self = self;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource.StarredChange rsrc, EmptyInput in)
+ throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed update starred changes");
+ }
+ return Response.none();
+ }
+ }
+
+ static class Delete implements
+ RestModifyView<AccountResource.StarredChange, EmptyInput> {
+ private final Provider<CurrentUser> self;
+ private final Provider<ReviewDb> dbProvider;
+
+ @Inject
+ Delete(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
+ this.self = self;
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource.StarredChange rsrc,
+ EmptyInput in) throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed remove starred change");
+ }
+ dbProvider.get().starredChanges().delete(Collections.singleton(
+ new StarredChange(new StarredChange.Key(
+ rsrc.getUser().getAccountId(),
+ rsrc.getChange().getId()))));
+ return Response.none();
+ }
+ }
+
+ static class EmptyInput {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index f64e424..5a3ab3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -31,6 +31,9 @@
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
@@ -64,6 +67,7 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.PatchSetInfo.ParentInfo;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
@@ -77,7 +81,8 @@
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -87,6 +92,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
@@ -97,6 +103,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
public class ChangeJson {
private static final Logger log = LoggerFactory.getLogger(ChangeJson.class);
@@ -122,7 +129,7 @@
private final Provider<CurrentUser> userProvider;
private final AnonymousUser anonymous;
private final IdentifiedUser.GenericFactory userFactory;
- private final ChangeControl.GenericFactory changeControlGenericFactory;
+ private final ProjectControl.GenericFactory projectControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final FileInfoJson fileInfoJson;
private final AccountInfo.Loader.Factory accountLoaderFactory;
@@ -131,20 +138,20 @@
private final DynamicMap<RestView<ChangeResource>> changes;
private final Revisions revisions;
- private ChangeControl.Factory changeControlUserFactory;
private EnumSet<ListChangesOption> options;
private AccountInfo.Loader accountLoader;
private ChangeControl lastControl;
private Set<Change.Id> reviewed;
+ private LoadingCache<Project.NameKey, ProjectControl> projectControls;
@Inject
ChangeJson(
Provider<ReviewDb> db,
LabelNormalizer ln,
- Provider<CurrentUser> userProvider,
+ Provider<CurrentUser> user,
AnonymousUser au,
IdentifiedUser.GenericFactory uf,
- ChangeControl.GenericFactory ccf,
+ ProjectControl.GenericFactory pcf,
PatchSetInfoFactory psi,
FileInfoJson fileInfoJson,
AccountInfo.Loader.Factory ailf,
@@ -154,10 +161,10 @@
Revisions revisions) {
this.db = db;
this.labelNormalizer = ln;
- this.userProvider = userProvider;
+ this.userProvider = user;
this.anonymous = au;
this.userFactory = uf;
- this.changeControlGenericFactory = ccf;
+ this.projectControlFactory = pcf;
this.patchSetInfoFactory = psi;
this.fileInfoJson = fileInfoJson;
this.accountLoaderFactory = ailf;
@@ -167,6 +174,15 @@
this.revisions = revisions;
options = EnumSet.noneOf(ListChangesOption.class);
+ projectControls = CacheBuilder.newBuilder()
+ .concurrencyLevel(1)
+ .build(new CacheLoader<Project.NameKey, ProjectControl>() {
+ @Override
+ public ProjectControl load(Project.NameKey key)
+ throws NoSuchProjectException, IOException {
+ return projectControlFactory.controlFor(key, userProvider.get());
+ }
+ });
}
public ChangeJson addOption(ListChangesOption o) {
@@ -179,11 +195,6 @@
return this;
}
- public ChangeJson setChangeControlFactory(ChangeControl.Factory cf) {
- changeControlUserFactory = cf;
- return this;
- }
-
public ChangeInfo format(ChangeResource rsrc) throws OrmException {
return format(new ChangeData(rsrc.getControl()));
}
@@ -321,13 +332,12 @@
}
try {
- if (changeControlUserFactory != null) {
- ctrl = changeControlUserFactory.controlFor(cd.change(db));
- } else {
- ctrl = changeControlGenericFactory.controlFor(cd.change(db),
- userProvider.get());
+ Change change = cd.change(db);
+ if (change == null) {
+ return null;
}
- } catch (NoSuchChangeException e) {
+ ctrl = projectControls.get(change.getProject()).controlFor(change);
+ } catch (ExecutionException e) {
return null;
}
lastControl = ctrl;
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 45328b1..e93a0d8 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
@@ -23,6 +23,7 @@
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.QueryChanges;
@@ -37,24 +38,27 @@
public class ChangesCollection implements
RestCollection<TopLevelResource, ChangeResource> {
private final Provider<ReviewDb> db;
- private final ChangeControl.Factory changeControlFactory;
+ private final Provider<CurrentUser> user;
+ private final ChangeControl.GenericFactory changeControlFactory;
private final Provider<QueryChanges> queryFactory;
private final DynamicMap<RestView<ChangeResource>> views;
@Inject
ChangesCollection(
Provider<ReviewDb> dbProvider,
- ChangeControl.Factory changeControlFactory,
+ Provider<CurrentUser> user,
+ ChangeControl.GenericFactory changeControlFactory,
Provider<QueryChanges> queryFactory,
DynamicMap<RestView<ChangeResource>> views) {
this.db = dbProvider;
+ this.user = user;
this.changeControlFactory = changeControlFactory;
this.queryFactory = queryFactory;
this.views = views;
}
@Override
- public RestView<TopLevelResource> list() {
+ public QueryChanges list() {
return queryFactory.get();
}
@@ -74,7 +78,7 @@
ChangeControl control;
try {
- control = changeControlFactory.validateFor(changes.get(0));
+ control = changeControlFactory.validateFor(changes.get(0), user.get());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(id);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index ec8234f..98e2ee9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -37,7 +37,6 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -45,9 +44,9 @@
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -219,23 +218,49 @@
List<String> pathList = Lists.newArrayListWithCapacity(sz);
RevWalk rw = new RevWalk(reader);
- RevTree o = rw.parseCommit(oldList.getNewId()).getTree();
- RevTree c = rw.parseCommit(curList.getNewId()).getTree();
- for (PatchListEntry p : curList.getPatches()) {
- String path = p.getNewName();
- if (!Patch.COMMIT_MSG.equals(path) && paths.contains(path)) {
- TreeWalk tw = TreeWalk.forPath(reader, path, o, c);
- if (tw != null
- && tw.getRawMode(0) != 0
- && tw.getRawMode(1) != 0
- && tw.idEqual(0, 1)) {
- inserts.add(new AccountPatchReview(
- new Patch.Key(
- resource.getPatchSet().getId(),
- path),
- userId));
- pathList.add(path);
- }
+ TreeWalk tw = new TreeWalk(reader);
+ tw.setFilter(PathFilterGroup.createFromStrings(paths));
+ tw.setRecursive(true);
+ int o = tw.addTree(rw.parseCommit(oldList.getNewId()).getTree());
+ int c = tw.addTree(rw.parseCommit(curList.getNewId()).getTree());
+
+ int op = -1;
+ if (oldList.getOldId() != null) {
+ op = tw.addTree(rw.parseCommit(oldList.getOldId()).getTree());
+ }
+
+ int cp = -1;
+ if (curList.getOldId() != null) {
+ cp = tw.addTree(rw.parseCommit(curList.getOldId()).getTree());
+ }
+
+ while (tw.next()) {
+ String path = tw.getPathString();
+ if (tw.getRawMode(o) != 0 && tw.getRawMode(c) != 0
+ && tw.idEqual(o, c)
+ && paths.contains(path)) {
+ // File exists in previously reviewed oldList and in curList.
+ // File content is identical.
+ inserts.add(new AccountPatchReview(
+ new Patch.Key(
+ resource.getPatchSet().getId(),
+ path),
+ userId));
+ pathList.add(path);
+ } else if (op >= 0 && cp >= 0
+ && tw.getRawMode(o) == 0 && tw.getRawMode(c) == 0
+ && tw.getRawMode(op) != 0 && tw.getRawMode(cp) != 0
+ && tw.idEqual(op, cp)
+ && paths.contains(path)) {
+ // File was deleted in previously reviewed oldList and curList.
+ // File exists in ancestor of oldList and curList.
+ // File content is identical in ancestors.
+ inserts.add(new AccountPatchReview(
+ new Patch.Key(
+ resource.getPatchSet().getId(),
+ path),
+ userId));
+ pathList.add(path);
}
}
db.get().accountPatchReviews().insert(inserts);
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 7fb09b5..9ed2bee 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
@@ -31,6 +31,7 @@
public class Module extends RestApiModule {
@Override
protected void configure() {
+ bind(ChangesCollection.class);
bind(Revisions.class);
bind(Reviewers.class);
bind(Drafts.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index d87db2f..698a0ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -94,6 +94,15 @@
return controlFor(change, user);
}
+ public ChangeControl validateFor(Change change, CurrentUser user)
+ throws NoSuchChangeException, OrmException {
+ ChangeControl c = controlFor(change, user);
+ if (!c.isVisible(db.get())) {
+ throw new NoSuchChangeException(c.getChange().getId());
+ }
+ return c;
+ }
+
public ChangeControl validateFor(Change.Id id, CurrentUser user)
throws NoSuchChangeException, OrmException {
ChangeControl c = controlFor(id, user);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index f98822f..c111c02 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -496,6 +496,10 @@
try {
Map<String, Ref> allRefs = repo.getRefDatabase().getRefs(ALL);
for (Entry<String, Ref> entry : allRefs.entrySet()) {
+ String refName = entry.getKey();
+ if (!refName.startsWith("refs/heads") && !refName.startsWith("refs/tags")) {
+ continue;
+ }
RevCommit tip;
try {
tip = rw.parseCommit(entry.getValue().getObjectId());
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 44c71a9..4b6a5a6 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
@@ -24,7 +24,6 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -82,16 +81,12 @@
}
@Inject
- QueryChanges(ChangeJson json,
- QueryProcessor qp,
- ChangeControl.Factory cf,
- Provider<CurrentUser> user) {
+ QueryChanges(ChangeJson json, QueryProcessor qp, Provider<CurrentUser> user) {
this.json = json;
this.imp = qp;
this.user = user;
options = EnumSet.noneOf(ListChangesOption.class);
- json.setChangeControlFactory(cf);
}
public void addQuery(String query) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 5624f45..2f3fe54 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -98,7 +98,8 @@
private final ChangeQueryRewriter queryRewriter;
private final Provider<ReviewDb> db;
private final GitRepositoryManager repoManager;
- private final ChangeControl.Factory changeControlFactory;
+ private final ChangeControl.GenericFactory changeControlFactory;
+ private final CurrentUser user;
private final int maxLimit;
private OutputFormat outputFormat = OutputFormat.TEXT;
@@ -123,13 +124,14 @@
ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db,
GitRepositoryManager repoManager,
- ChangeControl.Factory changeControlFactory) {
+ ChangeControl.GenericFactory changeControlFactory) {
this.eventFactory = eventFactory;
this.queryBuilder = queryBuilder.create(currentUser);
this.queryRewriter = queryRewriter;
this.db = db;
this.repoManager = repoManager;
this.changeControlFactory = changeControlFactory;
+ this.user = currentUser;
this.maxLimit = currentUser.getCapabilities()
.getRange(GlobalCapability.QUERY_LIMIT)
.getMax();
@@ -298,8 +300,11 @@
List<ChangeData> results = queryChanges(queryString);
ChangeAttribute c = null;
for (ChangeData d : results) {
- LabelTypes labelTypes = changeControlFactory.controlFor(d.getChange())
- .getLabelTypes();
+ ChangeControl cc = d.changeControl();
+ if (cc == null || cc.getCurrentUser() != user) {
+ cc = changeControlFactory.controlFor(d.change(db), user);
+ }
+ LabelTypes labelTypes = cc.getLabelTypes();
c = eventFactory.asChangeAttribute(d.getChange());
eventFactory.extend(c, d.getChange());
eventFactory.addTrackingIds(c, d.trackingIds(db));
@@ -307,7 +312,7 @@
if (includeSubmitRecords) {
PatchSet.Id psId = d.getChange().currentPatchSetId();
PatchSet patchSet = db.get().patchSets().get(psId);
- List<SubmitRecord> submitResult = d.changeControl().canSubmit( //
+ List<SubmitRecord> submitResult = cc.canSubmit( //
db.get(), patchSet, null, false, true, true);
eventFactory.addSubmitRecords(c, submitResult);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/GerritServerSession.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/GerritServerSession.java
new file mode 100644
index 0000000..b7f7c22
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/GerritServerSession.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2013 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.sshd;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.session.ServerSession;
+
+/* Expose addition of close session listeners */
+class GerritServerSession extends ServerSession {
+
+ GerritServerSession(ServerFactoryManager server,
+ IoSession ioSession) throws Exception {
+ super(server, ioSession);
+ }
+
+ void addCloseSessionListener(SshFutureListener<CloseFuture> l) {
+ closeFuture.addListener(l);
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 8519e94..bafc388 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -15,7 +15,6 @@
package com.google.gerrit.sshd;
import static com.google.gerrit.server.ssh.SshAddressesModule.IANA_SSH_PORT;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -35,20 +34,18 @@
import com.jcraft.jsch.HostKey;
import com.jcraft.jsch.JSchException;
-import org.apache.mina.core.future.IoFuture;
-import org.apache.mina.core.future.IoFutureListener;
-import org.apache.mina.core.service.IoAcceptor;
-import org.apache.mina.core.session.IoSession;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.Channel;
import org.apache.sshd.common.Cipher;
import org.apache.sshd.common.Compression;
+import org.apache.sshd.common.ForwardingFilter;
import org.apache.sshd.common.KeyExchange;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
import org.apache.sshd.common.Signature;
+import org.apache.sshd.common.SshdSocketAddress;
import org.apache.sshd.common.cipher.AES128CBC;
import org.apache.sshd.common.cipher.AES192CBC;
import org.apache.sshd.common.cipher.AES256CBC;
@@ -56,6 +53,19 @@
import org.apache.sshd.common.cipher.CipherNone;
import org.apache.sshd.common.cipher.TripleDESCBC;
import org.apache.sshd.common.compression.CompressionNone;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.FileSystemView;
+import org.apache.sshd.common.file.SshFile;
+import org.apache.sshd.common.forward.DefaultTcpipForwarderFactory;
+import org.apache.sshd.common.forward.TcpipServerChannel;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.mina.MinaServiceFactory;
+import org.apache.sshd.common.io.mina.MinaSession;
+import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
import org.apache.sshd.common.mac.HMACMD5;
import org.apache.sshd.common.mac.HMACMD596;
import org.apache.sshd.common.mac.HMACSHA1;
@@ -63,26 +73,21 @@
import org.apache.sshd.common.random.BouncyCastleRandom;
import org.apache.sshd.common.random.JceRandom;
import org.apache.sshd.common.random.SingletonRandomFactory;
+import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.signature.SignatureDSA;
import org.apache.sshd.common.signature.SignatureRSA;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.FileSystemFactory;
-import org.apache.sshd.server.FileSystemView;
-import org.apache.sshd.server.ForwardingFilter;
import org.apache.sshd.server.PublickeyAuthenticator;
-import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.auth.gss.UserAuthGSS;
-import org.apache.sshd.server.channel.ChannelDirectTcpip;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
-import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.SessionFactory;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
@@ -91,7 +96,6 @@
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
-import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
@@ -126,6 +130,11 @@
public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private static final Logger log = LoggerFactory.getLogger(SshDaemon.class);
+ public static enum SshSessionBackend {
+ MINA,
+ NIO2
+ }
+
private final List<SocketAddress> listen;
private final List<String> advertised;
private final boolean keepAlive;
@@ -144,7 +153,6 @@
this.listen = listen;
this.advertised = advertised;
- reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true);
keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
getProperties().put(SERVER_IDENTIFICATION,
@@ -161,12 +169,6 @@
long idleTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null,
"idleTimeout", 0, SECONDS);
- if (idleTimeoutSeconds == 0) {
- // Since Apache SSHD does not allow to turn off closing idle connections,
- // we fake it by using the highest timeout allowed by Apache SSHD, which
- // amounts to ~24 days.
- idleTimeoutSeconds = MILLISECONDS.toSeconds(Integer.MAX_VALUE);
- }
getProperties().put(
IDLE_TIMEOUT,
String.valueOf(SECONDS.toMillis(idleTimeoutSeconds)));
@@ -183,6 +185,14 @@
final String kerberosPrincipal = cfg.getString(
"sshd", null, "kerberosPrincipal");
+ SshSessionBackend backend = cfg.getEnum(
+ "sshd", null, "backend", SshSessionBackend.MINA);
+
+ System.setProperty(IoServiceFactory.class.getName(),
+ backend == SshSessionBackend.MINA
+ ? MinaServiceFactory.class.getName()
+ : Nio2ServiceFactory.class.getName());
+
if (SecurityUtils.isBouncyCastleRegistered()) {
initProviderBouncyCastle();
} else {
@@ -192,7 +202,7 @@
initMacs(cfg);
initSignatures();
initChannels();
- initForwardingFilter();
+ initForwarding();
initFileSystemFactory();
initSubsystems();
initCompression();
@@ -202,24 +212,28 @@
setShellFactory(noShell);
setSessionFactory(new SessionFactory() {
@Override
- protected ServerSession createSession(final IoSession io)
+ protected AbstractSession createSession(final IoSession io)
throws Exception {
- if (io.getConfig() instanceof SocketSessionConfig) {
- final SocketSessionConfig c = (SocketSessionConfig) io.getConfig();
- c.setKeepAlive(keepAlive);
+ if (io instanceof MinaSession) {
+ if (((MinaSession) io).getSession()
+ .getConfig() instanceof SocketSessionConfig) {
+ ((SocketSessionConfig) ((MinaSession) io).getSession()
+ .getConfig())
+ .setKeepAlive(keepAlive);
+ }
}
- final ServerSession s = (ServerSession) super.createSession(io);
- final int id = idGenerator.next();
- final SocketAddress peer = io.getRemoteAddress();
+ GerritServerSession s = (GerritServerSession)super.createSession(io);
+ int id = idGenerator.next();
+ SocketAddress peer = io.getRemoteAddress();
final SshSession sd = new SshSession(id, peer);
s.setAttribute(SshSession.KEY, sd);
// Log a session close without authentication as a failure.
//
- io.getCloseFuture().addListener(new IoFutureListener<IoFuture>() {
+ s.addCloseSessionListener(new SshFutureListener<CloseFuture>() {
@Override
- public void operationComplete(IoFuture future) {
+ public void operationComplete(CloseFuture future) {
if (sd.isAuthenticationError()) {
sshLog.onAuthFail(sd);
}
@@ -227,6 +241,12 @@
});
return s;
}
+
+ @Override
+ protected AbstractSession doCreateSession(IoSession ioSession)
+ throws Exception {
+ return new GerritServerSession(server, ioSession);
+ }
});
hostKeys = computeHostKeys();
@@ -245,13 +265,11 @@
public synchronized void start() {
if (acceptor == null && !listen.isEmpty()) {
checkConfig();
-
+ if (sessionFactory == null) {
+ sessionFactory = createSessionFactory();
+ }
+ sessionFactory.setServer(this);
acceptor = createAcceptor();
- configure(acceptor);
-
- final SessionFactory handler = getSessionFactory();
- handler.setServer(this);
- acceptor.setHandler(handler);
try {
acceptor.bind(listen);
@@ -259,7 +277,8 @@
throw new IllegalStateException("Cannot bind to " + addressList(), e);
}
- log.info("Started Gerrit SSHD on " + addressList());
+ log.info(String.format("Started Gerrit %s on %s",
+ version, addressList()));
}
}
@@ -473,7 +492,7 @@
private void initChannels() {
setChannelFactories(Arrays.<NamedFactory<Channel>> asList(
new ChannelSession.Factory(), //
- new ChannelDirectTcpip.Factory() //
+ new TcpipServerChannel.DirectTcpipFactory() //
));
}
@@ -514,28 +533,29 @@
setPublickeyAuthenticator(pubkey);
}
- private void initForwardingFilter() {
- setForwardingFilter(new ForwardingFilter() {
+ private void initForwarding() {
+ setTcpipForwardingFilter(new ForwardingFilter() {
@Override
- public boolean canForwardAgent(ServerSession session) {
- return false;
+ public boolean canForwardAgent(Session session) {
+ return false;
}
@Override
- public boolean canForwardX11(ServerSession session) {
- return false;
+ public boolean canForwardX11(Session session) {
+ return false;
}
@Override
- public boolean canConnect(InetSocketAddress address, ServerSession session) {
- return false;
+ public boolean canListen(SshdSocketAddress address, Session session) {
+ return false;
}
@Override
- public boolean canListen(InetSocketAddress address, ServerSession session) {
- return false;
+ public boolean canConnect(SshdSocketAddress address, Session session) {
+ return false;
}
});
+ setTcpipForwarderFactory(new DefaultTcpipForwarderFactory());
}
private void initFileSystemFactory() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
index fc1303c..a2f2c1d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
@@ -21,8 +21,8 @@
import com.google.gerrit.sshd.SshScope.Context;
import org.apache.commons.codec.binary.Base64;
-import org.apache.mina.core.future.IoFuture;
-import org.apache.mina.core.future.IoFutureListener;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.Buffer;
@@ -138,10 +138,11 @@
sshScope.set(old);
}
- session.getIoSession().getCloseFuture().addListener(
- new IoFutureListener<IoFuture>() {
+ GerritServerSession s = (GerritServerSession) session;
+ s.addCloseSessionListener(
+ new SshFutureListener<CloseFuture>() {
@Override
- public void operationComplete(IoFuture future) {
+ public void operationComplete(CloseFuture future) {
final Context ctx = sshScope.newContext(null, sd, null);
final Context old = sshScope.set(ctx);
try {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 89ba6ba..ff1de80 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -32,8 +32,9 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.apache.mina.core.service.IoAcceptor;
-import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.server.Environment;
import org.eclipse.jgit.internal.storage.file.WindowCacheStatAccessor;
import org.kohsuke.args4j.Option;
@@ -274,8 +275,12 @@
long now = TimeUtil.nowMs();
Collection<IoSession> list = acceptor.getManagedSessions().values();
long oldest = now;
+
for (IoSession s : list) {
- oldest = Math.min(oldest, s.getCreationTime());
+ if (s instanceof MinaSession) {
+ MinaSession minaSession = (MinaSession)s;
+ oldest = Math.min(oldest, minaSession.getSession().getCreationTime());
+ }
}
stdout.format(
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index f8531ed..d97d750 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -26,8 +26,9 @@
import com.google.gerrit.sshd.SshSession;
import com.google.inject.Inject;
-import org.apache.mina.core.service.IoAcceptor;
-import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.session.ServerSession;
import org.kohsuke.args4j.Option;
@@ -84,10 +85,16 @@
Collections.sort(list, new Comparator<IoSession>() {
@Override
public int compare(IoSession arg0, IoSession arg1) {
- if (arg0.getCreationTime() < arg1.getCreationTime()) {
- return -1;
- } else if (arg0.getCreationTime() > arg1.getCreationTime()) {
- return 1;
+ if (arg0 instanceof MinaSession) {
+ MinaSession mArg0 = (MinaSession) arg0;
+ MinaSession mArg1 = (MinaSession) arg1;
+ if (mArg0.getSession().getCreationTime() < mArg1.getSession()
+ .getCreationTime()) {
+ return -1;
+ } else if (mArg0.getSession().getCreationTime() > mArg1.getSession()
+ .getCreationTime()) {
+ return 1;
+ }
}
return (int) (arg0.getId() - arg1.getId());
}
@@ -104,8 +111,15 @@
SshSession sd = s != null ? s.getAttribute(SshSession.KEY) : null;
final SocketAddress remoteAddress = io.getRemoteAddress();
- final long start = io.getCreationTime();
- final long idle = now - io.getLastIoTime();
+ MinaSession minaSession = io instanceof MinaSession
+ ? (MinaSession) io
+ : null;
+ final long start = minaSession == null
+ ? 0
+ : minaSession.getSession().getCreationTime();
+ final long idle = minaSession == null
+ ? now
+ : now - minaSession.getSession().getLastIoTime();
stdout.print(String.format("%8s %8s %8s %-15.15s %s\n", //
id(sd), //
diff --git a/gerrit-war/src/main/resources/log4j.properties b/gerrit-war/src/main/resources/log4j.properties
index 1fcca6d..cb14916 100644
--- a/gerrit-war/src/main/resources/log4j.properties
+++ b/gerrit-war/src/main/resources/log4j.properties
@@ -26,7 +26,7 @@
log4j.logger.org.apache.sshd.common=WARN
log4j.logger.org.apache.sshd.server=WARN
log4j.logger.org.apache.sshd.common.keyprovider.FileKeyPairProvider=INFO
-log4j.logger.com.google.gerrit.server.ssh.GerritServerSession=WARN
+log4j.logger.com.google.gerrit.sshd.GerritServerSession=WARN
# Silence non-critical messages from Jetty.
#
diff --git a/lib/mina/BUCK b/lib/mina/BUCK
index 3e9558a..9467cc4 100644
--- a/lib/mina/BUCK
+++ b/lib/mina/BUCK
@@ -8,17 +8,18 @@
maven_jar(
name = 'core',
- id = 'org.apache.mina:mina-core:2.0.5',
- sha1 = '0e134a3761833a3c28c79331e806f64f985a9eec',
+ id = 'org.apache.mina:mina-core:2.0.7',
+ sha1 = 'c878e2aa82de748474a624ec3933e4604e446dec',
license = 'Apache2.0',
exclude = EXCLUDE,
)
maven_jar(
name = 'sshd',
- id = 'org.apache.sshd:sshd-core:0.6.0',
- sha1 = '2b9a119dd77a1decec78b0c511ba400c8655e96e',
+ id = 'org.apache.sshd:sshd-core:0.9.0.201311081',
+ sha1 = '38f7ac8602e70fa05fdc6147d204198e9cefe5bc',
license = 'Apache2.0',
deps = [':core'],
exclude = EXCLUDE,
+ repository = GERRIT,
)