Merge "OAuthRealm should not overwrite custom AuthRequest parameters"
diff --git a/.gitignore b/.gitignore
index d4ef1b8..e7fe393 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
/test_site
/.idea
*.iml
+*.eml
*.sublime-*
/gerrit-package-plugins
/.buckconfig.local
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 4881f2a..b88f1a3 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2400,10 +2400,10 @@
[[index.threads]]index.threads::
+
-Number of threads to use for indexing in normal interactive operations. If set
-to 0, indexing will be done in the same thread as the interactive operation.
+Number of threads to use for indexing in normal interactive operations.
+
-Defaults to 0 if not set, or set to a negative value.
+If not set or set to a negative value, defaults to 1 plus half of the number of
+logical CPUs as returned by the JVM.
[[index.batchThreads]]index.batchThreads::
+
diff --git a/Documentation/dev-stars.txt b/Documentation/dev-stars.txt
new file mode 100644
index 0000000..1a98438
--- /dev/null
+++ b/Documentation/dev-stars.txt
@@ -0,0 +1,73 @@
+= Gerrit Code Review - Stars
+
+== Description
+
+Changes can be starred with labels that behave like private hashtags.
+Any label can be applied to a change, but these labels are only visible
+to the user for which the labels have been set.
+
+Stars allow users to categorize changes by self-defined criteria and
+then build link:user-dashboards.html[dashboards] for them by making use
+of the link:#query-stars[star query operators].
+
+[[star-api]]
+== Star API
+
+The link:rest-api-accounts.html#star-endpoints[star REST API] supports:
+
+* link:rest-api-accounts.html#get-stars[
+ get star labels from a change]
+* link:rest-api-accounts.html#set-stars[
+ update star labels on a change]
+* link:rest-api-accounts.html#get-starred-changes[
+ list changes that are starred by any label]
+
+Star labels are also included in
+link:rest-api-changes.html#change-info[ChangeInfo] entities that are
+returned by the link:rest-api-changes.html[changes REST API].
+
+There are link:rest-api-accounts.html#default-star-endpoints[
+additional REST endpoints] for the link:#default-star[default star].
+
+Only the link:#default-star[default star] is shown in the WebUi and
+can be updated from there. Other stars do not show up in the WebUi.
+
+[[default-star]]
+== Default Star
+
+If the default star is set by a user, this user is automatically
+notified by email whenever updates are made to that change.
+
+The default star is the star that is shown in the WebUI and which can
+be updated from there.
+
+The default star is represented by the special star label 'star'.
+
+[[query-stars]]
+== Query Stars
+
+There are several query operators to find changes with stars:
+
+* link:user-search.html#star[star:<LABEL>]:
+ Matches any change that was starred by the current user with the
+ label `<LABEL>`.
+* link:user-search.html#has-stars[has:stars]:
+ Matches any change that was starred by the current user with any
+ label.
+* link:user-search.html#is-starred[is:starred] /
+ link:user-search.html#has-star[has:star]:
+ Matches any change that was starred by the current user with the
+ link:#default-star[default star].
+
+[[syntax]]
+== Syntax
+
+Star labels cannot contain whitespace characters. All other characters
+are allowed.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/index.txt b/Documentation/index.txt
index d4836e5..1693c18 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -68,6 +68,7 @@
.. link:dev-build-plugins.html[Building Gerrit plugins]
.. link:js-api.html[JavaScript Plugin API]
.. link:config-validation.html[Validation Interfaces]
+.. link:dev-stars.html[Starring Changes]
. link:dev-design.html[System Design]
. link:i18n-readme.html[i18n Support]
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 423d87d..8e2dae9 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1573,16 +1573,19 @@
]
----
-[[get-starred-changes]]
-=== Get Starred Changes
+[[default-star-endpoints]]
+== Default Star Endpoints
+
+[[get-changes-with-default-star]]
+=== Get Changes With Default Star
--
'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.
+Gets the changes that were starred with the default star 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
----
@@ -1607,6 +1610,9 @@
"created": "2013-02-01 09:59:32.126000000",
"updated": "2013-02-21 11:16:36.775000000",
"starred": true,
+ "stars": [
+ "star"
+ ],
"mergeable": true,
"submittable": false,
"insertions": 145,
@@ -1620,14 +1626,15 @@
----
[[star-change]]
-=== Star Change
+=== Put Default Star On Change
--
'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.
+Star a change with the default label. Changes starred with the default
+label are returned for the search query `is:starred` or `starredby:USER`
+and automatically notify the user whenever updates are made to the
+change.
.Request
----
@@ -1640,12 +1647,12 @@
----
[[unstar-change]]
-=== Unstar Change
+=== Remove Default Star From Change
--
'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
--
-Unstar a change. Removes the starred flag, stopping notifications.
+Remove the default star label from a change. This stops notifications.
.Request
----
@@ -1657,6 +1664,131 @@
HTTP/1.1 204 No Content
----
+[[star-endpoints]]
+== Star Endpoints
+
+[[get-starred-changes]]
+=== Get Starred Changes
+--
+'GET /accounts/link:#account-id[\{account-id\}]/stars.changes'
+--
+
+Gets the changes that were starred with any label by the identified
+user account. This URL endpoint is functionally identical to the
+changes query `GET /changes/?q=has:stars`. The result is a list of
+link:rest-api-changes.html#change-info[ChangeInfo] entities.
+
+.Request
+----
+ GET /a/accounts/self/stars.changes
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "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",
+ "stars": [
+ "ignore",
+ "risky"
+ ],
+ "mergeable": true,
+ "submittable": false,
+ "insertions": 145,
+ "deletions": 12,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ }
+ ]
+----
+
+[[get-stars]]
+=== Get Star Labels From Change
+--
+'GET /accounts/link:#account-id[\{account-id\}]/stars.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
+--
+
+Get star labels from a change.
+
+.Request
+----
+ GET /a/accounts/self/stars.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+----
+
+As response the star labels that the user applied on the change are
+returned. The labels are lexicographically sorted.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ "blue",
+ "green",
+ "red"
+ ]
+----
+
+[[set-stars]]
+=== Update Star Labels On Change
+--
+'POST /accounts/link:#account-id[\{account-id\}]/stars.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
+--
+
+Update star labels on a change. The star labels to be added/removed
+must be specified in the request body as link:#star-input[StarInput]
+entity. Starred changes are returned for the search query `has:stars`.
+
+.Request
+----
+ POST /a/accounts/self/stars.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "add": [
+ "blue",
+ "red"
+ ],
+ "remove": [
+ "yellow"
+ ]
+ }
+----
+
+As response the star labels that the user applied on the change are
+returned. The labels are lexicographically sorted.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ "blue",
+ "green",
+ "red"
+ ]
+----
+
[[ids]]
== IDs
@@ -2220,6 +2352,18 @@
|`valid` ||Whether the SSH key is valid.
|=============================
+[[stars-input]]
+=== StarsInput
+The `StarsInput` entity contains star labels that should be added to
+or removed from a change.
+
+[options="header",cols="1,^1,5"]
+|========================
+|Field Name ||Description
+|`add` |optional|List of labels to add to the change.
+|`remove` |optional|List of labels to remove from the change.
+|========================
+
[[username-input]]
=== UsernameInput
The `UsernameInput` entity contains information for setting the
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index da17806..b2cd1fe 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -4113,7 +4113,10 @@
The link:rest-api.html#timestamp[timestamp] of when the change was
submitted.
|`starred` |not set if `false`|
-Whether the calling user has starred this change.
+Whether the calling user has starred this change with the default label.
+|`stars` |optional|
+A list of star labels that are applied by the calling user to this
+change. The labels are lexicographically sorted.
|`reviewed` |not set if `false`|
Whether the change was reviewed by the calling user.
Only set if link:#reviewed[reviewed] is requested.
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index b2b3614..b04898e 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -247,25 +247,43 @@
Regular expression matching can be enabled by starting the string
with `^`. In this mode `file:` is an alias of `path:` (see above).
+[[star]]
+star:'LABEL'::
++
+Matches any change that was starred by the current user with the label
+'LABEL'.
++
+E.g. if changes that are not interesting are marked with an `ignore`
+star, they could be filtered out by '-star:ignore'.
++
+'star:star' is the same as 'has:star' and 'is:starred'.
+
[[has]]
has:draft::
+
True if there is a draft comment saved by the current user.
+[[has-star]]
has:star::
+
-Same as 'is:starred', true if the change has been starred by the
-current user.
+Same as 'is:starred' and 'star:star', true if the change has been
+starred by the current user with the default label.
+
+[[has-stars]]
+has:stars::
++
+True if the change has been starred by the current user with any label.
has:edit::
+
True if the change has inline edit created by the current user.
[[is]]
+[[is-starred]]
is:starred::
+
Same as 'has:star', true if the change has been starred by the
-current user.
+current user with the default label.
is:watched::
+
@@ -510,7 +528,7 @@
starredby:'USER'::
+
-Matches changes that have been starred by 'USER'.
+Matches changes that have been starred by 'USER' with the default label.
The special case `starredby:self` applies to the caller.
watchedby:'USER'::
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 1955c39..1b3b9d9 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -31,6 +31,7 @@
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -197,6 +198,9 @@
@Inject
protected ChangeNoteUtil changeNoteUtil;
+ @Inject
+ protected ChangeResource.Factory changeResourceFactory;
+
protected TestRepository<InMemoryRepository> testRepo;
protected GerritServer server;
protected TestAccount admin;
@@ -741,6 +745,19 @@
List<ChangeControl> ctls = changeFinder.find(
changeId, atrScope.get().getUser());
assertThat(ctls).hasSize(1);
- return new ChangeResource(ctls.get(0));
+ return changeResourceFactory.create(ctls.get(0));
+ }
+
+ protected String createGroup(String name) throws Exception {
+ return createGroup(name, "Administrators");
+ }
+
+ protected String createGroup(String name, String owner) throws Exception {
+ name = name(name);
+ GroupInput in = new GroupInput();
+ in.name = name;
+ in.ownerId = owner;
+ gApi.groups().create(in);
+ return name;
}
}
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index 839b393..71d738c 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -182,6 +182,14 @@
return old;
}
+ public Context reopenDb() {
+ // Setting a new context with the same fields is enough to get the ReviewDb
+ // provider to reopen the database.
+ Context old = current.get();
+ return set(
+ new Context(old.schemaFactory, old.session, old.user, old.created));
+ }
+
/** Returns exactly one instance per command executed. */
static final Scope REQUEST = new Scope() {
@Override
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 0c2e0c6..53a47a3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -23,20 +23,25 @@
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithSecondUserId;
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
+import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AccountCreator;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.api.accounts.EmailInput;
+import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.SshKeyInfo;
+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.ResourceNotFoundException;
@@ -173,11 +178,65 @@
gApi.accounts()
.self()
.starChange(triplet);
- assertThat(info(triplet).starred).isTrue();
+ ChangeInfo change = info(triplet);
+ assertThat(change.starred).isTrue();
+
gApi.accounts()
.self()
.unstarChange(triplet);
- assertThat(info(triplet).starred).isNull();
+ change = info(triplet);
+ assertThat(change.starred).isNull();
+ }
+
+ @Test
+ public void starUnstarChangeWithLabels() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String triplet = project.get() + "~master~" + r.getChangeId();
+ assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
+ assertThat(gApi.accounts().self().getStarredChanges()).isEmpty();
+
+ gApi.accounts().self().setStars(triplet,
+ new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "red", "blue")));
+ ChangeInfo change = info(triplet);
+ assertThat(change.starred).isTrue();
+ assertThat(gApi.accounts().self().getStars(triplet))
+ .containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
+ List<ChangeInfo> starredChanges =
+ gApi.accounts().self().getStarredChanges();
+ assertThat(starredChanges).hasSize(1);
+ ChangeInfo starredChange = starredChanges.get(0);
+ assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
+ assertThat(starredChange.starred).isTrue();
+
+ gApi.accounts().self().setStars(triplet,
+ new StarsInput(ImmutableSet.of("yellow"),
+ ImmutableSet.of(DEFAULT_LABEL, "blue")));
+ change = info(triplet);
+ assertThat(change.starred).isNull();
+ assertThat(gApi.accounts().self().getStars(triplet)).containsExactly(
+ "red", "yellow").inOrder();
+ starredChanges = gApi.accounts().self().getStarredChanges();
+ assertThat(starredChanges).hasSize(1);
+ starredChange = starredChanges.get(0);
+ assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
+ assertThat(starredChange.starred).isNull();
+
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ exception.expectMessage("not allowed to get stars of another account");
+ gApi.accounts().id(Integer.toString((admin.id.get()))).getStars(triplet);
+ }
+
+ @Test
+ public void starWithInvalidLabels() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String triplet = project.get() + "~master~" + r.getChangeId();
+ exception.expect(BadRequestException.class);
+ exception.expectMessage(
+ "invalid labels: another invalid label, invalid label");
+ gApi.accounts().self().setStars(triplet,
+ new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "invalid label", "blue",
+ "another invalid label")));
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 631b480..7dc4cda 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -1174,6 +1174,6 @@
List<ChangeControl> ctls = changeFinder.find(
r.getChangeId(), atrScope.get().getUser());
assertThat(ctls).hasSize(1);
- return new ChangeResource(ctls.get(0));
+ return changeResourceFactory.create(ctls.get(0));
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 6e2e761..9c064c7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -560,19 +560,6 @@
return groupCache.get(new AccountGroup.NameKey(name));
}
- private String createGroup(String name) throws Exception {
- return createGroup(name, "Administrators");
- }
-
- private String createGroup(String name, String owner) throws Exception {
- name = name(name);
- GroupInput in = new GroupInput();
- in.name = name;
- in.ownerId = owner;
- gApi.groups().create(in);
- return name;
- }
-
private String createAccount(String name, String group) throws Exception {
name = name(name);
accounts.create(name, group);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index f4ad11e..274b7d7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -36,6 +36,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchLineCommentsUtil;
@@ -90,7 +91,7 @@
public void setUp() {
assume().that(NoteDbMode.readWrite()).isFalse();
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
- notesMigration.setAllEnabled(false);
+ setNotesMigration(false, false);
}
@After
@@ -98,6 +99,12 @@
TestTimeUtil.useSystemTime();
}
+ private void setNotesMigration(boolean writeChanges, boolean readChanges) {
+ notesMigration.setWriteChanges(writeChanges);
+ notesMigration.setReadChanges(readChanges);
+ db = atrScope.reopenDb().getReviewDbProvider().get();
+ }
+
@Test
public void changeFields() throws Exception {
PushOneCommit.Result r = createChange();
@@ -199,8 +206,7 @@
checker.rebuildAndCheckChanges(id);
- notesMigration.setWriteChanges(true);
- notesMigration.setReadChanges(true);
+ setNotesMigration(true, true);
ChangeNotes notes = notesFactory.create(db, project, id);
Map<String, PatchSet.Id> psIds = new HashMap<>();
@@ -222,7 +228,7 @@
Change.Id id = r.getPatchSetId().getParentKey();
checker.assertNoChangeRef(project, id);
- notesMigration.setWriteChanges(true);
+ setNotesMigration(true, false);
gApi.changes().id(id.get()).topic(name("a-topic"));
// First write doesn't create the ref, but rebuilding works.
@@ -250,7 +256,7 @@
public void rebuildViaRestApi() throws Exception {
PushOneCommit.Result r = createChange();
Change.Id id = r.getPatchSetId().getParentKey();
- notesMigration.setWriteChanges(true);
+ setNotesMigration(true, false);
checker.assertNoChangeRef(project, id);
rebuildHandler.apply(
@@ -264,7 +270,7 @@
PushOneCommit.Result r1 = createChange();
Change.Id id1 = r1.getPatchSetId().getParentKey();
- notesMigration.setWriteChanges(true);
+ setNotesMigration(true, false);
gApi.changes().id(id1.get()).topic(name("a-topic"));
PushOneCommit.Result r2 = createChange();
Change.Id id2 = r2.getPatchSetId().getParentKey();
@@ -281,7 +287,7 @@
@Test
public void noteDbChangeState() throws Exception {
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
PushOneCommit.Result r = createChange();
Change.Id id = r.getPatchSetId().getParentKey();
@@ -316,20 +322,20 @@
@Test
public void rebuildAutomaticallyWhenChangeOutOfDate() throws Exception {
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
PushOneCommit.Result r = createChange();
Change.Id id = r.getPatchSetId().getParentKey();
assertChangeUpToDate(true, id);
// Make a ReviewDb change behind NoteDb's back and ensure it's detected.
- notesMigration.setAllEnabled(false);
+ setNotesMigration(false, false);
gApi.changes().id(id.get()).topic(name("a-topic"));
setInvalidNoteDbState(id);
assertChangeUpToDate(false, id);
// On next NoteDb read, the change is transparently rebuilt.
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
assertThat(gApi.changes().id(id.get()).info().topic)
.isEqualTo(name("a-topic"));
assertChangeUpToDate(true, id);
@@ -343,7 +349,7 @@
@Test
public void rebuildAutomaticallyWhenDraftsOutOfDate() throws Exception {
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
setApiUser(user);
PushOneCommit.Result r = createChange();
@@ -352,13 +358,13 @@
assertDraftsUpToDate(true, id, user);
// Make a ReviewDb change behind NoteDb's back and ensure it's detected.
- notesMigration.setAllEnabled(false);
+ setNotesMigration(false, false);
putDraft(user, id, 1, "comment");
setInvalidNoteDbState(id);
assertDraftsUpToDate(false, id, user);
// On next NoteDb read, the drafts are transparently rebuilt.
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
assertThat(gApi.changes().id(id.get()).current().drafts())
.containsKey(PushOneCommit.FILE_NAME);
assertDraftsUpToDate(true, id, user);
@@ -412,8 +418,7 @@
checker.rebuildAndCheckChanges(id);
- notesMigration.setWriteChanges(true);
- notesMigration.setReadChanges(true);
+ setNotesMigration(true, true);
// Rebuild and check was successful, but NoteDb doesn't support storing an
// empty topic, so it comes out as null.
@@ -480,7 +485,7 @@
checker.rebuildAndCheckChanges(id);
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
ChangeNotes notes = notesFactory.create(db, project, id);
Change nc = notes.getChange();
assertThat(nc.getSubject()).isEqualTo(c.getSubject());
@@ -503,7 +508,7 @@
checker.rebuildAndCheckChanges(id);
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
ChangeNotes notes = notesFactory.create(db, project, id);
assertThat(notes.getPatchSets().keySet()).containsExactly(psId);
}
@@ -527,11 +532,52 @@
checker.rebuildAndCheckChanges(id);
- notesMigration.setAllEnabled(true);
+ setNotesMigration(true, true);
ChangeNotes notes = notesFactory.create(db, project, id);
assertThat(notes.getComments()).isEmpty();
}
+ @Test
+ public void skipPatchSetsGreaterThanCurrentPatchSet() throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change change = r.getChange().change();
+ Change.Id id = change.getId();
+
+ PatchSet badPs =
+ new PatchSet(new PatchSet.Id(id, change.currentPatchSetId().get() + 1));
+ badPs.setCreatedOn(TimeUtil.nowTs());
+ badPs.setUploader(new Account.Id(12345));
+ badPs.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ db.patchSets().insert(Collections.singleton(badPs));
+ indexer.index(db, change.getProject(), id);
+
+ checker.rebuildAndCheckChanges(id);
+
+ setNotesMigration(true, true);
+ ChangeNotes notes = notesFactory.create(db, project, id);
+ assertThat(notes.getPatchSets().keySet())
+ .containsExactly(change.currentPatchSetId());
+ }
+
+ @Test
+ public void leadingSpacesInSubject() throws Exception {
+ String subj = " " + PushOneCommit.SUBJECT;
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo,
+ subj, PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT);
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ Change change = r.getChange().change();
+ assertThat(change.getSubject()).isEqualTo(subj);
+ Change.Id id = r.getPatchSetId().getParentKey();
+
+ checker.rebuildAndCheckChanges(id);
+
+ setNotesMigration(true, true);
+ ChangeNotes notes = notesFactory.create(db, project, id);
+ assertThat(notes.getChange().getSubject()).isNotEqualTo(subj);
+ assertThat(notes.getChange().getSubject()).isEqualTo(PushOneCommit.SUBJECT);
+ }
+
private void setInvalidNoteDbState(Change.Id id) throws Exception {
ReviewDb db = unwrapDb();
Change c = db.changes().get(id);
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 2fce1cd..3957a30 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
@@ -107,11 +107,6 @@
return QUERY + KeyUtil.encode(query);
}
- public static String toChangeQuery(String query, String start) {
- int s = Integer.parseInt(start);
- return QUERY + KeyUtil.encode(query) + (s > 0 ? "," + s : "");
- }
-
public static String toProjectDashboard(Project.NameKey name, String id) {
return PROJECTS + name.get() + DASHBOARDS + id;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index 5167fe6..5777396 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -35,30 +35,30 @@
FILE, SYMLINK, GITLINK
}
- protected Change.Key changeId;
- protected ChangeType changeType;
- protected String oldName;
- protected String newName;
- protected FileMode oldMode;
- protected FileMode newMode;
- protected List<String> header;
- protected DiffPreferencesInfo diffPrefs;
- protected SparseFileContent a;
- protected SparseFileContent b;
- protected List<Edit> edits;
- protected DisplayMethod displayMethodA;
- protected DisplayMethod displayMethodB;
- protected transient String mimeTypeA;
- protected transient String mimeTypeB;
- protected CommentDetail comments;
- protected List<Patch> history;
- protected boolean hugeFile;
- protected boolean intralineDifference;
- protected boolean intralineFailure;
- protected boolean intralineTimeout;
- protected boolean binary;
- protected transient String commitIdA;
- protected transient String commitIdB;
+ private Change.Key changeId;
+ private ChangeType changeType;
+ private String oldName;
+ private String newName;
+ private FileMode oldMode;
+ private FileMode newMode;
+ private List<String> header;
+ private DiffPreferencesInfo diffPrefs;
+ private SparseFileContent a;
+ private SparseFileContent b;
+ private List<Edit> edits;
+ private DisplayMethod displayMethodA;
+ private DisplayMethod displayMethodB;
+ private transient String mimeTypeA;
+ private transient String mimeTypeB;
+ private CommentDetail comments;
+ private List<Patch> history;
+ private boolean hugeFile;
+ private boolean intralineDifference;
+ private boolean intralineFailure;
+ private boolean intralineTimeout;
+ private boolean binary;
+ private transient String commitIdA;
+ private transient String commitIdB;
public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
final String nn, final FileMode om, final FileMode nm,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java
deleted file mode 100644
index 6ae5eb6..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2010 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.errors;
-
-/** Error indicating the account is currently inactive. */
-public class InactiveAccountException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public static final String MESSAGE = "Account Inactive: ";
-
- public InactiveAccountException(String who) {
- super(MESSAGE + who);
- }
-}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index 2105b8c..d17beca 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -14,11 +14,13 @@
package com.google.gerrit.extensions.api.accounts;
+import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -26,6 +28,7 @@
import java.util.List;
import java.util.Map;
+import java.util.SortedSet;
public interface AccountApi {
AccountInfo get() throws RestApiException;
@@ -50,8 +53,12 @@
void deleteWatchedProjects(List<String> in)
throws RestApiException;
- void starChange(String id) throws RestApiException;
- void unstarChange(String id) throws RestApiException;
+ void starChange(String changeId) throws RestApiException;
+ void unstarChange(String changeId) throws RestApiException;
+ void setStars(String changeId, StarsInput input) throws RestApiException;
+ SortedSet<String> getStars(String changeId) throws RestApiException;
+ List<ChangeInfo> getStarredChanges() throws RestApiException;
+
void addEmail(EmailInput input) throws RestApiException;
List<SshKeyInfo> listSshKeys() throws RestApiException;
@@ -130,12 +137,28 @@
}
@Override
- public void starChange(String id) throws RestApiException {
+ public void starChange(String changeId) throws RestApiException {
throw new NotImplementedException();
}
@Override
- public void unstarChange(String id) throws RestApiException {
+ public void unstarChange(String changeId) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void setStars(String changeId, StarsInput input)
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public SortedSet<String> getStars(String changeId) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public List<ChangeInfo> getStarredChanges() throws RestApiException {
throw new NotImplementedException();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/StarsInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/StarsInput.java
new file mode 100644
index 0000000..d3dff98
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/StarsInput.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import java.util.Set;
+
+public class StarsInput {
+ public Set<String> add;
+ public Set<String> remove;
+
+ public StarsInput() {
+ }
+
+ public StarsInput(Set<String> add) {
+ this.add = add;
+ }
+
+ public StarsInput(Set<String> add, Set<String> remove) {
+ this.add = add;
+ this.remove = remove;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
index 901951e..99b2cfa 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
@@ -14,17 +14,33 @@
package com.google.gerrit.extensions.auth.oauth;
+import java.io.Serializable;
+
/* OAuth token */
-public class OAuthToken {
+public class OAuthToken implements Serializable {
+
+ private static final long serialVersionUID = 1L;
private final String token;
private final String secret;
private final String raw;
+ /**
+ * Time of expiration of this token, or {@code Long#MAX_VALUE} if this
+ * token never expires, or time of expiration is unknown.
+ */
+ private final long expiresAt;
+
public OAuthToken(String token, String secret, String raw) {
+ this(token, secret, raw, Long.MAX_VALUE);
+ }
+
+ public OAuthToken(String token, String secret, String raw,
+ long expiresAt) {
this.token = token;
this.secret = secret;
this.raw = raw;
+ this.expiresAt = expiresAt;
}
public String getToken() {
@@ -38,4 +54,12 @@
public String getRaw() {
return raw;
}
+
+ public long getExpiresAt() {
+ return expiresAt;
+ }
+
+ public boolean isExpired() {
+ return System.currentTimeMillis() > expiresAt;
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthTokenEncrypter.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthTokenEncrypter.java
new file mode 100644
index 0000000..b2f4262
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthTokenEncrypter.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.auth.oauth;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public interface OAuthTokenEncrypter {
+
+ /**
+ * Encrypts the secret parts of the given OAuth access token.
+ *
+ * @param unencrypted a raw OAuth access token.
+ */
+ OAuthToken encrypt(OAuthToken unencrypted);
+
+ /**
+ * Decrypts the secret parts of the given OAuth access token.
+ *
+ * @param encrypted an encryppted OAuth access token.
+ */
+ OAuthToken decrypt(OAuthToken encrypted);
+}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpTextArea.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpTextArea.java
index 38a488e..fd0da74 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpTextArea.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpTextArea.java
@@ -14,7 +14,6 @@
package com.google.gwtexpui.globalkey.client;
-import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.TextArea;
public class NpTextArea extends TextArea {
@@ -22,11 +21,6 @@
addKeyPressHandler(GlobalKey.STOP_PROPAGATION);
}
- public NpTextArea(final Element element) {
- super(element);
- addKeyPressHandler(GlobalKey.STOP_PROPAGATION);
- }
-
public void setSpellCheck(boolean spell) {
getElement().setPropertyBoolean("spellcheck", spell);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java
index b2da7b5..4ca1721 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java
@@ -46,18 +46,6 @@
* @param size A requested size. Note that the size can be ignored depending
* on the avatar provider. A size <= 0 indicates to let the provider
* decide a default size.
- */
- public AvatarImage(AccountInfo account, int size) {
- this(account, size, true);
- }
-
- /**
- * An avatar image for the given account using the requested size.
- *
- * @param account The account in which we are interested
- * @param size A requested size. Note that the size can be ignored depending
- * on the avatar provider. A size <= 0 indicates to let the provider
- * decide a default size.
* @param addPopup show avatar popup with user info on hovering over the
* avatar image
*/
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
index a5c5659..87cb4b3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
@@ -27,7 +27,6 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtjsonrpc.client.RemoteJsonException;
@@ -96,11 +95,6 @@
body.add(message.toBlockWidget());
}
- public ErrorDialog(final Widget w) {
- this();
- body.add(w);
- }
-
/** Create a dialog box to nicely format an exception. */
public ErrorDialog(final Throwable what) {
this();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 920c285..54c5b92 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -104,6 +104,8 @@
suggestions.add("has:draft");
suggestions.add("has:edit");
suggestions.add("has:star");
+ suggestions.add("has:stars");
+ suggestions.add("star:");
suggestions.add("is:");
suggestions.add("is:starred");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
index efacbe3..44aae25 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
@@ -265,12 +265,4 @@
protected PostInput() {
}
}
-
- public static class Result extends JavaScriptObject {
- public final native JsArrayString hashtags() /*-{ return this.hashtags; }-*/;
- public final native String error() /*-{ return this.error; }-*/;
-
- protected Result() {
- }
- }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
index 3d14ebd..98f4eb5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
@@ -79,8 +79,8 @@
/** Base class for SideBySide and Unified */
abstract class DiffScreen extends Screen {
- static final KeyMap RENDER_ENTIRE_FILE_KEYMAP = KeyMap.create()
- .propagate("Ctrl-F");
+ private static final KeyMap RENDER_ENTIRE_FILE_KEYMAP = KeyMap.create()
+ .propagate("Ctrl-F").propagate("Ctrl-G").propagate("Shift-Ctrl-G");
enum FileSize {
SMALL(0),
@@ -269,10 +269,6 @@
return keysAction;
}
- KeyCommandSet getKeysComment() {
- return keysComment;
- }
-
@Override
protected void onUnload() {
super.onUnload();
@@ -317,7 +313,6 @@
.on("']'", header.navigate(Direction.NEXT))
.on("R", header.toggleReviewed())
.on("O", getCommentManager().toggleOpenBox(cm))
- .on("Enter", getCommentManager().toggleOpenBox(cm))
.on("N", maybeNextVimSearch(cm))
.on("Ctrl-Alt-E", openEditScreen(cm))
.on("P", getChunkManager().diffChunkNav(cm, Direction.PREV))
@@ -367,7 +362,34 @@
.on("Ctrl-F", new Runnable() {
@Override
public void run() {
- cm.vim().handleKey("/");
+ cm.execCommand("find");
+ }
+ })
+ .on("Ctrl-G", new Runnable() {
+ @Override
+ public void run() {
+ cm.execCommand("findNext");
+ }
+ })
+ .on("Enter", maybeNextCmSearch(cm))
+ .on("Shift-Ctrl-G", new Runnable() {
+ @Override
+ public void run() {
+ cm.execCommand("findPrev");
+ }
+ })
+ .on("Shift-Enter", new Runnable() {
+ @Override
+ public void run() {
+ cm.execCommand("findPrev");
+ }
+ })
+ .on("Esc", new Runnable() {
+ @Override
+ public void run() {
+ cm.setCursor(cm.getCursor());
+ cm.execCommand("clearSearch");
+ cm.vim().handleEx("nohlsearch");
}
})
.on("Ctrl-A", new Runnable() {
@@ -596,7 +618,7 @@
}
}
- void toggleShowIntraline() {
+ private void toggleShowIntraline() {
prefs.intralineDifference(!prefs.intralineDifference());
setShowIntraline(prefs.intralineDifference());
prefsAction.update();
@@ -738,7 +760,7 @@
abstract void operation(final Runnable apply);
- Runnable upToChange(final boolean openReplyBox) {
+ private Runnable upToChange(final boolean openReplyBox) {
return new Runnable() {
@Override
public void run() {
@@ -760,7 +782,7 @@
};
}
- Runnable maybePrevVimSearch(final CodeMirror cm) {
+ private Runnable maybePrevVimSearch(final CodeMirror cm) {
return new Runnable() {
@Override
public void run() {
@@ -773,7 +795,7 @@
};
}
- Runnable maybeNextVimSearch(final CodeMirror cm) {
+ private Runnable maybeNextVimSearch(final CodeMirror cm) {
return new Runnable() {
@Override
public void run() {
@@ -786,6 +808,20 @@
};
}
+ Runnable maybeNextCmSearch(final CodeMirror cm) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ if (cm.hasSearchHighlight()) {
+ cm.execCommand("findNext");
+ } else {
+ cm.execCommand("clearSearch");
+ getCommentManager().toggleOpenBox(cm).run();
+ }
+ }
+ };
+ }
+
boolean renderEntireFile() {
return prefs.renderEntireFile() && canRenderEntireFile(prefs);
}
@@ -894,7 +930,7 @@
});
}
- static FileSize bucketFileSize(DiffInfo diff) {
+ private static FileSize bucketFileSize(DiffInfo diff) {
FileMeta a = diff.metaA();
FileMeta b = diff.metaB();
FileSize[] sizes = FileSize.values();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java
index 35d6cac..006788f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java
@@ -369,10 +369,6 @@
return new CodeMirror[] {cm};
}
- CodeMirror getCm() {
- return cm;
- }
-
@Override
UnifiedTable getDiffTable() {
return diffTable;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index daac7cf..cd44fac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -17,7 +17,6 @@
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.NotSignedInDialog;
-import com.google.gerrit.common.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
@@ -42,9 +41,6 @@
new NotSignedInDialog().center();
} else if (isNoSuchEntity(caught)) {
new ErrorDialog(Gerrit.C.notFoundBody()).center();
- } else if (isInactiveAccount(caught)) {
- new ErrorDialog(Gerrit.C.inactiveAccountBody()).center();
-
} else if (isNoSuchAccount(caught)) {
final String msg = caught.getMessage();
final String who = msg.substring(NoSuchAccountException.MESSAGE.length());
@@ -97,11 +93,6 @@
&& caught.getMessage().equals(NoSuchEntityException.MESSAGE));
}
- protected static boolean isInactiveAccount(final Throwable caught) {
- return caught instanceof RemoteJsonException
- && caught.getMessage().startsWith(InactiveAccountException.MESSAGE);
- }
-
protected static boolean isNoSuchAccount(final Throwable caught) {
return caught instanceof RemoteJsonException
&& caught.getMessage().startsWith(NoSuchAccountException.MESSAGE);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
deleted file mode 100644
index c8bb12e..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (C) 2010 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.client.ui;
-
-import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.event.shared.HandlerRegistration;
-
-
-public class ListenableValue<T> implements HasValueChangeHandlers<T> {
-
- private HandlerManager manager = new HandlerManager(this);
-
- private T value;
-
- public T get() {
- return value;
- }
-
- public void set(final T value) {
- this.value = value;
- fireEvent(new ValueChangeEvent<T>(value) {});
- }
-
- @Override
- public void fireEvent(GwtEvent<?> event) {
- manager.fireEvent(event);
- }
-
- @Override
- public HandlerRegistration addValueChangeHandler(
- ValueChangeHandler<T> handler) {
- return manager.addHandler(ValueChangeEvent.getType(), handler);
- }
-
- public int getHandlerCount() {
- return manager.getHandlerCount(ValueChangeEvent.getType());
- }
-
- public ValueChangeHandler<?> getHandler(int index) {
- return manager.getHandler(ValueChangeEvent.getType(), index);
- }
-
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java
index 0933153..3994381 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java
@@ -14,7 +14,6 @@
package com.google.gerrit.client.ui;
-import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
@@ -31,11 +30,6 @@
init();
}
- public NpIntTextBox(Element element) {
- super(element);
- init();
- }
-
private void init() {
addKeyDownHandler(new KeyDownHandler() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
deleted file mode 100644
index 09244c9..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
+++ /dev/null
@@ -1,37 +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.client.ui;
-
-import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-
-public class PatchLink extends InlineHyperlink {
- private PatchLink(String text, String historyToken) {
- super(text, historyToken);
- }
-
- public static class SideBySide extends PatchLink {
- public SideBySide(String text, PatchSet.Id base, Patch.Key id) {
- super(text, Dispatcher.toSideBySide(base, id));
- }
- }
-
- public static class Unified extends PatchLink {
- public Unified(String text, PatchSet.Id base, Patch.Key id) {
- super(text, Dispatcher.toUnified(base, id));
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
index eccde6f..be914f3 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -395,6 +395,10 @@
return this.setGutterMarker(line, gutterId, value);
}-*/;
+ public final native boolean hasSearchHighlight() /*-{
+ return this.state.search && !!this.state.search.query;
+ }-*/;
+
protected CodeMirror() {
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java
index c3d58c9ba..84b4a6a 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java
@@ -57,6 +57,10 @@
$wnd.CodeMirror.Vim.handleKey(this, key)
}-*/;
+ public final native void handleEx(String exCommand) /*-{
+ $wnd.CodeMirror.Vim.handleEx(this, exCommand);
+ }-*/;
+
public final native boolean hasSearchHighlight() /*-{
var v = this.state.vim;
return v && v.searchState_ && !!v.searchState_.getOverlay();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 3083edd..45e5615 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -87,6 +87,9 @@
serve("/watched").with(query("is:watched status:open"));
serve("/starred").with(query("is:starred"));
+ // Forward PolyGerrit URLs to their respective GWT equivalents.
+ serveRegex("^/(c|q|x|admin|dashboard|settings)/(.*)").with(gerritUrl());
+
serveRegex("^/settings/?$").with(screen(PageLinks.SETTINGS));
serveRegex("^/register/?$").with(screen(PageLinks.REGISTER + "/"));
serveRegex("^/([1-9][0-9]*)/?$").with(directChangeById());
@@ -117,6 +120,18 @@
});
}
+ private Key<HttpServlet> gerritUrl() {
+ return key(new HttpServlet() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ toGerrit(req.getRequestURI(), req, rsp);
+ }
+ });
+ }
+
private Key<HttpServlet> screen(final String target) {
return key(new HttpServlet() {
private static final long serialVersionUID = 1L;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 275fa48..bdc7189 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -24,8 +24,10 @@
import static com.google.gerrit.server.index.change.IndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.IndexRewriter.OPEN_STATUSES;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -35,6 +37,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.FieldDef.FillArgs;
@@ -116,6 +119,8 @@
ChangeField.REVIEWEDBY.getName();
private static final String HASHTAG_FIELD =
ChangeField.HASHTAG_CASE_AWARE.getName();
+ private static final String STAR_FIELD = ChangeField.STAR.getName();
+ @Deprecated
private static final String STARREDBY_FIELD = ChangeField.STARREDBY.getName();
static Term idTerm(ChangeData cd) {
@@ -420,6 +425,9 @@
if (fields.contains(STARREDBY_FIELD)) {
decodeStarredBy(doc, cd);
}
+ if (fields.contains(STAR_FIELD)) {
+ decodeStar(doc, cd);
+ }
return cd;
}
@@ -482,6 +490,7 @@
cd.setHashtags(hashtags);
}
+ @Deprecated
private void decodeStarredBy(Document doc, ChangeData cd) {
IndexableField[] starredBy = doc.getFields(STARREDBY_FIELD);
Set<Account.Id> accounts =
@@ -492,6 +501,19 @@
cd.setStarredBy(accounts);
}
+ private void decodeStar(Document doc, ChangeData cd) {
+ IndexableField[] star = doc.getFields(STAR_FIELD);
+ Multimap<Account.Id, String> stars = ArrayListMultimap.create();
+ for (IndexableField r : star) {
+ StarredChangesUtil.StarField starField =
+ StarredChangesUtil.StarField.parse(r.stringValue());
+ if (starField != null) {
+ stars.put(starField.accountId(), starField.label());
+ }
+ }
+ cd.setStars(stars);
+ }
+
private static <T> List<T> decodeProtos(Document doc, String fieldName,
ProtobufCodec<T> codec) {
BytesRef[] bytesRefs = doc.getBinaryValues(fieldName);
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index d24c8a0..3e65912 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -58,8 +59,8 @@
private final Provider<IdentifiedUser> identifiedUser;
private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
+ private final OAuthTokenCache tokenCache;
private OAuthServiceProvider serviceProvider;
- private OAuthToken token;
private OAuthUserInfo user;
private String redirectToken;
private boolean linkMode;
@@ -68,16 +69,18 @@
OAuthSession(DynamicItem<WebSession> webSession,
Provider<IdentifiedUser> identifiedUser,
AccountManager accountManager,
- CanonicalWebUrl urlProvider) {
+ CanonicalWebUrl urlProvider,
+ OAuthTokenCache tokenCache) {
this.state = generateRandomState();
this.identifiedUser = identifiedUser;
this.webSession = webSession;
this.accountManager = accountManager;
this.urlProvider = urlProvider;
+ this.tokenCache = tokenCache;
}
boolean isLoggedIn() {
- return token != null && user != null;
+ return tokenCache.has(user);
}
boolean isOAuthFinal(HttpServletRequest request) {
@@ -95,9 +98,12 @@
}
log.debug("Login-Retrieve-User " + this);
- token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
-
+ OAuthToken token = oauth.getAccessToken(
+ new OAuthVerifier(request.getParameter("code")));
user = oauth.getUserInfo(token);
+ if (user != null && token != null) {
+ tokenCache.put(user, token);
+ }
if (isLoggedIn()) {
log.debug("Login-SUCCESS " + this);
@@ -211,7 +217,7 @@
}
void logout() {
- token = null;
+ tokenCache.remove(user);
user = null;
redirectToken = null;
serviceProvider = null;
@@ -243,7 +249,8 @@
@Override
public String toString() {
- return "OAuthSession [token=" + token + ", user=" + user + "]";
+ return "OAuthSession [token=" + tokenCache.get(user) + ", user=" + user
+ + "]";
}
public void setServiceProvider(OAuthServiceProvider provider) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java
index aa8b21f..047789e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java
@@ -28,6 +28,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleManager;
@@ -38,6 +39,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -50,7 +52,6 @@
import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -206,7 +207,7 @@
}
private Injector createSysInjector() {
- return dbInjector.createChildInjector(new AbstractModule() {
+ return dbInjector.createChildInjector(new FactoryModule() {
@Override
public void configure() {
install(dbInjector.getInstance(BatchProgramModule.class));
@@ -214,6 +215,7 @@
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(
ReindexAfterUpdate.class);
install(new DummyIndexModule());
+ factory(ChangeResource.Factory.class);
}
});
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index 0e647b9..9cd7c6a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -18,11 +18,13 @@
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.gerrit.common.Die;
+import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.util.BatchProgramModule;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.pgm.util.ThreadLimiter;
+import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.ScanningChangeCacheImpl;
import com.google.gerrit.server.index.Index;
@@ -129,6 +131,12 @@
// will have just deleted the old (possibly corrupt) index.
modules.add(ScanningChangeCacheImpl.module());
modules.add(dbInjector.getInstance(BatchProgramModule.class));
+ modules.add(new FactoryModule() {
+ @Override
+ protected void configure() {
+ factory(ChangeResource.Factory.class);
+ }
+ });
return dbInjector.createChildInjector(modules);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SecureStoreException.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SecureStoreException.java
deleted file mode 100644
index 9693399..0000000
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SecureStoreException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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.pgm.util;
-
-public class SecureStoreException extends RuntimeException {
- private static final long serialVersionUID = 5581700510568485065L;
-
- SecureStoreException(String msg) {
- super(msg);
- }
-
- SecureStoreException(String msg, Exception e) {
- super(msg, e);
- }
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
index 2460d57..5d782dd 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
@@ -21,7 +21,7 @@
/** Static utilities for ReviewDb types. */
public class ReviewDbUtil {
- private static final Function<IntKey<?>, Integer> INT_KEY_FUNCTION =
+ public static final Function<IntKey<?>, Integer> INT_KEY_FUNCTION =
new Function<IntKey<?>, Integer>() {
@Override
public Integer apply(IntKey<?> in) {
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
index 47f409a..cf2d289 100644
--- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
@@ -15,6 +15,8 @@
package com.google.gerrit.reviewdb.client;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import org.junit.Test;
@@ -79,6 +81,27 @@
.isEqualTo("refs/changes/34/1234/");
}
+ @Test
+ public void parseRefNameParts() {
+ assertRefPart(1, "01/1");
+
+ assertNotRefPart(null);
+ assertNotRefPart("");
+
+ // This method assumes that the common prefix "refs/changes/" was removed.
+ assertNotRefPart("refs/changes/01/1");
+
+ // Invalid characters.
+ assertNotRefPart("01a/1");
+ assertNotRefPart("01/a1");
+
+ // Mismatched shard.
+ assertNotRefPart("01/23");
+
+ // Shard too short.
+ assertNotRefPart("1/1");
+ }
+
private static void assertRef(int changeId, String refName) {
assertThat(Change.Id.fromRef(refName)).isEqualTo(new Change.Id(changeId));
}
@@ -86,4 +109,12 @@
private static void assertNotRef(String refName) {
assertThat(Change.Id.fromRef(refName)).isNull();
}
+
+ private static void assertRefPart(int changeId, String refName) {
+ assertEquals(new Change.Id(changeId), Change.Id.fromRefPart(refName));
+ }
+
+ private static void assertNotRefPart(String refName) {
+ assertNull(Change.Id.fromRefPart(refName));
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 6a8600f..f1242b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -79,6 +79,7 @@
public abstract GroupMembership getEffectiveGroups();
/** Set of changes starred by this user. */
+ @Deprecated
public abstract Set<Change.Id> getStarredChanges();
/** Filters selecting changes the user wants to monitor. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index a081197..f6fae8c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -348,6 +348,7 @@
return starredChanges;
}
+ @Deprecated
public void clearStarredChanges() {
// Async query may have started before an update that the caller expects
// to see the results of, so we can't trust it.
@@ -355,13 +356,14 @@
starredChanges = null;
}
- @SuppressWarnings("deprecation")
+ @Deprecated
public void asyncStarredChanges() {
if (starredChanges == null && starredChangesUtil != null) {
starredQuery = starredChangesUtil.queryFromIndex(accountId);
}
}
+ @Deprecated
public void abortStarredChanges() {
if (starredQuery != null) {
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
index 72ee0ef..77064fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -16,14 +16,18 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.auto.value.AutoValue;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
+import com.google.common.primitives.Ints;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -61,18 +65,66 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@Singleton
public class StarredChangesUtil {
+ @AutoValue
+ public abstract static class StarField {
+ private static final String SEPARATOR = ":";
+
+ public static StarField parse(String s) {
+ int p = s.indexOf(SEPARATOR);
+ if (p >= 0) {
+ Integer id = Ints.tryParse(s.substring(0, p));
+ if (id == null) {
+ return null;
+ }
+ Account.Id accountId = new Account.Id(id);
+ String label = s.substring(p + 1);
+ return create(accountId, label);
+ }
+ return null;
+ }
+
+ public static StarField create(Account.Id accountId, String label) {
+ return new AutoValue_StarredChangesUtil_StarField(accountId, label);
+ }
+
+ public abstract Account.Id accountId();
+ public abstract String label();
+
+ @Override
+ public String toString() {
+ return accountId() + SEPARATOR + label();
+ }
+ }
+
+ public static class IllegalLabelException extends IllegalArgumentException {
+ private static final long serialVersionUID = 1L;
+
+ static IllegalLabelException invalidLabels(Set<String> invalidLabels) {
+ return new IllegalLabelException(
+ String.format("invalid labels: %s",
+ Joiner.on(", ").join(invalidLabels)));
+ }
+
+ IllegalLabelException(String message) {
+ super(message);
+ }
+ }
+
private static final Logger log =
LoggerFactory.getLogger(StarredChangesUtil.class);
- private static final String DEFAULT_LABEL = "star";
+ public static final String DEFAULT_LABEL = "star";
public static final ImmutableSortedSet<String> DEFAULT_LABELS =
ImmutableSortedSet.of(DEFAULT_LABEL);
@@ -98,53 +150,44 @@
this.queryProvider = queryProvider;
}
- public void star(Account.Id accountId, Project.NameKey project,
+ public ImmutableSortedSet<String> getLabels(Account.Id accountId,
Change.Id changeId) throws OrmException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- String refName = RefNames.refsStarredChanges(changeId, accountId);
- ObjectId oldObjectId = getObjectId(repo, refName);
- SortedSet<String> labels = readLabels(repo, oldObjectId);
- labels.add(DEFAULT_LABEL);
- updateLabels(repo, refName, oldObjectId, labels);
- indexer.index(dbProvider.get(), project, changeId);
+ return ImmutableSortedSet.copyOf(
+ readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
} catch (IOException e) {
throw new OrmException(
- String.format("Star change %d for account %d failed",
+ String.format("Reading stars from change %d for account %d failed",
changeId.get(), accountId.get()), e);
}
}
- public void unstar(Account.Id accountId, Project.NameKey project,
- Change.Id changeId) throws OrmException {
- try (Repository repo = repoManager.openRepository(allUsers);
- RevWalk rw = new RevWalk(repo)) {
- RefUpdate u = repo.updateRef(
- RefNames.refsStarredChanges(changeId, accountId));
- u.setForceUpdate(true);
- u.setRefLogIdent(serverIdent);
- u.setRefLogMessage("Unstar change " + changeId.get(), true);
- RefUpdate.Result result = u.delete();
- switch (result) {
- case FORCED:
- indexer.index(dbProvider.get(), project, changeId);
- return;
- case FAST_FORWARD:
- case IO_FAILURE:
- case LOCK_FAILURE:
- case NEW:
- case NOT_ATTEMPTED:
- case NO_CHANGE:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case RENAMED:
- default:
- throw new OrmException(
- String.format("Unstar change %d for account %d failed: %s",
- changeId.get(), accountId.get(), result.name()));
+ public ImmutableSortedSet<String> star(Account.Id accountId,
+ Project.NameKey project, Change.Id changeId, Set<String> labelsToAdd,
+ Set<String> labelsToRemove) throws OrmException {
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ String refName = RefNames.refsStarredChanges(changeId, accountId);
+ ObjectId oldObjectId = getObjectId(repo, refName);
+
+ SortedSet<String> labels = readLabels(repo, oldObjectId);
+ if (labelsToAdd != null) {
+ labels.addAll(labelsToAdd);
}
+ if (labelsToRemove != null) {
+ labels.removeAll(labelsToRemove);
+ }
+
+ if (labels.isEmpty()) {
+ deleteRef(repo, refName, oldObjectId);
+ } else {
+ updateLabels(repo, refName, oldObjectId, labels);
+ }
+
+ indexer.index(dbProvider.get(), project, changeId);
+ return ImmutableSortedSet.copyOf(labels);
} catch (IOException e) {
throw new OrmException(
- String.format("Unstar change %d for account %d failed",
+ String.format("Star change %d for account %d failed",
changeId.get(), accountId.get()), e);
}
}
@@ -157,7 +200,7 @@
batchUpdate.setAllowNonFastForwards(true);
batchUpdate.setRefLogIdent(serverIdent);
batchUpdate.setRefLogMessage("Unstar change " + changeId.get(), true);
- for (Account.Id accountId : byChangeFromIndex(changeId)) {
+ for (Account.Id accountId : byChangeFromIndex(changeId).keySet()) {
String refName = RefNames.refsStarredChanges(changeId, accountId);
Ref ref = repo.getRefDatabase().getRef(refName);
batchUpdate.addCommand(new ReceiveCommand(ref.getObjectId(),
@@ -178,29 +221,84 @@
}
}
- public Set<Account.Id> byChange(Change.Id changeId)
+ public ImmutableMultimap<Account.Id, String> byChange(Change.Id changeId)
throws OrmException {
- return FluentIterable
- .from(getRefNames(RefNames.refsStarredChangesPrefix(changeId)))
- .transform(new Function<String, Account.Id>() {
- @Override
- public Account.Id apply(String refPart) {
- return Account.Id.parse(refPart);
- }
- }).toSet();
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ ImmutableMultimap.Builder<Account.Id, String> builder =
+ new ImmutableMultimap.Builder<>();
+ for (String refPart : getRefNames(repo,
+ RefNames.refsStarredChangesPrefix(changeId))) {
+ Integer id = Ints.tryParse(refPart);
+ if (id == null) {
+ continue;
+ }
+ Account.Id accountId = new Account.Id(id);
+ builder.putAll(accountId,
+ readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
+ }
+ return builder.build();
+ } catch (IOException e) {
+ throw new OrmException(String.format(
+ "Get accounts that starred change %d failed", changeId.get()), e);
+ }
}
- public Set<Account.Id> byChangeFromIndex(Change.Id changeId)
- throws OrmException, NoSuchChangeException {
+ public Set<Account.Id> byChange(final Change.Id changeId,
+ final String label) throws OrmException {
+ try (final Repository repo = repoManager.openRepository(allUsers)) {
+ return FluentIterable
+ .from(getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId)))
+ .transform(new Function<String, Account.Id>() {
+ @Override
+ public Account.Id apply(String refPart) {
+ return Account.Id.parse(refPart);
+ }
+ })
+ .filter(new Predicate<Account.Id>() {
+ @Override
+ public boolean apply(Account.Id accountId) {
+ try {
+ return readLabels(repo,
+ RefNames.refsStarredChanges(changeId, accountId))
+ .contains(label);
+ } catch (IOException e) {
+ log.error(String.format(
+ "Cannot query stars by account %d on change %d",
+ accountId.get(), changeId.get()), e);
+ return false;
+ }
+ }
+ }).toSet();
+ } catch (IOException e) {
+ throw new OrmException(
+ String.format("Get accounts that starred change %d failed",
+ changeId.get()), e);
+ }
+ }
+
+ public ImmutableMultimap<Account.Id, String> byChangeFromIndex(
+ Change.Id changeId) throws OrmException, NoSuchChangeException {
Set<String> fields = ImmutableSet.of(
ChangeField.ID.getName(),
- ChangeField.STARREDBY.getName());
+ ChangeField.STAR.getName());
List<ChangeData> changeData = queryProvider.get().setRequestedFields(fields)
.byLegacyChangeId(changeId);
if (changeData.size() != 1) {
throw new NoSuchChangeException(changeId);
}
- return changeData.get(0).starredBy();
+ return changeData.get(0).stars();
+ }
+
+ public Set<Account.Id> byChangeFromIndex(Change.Id changeId, String label)
+ throws OrmException, NoSuchChangeException {
+ Set<Account.Id> accounts = new HashSet<>();
+ for (Map.Entry<Account.Id, Collection<String>> e : byChangeFromIndex(
+ changeId).asMap().entrySet()) {
+ if (e.getValue().contains(label)) {
+ accounts.add(e.getKey());
+ }
+ }
+ return accounts;
}
@Deprecated
@@ -226,12 +324,21 @@
}
}
- private Set<String> getRefNames(String prefix) throws OrmException {
+ private static Set<String> getRefNames(Repository repo, String prefix)
+ throws IOException {
+ RefDatabase refDb = repo.getRefDatabase();
+ return refDb.getRefs(prefix).keySet();
+ }
+
+ public ObjectId getObjectId(Account.Id accountId, Change.Id changeId) {
try (Repository repo = repoManager.openRepository(allUsers)) {
- RefDatabase refDb = repo.getRefDatabase();
- return refDb.getRefs(prefix).keySet();
+ return getObjectId(repo,
+ RefNames.refsStarredChanges(changeId, accountId));
} catch (IOException e) {
- throw new OrmException(e);
+ log.error(String.format(
+ "Getting star object ID for account %d on change %d failed",
+ accountId.get(), changeId.get()), e);
+ return ObjectId.zeroId();
}
}
@@ -241,6 +348,11 @@
return ref != null ? ref.getObjectId() : ObjectId.zeroId();
}
+ private static SortedSet<String> readLabels(Repository repo, String refName)
+ throws IOException {
+ return readLabels(repo, getObjectId(repo, refName));
+ }
+
private static TreeSet<String> readLabels(Repository repo, ObjectId id)
throws IOException {
if (ObjectId.zeroId().equals(id)) {
@@ -259,13 +371,7 @@
public static ObjectId writeLabels(Repository repo, SortedSet<String> labels)
throws IOException {
- SortedSet<String> invalidLabels = validateLabels(labels);
- if (!invalidLabels.isEmpty()) {
- throw new IllegalArgumentException(
- String.format("Invalid star labels: %s",
- Joiner.on(", ").join(labels)));
- }
-
+ validateLabels(labels);
try (ObjectInserter oi = repo.newObjectInserter()) {
ObjectId id = oi.insert(Constants.OBJ_BLOB,
Joiner.on("\n").join(labels).getBytes(UTF_8));
@@ -274,9 +380,9 @@
}
}
- private static SortedSet<String> validateLabels(Set<String> labels) {
+ private static void validateLabels(Set<String> labels) {
if (labels == null) {
- return ImmutableSortedSet.of();
+ return;
}
SortedSet<String> invalidLabels = new TreeSet<>();
@@ -285,7 +391,9 @@
invalidLabels.add(label);
}
}
- return invalidLabels;
+ if (!invalidLabels.isEmpty()) {
+ throw IllegalLabelException.invalidLabels(invalidLabels);
+ }
}
private void updateLabels(Repository repo, String refName,
@@ -317,4 +425,29 @@
}
}
}
+
+ private void deleteRef(Repository repo, String refName, ObjectId oldObjectId)
+ throws IOException, OrmException {
+ RefUpdate u = repo.updateRef(refName);
+ u.setForceUpdate(true);
+ u.setExpectedOldObjectId(oldObjectId);
+ u.setRefLogIdent(serverIdent);
+ u.setRefLogMessage("Unstar change", true);
+ RefUpdate.Result result = u.delete();
+ switch (result) {
+ case FORCED:
+ return;
+ case NEW:
+ case NO_CHANGE:
+ case FAST_FORWARD:
+ case IO_FAILURE:
+ case LOCK_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case REJECTED_CURRENT_BRANCH:
+ case RENAMED:
+ throw new OrmException(String.format("Delete star ref %s failed: %s",
+ refName, result.name()));
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index e801eb5..596c440 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -344,8 +344,6 @@
public AuthResult link(Account.Id to, AuthRequest who)
throws AccountException, OrmException {
try (ReviewDb db = schema.open()) {
- who = realm.link(db, to, who);
-
AccountExternalId.Key key = id(who);
AccountExternalId extId = getAccountExternalId(db, key);
if (extId != null) {
@@ -435,8 +433,6 @@
public AuthResult unlink(Account.Id from, AuthRequest who)
throws AccountException, OrmException {
try (ReviewDb db = schema.open()) {
- who = realm.unlink(db, from, who);
-
AccountExternalId.Key key = id(who);
AccountExternalId extId = getAccountExternalId(db, key);
if (extId != null) {
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 75e5ae5..8bebf52 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
@@ -22,6 +22,8 @@
import com.google.gerrit.server.change.ChangeResource;
import com.google.inject.TypeLiteral;
+import java.util.Set;
+
public class AccountResource implements RestResource {
public static final TypeLiteral<RestView<AccountResource>> ACCOUNT_KIND =
new TypeLiteral<RestView<AccountResource>>() {};
@@ -108,4 +110,32 @@
return change.getChange();
}
}
+
+ public static class Star implements RestResource {
+ public static final TypeLiteral<RestView<Star>> STAR_KIND =
+ new TypeLiteral<RestView<Star>>() {};
+
+ private final IdentifiedUser user;
+ private final ChangeResource change;
+ private final Set<String> labels;
+
+ public Star(IdentifiedUser user, ChangeResource change,
+ Set<String> labels) {
+ this.user = user;
+ this.change = change;
+ this.labels = labels;
+ }
+
+ public IdentifiedUser getUser() {
+ return user;
+ }
+
+ public Change getChange() {
+ return change.getChange();
+ }
+
+ public Set<String> getLabels() {
+ return labels;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 96dc4b8..5b7a9a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -17,7 +17,6 @@
import com.google.common.base.Strings;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AuthType;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -75,16 +74,6 @@
}
@Override
- public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) {
- return who;
- }
-
- @Override
- public AuthRequest unlink(ReviewDb db, Account.Id from, AuthRequest who) {
- return who;
- }
-
- @Override
public void onCreateAccount(final AuthRequest who, final Account account) {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java
index f8d6bd1..d3b938f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java
@@ -16,7 +16,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.FieldName;
-import com.google.gerrit.reviewdb.server.ReviewDb;
/** Fake implementation of {@link Realm} that does not communicate. */
public class FakeRealm extends AbstractRealm {
@@ -31,16 +30,6 @@
}
@Override
- public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) {
- return who;
- }
-
- @Override
- public AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who) {
- return who;
- }
-
- @Override
public void onCreateAccount(AuthRequest who, Account account) {
// Do nothing.
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
index 713cbda..94feb7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
@@ -23,7 +23,7 @@
import com.google.gerrit.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.group.GroupInfoCacheFactory;
+import com.google.gerrit.server.group.GroupInfoCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -44,18 +44,19 @@
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final AccountInfoCacheFactory aic;
- private final GroupInfoCacheFactory gic;
+ private final GroupInfoCache gic;
private final AccountGroup.Id groupId;
private GroupControl control;
@Inject
- GroupDetailFactory(final ReviewDb db,
- final GroupControl.Factory groupControl, final GroupCache groupCache,
- final GroupBackend groupBackend,
- final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
- final GroupInfoCacheFactory.Factory groupInfoCacheFactory,
- @Assisted final AccountGroup.Id groupId) {
+ GroupDetailFactory(ReviewDb db,
+ GroupControl.Factory groupControl,
+ GroupCache groupCache,
+ GroupBackend groupBackend,
+ AccountInfoCacheFactory.Factory accountInfoCacheFactory,
+ GroupInfoCache.Factory groupInfoCacheFactory,
+ @Assisted AccountGroup.Id groupId) {
this.db = db;
this.groupControl = groupControl;
this.groupCache = groupCache;
@@ -69,8 +70,8 @@
@Override
public GroupDetail call() throws OrmException, NoSuchGroupException {
control = groupControl.validateFor(groupId);
- final AccountGroup group = groupCache.get(groupId);
- final GroupDetail detail = new GroupDetail();
+ AccountGroup group = groupCache.get(groupId);
+ GroupDetail detail = new GroupDetail();
detail.setGroup(group);
GroupDescription.Basic ownerGroup = groupBackend.get(group.getOwnerGroupUUID());
if (ownerGroup != null) {
@@ -85,7 +86,7 @@
private List<AccountGroupMember> loadMembers() throws OrmException {
List<AccountGroupMember> members = new ArrayList<>();
- for (final AccountGroupMember m : db.accountGroupMembers().byGroup(groupId)) {
+ for (AccountGroupMember m : db.accountGroupMembers().byGroup(groupId)) {
if (control.canSeeMember(m.getAccountId())) {
aic.want(m.getAccountId());
members.add(m);
@@ -94,10 +95,9 @@
Collections.sort(members, new Comparator<AccountGroupMember>() {
@Override
- public int compare(final AccountGroupMember o1,
- final AccountGroupMember o2) {
- final Account a = aic.get(o1.getAccountId());
- final Account b = aic.get(o2.getAccountId());
+ public int compare(AccountGroupMember o1, AccountGroupMember o2) {
+ Account a = aic.get(o1.getAccountId());
+ Account b = aic.get(o2.getAccountId());
return n(a).compareTo(n(b));
}
@@ -121,7 +121,7 @@
private List<AccountGroupById> loadIncludes() throws OrmException {
List<AccountGroupById> groups = new ArrayList<>();
- for (final AccountGroupById m : db.accountGroupById().byGroup(groupId)) {
+ for (AccountGroupById m : db.accountGroupById().byGroup(groupId)) {
if (control.canSeeGroup()) {
gic.want(m.getIncludeUUID());
groups.add(m);
@@ -130,8 +130,7 @@
Collections.sort(groups, new Comparator<AccountGroupById>() {
@Override
- public int compare(final AccountGroupById o1,
- final AccountGroupById o2) {
+ public int compare(AccountGroupById o1, AccountGroupById o2) {
GroupDescription.Basic a = gic.get(o1.getIncludeUUID());
GroupDescription.Basic b = gic.get(o2.getIncludeUUID());
return n(a).compareTo(n(b));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
index f9d5294..c45b7b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
@@ -46,7 +46,7 @@
* Implementors may implement the method as:
*
* <pre>
- * Set<AccountGroup.UUID> r = new HashSet<>();
+ * Set<AccountGroup.UUID> r = new HashSet<>();
* for (AccountGroup.UUID id : groupIds)
* if (contains(id)) r.add(id);
* </pre>
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 b11be39..52e9d4c 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
@@ -19,6 +19,7 @@
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 static com.google.gerrit.server.account.AccountResource.Star.STAR_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -34,6 +35,7 @@
DynamicMap.mapOf(binder(), EMAIL_KIND);
DynamicMap.mapOf(binder(), SSH_KEY_KIND);
DynamicMap.mapOf(binder(), STARRED_CHANGE_KIND);
+ DynamicMap.mapOf(binder(), STAR_KIND);
put(ACCOUNT_KIND).to(PutAccount.class);
get(ACCOUNT_KIND).to(GetAccount.class);
@@ -83,6 +85,10 @@
delete(STARRED_CHANGE_KIND).to(StarredChanges.Delete.class);
bind(StarredChanges.Create.class);
+ child(ACCOUNT_KIND, "stars.changes").to(Stars.class);
+ get(STAR_KIND).to(Stars.Get.class);
+ post(STAR_KIND).to(Stars.Post.class);
+
factory(CreateAccount.Factory.class);
factory(CreateEmail.Factory.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
index 6fd76d4..85fde4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import java.util.Set;
@@ -29,12 +28,6 @@
AuthRequest authenticate(AuthRequest who) throws AccountException;
- AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who)
- throws AccountException;
-
- AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who)
- throws AccountException;
-
void onCreateAccount(AuthRequest who, Account account);
/** @return true if the user has the given email address. */
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
index 1ed29a7..b71fc68 100644
--- 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
@@ -53,31 +53,29 @@
private final ChangesCollection changes;
private final DynamicMap<RestView<AccountResource.StarredChange>> views;
private final Provider<Create> createProvider;
+ private final StarredChangesUtil starredChangesUtil;
@Inject
StarredChanges(ChangesCollection changes,
DynamicMap<RestView<AccountResource.StarredChange>> views,
- Provider<Create> createProvider) {
+ Provider<Create> createProvider,
+ StarredChangesUtil starredChangesUtil) {
this.changes = changes;
this.views = views;
this.createProvider = createProvider;
+ this.starredChangesUtil = starredChangesUtil;
}
@Override
public AccountResource.StarredChange parse(AccountResource parent, IdString id)
throws ResourceNotFoundException, OrmException {
IdentifiedUser user = parent.getUser();
- try {
- user.asyncStarredChanges();
-
- ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
- if (user.getStarredChanges().contains(change.getId())) {
- return new AccountResource.StarredChange(user, change);
- }
- throw new ResourceNotFoundException(id);
- } finally {
- user.abortStarredChanges();
+ ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
+ if (starredChangesUtil.getLabels(user.getAccountId(), change.getId())
+ .contains(StarredChangesUtil.DEFAULT_LABEL)) {
+ return new AccountResource.StarredChange(user, change);
}
+ throw new ResourceNotFoundException(id);
}
@Override
@@ -138,7 +136,7 @@
}
try {
starredChangesUtil.star(self.get().getAccountId(), change.getProject(),
- change.getId());
+ change.getId(), StarredChangesUtil.DEFAULT_LABELS, null);
} catch (OrmDuplicateKeyException e) {
return Response.none();
}
@@ -184,8 +182,9 @@
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed remove starred change");
}
- starredChangesUtil.unstar(self.get().getAccountId(),
- rsrc.getChange().getProject(), rsrc.getChange().getId());
+ starredChangesUtil.star(self.get().getAccountId(),
+ rsrc.getChange().getProject(), rsrc.getChange().getId(), null,
+ StarredChangesUtil.DEFAULT_LABELS);
return Response.none();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Stars.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Stars.java
new file mode 100644
index 0000000..fddbb6a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Stars.java
@@ -0,0 +1,167 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.extensions.api.changes.StarsInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
+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.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.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
+import com.google.gerrit.server.account.AccountResource.Star;
+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.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+@Singleton
+public class Stars implements
+ ChildCollection<AccountResource, AccountResource.Star> {
+
+ private final ChangesCollection changes;
+ private final ListStarredChanges listStarredChanges;
+ private final StarredChangesUtil starredChangesUtil;
+ private final DynamicMap<RestView<AccountResource.Star>> views;
+
+ @Inject
+ Stars(ChangesCollection changes,
+ ListStarredChanges listStarredChanges,
+ StarredChangesUtil starredChangesUtil,
+ DynamicMap<RestView<AccountResource.Star>> views) {
+ this.changes = changes;
+ this.listStarredChanges = listStarredChanges;
+ this.starredChangesUtil = starredChangesUtil;
+ this.views = views;
+ }
+
+ @Override
+ public Star parse(AccountResource parent, IdString id)
+ throws ResourceNotFoundException, OrmException {
+ IdentifiedUser user = parent.getUser();
+ ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
+ Set<String> labels =
+ starredChangesUtil.getLabels(user.getAccountId(), change.getId());
+ return new AccountResource.Star(user, change, labels);
+ }
+
+ @Override
+ public DynamicMap<RestView<Star>> views() {
+ return views;
+ }
+
+ @Override
+ public ListStarredChanges list() {
+ return listStarredChanges;
+ }
+
+ @Singleton
+ public static class ListStarredChanges
+ implements RestReadView<AccountResource> {
+ private final Provider<CurrentUser> self;
+ private final ChangesCollection changes;
+
+ @Inject
+ ListStarredChanges(Provider<CurrentUser> self,
+ ChangesCollection changes) {
+ this.self = self;
+ this.changes = changes;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<ChangeInfo> apply(AccountResource rsrc)
+ throws BadRequestException, AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException(
+ "not allowed to list stars of another account");
+ }
+ QueryChanges query = changes.list();
+ query.addQuery("has:stars");
+ return (List<ChangeInfo>) query.apply(TopLevelResource.INSTANCE);
+ }
+ }
+
+ @Singleton
+ public static class Get implements
+ RestReadView<AccountResource.Star> {
+ private final Provider<CurrentUser> self;
+ private final StarredChangesUtil starredChangesUtil;
+
+ @Inject
+ Get(Provider<CurrentUser> self,
+ StarredChangesUtil starredChangesUtil) {
+ this.self = self;
+ this.starredChangesUtil = starredChangesUtil;
+ }
+
+ @Override
+ public SortedSet<String> apply(AccountResource.Star rsrc)
+ throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed to get stars of another account");
+ }
+ return starredChangesUtil.getLabels(self.get().getAccountId(),
+ rsrc.getChange().getId());
+ }
+ }
+
+ @Singleton
+ public static class Post implements
+ RestModifyView<AccountResource.Star, StarsInput> {
+ private final Provider<CurrentUser> self;
+ private final StarredChangesUtil starredChangesUtil;
+
+ @Inject
+ Post(Provider<CurrentUser> self,
+ StarredChangesUtil starredChangesUtil) {
+ this.self = self;
+ this.starredChangesUtil = starredChangesUtil;
+ }
+
+ @Override
+ public Collection<String> apply(AccountResource.Star rsrc, StarsInput in)
+ throws AuthException, BadRequestException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException(
+ "not allowed to update stars of another account");
+ }
+ try {
+ return starredChangesUtil.star(self.get().getAccountId(),
+ rsrc.getChange().getProject(), rsrc.getChange().getId(), in.add,
+ in.remove);
+ } catch (IllegalLabelException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 8e4476b..154780a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -19,11 +19,13 @@
import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
+import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.IdString;
@@ -48,6 +50,7 @@
import com.google.gerrit.server.account.SetPreferences;
import com.google.gerrit.server.account.SshKeys;
import com.google.gerrit.server.account.StarredChanges;
+import com.google.gerrit.server.account.Stars;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gwtorm.server.OrmException;
@@ -59,6 +62,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
+import java.util.SortedSet;
public class AccountApiImpl implements AccountApi {
interface Factory {
@@ -80,6 +84,9 @@
private final DeleteWatchedProjects deleteWatchedProjects;
private final StarredChanges.Create starredChangesCreate;
private final StarredChanges.Delete starredChangesDelete;
+ private final Stars stars;
+ private final Stars.Get starsGet;
+ private final Stars.Post starsPost;
private final CreateEmail.Factory createEmailFactory;
private final GpgApiAdapter gpgApiAdapter;
private final GetSshKeys getSshKeys;
@@ -102,6 +109,9 @@
DeleteWatchedProjects deleteWatchedProjects,
StarredChanges.Create starredChangesCreate,
StarredChanges.Delete starredChangesDelete,
+ Stars stars,
+ Stars.Get starsGet,
+ Stars.Post starsPost,
CreateEmail.Factory createEmailFactory,
GpgApiAdapter gpgApiAdapter,
GetSshKeys getSshKeys,
@@ -124,6 +134,9 @@
this.deleteWatchedProjects = deleteWatchedProjects;
this.starredChangesCreate = starredChangesCreate;
this.starredChangesDelete = starredChangesDelete;
+ this.stars = stars;
+ this.starsGet = starsGet;
+ this.starsPost = starsPost;
this.createEmailFactory = createEmailFactory;
this.getSshKeys = getSshKeys;
this.addSshKey = addSshKey;
@@ -234,11 +247,11 @@
}
@Override
- public void starChange(String id) throws RestApiException {
+ public void starChange(String changeId) throws RestApiException {
try {
ChangeResource rsrc = changes.parse(
TopLevelResource.INSTANCE,
- IdString.fromUrl(id));
+ IdString.fromUrl(changeId));
starredChangesCreate.setChange(rsrc);
starredChangesCreate.apply(account, new StarredChanges.EmptyInput());
} catch (OrmException | IOException e) {
@@ -247,10 +260,10 @@
}
@Override
- public void unstarChange(String id) throws RestApiException {
+ public void unstarChange(String changeId) throws RestApiException {
try {
ChangeResource rsrc =
- changes.parse(TopLevelResource.INSTANCE, IdString.fromUrl(id));
+ changes.parse(TopLevelResource.INSTANCE, IdString.fromUrl(changeId));
AccountResource.StarredChange starredChange =
new AccountResource.StarredChange(account.getUser(), rsrc);
starredChangesDelete.apply(starredChange,
@@ -261,6 +274,38 @@
}
@Override
+ public void setStars(String changeId, StarsInput input)
+ throws RestApiException {
+ try {
+ AccountResource.Star rsrc =
+ stars.parse(account, IdString.fromUrl(changeId));
+ starsPost.apply(rsrc, input);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot post stars", e);
+ }
+ }
+
+ @Override
+ public SortedSet<String> getStars(String changeId) throws RestApiException {
+ try {
+ AccountResource.Star rsrc =
+ stars.parse(account, IdString.fromUrl(changeId));
+ return starsGet.apply(rsrc);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot get stars", e);
+ }
+ }
+
+ @Override
+ public List<ChangeInfo> getStarredChanges() throws RestApiException {
+ try {
+ return stars.list().apply(account);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot get starred changes", e);
+ }
+ }
+
+ @Override
public void addEmail(EmailInput input) throws RestApiException {
AccountResource.Email rsrc =
new AccountResource.Email(account.getUser(), input.email);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 5d57ed7..f143bd6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -336,6 +336,7 @@
}
}
+ @SuppressWarnings("deprecation")
@Override
public ChangeInfo get(EnumSet<ListChangesOption> s)
throws RestApiException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index 5485052..f4c671f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -117,6 +117,7 @@
return query().withQuery(query);
}
+ @SuppressWarnings("deprecation")
private List<ChangeInfo> get(final QueryRequest q) throws RestApiException {
QueryChanges qc = queryProvider.get();
if (q.getQuery() != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index cd1c4d3..d55bbc3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -283,16 +283,6 @@
}
@Override
- public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) {
- return who;
- }
-
- @Override
- public AuthRequest unlink(ReviewDb db, Account.Id from, AuthRequest who) {
- return who;
- }
-
- @Override
public void onCreateAccount(final AuthRequest who, final Account account) {
usernameCache.put(who.getLocalUser(), Optional.of(account.getId()));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
index a7bb074..cf9000d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
@@ -20,7 +20,6 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.FieldName;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AbstractRealm;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
@@ -118,17 +117,6 @@
}
@Override
- public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) {
- return who;
- }
-
- @Override
- public AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who)
- throws AccountException {
- return who;
- }
-
- @Override
public void onCreateAccount(AuthRequest who, Account account) {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
new file mode 100644
index 0000000..bcc5eab
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.auth.oauth;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.cache.Cache;
+import com.google.gerrit.extensions.auth.oauth.OAuthToken;
+import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
+import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+@Singleton
+public class OAuthTokenCache {
+ public static final String OAUTH_TOKENS = "oauth_tokens";
+
+ private final DynamicItem<OAuthTokenEncrypter> encrypter;
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ persist(OAUTH_TOKENS, String.class, OAuthToken.class);
+ }
+ };
+ }
+
+ private final Cache<String, OAuthToken> cache;
+
+ @Inject
+ OAuthTokenCache(@Named(OAUTH_TOKENS) Cache<String, OAuthToken> cache,
+ DynamicItem<OAuthTokenEncrypter> encrypter) {
+ this.cache = cache;
+ this.encrypter = encrypter;
+ }
+
+ public boolean has(OAuthUserInfo user) {
+ return user != null
+ ? cache.getIfPresent(user.getUserName()) != null
+ : false;
+ }
+
+ public OAuthToken get(OAuthUserInfo user) {
+ return user != null
+ ? get(user.getUserName())
+ : null;
+ }
+
+ public OAuthToken get(String userName) {
+ OAuthToken accessToken = cache.getIfPresent(userName);
+ if (accessToken == null) {
+ return null;
+ }
+ accessToken = decrypt(accessToken);
+ if (accessToken.isExpired()) {
+ cache.invalidate(userName);
+ return null;
+ }
+ return accessToken;
+ }
+
+ public void put(OAuthUserInfo user, OAuthToken accessToken) {
+ cache.put(checkNotNull(user.getUserName()),
+ encrypt(checkNotNull(accessToken)));
+ }
+
+ public void remove(OAuthUserInfo user) {
+ if (user != null) {
+ cache.invalidate(user.getUserName());
+ }
+ }
+
+ private OAuthToken encrypt(OAuthToken token) {
+ OAuthTokenEncrypter enc = encrypter.get();
+ if (enc == null) {
+ return token;
+ }
+ return enc.encrypt(token);
+ }
+
+ private OAuthToken decrypt(OAuthToken token) {
+ OAuthTokenEncrypter enc = encrypter.get();
+ if (enc == null) {
+ return token;
+ }
+ return enc.decrypt(token);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index db8c53b..4992c8e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -35,13 +35,16 @@
@Singleton
public class ActionJson {
private final Revisions revisions;
+ private final ChangeResource.Factory changeResourceFactory;
private final DynamicMap<RestView<ChangeResource>> changeViews;
@Inject
ActionJson(
Revisions revisions,
+ ChangeResource.Factory changeResourceFactory,
DynamicMap<RestView<ChangeResource>> changeViews) {
this.revisions = revisions;
+ this.changeResourceFactory = changeResourceFactory;
this.changeViews = changeViews;
}
@@ -69,7 +72,7 @@
Provider<CurrentUser> userProvider = Providers.of(ctl.getUser());
for (UiAction.Description d : UiActions.from(
changeViews,
- new ChangeResource(ctl),
+ changeResourceFactory.create(ctl),
userProvider)) {
out.put(d.getId(), new ActionInfo(d));
}
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 9e2d5f0..eaf51e4 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
@@ -161,6 +161,7 @@
private final ActionJson actionJson;
private final GpgApiAdapter gpgApi;
private final ChangeNotes.Factory notesFactory;
+ private final ChangeResource.Factory changeResourceFactory;
private AccountLoader accountLoader;
private Map<Change.Id, List<SubmitRecord>> submitRecords;
@@ -187,6 +188,7 @@
ActionJson actionJson,
GpgApiAdapter gpgApi,
ChangeNotes.Factory notesFactory,
+ ChangeResource.Factory changeResourceFactory,
@Assisted Set<ListChangesOption> options) {
this.db = db;
this.labelNormalizer = ln;
@@ -207,6 +209,7 @@
this.actionJson = actionJson;
this.gpgApi = gpgApi;
this.notesFactory = notesFactory;
+ this.changeResourceFactory = changeResourceFactory;
this.options = options.isEmpty()
? EnumSet.noneOf(ListChangesOption.class)
: EnumSet.copyOf(options);
@@ -374,6 +377,7 @@
return info;
}
+ @SuppressWarnings("deprecation")
private ChangeInfo toChangeInfo(ChangeData cd,
Optional<PatchSet.Id> limitToPsId) throws PatchListNotAvailableException,
GpgException, OrmException, IOException {
@@ -956,7 +960,7 @@
&& userProvider.get().isIdentifiedUser()) {
actionJson.addRevisionActions(out,
- new RevisionResource(new ChangeResource(ctl), in));
+ new RevisionResource(changeResourceFactory.create(ctl), in));
}
if (has(PUSH_CERTIFICATES)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 3171245..05d12b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.change;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.MoreObjects;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
@@ -25,11 +27,14 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.jgit.lib.ObjectId;
@@ -45,9 +50,17 @@
public static final TypeLiteral<RestView<ChangeResource>> CHANGE_KIND =
new TypeLiteral<RestView<ChangeResource>>() {};
+ public interface Factory {
+ ChangeResource create(ChangeControl ctl);
+ }
+
+ private final StarredChangesUtil starredChangesUtil;
private final ChangeControl control;
- public ChangeResource(ChangeControl control) {
+ @AssistedInject
+ ChangeResource(StarredChangesUtil starredChangesUtil,
+ @Assisted ChangeControl control) {
+ this.starredChangesUtil = starredChangesUtil;
this.control = control;
}
@@ -108,8 +121,12 @@
@Override
public String getETag() {
CurrentUser user = control.getUser();
- Hasher h = Hashing.md5().newHasher()
- .putBoolean(user.getStarredChanges().contains(getId()));
+ Hasher h = Hashing.md5().newHasher();
+ if (user.isIdentifiedUser()) {
+ h.putString(
+ starredChangesUtil.getObjectId(user.getAccountId(), getId()).name(),
+ UTF_8);
+ }
prepareETag(h, user);
return h.hash().toString();
}
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 058c7d5..b89691a 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
@@ -45,6 +45,7 @@
private final DynamicMap<RestView<ChangeResource>> views;
private final ChangeFinder changeFinder;
private final CreateChange createChange;
+ private final ChangeResource.Factory changeResourceFactory;
@Inject
ChangesCollection(
@@ -53,13 +54,15 @@
Provider<QueryChanges> queryFactory,
DynamicMap<RestView<ChangeResource>> views,
ChangeFinder changeFinder,
- CreateChange createChange) {
+ CreateChange createChange,
+ ChangeResource.Factory changeResourceFactory) {
this.db = db;
this.user = user;
this.queryFactory = queryFactory;
this.views = views;
this.changeFinder = changeFinder;
this.createChange = createChange;
+ this.changeResourceFactory = changeResourceFactory;
}
@Override
@@ -86,7 +89,7 @@
if (!ctl.isVisible(db.get())) {
throw new ResourceNotFoundException(id);
}
- return new ChangeResource(ctl);
+ return changeResourceFactory.create(ctl);
}
public ChangeResource parse(Change.Id id)
@@ -102,7 +105,7 @@
if (!ctl.isVisible(db.get())) {
throw new ResourceNotFoundException(toIdString(id));
}
- return new ChangeResource(ctl);
+ return changeResourceFactory.create(ctl);
}
private static IdString toIdString(Change.Id id) {
@@ -110,7 +113,7 @@
}
public ChangeResource parse(ChangeControl control) {
- return new ChangeResource(control);
+ return changeResourceFactory.create(control);
}
@SuppressWarnings("unchecked")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index c5837b4..d758a77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -42,16 +42,19 @@
private final Config config;
private final Provider<ReviewDb> dbProvider;
private final MergeSuperSet mergeSuperSet;
+ private final ChangeResource.Factory changeResourceFactory;
@Inject
GetRevisionActions(
ActionJson delegate,
Provider<ReviewDb> dbProvider,
MergeSuperSet mergeSuperSet,
+ ChangeResource.Factory changeResourceFactory,
@GerritServerConfig Config config) {
this.delegate = delegate;
this.dbProvider = dbProvider;
this.mergeSuperSet = mergeSuperSet;
+ this.changeResourceFactory = changeResourceFactory;
this.config = config;
}
@@ -71,7 +74,7 @@
ChangeSet cs =
mergeSuperSet.completeChangeSet(db, rsrc.getChange(), user);
for (ChangeData cd : cs.changes()) {
- new ChangeResource(cd.changeControl()).prepareETag(h, user);
+ changeResourceFactory.create(cd.changeControl()).prepareETag(h, user);
}
} catch (IOException | OrmException e) {
throw new OrmRuntimeException(e);
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 41b1019..c59bf5a 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
@@ -136,5 +136,6 @@
factory(RebaseChangeOp.Factory.class);
factory(ReviewerResource.Factory.class);
factory(SetHashtagsOp.Factory.class);
+ factory(ChangeResource.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index c7e11d2..50a8be0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -56,6 +56,7 @@
private final PatchSetInserter.Factory patchSetInserterFactory;
private final MergeUtil.Factory mergeUtilFactory;
private final RebaseUtil rebaseUtil;
+ private final ChangeResource.Factory changeResourceFactory;
private final ChangeControl ctl;
private final PatchSet originalPatchSet;
@@ -77,12 +78,14 @@
PatchSetInserter.Factory patchSetInserterFactory,
MergeUtil.Factory mergeUtilFactory,
RebaseUtil rebaseUtil,
+ ChangeResource.Factory changeResourceFactory,
@Assisted ChangeControl ctl,
@Assisted PatchSet originalPatchSet,
@Assisted @Nullable String baseCommitish) {
this.patchSetInserterFactory = patchSetInserterFactory;
this.mergeUtilFactory = mergeUtilFactory;
this.rebaseUtil = rebaseUtil;
+ this.changeResourceFactory = changeResourceFactory;
this.ctl = ctl;
this.originalPatchSet = originalPatchSet;
this.baseCommitish = baseCommitish;
@@ -138,7 +141,8 @@
RevId baseRevId = new RevId((baseCommitish != null) ? baseCommitish
: ObjectId.toString(baseCommit.getId()));
Base base = rebaseUtil.parseBase(
- new RevisionResource(new ChangeResource(ctl), originalPatchSet),
+ new RevisionResource(
+ changeResourceFactory.create(ctl), originalPatchSet),
baseRevId.get());
rebasedPatchSetId = ChangeUtil.nextPatchSetId(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index b5b3b6a..81fca18 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -21,6 +21,7 @@
import com.google.gerrit.common.EventListener;
import com.google.gerrit.common.UserScopedEventListener;
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
+import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.config.CloneCommand;
import com.google.gerrit.extensions.config.DownloadCommand;
@@ -74,6 +75,7 @@
import com.google.gerrit.server.api.accounts.AccountExternalIdCreator;
import com.google.gerrit.server.auth.AuthBackend;
import com.google.gerrit.server.auth.UniversalAuthBackend;
+import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gerrit.server.cache.CacheRemovalListener;
import com.google.gerrit.server.change.ChangeJson;
@@ -103,7 +105,7 @@
import com.google.gerrit.server.git.validators.RefOperationValidators;
import com.google.gerrit.server.git.validators.UploadValidationListener;
import com.google.gerrit.server.git.validators.UploadValidators;
-import com.google.gerrit.server.group.GroupInfoCacheFactory;
+import com.google.gerrit.server.group.GroupInfoCache;
import com.google.gerrit.server.group.GroupModule;
import com.google.gerrit.server.index.change.ReindexAfterUpdate;
import com.google.gerrit.server.mail.AddKeySender;
@@ -191,6 +193,7 @@
install(SectionSortCache.module());
install(SubmitStrategy.module());
install(TagCache.module());
+ install(OAuthTokenCache.module());
install(new AccessControlModule());
install(new CmdLineParserModule());
@@ -214,7 +217,7 @@
factory(ChangeJson.Factory.class);
factory(CreateChangeSender.Factory.class);
factory(GroupDetailFactory.Factory.class);
- factory(GroupInfoCacheFactory.Factory.class);
+ factory(GroupInfoCache.Factory.class);
factory(GroupMembers.Factory.class);
factory(EmailMerge.Factory.class);
factory(MergedSender.Factory.class);
@@ -315,6 +318,7 @@
DynamicSet.setOf(binder(), ProjectWebLink.class);
DynamicSet.setOf(binder(), BranchWebLink.class);
DynamicMap.mapOf(binder(), OAuthLoginProvider.class);
+ DynamicItem.itemOf(binder(), OAuthTokenEncrypter.class);
DynamicSet.setOf(binder(), AccountExternalIdCreator.class);
DynamicSet.setOf(binder(), WebUiPlugin.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 56b7b90..d07dea5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -575,7 +575,7 @@
Config rc, Map<String, GroupReference> groupsByName) {
accessSections = new HashMap<>();
for (String refName : rc.getSubsections(ACCESS)) {
- if (RefConfigSection.isValid(refName) & isValidRegex(refName)) {
+ if (RefConfigSection.isValid(refName) && isValidRegex(refName)) {
AccessSection as = getAccessSection(refName, true);
for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
index e98653a..2deb44a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
@@ -167,9 +167,6 @@
@Singleton
static class UpdateIncludedGroup implements RestModifyView<IncludedGroupResource, PutIncludedGroup.Input> {
- static class Input {
- }
-
private final Provider<GetIncludedGroup> get;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index 4cdbb5a..0039c3c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -187,12 +187,13 @@
}
}
}
-
- auditService.dispatchAddAccountsToGroup(self.get().getAccountId(),
- newAccountGroupMembers.values());
- db.get().accountGroupMembers().insert(newAccountGroupMembers.values());
- for (AccountGroupMember m : newAccountGroupMembers.values()) {
- accountCache.evict(m.getAccountId());
+ if (!newAccountGroupMembers.isEmpty()) {
+ auditService.dispatchAddAccountsToGroup(self.get().getAccountId(),
+ newAccountGroupMembers.values());
+ db.get().accountGroupMembers().insert(newAccountGroupMembers.values());
+ for (AccountGroupMember m : newAccountGroupMembers.values()) {
+ accountCache.evict(m.getAccountId());
+ }
}
}
@@ -254,9 +255,6 @@
@Singleton
static class UpdateMember implements RestModifyView<MemberResource, PutMember.Input> {
- static class Input {
- }
-
private final GetMember get;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
index b7d499d..d660db0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
@@ -14,67 +14,42 @@
package com.google.gerrit.server.group;
-import com.google.gerrit.common.data.GroupInfo;
+import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.inject.Inject;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-/** In-memory table of {@link GroupInfo}, indexed by {@code AccountGroup.Id}. */
+/** Efficiently builds a {@link GroupInfoCache}. */
public class GroupInfoCache {
- private static final GroupInfoCache EMPTY;
- static {
- EMPTY = new GroupInfoCache();
- EMPTY.groups = Collections.emptyMap();
+ public interface Factory {
+ GroupInfoCache create();
}
- /** Obtain an empty cache singleton. */
- public static GroupInfoCache empty() {
- return EMPTY;
- }
+ private final GroupBackend groupBackend;
+ private final Map<AccountGroup.UUID, GroupDescription.Basic> out;
- protected Map<AccountGroup.UUID, GroupInfo> groups;
-
- protected GroupInfoCache() {
- }
-
- public GroupInfoCache(final Iterable<GroupInfo> list) {
- groups = new HashMap<>();
- for (final GroupInfo gi : list) {
- groups.put(gi.getId(), gi);
- }
+ @Inject
+ GroupInfoCache(GroupBackend groupBackend) {
+ this.groupBackend = groupBackend;
+ this.out = new HashMap<>();
}
/**
- * Lookup the group summary
- * <p>
- * The return value can take on one of three forms:
- * <ul>
- * <li>{@code null}, if {@code id == null}.</li>
- * <li>a valid info block, if {@code id} was loaded.</li>
- * <li>an anonymous info block, if {@code id} was not loaded.</li>
- * </ul>
+ * Indicate a group will be needed later on.
*
- * @param uuid the id desired.
- * @return info block for the group.
+ * @param uuid identity that will be needed in the future; may be null.
*/
- public GroupInfo get(final AccountGroup.UUID uuid) {
- if (uuid == null) {
- return null;
+ public void want(final AccountGroup.UUID uuid) {
+ if (uuid != null && !out.containsKey(uuid)) {
+ out.put(uuid, groupBackend.get(uuid));
}
-
- GroupInfo r = groups.get(uuid);
- if (r == null) {
- r = new GroupInfo(uuid);
- groups.put(uuid, r);
- }
- return r;
}
- /** Merge the information from another cache into this one. */
- public void merge(final GroupInfoCache other) {
- assert this != EMPTY;
- groups.putAll(other.groups);
+ public GroupDescription.Basic get(final AccountGroup.UUID uuid) {
+ want(uuid);
+ return out.get(uuid);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java
deleted file mode 100644
index 049680e..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2011 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.group;
-
-import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.inject.Inject;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Efficiently builds a {@link GroupInfoCache}. */
-public class GroupInfoCacheFactory {
- public interface Factory {
- GroupInfoCacheFactory create();
- }
-
- private final GroupBackend groupBackend;
- private final Map<AccountGroup.UUID, GroupDescription.Basic> out;
-
- @Inject
- GroupInfoCacheFactory(GroupBackend groupBackend) {
- this.groupBackend = groupBackend;
- this.out = new HashMap<>();
- }
-
- /**
- * Indicate a group will be needed later on.
- *
- * @param uuid identity that will be needed in the future; may be null.
- */
- public void want(final AccountGroup.UUID uuid) {
- if (uuid != null && !out.containsKey(uuid)) {
- out.put(uuid, groupBackend.get(uuid));
- }
- }
-
- /** Indicate one or more groups will be needed later on. */
- public void want(final Iterable<AccountGroup.UUID> uuids) {
- for (final AccountGroup.UUID uuid : uuids) {
- want(uuid);
- }
- }
-
- public GroupDescription.Basic get(final AccountGroup.UUID uuid) {
- want(uuid);
- return out.get(uuid);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index befd5ed..a3d3ad5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -140,7 +140,7 @@
threads = config.getInt("index", null, "threads", 0);
}
if (threads <= 0) {
- return MoreExecutors.newDirectExecutorService();
+ threads = Runtime.getRuntime().availableProcessors() / 2 + 1;
}
return MoreExecutors.listeningDecorator(
workQueue.createQueue(threads, "Index-Interactive"));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
index a8ec51f..10f5ecb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
@@ -21,6 +21,7 @@
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gwtorm.server.OrmException;
@@ -28,10 +29,38 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
/** Specific version of a secondary index schema. */
public class Schema<T> {
+ public static class Builder<T> {
+ private final List<FieldDef<T, ?>> fields = new ArrayList<>();
+
+ public Builder<T> add(Schema<T> schema) {
+ this.fields.addAll(schema.getFields().values());
+ return this;
+ }
+
+ @SafeVarargs
+ public final Builder<T> add(FieldDef<T, ?>... fields) {
+ this.fields.addAll(Arrays.asList(fields));
+ return this;
+ }
+
+ @SafeVarargs
+ public final Builder<T> remove(FieldDef<T, ?>... fields) {
+ this.fields.removeAll(Arrays.asList(fields));
+ return this;
+ }
+
+ public Schema<T> build() {
+ return new Schema<>(ImmutableList.copyOf(fields));
+ }
+ }
+
private static final Logger log = LoggerFactory.getLogger(Schema.class);
public static class Values<T> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index d61040c..e7d2ab2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -30,6 +30,8 @@
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDbUtil;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.SchemaUtil;
@@ -51,6 +53,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -78,7 +81,7 @@
/** Newer style Change-Id key. */
public static final FieldDef<ChangeData, String> ID =
- new FieldDef.Single<ChangeData, String>("change_id",
+ new FieldDef.Single<ChangeData, String>(ChangeQueryBuilder.FIELD_CHANGE_ID,
FieldType.PREFIX, false) {
@Override
public String get(ChangeData input, FillArgs args)
@@ -176,7 +179,7 @@
/** Submission id assigned by MergeOp. */
public static final FieldDef<ChangeData, String> SUBMISSIONID =
new FieldDef.Single<ChangeData, String>(
- "submissionid", FieldType.EXACT, false) {
+ ChangeQueryBuilder.FIELD_SUBMISSIONID, FieldType.EXACT, false) {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
@@ -207,7 +210,7 @@
public static final FieldDef<ChangeData, Iterable<String>> PATH =
new FieldDef.Repeatable<ChangeData, String>(
// Named for backwards compatibility.
- "file", FieldType.EXACT, false) {
+ ChangeQueryBuilder.FIELD_FILE, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
@@ -234,7 +237,7 @@
/** Hashtags tied to a change */
public static final FieldDef<ChangeData, Iterable<String>> HASHTAG =
new FieldDef.Repeatable<ChangeData, String>(
- "hashtag", FieldType.EXACT, false) {
+ ChangeQueryBuilder.FIELD_HASHTAG, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
@@ -268,7 +271,7 @@
/** Components of each file path modified in the current patch set. */
public static final FieldDef<ChangeData, Iterable<String>> FILE_PART =
new FieldDef.Repeatable<ChangeData, String>(
- "filepart", FieldType.EXACT, false) {
+ ChangeQueryBuilder.FIELD_FILEPART, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
@@ -327,7 +330,7 @@
/** Commit ID of any patch set on the change, using exact match. */
public static final FieldDef<ChangeData, Iterable<String>> EXACT_COMMIT =
new FieldDef.Repeatable<ChangeData, String>(
- "exactcommit", FieldType.EXACT, false) {
+ ChangeQueryBuilder.FIELD_EXACTCOMMIT, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
@@ -519,7 +522,7 @@
/** Whether the change is mergeable. */
public static final FieldDef<ChangeData, String> MERGEABLE =
new FieldDef.Single<ChangeData, String>(
- "mergeable2", FieldType.EXACT, true) {
+ ChangeQueryBuilder.FIELD_MERGEABLE, FieldType.EXACT, true) {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
@@ -593,6 +596,7 @@
};
/** Users who have starred this change. */
+ @Deprecated
public static final FieldDef<ChangeData, Iterable<Integer>> STARREDBY =
new FieldDef.Repeatable<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_STARREDBY, FieldType.INTEGER, true) {
@@ -609,10 +613,42 @@
}
};
+ /**
+ * Star labels on this change in the format: <account-id>:<label>
+ */
+ public static final FieldDef<ChangeData, Iterable<String>> STAR =
+ new FieldDef.Repeatable<ChangeData, String>(
+ ChangeQueryBuilder.FIELD_STAR, FieldType.EXACT, true) {
+ @Override
+ public Iterable<String> get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return Iterables.transform(input.stars().entries(),
+ new Function<Map.Entry<Account.Id, String>, String>() {
+ @Override
+ public String apply(Map.Entry<Account.Id, String> e) {
+ return StarredChangesUtil.StarField.create(
+ e.getKey(), e.getValue()).toString();
+ }
+ });
+ }
+ };
+
+ /** Users that have starred the change with any label. */
+ public static final FieldDef<ChangeData, Iterable<Integer>> STARBY =
+ new FieldDef.Repeatable<ChangeData, Integer>(
+ ChangeQueryBuilder.FIELD_STARBY, FieldType.INTEGER, false) {
+ @Override
+ public Iterable<Integer> get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return Iterables.transform(input.stars().keySet(),
+ ReviewDbUtil.INT_KEY_FUNCTION);
+ }
+ };
+
/** Opaque group identifiers for this change's patch sets. */
public static final FieldDef<ChangeData, Iterable<String>> GROUP =
new FieldDef.Repeatable<ChangeData, String>(
- "group", FieldType.EXACT, false) {
+ ChangeQueryBuilder.FIELD_GROUP, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 1e59cdb..db889c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -67,9 +67,13 @@
@Deprecated
static final Schema<ChangeData> V28 = schema(V27, ChangeField.STARREDBY);
+ @Deprecated
static final Schema<ChangeData> V29 =
schema(V28, ChangeField.HASHTAG_CASE_AWARE);
+ static final Schema<ChangeData> V30 =
+ schema(V29, ChangeField.STAR, ChangeField.STARBY);
+
public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE =
new ChangeSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 4e1891a..367159d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -28,6 +28,7 @@
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.mail.ProjectWatch.Watchers;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -305,8 +306,8 @@
try {
// BCC anyone who has starred this change.
//
- for (Account.Id accountId : args.starredChangesUtil
- .byChangeFromIndex(change.getId())) {
+ for (Account.Id accountId : args.starredChangesUtil.byChangeFromIndex(
+ change.getId(), StarredChangesUtil.DEFAULT_LABEL)) {
super.add(RecipientType.BCC, accountId);
}
} catch (OrmException | NoSuchChangeException err) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 22255b5..4d2a719 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -353,7 +353,7 @@
private Map<PatchSetApproval.Key, PatchSetApproval>
filterPatchSetApprovals() {
- return limitToExistingPatchSets(patchSetApprovals,
+ return limitToValidPatchSets(patchSetApprovals,
new Function<PatchSetApproval.Key, PatchSet.Id>() {
@Override
public PatchSet.Id apply(PatchSetApproval.Key in) {
@@ -364,7 +364,7 @@
private Map<PatchLineComment.Key, PatchLineComment>
filterPatchLineComments() {
- return limitToExistingPatchSets(patchLineComments,
+ return limitToValidPatchSets(patchLineComments,
new Function<PatchLineComment.Key, PatchSet.Id>() {
@Override
public PatchSet.Id apply(PatchLineComment.Key in) {
@@ -373,13 +373,15 @@
});
}
- private <K, V> Map<K, V> limitToExistingPatchSets(Map<K, V> in,
+ private <K, V> Map<K, V> limitToValidPatchSets(Map<K, V> in,
final Function<K, PatchSet.Id> func) {
+ final Predicate<PatchSet.Id> upToCurrent = upToCurrentPredicate();
return Maps.filterKeys(
in, new Predicate<K>() {
@Override
public boolean apply(K in) {
- return patchSets.containsKey(func.apply(in));
+ PatchSet.Id psId = func.apply(in);
+ return upToCurrent.apply(psId) && patchSets.containsKey(psId);
}
});
}
@@ -395,6 +397,20 @@
});
}
+ private Predicate<PatchSet.Id> upToCurrentPredicate() {
+ final int max = change.currentPatchSetId().get();
+ return new Predicate<PatchSet.Id>() {
+ @Override
+ public boolean apply(PatchSet.Id in) {
+ return in.get() <= max;
+ }
+ };
+ }
+
+ private Map<PatchSet.Id, PatchSet> filterPatchSets() {
+ return Maps.filterKeys(patchSets, upToCurrentPredicate());
+ }
+
private static void diffChanges(List<String> diffs, ChangeBundle bundleA,
ChangeBundle bundleB) {
Change a = bundleA.change;
@@ -402,12 +418,16 @@
String desc = a.getId().equals(b.getId()) ? describe(a.getId()) : "Changes";
boolean excludeCreatedOn = false;
- boolean excludeSubject = false;
- boolean excludeOrigSubj = false;
boolean excludeTopic = false;
Timestamp aUpdated = a.getLastUpdatedOn();
Timestamp bUpdated = b.getLastUpdatedOn();
+ CharMatcher s = CharMatcher.is(' ');
+ boolean excludeSubject = false;
+ boolean excludeOrigSubj = false;
+ String aSubj = a.getSubject();
+ String bSubj = b.getSubject();
+
// Allow created timestamp in NoteDb to be either the created timestamp of
// the change, or the timestamp of the first remaining patch set.
//
@@ -433,34 +453,39 @@
// Ignore original subject on the ReviewDb side if it equals the subject of
// the current patch set.
//
+ // For all of the above subject comparisons, first trim any leading spaces
+ // from the NoteDb strings. (We actually do represent the leading spaces
+ // faithfully during conversion, but JGit's FooterLine parser trims them
+ // when reading.)
+ //
// Ignore empty topic on the ReviewDb side if it is null on the NoteDb side.
//
// Use max timestamp of all ReviewDb entities when comparing with NoteDb.
if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
excludeCreatedOn = !timestampsDiffer(
bundleA, bundleA.getFirstPatchSetTime(), bundleB, b.getCreatedOn());
- excludeSubject = b.getSubject().startsWith(a.getSubject());
+ aSubj = s.trimLeadingFrom(aSubj);
+ excludeSubject = bSubj.startsWith(aSubj);
excludeOrigSubj = true;
excludeTopic = "".equals(a.getTopic()) && b.getTopic() == null;
aUpdated = bundleA.getLatestTimestamp();
} else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
excludeCreatedOn = !timestampsDiffer(
bundleA, a.getCreatedOn(), bundleB, bundleB.getFirstPatchSetTime());
- excludeSubject = a.getSubject().startsWith(b.getSubject());
+ bSubj = s.trimLeadingFrom(bSubj);
+ excludeSubject = aSubj.startsWith(bSubj);
excludeOrigSubj = true;
excludeTopic = a.getTopic() == null && "".equals(b.getTopic());
bUpdated = bundleB.getLatestTimestamp();
}
+ String subjectField = "subject";
String updatedField = "lastUpdatedOn";
- List<String> exclude =
- Lists.newArrayList(updatedField, "noteDbState", "rowVersion");
+ List<String> exclude = Lists.newArrayList(
+ subjectField, updatedField, "noteDbState", "rowVersion");
if (excludeCreatedOn) {
exclude.add("createdOn");
}
- if (excludeSubject) {
- exclude.add("subject");
- }
if (excludeOrigSubj) {
exclude.add("originalSubject");
}
@@ -478,6 +503,9 @@
diffTimestamps(diffs, desc, bundleA, aUpdated, bundleB, bUpdated,
"effective last updated time");
}
+ if (!excludeSubject) {
+ diffValues(diffs, desc, aSubj, bSubj, subjectField);
+ }
}
/**
@@ -599,8 +627,8 @@
private static void diffPatchSets(List<String> diffs, ChangeBundle bundleA,
ChangeBundle bundleB) {
- Map<PatchSet.Id, PatchSet> as = bundleA.patchSets;
- Map<PatchSet.Id, PatchSet> bs = bundleB.patchSets;
+ Map<PatchSet.Id, PatchSet> as = bundleA.filterPatchSets();
+ Map<PatchSet.Id, PatchSet> bs = bundleB.filterPatchSets();
for (PatchSet.Id id : diffKeySets(diffs, as, bs)) {
PatchSet a = as.get(id);
PatchSet b = bs.get(id);
@@ -732,9 +760,17 @@
if (bundleA.source == bundleB.source || ta == null || tb == null) {
diffValues(diffs, desc, ta, tb, fieldDesc);
} else if (bundleA.source == NOTE_DB) {
- diffTimestamps(diffs, desc, ta, tb, fieldDesc);
+ diffTimestamps(
+ diffs, desc,
+ bundleA.getChange(), ta,
+ bundleB.getChange(), tb,
+ fieldDesc);
} else {
- diffTimestamps(diffs, desc, tb, ta, fieldDesc);
+ diffTimestamps(
+ diffs, desc,
+ bundleB.getChange(), tb,
+ bundleA.getChange(), ta,
+ fieldDesc);
}
}
@@ -746,13 +782,25 @@
}
private static void diffTimestamps(List<String> diffs, String desc,
- Timestamp tsFromNoteDb, Timestamp tsFromReviewDb, String field) {
+ Change changeFromNoteDb, Timestamp tsFromNoteDb,
+ Change changeFromReviewDb, Timestamp tsFromReviewDb,
+ String field) {
// Because ChangeRebuilder may batch events together that are several
// seconds apart, the timestamp in NoteDb may actually be several seconds
// *earlier* than the timestamp in ReviewDb that it was converted from.
checkArgument(tsFromNoteDb.equals(roundToSecond(tsFromNoteDb)),
"%s from NoteDb has non-rounded %s timestamp: %s",
desc, field, tsFromNoteDb);
+
+ if (tsFromReviewDb.before(changeFromReviewDb.getCreatedOn())
+ && tsFromNoteDb.equals(changeFromNoteDb.getCreatedOn())) {
+ // Timestamp predates change creation. These are truncated to change
+ // creation time during NoteDb conversion, so allow this if the timestamp
+ // in NoteDb matches the createdOn time in NoteDb.
+ return;
+ }
+
+
long delta = tsFromReviewDb.getTime() - tsFromNoteDb.getTime();
long max = ChangeRebuilderImpl.MAX_WINDOW_MS;
if (delta < 0 || delta > max) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
index 87155d4..1a44f081 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
@@ -228,6 +228,7 @@
private void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle)
throws IOException, OrmException {
Change change = new Change(bundle.getChange());
+ PatchSet.Id currPsId = change.currentPatchSetId();
// We will rebuild all events, except for draft comments, in buckets based
// on author and timestamp.
List<Event> events = new ArrayList<>();
@@ -245,6 +246,12 @@
Sets.newHashSetWithExpectedSize(bundle.getPatchSets().size());
for (PatchSet ps : bundle.getPatchSets()) {
+ if (ps.getId().get() > currPsId.get()) {
+ log.info(
+ "Skipping patch set {}, which is higher than current patch set {}",
+ ps.getId(), currPsId);
+ continue;
+ }
psIds.add(ps.getId());
events.add(new PatchSetEvent(
change, ps, manager.getChangeRepo().rw));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index fb1dce5..075069f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -73,7 +73,6 @@
@Singleton
public class PluginLoader implements LifecycleListener {
- static final String PLUGIN_TMP_PREFIX = "plugin_";
static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
public String getPluginName(Path srcPath) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index 115ddb5..59ed261 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -37,22 +37,6 @@
import java.util.jar.Manifest;
public class ServerPlugin extends Plugin {
-
- /** Unique key that changes whenever a plugin reloads. */
- public static final class CacheKey {
- private final String name;
-
- CacheKey(String name) {
- this.name = name;
- }
-
- @Override
- public String toString() {
- int id = System.identityHashCode(this);
- return String.format("Plugin[%s@%x]", name, id);
- }
- }
-
private final Manifest manifest;
private final PluginContentScanner scanner;
private final Path dataDir;
@@ -124,10 +108,6 @@
return (Class<? extends Module>) clazz;
}
- Path getSrcJar() {
- return getSrcFile();
- }
-
Path getDataDir() {
return dataDir;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java
deleted file mode 100644
index f3ca8b6..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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.project;
-
-public class ChangeModifiedException extends InvalidChangeOperationException {
- private static final long serialVersionUID = 1L;
-
- public ChangeModifiedException(String msg) {
- super(msg);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java
deleted file mode 100644
index 9bbc02f..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2010 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.query.change;
-
-import com.google.gwtorm.server.ResultSet;
-
-import java.util.ArrayList;
-import java.util.List;
-
-abstract class AbstractResultSet<T> implements ResultSet<T> {
- @Override
- public List<T> toList() {
- ArrayList<T> r = new ArrayList<>();
- for (T t : this) {
- r.add(t);
- }
- return r;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index b32ccab..5eed391 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -24,10 +24,12 @@
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
@@ -345,7 +347,9 @@
private Set<Account.Id> editsByUser;
private Set<Account.Id> reviewedBy;
private Set<Account.Id> draftsByUser;
+ @Deprecated
private Set<Account.Id> starredByUser;
+ private ImmutableMultimap<Account.Id, String> stars;
private PersonIdent author;
private PersonIdent committer;
@@ -1050,17 +1054,31 @@
this.hashtags = hashtags;
}
+ @Deprecated
public Set<Account.Id> starredBy() throws OrmException {
if (starredByUser == null) {
- starredByUser = checkNotNull(starredChangesUtil).byChange(legacyId);
+ starredByUser = checkNotNull(starredChangesUtil).byChange(
+ legacyId, StarredChangesUtil.DEFAULT_LABEL);
}
return starredByUser;
}
+ @Deprecated
public void setStarredBy(Set<Account.Id> starredByUser) {
this.starredByUser = starredByUser;
}
+ public ImmutableMultimap<Account.Id, String> stars() throws OrmException {
+ if (stars == null) {
+ stars = checkNotNull(starredChangesUtil).byChange(legacyId);
+ }
+ return stars;
+ }
+
+ public void setStars(Multimap<Account.Id, String> stars) {
+ this.stars = ImmutableMultimap.copyOf(stars);
+ }
+
@AutoValue
abstract static class ReviewedByEvent {
private static ReviewedByEvent create(ChangeMessage msg) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 230bf31..07c8c72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
@@ -102,12 +103,11 @@
// SearchSuggestOracle up to date.
public static final String FIELD_ADDED = "added";
- public static final String FIELD_AFTER = "after";
public static final String FIELD_AGE = "age";
public static final String FIELD_AUTHOR = "author";
public static final String FIELD_BEFORE = "before";
- public static final String FIELD_BRANCH = "branch";
public static final String FIELD_CHANGE = "change";
+ public static final String FIELD_CHANGE_ID = "change_id";
public static final String FIELD_COMMENT = "comment";
public static final String FIELD_COMMENTBY = "commentby";
public static final String FIELD_COMMIT = "commit";
@@ -118,14 +118,15 @@
public static final String FIELD_DESTINATION = "destination";
public static final String FIELD_DRAFTBY = "draftby";
public static final String FIELD_EDITBY = "editby";
+ public static final String FIELD_EXACTCOMMIT = "exactcommit";
public static final String FIELD_FILE = "file";
- public static final String FIELD_IS = "is";
- public static final String FIELD_HAS = "has";
+ public static final String FIELD_FILEPART = "filepart";
+ public static final String FIELD_GROUP = "group";
public static final String FIELD_HASHTAG = "hashtag";
public static final String FIELD_LABEL = "label";
public static final String FIELD_LIMIT = "limit";
public static final String FIELD_MERGE = "merge";
- public static final String FIELD_MERGEABLE = "mergeable";
+ public static final String FIELD_MERGEABLE = "mergeable2";
public static final String FIELD_MESSAGE = "message";
public static final String FIELD_OWNER = "owner";
public static final String FIELD_OWNERIN = "ownerin";
@@ -133,14 +134,15 @@
public static final String FIELD_PATH = "path";
public static final String FIELD_PROJECT = "project";
public static final String FIELD_PROJECTS = "projects";
- public static final String FIELD_QUERY = "query";
public static final String FIELD_REF = "ref";
public static final String FIELD_REVIEWEDBY = "reviewedby";
public static final String FIELD_REVIEWER = "reviewer";
public static final String FIELD_REVIEWERIN = "reviewerin";
+ public static final String FIELD_STAR = "star";
+ public static final String FIELD_STARBY = "starby";
public static final String FIELD_STARREDBY = "starredby";
public static final String FIELD_STATUS = "status";
- public static final String FIELD_TOPIC = "topic";
+ public static final String FIELD_SUBMISSIONID = "submissionid";
public static final String FIELD_TR = "tr";
public static final String FIELD_VISIBLETO = "visibleto";
public static final String FIELD_WATCHEDBY = "watchedby";
@@ -422,6 +424,10 @@
return starredby(self());
}
+ if ("stars".equalsIgnoreCase(value)) {
+ return new HasStarsPredicate(self());
+ }
+
if ("draft".equalsIgnoreCase(value)) {
return draftby(self());
}
@@ -646,6 +652,11 @@
}
@Operator
+ public Predicate<ChangeData> star(String label) throws QueryParseException {
+ return new StarPredicate(self(), label);
+ }
+
+ @Operator
public Predicate<ChangeData> starredby(String who)
throws QueryParseException, OrmException {
return starredby(parseAccount(who));
@@ -663,6 +674,10 @@
@SuppressWarnings("deprecation")
private Predicate<ChangeData> starredby(Account.Id who)
throws QueryParseException {
+ if (args.getSchema().hasField(ChangeField.STAR)) {
+ return new StarPredicate(who, StarredChangesUtil.DEFAULT_LABEL);
+ }
+
return args.getSchema().hasField(ChangeField.STARREDBY)
? new IsStarredByPredicate(who)
: new IsStarredByLegacyPredicate(args.asUser(who));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
new file mode 100644
index 0000000..83990bc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gwtorm.server.OrmException;
+
+public class HasStarsPredicate extends IndexPredicate<ChangeData> {
+ private final Account.Id accountId;
+
+ HasStarsPredicate(Account.Id accountId) {
+ super(ChangeField.STARBY, accountId.toString());
+ this.accountId = accountId;
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.stars().containsKey(accountId);
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+
+ @Override
+ public String toString() {
+ return ChangeQueryBuilder.FIELD_STARBY + ":" + accountId;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 37cdfaf..81d8754 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -298,6 +298,7 @@
return query(and(project(project), or(groupPredicates)));
}
+ @SuppressWarnings("deprecation")
public List<ChangeData> byIsStarred(Account.Id id) throws OrmException {
return query(new IsStarredByPredicate(id));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
index 50c54f6..634bc4a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -19,6 +19,7 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
+@Deprecated
class IsStarredByPredicate extends IndexPredicate<ChangeData> {
private final Account.Id accountId;
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 88f1911..43fb0c5 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
@@ -111,6 +111,7 @@
return out.size() == 1 ? out.get(0) : out;
}
+ @SuppressWarnings("deprecation")
private List<List<ChangeInfo>> query()
throws OrmException, QueryParseException {
if (imp.isDisabled()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java
new file mode 100644
index 0000000..2facdb7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gwtorm.server.OrmException;
+
+public class StarPredicate extends IndexPredicate<ChangeData> {
+ private final Account.Id accountId;
+ private final String label;
+
+ StarPredicate(Account.Id accountId, String label) {
+ super(ChangeField.STAR,
+ StarredChangesUtil.StarField.create(accountId, label).toString());
+ this.accountId = accountId;
+ this.label = label;
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.stars().get(accountId).contains(label);
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+
+ @Override
+ public String toString() {
+ return ChangeQueryBuilder.FIELD_STAR + ":" + label;
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index f57fba4..ea1120f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -40,10 +40,13 @@
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.StandardKeyEncoder;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -366,6 +369,64 @@
}
@Test
+ public void diffChangesTrimsLeadingSpacesFromReviewDbComparingToNoteDb()
+ throws Exception {
+ Change c1 = TestChanges.newChange(
+ new Project.NameKey("project"), new Account.Id(100));
+ Change c2 = clone(c1);
+ c2.setCurrentPatchSet(c1.currentPatchSetId(),
+ " " + c1.getSubject(), c1.getOriginalSubject());
+
+ // Both ReviewDb, exact match required.
+ ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+ comments(), REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+ comments(), REVIEW_DB);
+ assertDiffs(b1, b2,
+ "subject differs for Change.Id " + c1.getId() + ":"
+ + " {Change subject} != { Change subject}");
+
+ // ReviewDb is missing leading spaces, allowed.
+ b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+ comments(), NOTE_DB);
+ b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+ comments(), REVIEW_DB);
+ assertNoDiffs(b1, b2);
+ assertNoDiffs(b2, b1);
+ }
+
+ @Test
+ public void diffChangesDoesntTrimLeadingNonSpaceWhitespaceFromSubject()
+ throws Exception {
+ Change c1 = TestChanges.newChange(
+ new Project.NameKey("project"), new Account.Id(100));
+ Change c2 = clone(c1);
+ c2.setCurrentPatchSet(c1.currentPatchSetId(),
+ "\t" + c1.getSubject(), c1.getOriginalSubject());
+
+ // Both ReviewDb.
+ ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+ comments(), REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+ comments(), REVIEW_DB);
+ assertDiffs(b1, b2,
+ "subject differs for Change.Id " + c1.getId() + ":"
+ + " {Change subject} != {\tChange subject}");
+
+ // One NoteDb.
+ b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+ comments(), NOTE_DB);
+ b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+ comments(), REVIEW_DB);
+ assertDiffs(b1, b2,
+ "subject differs for Change.Id " + c1.getId() + ":"
+ + " {Change subject} != {\tChange subject}");
+ assertDiffs(b2, b1,
+ "subject differs for Change.Id " + c1.getId() + ":"
+ + " {\tChange subject} != {Change subject}");
+ }
+
+ @Test
public void diffChangeMessageKeySets() throws Exception {
Change c = TestChanges.newChange(project, accountId);
int id = c.getId().get();
@@ -706,6 +767,54 @@
}
@Test
+ public void diffIgnoresPatchSetsGreaterThanCurrent() throws Exception {
+ Change c = TestChanges.newChange(project, accountId);
+
+ PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
+ ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ ps1.setUploader(accountId);
+ ps1.setCreatedOn(TimeUtil.nowTs());
+ PatchSet ps2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
+ ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
+ ps2.setUploader(accountId);
+ ps2.setCreatedOn(TimeUtil.nowTs());
+ assertThat(ps2.getId().get()).isGreaterThan(c.currentPatchSetId().get());
+
+ PatchSetApproval a1 = new PatchSetApproval(
+ new PatchSetApproval.Key(
+ ps1.getId(), accountId, new LabelId("Code-Review")),
+ (short) 1,
+ TimeUtil.nowTs());
+ PatchSetApproval a2 = new PatchSetApproval(
+ new PatchSetApproval.Key(
+ ps2.getId(), accountId, new LabelId("Code-Review")),
+ (short) 1,
+ TimeUtil.nowTs());
+
+ // Both ReviewDb.
+ ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
+ approvals(a1), comments(), REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2),
+ approvals(a1, a2), comments(), REVIEW_DB);
+ assertNoDiffs(b1, b2);
+
+ // One NoteDb.
+ b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(a1),
+ comments(), NOTE_DB);
+ b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2), approvals(a1, a2),
+ comments(), REVIEW_DB);
+ assertNoDiffs(b1, b2);
+ assertNoDiffs(b2, b1);
+
+ // Both NoteDb.
+ b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(a1),
+ comments(), NOTE_DB);
+ b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2), approvals(a1, a2),
+ comments(), NOTE_DB);
+ assertNoDiffs(b1, b2);
+ }
+
+ @Test
public void diffPatchSetApprovalKeySets() throws Exception {
Change c = TestChanges.newChange(project, accountId);
int id = c.getId().get();
@@ -799,6 +908,39 @@
}
@Test
+ public void diffPatchSetApprovalsAllowsTruncatedTimestampInNoteDb()
+ throws Exception {
+ Change c = TestChanges.newChange(project, accountId);
+ PatchSetApproval a1 = new PatchSetApproval(
+ new PatchSetApproval.Key(
+ c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
+ (short) 1,
+ c.getCreatedOn());
+ PatchSetApproval a2 = clone(a1);
+ a2.setGranted(new Timestamp(new DateTime(
+ 1900, 1, 1, 0, 0, 0, DateTimeZone.forTimeZone(TimeZone.getDefault()))
+ .getMillis()));
+
+ // Both are ReviewDb, exact match is required.
+ ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
+ comments(), REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
+ comments(), REVIEW_DB);
+ assertDiffs(b1, b2,
+ "granted differs for PatchSetApproval.Key "
+ + c.getId() + "%2C1,100,Code-Review:"
+ + " {2009-09-30 17:00:00.0} != {1900-01-01 00:00:00.0}");
+
+ // Truncating NoteDb timestamp is allowed.
+ b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(),
+ NOTE_DB);
+ b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2), comments(),
+ REVIEW_DB);
+ assertNoDiffs(b1, b2);
+ assertNoDiffs(b2, b1);
+ }
+
+ @Test
public void diffPatchLineCommentKeySets() throws Exception {
Change c = TestChanges.newChange(project, accountId);
int id = c.getId().get();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index f1b5c9a..3f77498 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -47,6 +47,7 @@
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
+import com.google.gerrit.testutil.TestChanges;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -753,6 +754,34 @@
}
@Test
+ public void subjectLeadingWhitespaceChangeNotes() throws Exception {
+ Change c = TestChanges.newChange(project, changeOwner.getAccountId());
+ String trimmedSubj = c.getSubject();
+ c.setCurrentPatchSet(c.currentPatchSetId(), " " + trimmedSubj,
+ c.getOriginalSubject());
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeId(c.getKey().get());
+ update.setBranch(c.getDest().get());
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ assertThat(notes.getChange().getSubject()).isEqualTo(trimmedSubj);
+
+ String tabSubj = "\t\t" + trimmedSubj;
+
+ c = TestChanges.newChange(project, changeOwner.getAccountId());
+ c.setCurrentPatchSet(c.currentPatchSetId(), tabSubj,
+ c.getOriginalSubject());
+ update = newUpdate(c, changeOwner);
+ update.setChangeId(c.getKey().get());
+ update.setBranch(c.getDest().get());
+ update.commit();
+
+ notes = newNotes(c);
+ assertThat(notes.getChange().getSubject()).isEqualTo(tabSubj);
+ }
+
+ @Test
public void commitChangeNotesUnique() throws Exception {
// PatchSetId -> RevId must be a one to one mapping
Change c = newChange();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 47c8a03..46d8818 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -289,6 +289,43 @@
update.getResult());
}
+ @Test
+ public void leadingWhitespace() throws Exception {
+ Change c = TestChanges.newChange(project, changeOwner.getAccountId());
+ c.setCurrentPatchSet(c.currentPatchSetId(), " " + c.getSubject(),
+ c.getOriginalSubject());
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeId(c.getKey().get());
+ update.setBranch(c.getDest().get());
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Change-id: " + c.getKey().get() + "\n"
+ + "Subject: Change subject\n"
+ + "Branch: refs/heads/master\n"
+ + "Commit: " + update.getCommit().name() + "\n",
+ update.getResult());
+
+ c = TestChanges.newChange(project, changeOwner.getAccountId());
+ c.setCurrentPatchSet(c.currentPatchSetId(), "\t\t" + c.getSubject(),
+ c.getOriginalSubject());
+ update = newUpdate(c, changeOwner);
+ update.setChangeId(c.getKey().get());
+ update.setBranch(c.getDest().get());
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Change-id: " + c.getKey().get() + "\n"
+ + "Subject: \t\tChange subject\n"
+ + "Branch: refs/heads/master\n"
+ + "Commit: " + update.getCommit().name() + "\n",
+ update.getResult());
+ }
+
private RevCommit parseCommit(ObjectId id) throws Exception {
if (id instanceof RevCommit) {
return (RevCommit) id;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 61d7590..bcc9b38 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -38,6 +38,7 @@
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -55,6 +56,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.change.ChangeInserter;
@@ -94,6 +96,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -1241,6 +1244,35 @@
}
@Test
+ public void byStar() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChange(repo));
+ Change change2 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+ insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+
+ gApi.accounts()
+ .self()
+ .setStars(change1.getId().toString(),
+ new StarsInput(new HashSet<>(Arrays.asList("red", "blue"))));
+ gApi.accounts()
+ .self()
+ .setStars(change2.getId().toString(),
+ new StarsInput(new HashSet<>(Arrays.asList(
+ StarredChangesUtil.DEFAULT_LABEL, "green", "blue"))));
+
+ // check labeled stars
+ assertQuery("star:red", change1);
+ assertQuery("star:blue", change2, change1);
+ assertQuery("has:stars", change2, change1);
+
+ // check default star
+ assertQuery("has:star", change2);
+ assertQuery("is:starred", change2);
+ assertQuery("starredby:self", change2);
+ assertQuery("star:" + StarredChangesUtil.DEFAULT_LABEL, change2);
+ }
+
+ @Test
public void byFrom() throws Exception {
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChange(repo));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 5971b8d..34f281e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -52,7 +52,7 @@
private IdentifiedUser currentUser;
private int columns = 80;
- private int taskNameWidth;
+ private int maxCommandWidth;
@Override
public void start(Environment env) throws IOException {
@@ -69,7 +69,7 @@
@Override
protected void run() throws UnloggedFailure {
- taskNameWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4;
+ maxCommandWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4;
stdout.print(String.format("%-8s %-12s %-12s %-4s %s\n", //
"Task", "State", "StartTime", "", "Command"));
stdout.print("----------------------------------------------"
@@ -97,9 +97,9 @@
// Shows information about tasks depending on the user rights
if (viewAll || task.projectName == null) {
- String command = task.command.length() < taskNameWidth
+ String command = task.command.length() < maxCommandWidth
? task.command
- : task.command.substring(0, taskNameWidth);
+ : task.command.substring(0, maxCommandWidth);
stdout.print(String.format("%8s %-12s %-12s %-4s %s\n",
task.id, start, startTime(task.startTime), "", command));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 3139e89..4480319 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -103,6 +103,9 @@
.prefsButton {
text-align: right;
}
+ #modeSelect {
+ margin-left: .5em;
+ }
@media screen and (max-width: 50em) {
.dash {
display: none;
@@ -168,11 +171,17 @@
patch-range="[[_patchRange]]"
available-patches="[[_computeAvailablePatches(_change.revisions)]]">
</gr-patch-range-select>
- <gr-button link
- class="prefsButton"
- on-tap="_handlePrefsTap"
- hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
- hidden>Diff View Preferences</gr-button>
+ <div>
+ <gr-button link
+ class="prefsButton"
+ on-tap="_handlePrefsTap"
+ hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
+ hidden>Diff View Preferences</gr-button>
+ <select id="modeSelect" on-change="_handleModeChange">
+ <option value="SIDE_BY_SIDE">Side By Side</option>
+ <option value="UNIFIED_DIFF">Unified</option>
+ </select>
+ </div>
</div>
<gr-overlay id="prefsOverlay" with-backdrop>
<gr-diff-preferences
@@ -186,6 +195,7 @@
path="[[_path]]"
prefs="[[_prefs]]"
project-config="[[_projectConfig]]"
+ view-mode="[[_diffMode]]"
on-render="_handleDiffRender">
</gr-diff>
</div>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index b2adcf4..6932d10 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -16,6 +16,11 @@
var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+ var DiffViewMode = {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ };
+
Polymer({
is: 'gr-diff-view',
@@ -64,6 +69,11 @@
value: true,
},
_prefs: Object,
+ _userPrefs: Object,
+ _diffMode: {
+ type: String,
+ computed: '_getDiffViewMode(changeViewState.diffMode, _userPrefs)'
+ },
},
behaviors: [
@@ -74,6 +84,7 @@
'_getChangeDetail(_changeNum)',
'_getProjectConfig(_change.project)',
'_getFiles(_changeNum, _patchRange.patchNum)',
+ '_updateModeSelect(_diffMode)',
],
attached: function() {
@@ -92,6 +103,9 @@
},
detached: function() {
+ // Reset the diff mode to null so that it reverts to the user preference.
+ this.changeViewState.diffMode = null;
+
window.removeEventListener('resize', this._boundWindowResizeHandler);
},
@@ -124,6 +138,10 @@
return this.$.restAPI.getDiffPreferences();
},
+ _getPreferences: function() {
+ return this.$.restAPI.getPreferences();
+ },
+
_handleReviewedChange: function(e) {
this._setReviewed(Polymer.dom(e).rootTarget.checked);
},
@@ -242,6 +260,10 @@
this._prefs = prefs;
}.bind(this)));
+ promises.push(this._getPreferences().then(function(prefs) {
+ this._userPrefs = prefs;
+ }.bind(this)));
+
promises.push(this.$.diff.reload());
Promise.all(promises).then(function() {
@@ -360,5 +382,43 @@
e.stopPropagation();
this.$.prefsOverlay.close();
},
+
+ _handleModeChange: function(e) {
+ this.set('changeViewState.diffMode', this.$.modeSelect.value);
+ },
+
+ /**
+ * _getDiffViewMode: Get the diff view (side-by-side or unified) based on
+ * the current state.
+ *
+ * The expected behavior is to use the mode specified in the user's
+ * preferences unless they have manually chosen the alternative view. If the
+ * user navigates up to the change view, it should clear this choice and
+ * revert to the preference the next time a diff is viewed.
+ *
+ * Use side-by-side if the user is not logged in.
+ *
+ * @return {String}
+ */
+ _getDiffViewMode: function() {
+ if (this.changeViewState.diffMode) {
+ return this.changeViewState.diffMode;
+ } else if (this._userPrefs && this._userPrefs.diff_view) {
+ return this.changeViewState.diffMode = this._userPrefs.diff_view;
+ }
+
+ return DiffViewMode.SIDE_BY_SIDE;
+ },
+
+ /**
+ * Synchronize the mode select if it deviates from the selected mode state.
+ * This is mainly to keep it accurate when the page loads.
+ */
+ _updateModeSelect: function() {
+ var mode = this._getDiffViewMode();
+ if (this.$.modeSelect.value !== mode) {
+ this.$.modeSelect.value = mode;
+ }
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index e7e4055..4167424 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -356,5 +356,37 @@
done();
});
});
+
+ test('diff mode selector correctly toggles the diff', function() {
+ var select = element.$.modeSelect;
+ var diffDisplay = element.$.diff;
+
+ element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
+
+ // The mode selected in the view state reflects the selected option.
+ assert.equal(element._getDiffViewMode(), select.value);
+
+ // The mode selected in the view state reflects the view rednered in the
+ // diff.
+ assert.equal(select.value, diffDisplay.viewMode);
+
+ // We will simulate a user change of the selected mode.
+ var newMode = 'UNIFIED_DIFF';
+
+ // Listen to the change handler.
+ var eventStub = sinon.spy(element, '_handleModeChange');
+
+ // Set the actual value of the select, and simulate the change event.
+ select.value = newMode;
+ element.fire('change', {}, { node: select });
+
+ // Make sure the handler was called and the state is still coherent.
+ assert.isTrue(eventStub.called);
+ assert.equal(element._getDiffViewMode(), newMode);
+ assert.equal(element._getDiffViewMode(), select.value);
+ assert.equal(element._getDiffViewMode(), diffDisplay.viewMode);
+
+ eventStub.restore();
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 2c70ef1..7b1ce00 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -131,7 +131,7 @@
content: '\00BB';
}
</style>
- <div class$="[[_computeContainerClass(_loggedIn, _viewMode)]]"
+ <div class$="[[_computeContainerClass(_loggedIn, viewMode)]]"
on-tap="_handleTap"
on-mousedown="_handleMouseDown"
on-copy="_handleCopy">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 8ee3a0b..8fef29c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -47,7 +47,7 @@
type: Boolean,
value: false,
},
- _viewMode: {
+ viewMode: {
type: String,
value: DiffViewMode.SIDE_BY_SIDE,
},
@@ -69,7 +69,7 @@
},
observers: [
- '_prefsChanged(prefs.*)',
+ '_prefsChanged(prefs.*, viewMode)',
],
attached: function() {
@@ -439,14 +439,14 @@
},
_getDiffBuilder: function(diff, comments, prefs) {
- if (this._viewMode === DiffViewMode.SIDE_BY_SIDE) {
+ if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
return new GrDiffBuilderSideBySide(diff, comments, prefs,
this.$.diffTable);
- } else if (this._viewMode === DiffViewMode.UNIFIED) {
+ } else if (this.viewMode === DiffViewMode.UNIFIED) {
return new GrDiffBuilderUnified(diff, comments, prefs,
this.$.diffTable);
}
- throw Error('Unsupported diff view mode: ' + this._viewMode);
+ throw Error('Unsupported diff view mode: ' + this.viewMode);
},
_projectConfigChanged: function(projectConfig) {
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index b093a8c..8897967 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -71,6 +71,7 @@
patchNum: null,
selectedFileIndex: 0,
showReplyDialog: false,
+ diffMode: null,
},
changeListView: {
query: null,
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 1ada7e9..a2fa039 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -143,9 +143,7 @@
encodeURIComponent(values[i]));
}
}
- // Sorting the params leaves the order deterministic which is easier
- // to test.
- return url + '?' + params.sort().join('&');
+ return url + '?' + params.join('&');
},
getResponseObject: function(response) {
@@ -630,16 +628,12 @@
_sendDiffDraftRequest: function(method, changeNum, patchNum, draft) {
var url = this.getChangeActionURL(changeNum, patchNum, '/drafts');
+ if (draft.id) {
+ url += '/' + draft.id;
+ }
var body;
- switch(method) {
- case 'PUT':
- body = draft;
- break;
- case 'DELETE':
- url += '/' + draft.id;
- break;
- default:
- throw Error('Unsupported HTTP method: ' + method);
+ if (method === 'PUT') {
+ body = draft;
}
return this.send(method, url, body);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 0004fa9..e7f7a21 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -88,13 +88,19 @@
gr: 'guten tag',
noval: null,
});
- assert.equal(url, '/path/?gr=guten%20tag&noval&sp=hola');
+ assert.equal(url, '/path/?sp=hola&gr=guten%20tag&noval');
url = element._urlWithParams('/path/', {
sp: 'hola',
en: ['hey', 'hi'],
});
- assert.equal(url, '/path/?en=hey&en=hi&sp=hola');
+ assert.equal(url, '/path/?sp=hola&en=hey&en=hi');
+
+ // Order must be maintained with array params.
+ url = element._urlWithParams('/path/', {
+ l: ['c', 'b', 'a'],
+ });
+ assert.equal(url, '/path/?l=c&l=b&l=a');
});
test('request callbacks can be canceled', function(done) {
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 9d16b84..efdbaef 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -49,6 +49,7 @@
'../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
'../elements/diff/gr-diff-view/gr-diff-view_test.html',
'../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
+ '../elements/shared/gr-alert/gr-alert_test.html',
'../elements/shared/gr-account-label/gr-account-label_test.html',
'../elements/shared/gr-account-link/gr-account-link_test.html',
'../elements/shared/gr-alert/gr-alert_test.html',