Merge branch 'stable-2.10'
* stable-2.10:
Replace "line" with "end_line" when range is given in inline comment
Add example of range comment in REST API documentation
diff --git a/.buckconfig b/.buckconfig
index 43bfa5e..211e878 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -12,6 +12,7 @@
release = //:release
safari = //:safari
withdocs = //:withdocs
+ codeserver = //lib/gwt:codeserver
[buildfile]
includes = //tools/default.defs
diff --git a/.buckversion b/.buckversion
index a0c6bc2..3436aba 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-0fe4569e871fd6588f7cbfb4b1d4a14baa791a9f
+63e0e52ddf3de13ae012ba6d8563bd3f82bcf9de
diff --git a/.watchmanconfig b/.watchmanconfig
new file mode 100644
index 0000000..b1869ba
--- /dev/null
+++ b/.watchmanconfig
@@ -0,0 +1,8 @@
+{
+ "ignore_dirs": [
+ "buck-out"
+ ],
+ "ignore_vcs": [
+ ".git"
+ ]
+}
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 8ff2eb6..2f92714 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1208,6 +1208,12 @@
a replication task or a user initiated task such as an upload-pack or
receive-pack.
+[[capability_modifyAccount]]
+=== Modify Account
+
+Allow to link:cmd-set-account.html[modify accounts over the ssh prompt].
+This capability allows the granted group members to modify any user account
+setting.
[[capability_priority]]
=== Priority
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 7adf265..4d00c50 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -24,6 +24,7 @@
asciidoc = [
'$(exe //lib/asciidoctor:asciidoc)',
'-z', '$OUT',
+ '--base-dir', '$SRCDIR',
'--tmp', '$TMP',
'--in-ext', '".txt%s"' % EXPN,
'--out-ext', '".html"',
@@ -57,7 +58,9 @@
out = ex,
)
- asciidoc.append('$(location :%s)' % ex)
+ # The new AsciiDoctor requires both the css file and include files are under
+ # the same directory. Luckily Buck allows us to use :target as SRCS now.
+ newsrcs.append(':%s' % ex)
genrule(
name = name,
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 4c9962d..7171140 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -14,6 +14,7 @@
[--submit | -s]
[--abandon | --restore]
[--publish]
+ [--json | -j]
[--delete]
[--verified <N>] [--code-review <N>]
[--label Label-Name=<N>]
@@ -56,6 +57,12 @@
Optional cover letter to include as part of the message
sent to reviewers when the approval states are updated.
+--json::
+-j::
+ Read review input from JSON file. See
+ link:rest-api-changes.html#review-input[ReviewInput] entity for the
+ format.
+
--notify::
-n::
Who to send email notifications to after the review is stored.
diff --git a/Documentation/cmd-set-account.txt b/Documentation/cmd-set-account.txt
index cec5a8e..40f2378 100644
--- a/Documentation/cmd-set-account.txt
+++ b/Documentation/cmd-set-account.txt
@@ -7,9 +7,11 @@
--
set-account [--full-name <FULLNAME>] [--active|--inactive] \
[--add-email <EMAIL>] [--delete-email <EMAIL> | ALL] \
+ [--preferred-email <EMAIL>] \
[--add-ssh-key - | <KEY>] \
[--delete-ssh-key - | <KEY> | ALL] \
- [--http-password <PASSWORD>] <USER>
+ [--http-password <PASSWORD>] \
+ [--clear-http-password] <USER>
--
== DESCRIPTION
@@ -21,7 +23,15 @@
verification step we force within the UI.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted
+link:access-control.html#capability_modifyAccount[the 'Modify Account' global capability].
+
+To set the HTTP password for the user account (option --http-password) or
+to clear the HTTP password (option --clear-http-password) caller must be
+a member of the privileged 'Administrators' group, or have been granted
+link:access-control.html#capability_generateHttpPassword[the 'Generate HTTP Password' global capability]
+in addition to 'Modify Account' global capability.
== SCRIPTING
This command is intended to be used in scripts.
@@ -58,6 +68,13 @@
Maybe supplied more than once to remove multiple emails
from an account in a single command execution.
+--preferred-email::
+ Sets the preferred email address for the user's account.
+ The email address must already have been registered
+ with the user's account before it can be set.
+ May be supplied with the delete-email option as long as
+ the emails are not the same.
+
--add-ssh-key::
Content of the public SSH key to add to the account's
keyring. If `-` the key is read from stdin, rather than
@@ -77,6 +94,9 @@
--http-password::
Set the HTTP password for the user account.
+--clear-http-password::
+ Clear the HTTP password for the user account.
+
== EXAMPLES
Add an email and SSH key to `watcher`'s account:
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index c95ff73..02bed57 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2369,6 +2369,91 @@
must have the DWORD value `allowtgtsessionkey` set to 1 and the account must not
have local administrator privileges.
+[[ldap.useConnectionPooling]]ldap.useConnectionPooling::
++
+_(Optional)_ Enable the LDAP connection pooling or not.
++
+If it is true, the LDAP service provider maintains a pool of (possibly)
+previously used connections and assigns them to a Context instance as
+needed. When a Context instance is done with a connection (closed or
+garbage collected), the connection is returned to the pool for future use.
++
+For details, see link:http://docs.oracle.com/javase/tutorial/jndi/ldap/pool.html[
+LDAP connection management (Pool)] and link:http://docs.oracle.com/javase/tutorial/jndi/ldap/config.html[
+LDAP connection management (Configuration)]
++
+By default, false.
+
+[[ldap.connectTimeout]]ldap.connectTimeout::
++
+_(Optional)_ Specify how long to wait for a pooled connection.
+This is also used to specify a timeout period for establishment
+of the LDAP connection.
++
+The value is in the usual time-unit format like "1 s", "100 ms",
+etc...
++
+By default there is no timeout and Gerrit will wait indefinitely.
+
+[[ldap.poolAuthentication]]ldap.poolAuthentication::
++
+_(Optional)_ A list of space-separated authentication types of
+connections that may be pooled. Valid types are "none", "simple",
+and "DIGEST-MD5".
++
+Default is "none simple".
+
+[[ldap.poolDebug]]ldap.poolDebug::
++
+_(Optional)_ A string that indicates the level of debug output
+to produce. Valid values are "fine" (trace connection creation
+and removal) and "all" (all debugging information).
+
+[[ldap.poolInitsize]]ldap.poolInitsize::
++
+_(Optional)_ The string representation of an integer that
+represents the number of connections per connection identity
+to create when initially creating a connection for the identity.
++
+Default is 1.
+
+[[ldap.poolMaxsize]]ldap.poolMaxsize::
++
+_(Optional)_ The string representation of an integer that
+represents the maximum number of connections per connection
+identity that can be maintained concurrently.
++
+Default is 0, means that there is no maximum size: A request for
+a pooled connection will use an existing pooled idle connection
+or a newly created pooled connection.
+
+[[ldap.poolPrefsize]]ldap.poolPrefsize::
++
+_(Optional)_ The string representation of an integer that
+represents the preferred number of connections per connection
+identity that should be maintained concurrently.
++
+Default is 0, means that there is no preferred size: A request
+for a pooled connection will result in a newly created connection
+only if no idle ones are available.
+
+[[ldap.poolProtocol]]ldap.poolProtocol::
++
+_(Optional)_ A list of space-separated protocol types of
+connections that may be pooled. Valid types are "plain" and "ssl".
++
+Default is "plain".
+
+[[ldap.poolTimeout]]ldap.poolTimeout::
++
+_(Optional)_ Specify how long an idle connection may remain
+in the pool without being closed and removed from the pool.
++
+The value is in the usual time-unit format like "1 s", "100 ms",
+etc...
++
+By default there is no timeout.
+
[[mimetype]]
=== Section mimetype
diff --git a/Documentation/config.defs b/Documentation/config.defs
index 642b915..8d67173 100644
--- a/Documentation/config.defs
+++ b/Documentation/config.defs
@@ -16,5 +16,6 @@
'last-update-label!',
'source-highlighter=prettify',
'stylesheet=doc.css',
+ 'linkcss=true',
'revnumber="%s"' % revision,
]
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index ce40a01..fee3996 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -288,6 +288,13 @@
buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:HttpPushForReviewIT
----
+To create test coverage report:
+
+----
+ buck test --code-coverage --code-coverage-format html --no-results-cache
+----
+
+The HTML report is created in `buck-out/gen/jacoco/code-coverage/index.html`.
== Dependencies
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 384bb74..d8db555 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -67,6 +67,53 @@
* Close the Debug Configurations dialog and save the changes when prompted.
+=== Running Super Dev Mode
+
+Install dependencies:
+
+----
+ buck build codeserver
+----
+
+Due to codeserver not correctly identifying user agent problem, that was
+already fixed upstream but not released yet, the used user agent must be
+explicitly set in `GerritGwtUI.gwt.xml` for SDM to work:
+
+[source,xml]
+----
+ <set-property name="user.agent" value="geko1_8" />
+----
+
+or
+
+[source,xml]
+----
+ <set-property name="user.agent" value="safari" />
+----
+
+* Select in Eclipse Run -> Debug Configurations `gerrit_gwt_codeserver.launch`
+* Select in Eclipse Run -> Debug Configurations `gerrit_daemon.launch`
+* Only once: add bookmarks for `Dev Mode On/Off` from codeserver URL:
+`http://localhost:9876/` to your bookmark bar
+* Make sure to activate source maps feature in your browser
+* Load Gerrit page `http://localhost:8080`
+* Open developer tools, source tab
+* Click on `Dev Mode On` bookmark
+* Select `gerrit_ui` module to compile (the `Compile` button can also be used
+as a bookmarklet).
+* Navigate on the left to: sourcemaps/gerrit_ui folder (`Ctrl+O` key shortcut
+can be used)
+* Select a file, for example com.google.gerrit.client.change.ChangeScreen2
+and put a breakpoint
+* Navigate in application in change screen and confirm hitting the breakpoint
+* Select `Dev Mode Off` when the debugging session is finished
+
+After changing the client side code:
+* click `Dev Mode On` then `Compile` to reflect your changes in debug session
+* Hitting `F5` in the browser will just load the last compile output, without
+recompiling
+
+
[[known-problems]]
== Known problems
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 6cf9edf..84613c2 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -331,7 +331,11 @@
make -C ReleaseNotes
----
-* Upload html files to the storage bucket via `https://cloud.google.com/console` (manual via web browser)
+* Upload the html files manually via web browser to the
+link:https://console.developers.google.com/project/164060093628/storage/gerrit-documentation/[
+gerrit-documentation] storage bucket. The `gerrit-documentation`
+storage bucket is accessible via the
+link:https://cloud.google.com/console[Google Developers Console].
** Documentation html files must be extracted from `buck-out/gen/Documentation/html.zip`
* Update Google Code project links
** Go to http://code.google.com/p/gerrit/admin
@@ -369,31 +373,6 @@
** A link to the docs
** Describe the type of release (stable, bug fix, RC)
-----
-To: Repo and Gerrit Discussion <repo-discuss@googlegroups.com>
-Subject: Announce: Gerrit 2.2.2.1 (Stable bug fix update)
-
-I am pleased to announce Gerrit Code Review 2.2.2.1.
-
-Download:
-
- http://code.google.com/p/gerrit/downloads/list
-
-
-This release is a stable bug fix release with some
-documentation updates including a new "Contributing to
-Gerrit" doc:
-
- http://gerrit-documentation.googlecode.com/svn/Documentation/2.2.2/dev-contributing.html
-
-
-To read more about the bug fixes:
-
- http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.1.html
-
--Martin
-----
-
* Add an entry to the `NEWS` section of the main Gerrit project web page
** Go to: http://code.google.com/p/gerrit/admin
** Add entry like:
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 8b1c9bd..662bbfe 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1139,6 +1139,226 @@
HTTP/1.1 204 No Content
----
+[[edit-endpoints]]
+== Change Edit Endpoints
+
+These endpoints are considered to be unstable and can be changed in
+backwards incompatible way any time without notice.
+
+[[get-edit-detail]]
+=== Get Change Edit Details
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit
+--
+
+Retrieves a change edit details.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+----
+
+As response an link:#edit-info[EditInfo] entity is returned that
+describes the change edit, or "`204 No Content`" when change edit doesn't
+exist for this change. Change edits are stored on special branches and there
+can be max one edit per user per change. Edits aren't tracked in the database.
+When request parameter `list` is provided the response also includes the file
+list. When `base` request parameter is provided the file list is computed
+against this base revision.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "commit":{
+ "parents":[
+ {
+ "commit":"1eee2c9d8f352483781e772f35dc586a69ff5646",
+ }
+ ],
+ "author":{
+ "name":"Shawn O. Pearce",
+ "email":"sop@google.com",
+ "date":"2012-04-24 18:08:08.000000000",
+ "tz":-420
+ },
+ "committer":{
+ "name":"Shawn O. Pearce",
+ "email":"sop@google.com",
+ "date":"2012-04-24 18:08:08.000000000",
+ "tz":-420
+ },
+ "subject":"Use an EventBus to manage star icons",
+ "message":"Use an EventBus to manage star icons\n\nImage widgets that need to ..."
+ },
+ }
+----
+
+[[put-edit-file]]
+=== Change file content in Change Edit
+--
+'PUT /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile
+--
+
+Put content of a file to a change edit.
+
+.Request
+----
+ PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+When change edit doesn't exist for this change yet it is created. When file
+content isn't provided, it is wiped out for that file. As response
+"`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[post-edit]]
+=== Restore file content in Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/edit
+--
+
+Creates empty change edit or restores file content in change edit. The
+request body needs to include a link:#change-edit-input[ChangeEditInput]
+entity when a file within change edit should be restored.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "restore_path": "foo"
+ }
+----
+
+When change edit doesn't exist for this change yet it is created. When path
+and restore flag are provided in request body, this file is restored. As
+response "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[delete-edit-file]]
+=== Delete file in Change Edit
+--
+'DELETE /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile'
+--
+
+Deletes a file from a change edit. This deletes the file from the repository
+completely. This is not the same as reverting or restoring a file to its
+previous contents.
+
+.Request
+----
+ DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+When change edit doesn't exist for this change yet it is created.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[get-edit-file]]
+=== Retrieve file content from Change Edit
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile
+--
+
+Retrieves content of a file from a change edit.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+The content of the file is returned as text encoded inside base64. When
+specified file was deleted in the change edit "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: text/plain;charset=ISO-8859-1
+ X-FYI-Content-Encoding: base64
+
+ RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
+----
+
+[[publish-edit]]
+=== Publish Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/publish_edit
+--
+
+Promotes change edit to a regular patch set.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish_edit HTTP/1.0
+----
+
+As response "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[rebase-edit]]
+=== Rebase Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/rebase_edit
+--
+
+Rebases change edit on top of latest patch set.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/rebase_edit HTTP/1.0
+----
+
+When change was rebased on top of latest patch set, response
+"`204 No Content`" is returned. When change edit is aready
+based on top of the latest patch set, the response
+"`409 Conflict`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[delete-edit]]
+=== Delete Change Edit
+--
+'DELETE /changes/link:#change-id[\{change-id\}]/edit'
+--
+
+Deletes change edit.
+
+.Request
+----
+ DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+----
+
+As response "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
[[reviewer-endpoints]]
== Reviewer Endpoints
@@ -3598,6 +3818,31 @@
|`url` |The link URL.
|======================
+[[edit-info]]
+=== EditInfo
+The `EditInfo` entity contains information about a change edit.
+
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`commit` ||The commit of change edit as
+link:#commit-info[CommitInfo] entity.
+|`files` |optional|
+The files of the change edit as a map that maps the file names to
+link:#file-info[FileInfo] entities.
+|===========================
+
+[[change-edit-input]]
+=== ChangeEditInput
+The `ChangeEditInput` entity contains information for restoring a
+path within change edit.
+
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`restore_path`|optional|Path to file to restore.
+|===========================
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 2968ebb..46622b1 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -375,7 +375,7 @@
Content-Type: application/json;charset=UTF-8
{
- "operation": "FLUSH"
+ "operation": "FLUSH",
"caches": [
"projects",
"project_list"
diff --git a/VERSION b/VERSION
index c0b2c6d..b05afb0 100644
--- a/VERSION
+++ b/VERSION
@@ -2,4 +2,4 @@
# Used by :api_install and :api_deploy targets
# when talking to the destination repository.
#
-GERRIT_VERSION = '2.10-rc0'
+GERRIT_VERSION = '2.11-SNAPSHOT'
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 3c26720..4549c47 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -8,8 +8,9 @@
'//gerrit-launcher:launcher',
'//gerrit-lucene:lucene',
'//gerrit-httpd:httpd',
- '//gerrit-pgm:init-base',
+ '//gerrit-pgm:init',
'//gerrit-pgm:pgm',
+ '//gerrit-pgm:util',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-server:testutil',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 2b01a22..598570da 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -27,11 +27,16 @@
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ListChangesOption;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.Util;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gson.Gson;
import com.google.gwtorm.server.SchemaFactory;
@@ -77,6 +82,12 @@
@Inject
protected PushOneCommit.Factory pushFactory;
+ @Inject
+ protected MetaDataUpdate.Server metaDataUpdateFactory;
+
+ @Inject
+ protected ProjectCache projectCache;
+
protected Git git;
protected GerritServer server;
protected TestAccount admin;
@@ -218,4 +229,29 @@
.id(r.getChangeId())
.current();
}
+
+ protected void allow(String permission, AccountGroup.UUID id, String ref)
+ throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg, permission, id, ref);
+ saveProjectConfig(project, cfg);
+ }
+
+ protected void deny(String permission, AccountGroup.UUID id, String ref)
+ throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.deny(cfg, permission, id, ref);
+ saveProjectConfig(project, cfg);
+ }
+
+ protected void saveProjectConfig(Project.NameKey p, ProjectConfig cfg)
+ throws Exception {
+ MetaDataUpdate md = metaDataUpdateFactory.create(p);
+ try {
+ cfg.commit(md);
+ } finally {
+ md.close();
+ }
+ projectCache.evict(cfg.getProject());
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index 0fc053e..a9a9087 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -14,12 +14,6 @@
package com.google.gerrit.acceptance;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -38,6 +32,12 @@
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.KeyPair;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+
+import javax.inject.Inject;
+
public class AccountCreator {
private SchemaFactory<ReviewDb> reviewDbProvider;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
index 3234ffd..3371eb2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
@@ -100,6 +100,10 @@
b.append("\"");
}
s.exec(b.toString());
+ if (s.hasError()) {
+ throw new IllegalStateException(
+ "gerrit create-project returned error: " + s.getError());
+ }
}
public static Git cloneProject(String url) throws GitAPIException, IOException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 45befd0..bf4918c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -31,6 +31,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
public class RestSession extends HttpSession {
@@ -91,12 +92,14 @@
}
- public static RawInput newRawInput(final String content) throws IOException {
- Preconditions.checkNotNull(content);
- Preconditions.checkArgument(!content.isEmpty());
- return new RawInput() {
- byte bytes[] = content.getBytes("UTF-8");
+ public static RawInput newRawInput(String content) throws IOException {
+ return newRawInput(content.getBytes(StandardCharsets.UTF_8));
+ }
+ public static RawInput newRawInput(final byte[] bytes) throws IOException {
+ Preconditions.checkNotNull(bytes);
+ Preconditions.checkArgument(bytes.length > 0);
+ return new RawInput() {
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
index dc648fc..701b337 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -14,18 +14,19 @@
package com.google.gerrit.acceptance;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.util.Scanner;
+import static com.google.common.base.Preconditions.checkState;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
-public class SshSession {
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.Scanner;
+public class SshSession {
private final InetSocketAddress addr;
private final TestAccount account;
private Session session;
@@ -36,6 +37,10 @@
this.account = account;
}
+ public void open() throws JSchException {
+ getSession();
+ }
+
@SuppressWarnings("resource")
public String exec(String command) throws JSchException, IOException {
ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
@@ -86,6 +91,7 @@
}
public String getUrl() {
+ checkState(session != null, "session must be opened");
StringBuilder b = new StringBuilder();
b.append("ssh://");
b.append(session.getUserName());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
index 31ed136..bd5f19f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -16,12 +16,12 @@
import com.google.gerrit.reviewdb.client.Account;
-import java.io.ByteArrayOutputStream;
-
import com.jcraft.jsch.KeyPair;
import org.eclipse.jgit.lib.PersonIdent;
+import java.io.ByteArrayOutputStream;
+
public class TestAccount {
public final Account.Id id;
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 c79b198..6a58933 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
@@ -26,7 +26,6 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
-import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -72,7 +71,7 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.abandon();
}
@@ -81,10 +80,10 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.abandon();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.restore();
}
@@ -93,15 +92,15 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.review(ReviewInput.approve());
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.submit();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revert();
}
@@ -111,12 +110,14 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.rebase();
}
- private static Set<Account.Id> getReviewers(ChangeInfo ci) {
+ private Set<Account.Id> getReviewers(String changeId)
+ throws RestApiException {
+ ChangeInfo ci = gApi.changes().id(changeId).get();
Set<Account.Id> result = Sets.newHashSet();
for (LabelInfo li : ci.labels.values()) {
for (ApprovalInfo ai : li.all) {
@@ -132,30 +133,34 @@
PushOneCommit.Result r = createChange();
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
- ChangeApi cApi = gApi.changes().id("p~master~" + r.getChangeId());
- cApi.addReviewer(in);
- assertEquals(ImmutableSet.of(user.id), getReviewers(cApi.get()));
+ gApi.changes()
+ .id(r.getChangeId())
+ .addReviewer(in);
+ assertEquals(ImmutableSet.of(user.id), getReviewers(r.getChangeId()));
}
@Test
public void addReviewerToClosedChange() throws GitAPIException,
IOException, RestApiException {
PushOneCommit.Result r = createChange();
- ChangeApi cApi = gApi.changes().id("p~master~" + r.getChangeId());
- cApi.revision(r.getCommit().name())
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
.review(ReviewInput.approve());
- cApi.revision(r.getCommit().name())
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
.submit();
- assertEquals(ImmutableSet.of(admin.getId()), getReviewers(cApi.get()));
+ assertEquals(ImmutableSet.of(admin.getId()), getReviewers(r.getChangeId()));
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.addReviewer(in);
assertEquals(ImmutableSet.of(admin.getId(), user.id),
- getReviewers(cApi.get()));
+ getReviewers(r.getChangeId()));
}
@Test
@@ -278,4 +283,24 @@
revision(r).review(ReviewInput.recommend());
assertTrue(get(r.getChangeId()).reviewed);
}
+
+ @Test
+ public void topic() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertEquals("", gApi.changes()
+ .id(r.getChangeId())
+ .topic());
+ gApi.changes()
+ .id(r.getChangeId())
+ .topic("mytopic");
+ assertEquals("mytopic", gApi.changes()
+ .id(r.getChangeId())
+ .topic());
+ gApi.changes()
+ .id(r.getChangeId())
+ .topic("");
+ assertEquals("", gApi.changes()
+ .id(r.getChangeId())
+ .topic());
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
index a10e026..9aa3917 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -14,22 +14,22 @@
package com.google.gerrit.acceptance.api.project;
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
@NoHttpd
@@ -68,8 +68,8 @@
}
@Test
- public void createBranch() throws GitAPIException,
- IOException, RestApiException {
+ public void createBranch() throws Exception {
+ allow(Permission.READ, ANONYMOUS_USERS, "refs/*");
gApi.projects()
.name(project.get())
.branch("foo")
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 9653ffa..54e8799 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -26,7 +27,6 @@
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -145,10 +145,67 @@
assertEquals(2, orig.get().messages.size());
assertTrue(cherry.get().subject.contains(in.message));
- cherry.current()
- .review(ReviewInput.approve());
- cherry.current()
- .submit();
+ cherry.current().review(ReviewInput.approve());
+ cherry.current().submit();
+ }
+
+ @Test
+ public void cherryPickIdenticalTree() throws GitAPIException,
+ IOException, RestApiException {
+ PushOneCommit.Result r = createChange();
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "foo";
+ in.message = "it goes to stable branch";
+ gApi.projects()
+ .name(project.get())
+ .branch(in.destination)
+ .create(new BranchInput());
+ ChangeApi orig = gApi.changes()
+ .id("p~master~" + r.getChangeId());
+
+ assertEquals(1, orig.get().messages.size());
+ ChangeApi cherry = orig.revision(r.getCommit().name())
+ .cherryPick(in);
+ assertEquals(2, orig.get().messages.size());
+
+ assertTrue(cherry.get().subject.contains(in.message));
+ cherry.current().review(ReviewInput.approve());
+ cherry.current().submit();
+
+ try {
+ orig.revision(r.getCommit().name()).cherryPick(in);
+ fail("Cherry-pick identical tree error expected");
+ } catch (RestApiException e) {
+ assertEquals("Cherry pick failed: identical tree", e.getMessage());
+ }
+ }
+
+ @Test
+ public void cherryPickConflict() throws GitAPIException,
+ IOException, RestApiException {
+ PushOneCommit.Result r = createChange();
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "foo";
+ in.message = "it goes to stable branch";
+ gApi.projects()
+ .name(project.get())
+ .branch(in.destination)
+ .create(new BranchInput());
+
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ PushOneCommit.FILE_NAME, "another content");
+ push.to(git, "refs/heads/foo");
+
+ ChangeApi orig = gApi.changes().id("p~master~" + r.getChangeId());
+ assertEquals(1, orig.get().messages.size());
+
+ try {
+ orig.revision(r.getCommit().name()).cherryPick(in);
+ fail("Cherry-pick merge conflict error expected");
+ } catch (RestApiException e) {
+ assertEquals("Cherry pick failed: merge conflict", e.getMessage());
+ }
}
@Test
@@ -206,12 +263,6 @@
.isEmpty());
}
- protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
- return gApi.changes()
- .id(r.getChangeId())
- .current();
- }
-
private void merge(PushOneCommit.Result r) throws Exception {
revision(r).review(ReviewInput.approve());
revision(r).submit();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
new file mode 100644
index 0000000..be6fcdc
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
@@ -0,0 +1,10 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+ srcs = ['ChangeEditIT.java'],
+ labels = ['edit'],
+ deps = [
+ '//lib/commons:codec',
+ '//lib/joda:joda-time',
+ ],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
new file mode 100644
index 0000000..b4631190
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -0,0 +1,722 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.edit;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
+import static org.apache.http.HttpStatus.SC_NO_CONTENT;
+import static org.apache.http.HttpStatus.SC_OK;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeEdits.Post;
+import com.google.gerrit.server.change.ChangeEdits.Put;
+import com.google.gerrit.server.change.FileContentUtil;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.util.Providers;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.StringUtils;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ChangeEditIT extends AbstractDaemonTest {
+
+ private final static String FILE_NAME = "foo";
+ private final static String FILE_NAME2 = "foo2";
+ private final static byte[] CONTENT_OLD = "bar".getBytes(UTF_8);
+ private final static byte[] CONTENT_NEW = "baz".getBytes(UTF_8);
+ private final static byte[] CONTENT_NEW2 = "qux".getBytes(UTF_8);
+
+ @Inject
+ private SchemaFactory<ReviewDb> reviewDbProvider;
+
+ @Inject
+ private IdentifiedUser.GenericFactory identifiedUserFactory;
+
+ @Inject
+ private PushOneCommit.Factory pushFactory;
+
+ @Inject
+ ChangeEditUtil editUtil;
+
+ @Inject
+ private ChangeEditModifier modifier;
+
+ @Inject
+ private FileContentUtil fileUtil;
+
+ @Inject
+ private AcceptanceTestRequestScope atrScope;
+
+ private ReviewDb db;
+ private Change change;
+ private String changeId;
+ private Change change2;
+ private PatchSet ps;
+ private PatchSet ps2;
+ RestSession session;
+
+ @Before
+ public void setUp() throws Exception {
+ db = reviewDbProvider.open();
+ changeId = newChange(git, admin.getIdent());
+ ps = getCurrentPatchSet(changeId);
+ amendChange(git, admin.getIdent(), changeId);
+ change = getChange(changeId);
+ assertNotNull(ps);
+ String changeId2 = newChange2(git, admin.getIdent());
+ change2 = getChange(changeId2);
+ assertNotNull(change2);
+ ps2 = getCurrentPatchSet(changeId2);
+ assertNotNull(ps2);
+ session = new RestSession(server, admin);
+ atrScope.set(atrScope.newContext(reviewDbProvider, sshSession,
+ identifiedUserFactory.create(Providers.of(db), admin.getId())));
+ final long clockStepMs = MILLISECONDS.convert(1, SECONDS);
+ final AtomicLong clockMs = new AtomicLong(
+ new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
+ DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
+ @Override
+ public long getMillis() {
+ return clockMs.getAndAdd(clockStepMs);
+ }
+ });
+ }
+
+ @After
+ public void cleanup() {
+ DateTimeUtils.setCurrentMillisSystem();
+ db.close();
+ }
+
+ @Test
+ public void deleteEdit() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ editUtil.delete(editUtil.byChange(change).get());
+ assertFalse(editUtil.byChange(change).isPresent());
+ }
+
+ @Test
+ public void publishEdit() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ getCurrentPatchSet(changeId)));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_NEW2));
+ editUtil.publish(editUtil.byChange(change).get());
+ assertFalse(editUtil.byChange(change).isPresent());
+ }
+
+ @Test
+ public void publishEditRest() throws Exception {
+ PatchSet oldCurrentPatchSet = getCurrentPatchSet(changeId);
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ oldCurrentPatchSet));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ RestResponse r = session.post(urlPublish());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ edit = editUtil.byChange(change);
+ assertFalse(edit.isPresent());
+ PatchSet newCurrentPatchSet = getCurrentPatchSet(changeId);
+ assertFalse(oldCurrentPatchSet.getId().equals(newCurrentPatchSet.getId()));
+ }
+
+ @Test
+ public void deleteEditRest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ RestResponse r = session.delete(urlEdit());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ edit = editUtil.byChange(change);
+ assertFalse(edit.isPresent());
+ }
+
+ @Test
+ public void rebaseEdit() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ ChangeEdit edit = editUtil.byChange(change).get();
+ PatchSet current = getCurrentPatchSet(changeId);
+ assertEquals(current.getPatchSetId() - 1,
+ edit.getBasePatchSet().getPatchSetId());
+ Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ modifier.rebaseEdit(edit, current);
+ edit = editUtil.byChange(change).get();
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.getChange().getProject(),
+ edit.getRevision().get(), FILE_NAME)));
+ assertArrayEquals(CONTENT_NEW2,
+ toBytes(fileUtil.getContent(edit.getChange().getProject(),
+ edit.getRevision().get(), FILE_NAME2)));
+ assertEquals(current.getPatchSetId(),
+ edit.getBasePatchSet().getPatchSetId());
+ Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ assertFalse(beforeRebase.equals(afterRebase));
+ }
+
+ @Test
+ public void rebaseEditRest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ ChangeEdit edit = editUtil.byChange(change).get();
+ PatchSet current = getCurrentPatchSet(changeId);
+ assertEquals(current.getPatchSetId() - 1,
+ edit.getBasePatchSet().getPatchSetId());
+ Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ RestResponse r = session.post(urlRebase());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ edit = editUtil.byChange(change).get();
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.getChange().getProject(),
+ edit.getRevision().get(), FILE_NAME)));
+ assertArrayEquals(CONTENT_NEW2,
+ toBytes(fileUtil.getContent(edit.getChange().getProject(),
+ edit.getRevision().get(), FILE_NAME2)));
+ assertEquals(current.getPatchSetId(),
+ edit.getBasePatchSet().getPatchSetId());
+ Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ assertFalse(beforeRebase.equals(afterRebase));
+ }
+
+ @Test
+ public void updateExistingFile() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ editUtil.delete(edit.get());
+ edit = editUtil.byChange(change);
+ assertFalse(edit.isPresent());
+ }
+
+ @Test
+ public void retrieveEdit() throws Exception {
+ RestResponse r = session.get(urlEdit());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ edit = editUtil.byChange(change);
+ EditInfo info = toEditInfo(false);
+ assertEquals(edit.get().getRevision().get(), info.commit.commit);
+ assertEquals(1, info.commit.parents.size());
+
+ edit = editUtil.byChange(change);
+ editUtil.delete(edit.get());
+
+ r = session.get(urlEdit());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ }
+
+ @Test
+ public void retrieveFilesInEdit() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW));
+
+ EditInfo info = toEditInfo(true);
+ assertEquals(2, info.files.size());
+ List<String> l = Lists.newArrayList(info.files.keySet());
+ assertEquals("/COMMIT_MSG", l.get(0));
+ assertEquals("foo", l.get(1));
+ }
+
+ @Test
+ public void deleteExistingFile() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.deleteFile(
+ edit.get(),
+ FILE_NAME));
+ edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void createEditByDeletingExistingFileRest() throws Exception {
+ RestResponse r = session.delete(urlEditFile());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void deletingNonExistingEditRest() throws Exception {
+ RestResponse r = session.delete(urlEdit());
+ assertEquals(SC_BAD_REQUEST, r.getStatusCode());
+ }
+
+ @Test
+ public void deleteExistingFileRest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(SC_NO_CONTENT, session.delete(urlEditFile()).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void restoreDeletedFileInEdit() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.deleteFile(
+ edit.get(),
+ FILE_NAME));
+ edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.restoreFile(
+ edit.get(),
+ FILE_NAME));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_OLD,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void restoreDeletedFileInPatchSet() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change2,
+ ps2));
+ Optional<ChangeEdit> edit = editUtil.byChange(change2);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.restoreFile(
+ edit.get(),
+ FILE_NAME));
+ edit = editUtil.byChange(change2);
+ assertArrayEquals(CONTENT_OLD,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void restoreDeletedFileInPatchSetRest() throws Exception {
+ Post.Input in = new Post.Input();
+ in.restorePath = FILE_NAME;
+ assertEquals(SC_NO_CONTENT, session.post(urlEdit2(),
+ in).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change2);
+ assertArrayEquals(CONTENT_OLD,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void amendExistingFile() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW2));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW2,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void createAndChangeEditInOneRequestRest() throws Exception {
+ Put.Input in = new Put.Input();
+ in.content = RestSession.newRawInput(CONTENT_NEW);
+ assertEquals(SC_NO_CONTENT, session.putRaw(urlEditFile(),
+ in.content).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ in.content = RestSession.newRawInput(CONTENT_NEW2);
+ assertEquals(SC_NO_CONTENT, session.putRaw(urlEditFile(),
+ in.content).getStatusCode());
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW2,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void changeEditRest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Put.Input in = new Put.Input();
+ in.content = RestSession.newRawInput(CONTENT_NEW);
+ assertEquals(SC_NO_CONTENT, session.putRaw(urlEditFile(),
+ in.content).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void emptyPutRequest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(SC_NO_CONTENT, session.put(urlEditFile()).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertArrayEquals("".getBytes(),
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void createEmptyEditRest() throws Exception {
+ assertEquals(SC_NO_CONTENT, session.post(urlEdit()).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_OLD,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void getFileContentRest() throws Exception {
+ Put.Input in = new Put.Input();
+ in.content = RestSession.newRawInput(CONTENT_NEW);
+ assertEquals(SC_NO_CONTENT, session.putRaw(urlEditFile(),
+ in.content).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME,
+ CONTENT_NEW2));
+ edit = editUtil.byChange(change);
+ RestResponse r = session.get(urlEditFile());
+ assertEquals(SC_OK, r.getStatusCode());
+ String content = r.getEntityContent();
+ assertEquals(StringUtils.newStringUtf8(CONTENT_NEW2),
+ StringUtils.newStringUtf8(Base64.decodeBase64(content)));
+ }
+
+ @Test
+ public void getFileNotFoundRest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(SC_NO_CONTENT, session.delete(urlEditFile()).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ RestResponse r = session.get(urlEditFile());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ }
+
+ @Test
+ public void addNewFile() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME2,
+ CONTENT_NEW));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME2)));
+ }
+
+ @Test
+ public void addNewFileAndAmend() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME2,
+ CONTENT_NEW));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME2)));
+ assertEquals(RefUpdate.Result.FORCED,
+ modifier.modifyFile(
+ edit.get(),
+ FILE_NAME2,
+ CONTENT_NEW2));
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW2,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME2)));
+ }
+
+ @Test
+ public void writeNoChanges() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ try {
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ CONTENT_OLD);
+ fail();
+ } catch (InvalidChangeOperationException e) {
+ assertEquals("no changes were made", e.getMessage());
+ }
+ }
+
+ private String newChange(Git git, PersonIdent ident) throws Exception {
+ PushOneCommit push =
+ pushFactory.create(db, ident, PushOneCommit.SUBJECT, FILE_NAME,
+ new String(CONTENT_OLD));
+ return push.to(git, "refs/for/master").getChangeId();
+ }
+
+ private String amendChange(Git git, PersonIdent ident, String changeId) throws Exception {
+ PushOneCommit push =
+ pushFactory.create(db, ident, PushOneCommit.SUBJECT, FILE_NAME2,
+ new String(CONTENT_NEW2), changeId);
+ return push.to(git, "refs/for/master").getChangeId();
+ }
+
+ private String newChange2(Git git, PersonIdent ident) throws Exception {
+ PushOneCommit push =
+ pushFactory.create(db, ident, PushOneCommit.SUBJECT, FILE_NAME,
+ new String(CONTENT_OLD));
+ return push.rm(git, "refs/for/master").getChangeId();
+ }
+
+ private Change getChange(String changeId) throws Exception {
+ return Iterables.getOnlyElement(db.changes()
+ .byKey(new Change.Key(changeId)));
+ }
+
+ private PatchSet getCurrentPatchSet(String changeId) throws Exception {
+ return db.patchSets()
+ .get(getChange(changeId).currentPatchSetId());
+ }
+
+ private static byte[] toBytes(BinaryResult content) throws Exception {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ content.writeTo(os);
+ return os.toByteArray();
+ }
+
+ private String urlEdit() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/edit";
+ }
+
+ private String urlEdit2() {
+ return "/changes/"
+ + change2.getChangeId()
+ + "/edit/";
+ }
+
+ private String urlEditFile() {
+ return urlEdit()
+ + "/"
+ + FILE_NAME;
+ }
+
+ private String urlGetFiles() {
+ return urlEdit()
+ + "?list";
+ }
+
+ private String urlPublish() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/publish_edit";
+ }
+
+ private String urlRebase() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/rebase_edit";
+ }
+
+ private EditInfo toEditInfo(boolean files) throws IOException {
+ RestResponse r = session.get(files ? urlGetFiles() : urlEdit());
+ assertEquals(SC_OK, r.getStatusCode());
+ return newGson().fromJson(r.getReader(), EditInfo.class);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
index 3ead2a1..bce2f65 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
@@ -1,7 +1,11 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
- srcs = ['DraftChangeBlockedIT.java', 'SubmitOnPushIT.java'],
+ srcs = [
+ 'DraftChangeBlockedIT.java',
+ 'SubmitOnPushIT.java',
+ 'VisibleRefFilterIT.java',
+ ],
labels = ['git'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
index 465befd..71f008a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
@@ -15,13 +15,18 @@
package com.google.gerrit.acceptance.git;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.junit.Before;
import java.io.IOException;
+import java.net.URISyntaxException;
public class HttpPushForReviewIT extends AbstractPushForReview {
@Before
- public void selectHttpUrl() throws GitAPIException, IOException {
+ public void selectHttpUrl() throws GitAPIException, IOException, URISyntaxException {
+ CredentialsProvider.setDefault(new UsernamePasswordCredentialsProvider(
+ admin.username, admin.httpPassword));
selectProtocol(Protocol.HTTP);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index a73169d..917c283 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -14,8 +14,6 @@
package com.google.gerrit.acceptance.git;
-import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.GitUtil.createProject;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -24,7 +22,6 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
@@ -33,7 +30,6 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.GroupCache;
@@ -44,10 +40,8 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
-import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -57,18 +51,12 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@NoHttpd
public class SubmitOnPushIT extends AbstractDaemonTest {
-
- @Inject
- private SchemaFactory<ReviewDb> reviewDbProvider;
-
@Inject
private GitRepositoryManager repoManager;
@@ -93,26 +81,6 @@
@Inject
private PushOneCommit.Factory pushFactory;
- private Project.NameKey project;
- private Git git;
- private ReviewDb db;
-
- @Before
- public void setUp() throws Exception {
- project = new Project.NameKey("p");
- SshSession sshSession = new SshSession(server, admin);
- createProject(sshSession, project.get());
- git = cloneProject(sshSession.getUrl() + "/" + project.get());
- sshSession.close();
-
- db = reviewDbProvider.open();
- }
-
- @After
- public void cleanup() {
- db.close();
- }
-
@Test
public void submitOnPush() throws GitAPIException, OrmException,
IOException, ConfigInvalidException {
@@ -129,6 +97,7 @@
IOException, ConfigInvalidException {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
final String tag = "v1.0";
PushOneCommit push = pushFactory.create(db, admin.getIdent());
push.setTag(tag);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
new file mode 100644
index 0000000..c06b0ff
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -0,0 +1,212 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.git;
+
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.project.Util;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(ConfigSuite.class)
+public class VisibleRefFilterIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config noteDbWriteEnabled() {
+ Config cfg = new Config();
+ cfg.setBoolean("notedb", "changes", "write", true);
+ return cfg;
+ }
+
+ @Inject
+ private AllProjectsName allProjects;
+
+ @Inject
+ private NotesMigration notesMigration;
+
+ @Inject
+ private GitRepositoryManager repoManager;
+
+ @Inject
+ private GroupCache groupCache;
+
+ private AccountGroup.UUID admins;
+
+ @Before
+ public void setUp() throws Exception {
+ admins = groupCache.get(new AccountGroup.NameKey("Administrators"))
+ .getGroupUUID();
+ setUpChanges();
+ setUpPermissions();
+ }
+
+ private void setUpPermissions() throws Exception {
+ ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
+ for (AccessSection sec : pc.getAccessSections()) {
+ sec.removePermission(Permission.READ);
+ }
+ saveProjectConfig(allProjects, pc);
+ }
+
+ private void setUpChanges() throws Exception {
+ gApi.projects()
+ .name(project.get())
+ .branch("branch")
+ .create(new BranchInput());
+
+ allow(Permission.SUBMIT, admins, "refs/for/refs/heads/*");
+ PushOneCommit.Result mr = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master%submit");
+ mr.assertOkStatus();
+ PushOneCommit.Result br = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/branch%submit");
+ br.assertOkStatus();
+
+ Repository repo = repoManager.openRepository(project);
+ try {
+ // master-tag -> master
+ RefUpdate mtu = repo.updateRef("refs/tags/master-tag");
+ mtu.setExpectedOldObjectId(ObjectId.zeroId());
+ mtu.setNewObjectId(repo.getRef("refs/heads/master").getObjectId());
+ assertEquals(RefUpdate.Result.NEW, mtu.update());
+
+ // branch-tag -> branch
+ RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(repo.getRef("refs/heads/branch").getObjectId());
+ assertEquals(RefUpdate.Result.NEW, btu.update());
+ } finally {
+ repo.close();
+ }
+ }
+
+ @Test
+ public void allRefsVisibleNoRefsMetaConfig() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg, Permission.READ, REGISTERED_USERS, "refs/*");
+ Util.allow(cfg, Permission.READ, admins, "refs/meta/config");
+ Util.doNotInherit(cfg, Permission.READ, "refs/meta/config");
+ saveProjectConfig(project, cfg);
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/heads/master",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void allRefsVisibleWithRefsMetaConfig() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/*");
+ allow(Permission.READ, REGISTERED_USERS, "refs/meta/config");
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/heads/master",
+ "refs/meta/config",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void subsetOfBranchesVisibleIncludingHead() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/heads/master",
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void subsetOfBranchesVisibleNotIncludingHead() throws Exception {
+ deny(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
+
+ assertRefs(
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // master branch is not visible but master-tag is reachable from branch
+ // (since PushOneCommit always bases changes on each other).
+ "refs/tags/master-tag");
+ }
+
+ /**
+ * Assert that refs seen by a non-admin user match expected.
+ *
+ * @param expected expected refs, in order. If notedb is disabled by the
+ * configuration, any notedb refs (i.e. ending in "/meta") are removed
+ * from the expected list before comparing to the actual results.
+ * @throws Exception
+ */
+ private void assertRefs(String... expected) throws Exception {
+ String out = sshSession.exec(String.format(
+ "gerrit ls-user-refs -p %s -u %s",
+ project.get(), user.getId().get()));
+ assertFalse(sshSession.getError(), sshSession.hasError());
+
+ List<String> filtered = new ArrayList<>(expected.length);
+ for (String r : expected) {
+ if (notesMigration.writeChanges() || !r.endsWith(RefNames.META_SUFFIX)) {
+ filtered.add(r);
+ }
+ }
+
+ Splitter s = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings();
+ assertEquals(filtered, Ordering.natural().sortedCopy(s.split(out)));
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
index adbf10a..7a2f569 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
@@ -24,6 +24,7 @@
public boolean flushCaches;
public boolean generateHttpPassword;
public boolean killTask;
+ public boolean modifyAccount;
public boolean priority;
public QueryLimit queryLimit;
public boolean runAs;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index acf50db..aa0412c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -25,6 +25,7 @@
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.testutil.ConfigSuite;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -48,10 +49,7 @@
@ConfigSuite.Config
public static Config noteDbEnabled() {
- Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "changeMessages", "read", true);
- return cfg;
+ return NotesMigration.allEnabledConfig();
}
@Before
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
index 83efd30..42043cc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
@@ -15,7 +15,6 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.common.data.Permission.LABEL;
import static org.junit.Assert.assertEquals;
@@ -63,8 +62,7 @@
sessionOwner = new RestSession(server, user);
SshSession sshSession = new SshSession(server, user);
initSsh(user);
- // need to initialize intern session
- createProject(sshSession, "foo");
+ sshSession.open();
git = cloneProject(sshSession.getUrl() + "/" + project.get());
sshSession.close();
user2 = accounts.user2();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index 12d002e..eb46b5d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -19,8 +19,8 @@
import static org.junit.Assert.assertSame;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gwtorm.server.OrmException;
import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gwtorm.server.OrmException;
import com.jcraft.jsch.JSchException;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
index d2174bc..deb8066 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
@@ -17,7 +17,6 @@
import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH;
import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH_ALL;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.allow;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -26,13 +25,14 @@
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.config.PostCaches;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
import org.apache.http.HttpStatus;
@@ -140,8 +140,8 @@
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
AccountGroup.UUID registeredUsers =
SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
- allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
saveProjectConfig(cfg);
RestResponse r = userSession.post("/config/server/caches/",
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
index aa8d7ba..31ffd7b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
@@ -15,7 +15,6 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.allow;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -30,6 +29,7 @@
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
import org.apache.http.HttpStatus;
@@ -92,8 +92,8 @@
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
AccountGroup.UUID registeredUsers =
SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
- allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
saveProjectConfig(cfg);
RestResponse r = userSession.post("/config/server/caches/accounts/flush");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
index 0d229ca..f60b5dd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
@@ -14,6 +14,7 @@
package com.google.gerrit.acceptance.rest.group;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.Sets;
@@ -47,6 +48,7 @@
public void defaultGroupsCreated_ssh() throws JSchException, IOException {
SshSession session = new SshSession(server, admin);
String result = session.exec("gerrit ls-groups");
+ assertFalse(session.getError(), session.hasError());
assertTrue(result.contains("Administrators"));
assertTrue(result.contains("Non-Interactive Users"));
session.close();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 23cd278..a9bba75 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -16,7 +16,6 @@
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.allow;
import static com.google.gerrit.server.project.Util.block;
import static org.junit.Assert.assertEquals;
@@ -24,28 +23,16 @@
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import org.apache.http.HttpStatus;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class CreateBranchIT extends AbstractDaemonTest {
@Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
private AllProjectsName allProjects;
private Branch.NameKey branch;
@@ -56,7 +43,7 @@
}
@Test
- public void createBranch_Forbidden() throws IOException {
+ public void createBranch_Forbidden() throws Exception {
RestResponse r =
userSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
@@ -64,7 +51,7 @@
}
@Test
- public void createBranchByAdmin() throws IOException {
+ public void createBranchByAdmin() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
@@ -77,7 +64,7 @@
}
@Test
- public void branchAlreadyExists_Conflict() throws IOException {
+ public void branchAlreadyExists_Conflict() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
@@ -90,8 +77,7 @@
}
@Test
- public void createBranchByProjectOwner() throws IOException,
- ConfigInvalidException {
+ public void createBranchByProjectOwner() throws Exception {
grantOwner();
RestResponse r =
@@ -106,8 +92,7 @@
}
@Test
- public void createBranchByAdminCreateReferenceBlocked() throws IOException,
- ConfigInvalidException {
+ public void createBranchByAdminCreateReferenceBlocked() throws Exception {
blockCreateReference();
RestResponse r =
adminSession.put("/projects/" + project.get()
@@ -122,7 +107,7 @@
@Test
public void createBranchByProjectOwnerCreateReferenceBlocked_Forbidden()
- throws IOException, ConfigInvalidException {
+ throws Exception {
grantOwner();
blockCreateReference();
RestResponse r =
@@ -131,27 +116,13 @@
assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
}
- private void blockCreateReference() throws IOException, ConfigInvalidException {
+ private void blockCreateReference() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
block(cfg, Permission.CREATE, ANONYMOUS_USERS, "refs/*");
saveProjectConfig(allProjects, cfg);
- projectCache.evict(cfg.getProject());
}
- private void grantOwner() throws IOException, ConfigInvalidException {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- allow(cfg, Permission.OWNER, REGISTERED_USERS, "refs/*");
- saveProjectConfig(project, cfg);
- projectCache.evict(cfg.getProject());
- }
-
- private void saveProjectConfig(Project.NameKey p, ProjectConfig cfg)
- throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
+ private void grantOwner() throws Exception {
+ allow(Permission.OWNER, REGISTERED_USERS, "refs/*");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index cdf671d..983198a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -25,7 +25,6 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.common.InheritableBoolean;
import com.google.gerrit.extensions.common.ProjectInfo;
@@ -74,8 +73,7 @@
@Test
public void testCreateProjectApi() throws RestApiException, IOException {
final String newProjectName = "newProject";
- ProjectApi projectApi = gApi.projects().name(newProjectName).create();
- ProjectInfo p = projectApi.get();
+ ProjectInfo p = gApi.projects().name(newProjectName).create().get();
assertEquals(newProjectName, p.name);
ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
assertNotNull(projectState);
@@ -252,7 +250,6 @@
tw.reset();
}
} finally {
- tw.release();
rw.release();
repo.close();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index 0d6ec5b..9a5aaa3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -16,7 +16,6 @@
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.allow;
import static com.google.gerrit.server.project.Util.block;
import static org.junit.Assert.assertEquals;
@@ -24,28 +23,17 @@
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import org.apache.http.HttpStatus;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class DeleteBranchIT extends AbstractDaemonTest {
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
@Inject
private AllProjectsName allProjects;
@@ -82,8 +70,7 @@
}
@Test
- public void deleteBranchByProjectOwner() throws IOException,
- ConfigInvalidException {
+ public void deleteBranchByProjectOwner() throws Exception {
grantOwner();
RestResponse r =
@@ -99,8 +86,7 @@
}
@Test
- public void deleteBranchByAdminForcePushBlocked() throws IOException,
- ConfigInvalidException {
+ public void deleteBranchByAdminForcePushBlocked() throws Exception {
blockForcePush();
RestResponse r =
adminSession.delete("/projects/" + project.get()
@@ -116,7 +102,7 @@
@Test
public void deleteBranchByProjectOwnerForcePushBlocked_Forbidden()
- throws IOException, ConfigInvalidException {
+ throws Exception {
grantOwner();
blockForcePush();
RestResponse r =
@@ -126,26 +112,13 @@
r.consume();
}
- private void blockForcePush() throws IOException, ConfigInvalidException {
+ private void blockForcePush() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
block(cfg, Permission.PUSH, ANONYMOUS_USERS, "refs/heads/*").setForce(true);
saveProjectConfig(allProjects, cfg);
- projectCache.evict(cfg.getProject());
}
- private void grantOwner() throws IOException, ConfigInvalidException {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- allow(cfg, Permission.OWNER, REGISTERED_USERS, "refs/*");
- saveProjectConfig(project, cfg);
- projectCache.evict(cfg.getProject());
- }
-
- private void saveProjectConfig(Project.NameKey p, ProjectConfig cfg) throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
+ private void grantOwner() throws Exception {
+ allow(Permission.OWNER, REGISTERED_USERS, "refs/*");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
index 2aacf20..d4a5b3d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -14,98 +14,147 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ListBranches.BranchInfo;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import org.apache.http.HttpStatus;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class GetCommitIT extends AbstractDaemonTest {
-
- @Inject
- private ProjectCache projectCache;
-
@Inject
private AllProjectsName allProjects;
@Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
+ private GitRepositoryManager repoManager;
- @Test
- public void getCommit() throws IOException {
- RestResponse r =
- adminSession.get("/projects/" + project.get() + "/branches/"
- + IdString.fromDecoded(RefNames.REFS_CONFIG).encoded());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- BranchInfo branchInfo =
- newGson().fromJson(r.getReader(), BranchInfo.class);
- r.consume();
+ @Inject
+ private ProjectCache projectCache;
- r = adminSession.get("/projects/" + project.get() + "/commits/"
- + branchInfo.revision);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- CommitInfo commitInfo =
- newGson().fromJson(r.getReader(), CommitInfo.class);
- assertEquals(branchInfo.revision, commitInfo.commit);
- assertEquals("Created project", commitInfo.subject);
- assertEquals("Created project\n", commitInfo.message);
- assertNotNull(commitInfo.author);
- assertEquals("Administrator", commitInfo.author.name);
- assertNotNull(commitInfo.committer);
- assertEquals("Gerrit Code Review", commitInfo.committer.name);
- assertTrue(commitInfo.parents.isEmpty());
- }
+ private TestRepository<Repository> repo;
- @Test
- public void getNonExistingCommit_NotFound() throws IOException {
- RestResponse r = adminSession.get("/projects/" + project.get() + "/commits/"
- + ObjectId.zeroId().name());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
- }
+ @Before
+ public void setUp() throws Exception {
+ repo = new TestRepository<>(repoManager.openRepository(project));
- @Test
- public void getNonVisibleCommit_NotFound() throws IOException {
- RestResponse r =
- adminSession.get("/projects/" + project.get() + "/branches/"
- + IdString.fromDecoded(RefNames.REFS_CONFIG).encoded());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- BranchInfo branchInfo =
- newGson().fromJson(r.getReader(), BranchInfo.class);
- r.consume();
-
- ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
- cfg.getAccessSection("refs/*", false).removePermission(Permission.READ);
- saveProjectConfig(cfg);
- projectCache.evict(cfg.getProject());
-
- r = adminSession.get("/projects/" + project.get() + "/commits/"
- + branchInfo.revision);
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
- }
-
- private void saveProjectConfig(ProjectConfig cfg) throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
- try {
- cfg.commit(md);
- } finally {
- md.close();
+ ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
+ for (AccessSection sec : pc.getAccessSections()) {
+ sec.removePermission(Permission.READ);
}
+ saveProjectConfig(allProjects, pc);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (repo != null) {
+ repo.getRepository().close();
+ }
+ }
+
+ @Test
+ public void getNonExistingCommit_NotFound() throws Exception {
+ assertNotFound(
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ }
+
+ @Test
+ public void getMergedCommit_Found() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
+ RevCommit commit = repo.parseBody(repo.branch("master")
+ .commit()
+ .message("Create\n\nNew commit\n")
+ .create());
+
+ CommitInfo info = getCommit(commit);
+ assertEquals(commit.name(), info.commit);
+ assertEquals("Create", info.subject);
+ assertEquals("Create\n\nNew commit\n", info.message);
+ assertEquals("J. Author", info.author.name);
+ assertEquals("jauthor@example.com", info.author.email);
+ assertEquals("J. Committer", info.committer.name);
+ assertEquals("jcommitter@example.com", info.committer.email);
+
+ CommitInfo parent = Iterables.getOnlyElement(info.parents);
+ assertEquals(commit.getParent(0).name(), parent.commit);
+ assertEquals("Initial empty repository", parent.subject);
+ assertNull(parent.message);
+ assertNull(parent.author);
+ assertNull(parent.committer);
+ }
+
+ @Test
+ public void getMergedCommit_NotFound() throws Exception {
+ RevCommit commit = repo.parseBody(repo.branch("master")
+ .commit()
+ .message("Create\n\nNew commit\n")
+ .create());
+ assertNotFound(commit);
+ }
+
+ @Test
+ public void getOpenChange_Found() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
+ PushOneCommit.Result r = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master");
+ r.assertOkStatus();
+
+ CommitInfo info = getCommit(r.getCommitId());
+ assertEquals(r.getCommitId().name(), info.commit);
+ assertEquals("test commit", info.subject);
+ assertEquals("test commit\n\nChange-Id: " + r.getChangeId() + "\n",
+ info.message);
+ assertEquals("admin", info.author.name);
+ assertEquals("admin@example.com", info.author.email);
+ assertEquals("admin", info.committer.name);
+ assertEquals("admin@example.com", info.committer.email);
+
+ CommitInfo parent = Iterables.getOnlyElement(info.parents);
+ assertEquals(r.getCommit().getParent(0).name(), parent.commit);
+ assertEquals("Initial empty repository", parent.subject);
+ assertNull(parent.message);
+ assertNull(parent.author);
+ assertNull(parent.committer);
+ }
+
+ @Test
+ public void getOpenChange_NotFound() throws Exception {
+ PushOneCommit.Result r = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master");
+ r.assertOkStatus();
+ assertNotFound(r.getCommitId());
+ }
+
+ private void assertNotFound(ObjectId id) throws Exception {
+ RestResponse r = userSession.get(
+ "/projects/" + project.get() + "/commits/" + id.name());
+ assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ }
+
+ private CommitInfo getCommit(ObjectId id) throws Exception {
+ RestResponse r = userSession.get(
+ "/projects/" + project.get() + "/commits/" + id.name());
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ CommitInfo result = newGson().fromJson(r.getReader(), CommitInfo.class);
+ r.consume();
+ return result;
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
index e6fde73..b51a4e2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -26,20 +26,14 @@
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.ListBranches.BranchInfo;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.junit.Test;
import java.io.IOException;
@@ -47,13 +41,6 @@
import java.util.List;
public class ListBranchesIT extends AbstractDaemonTest {
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
@Test
public void listBranchesOfNonExistingProject_NotFound() throws IOException {
assertEquals(HttpStatus.SC_NOT_FOUND,
@@ -61,8 +48,7 @@
}
@Test
- public void listBranchesOfNonVisibleProject_NotFound() throws IOException,
- OrmException, JSchException, ConfigInvalidException {
+ public void listBranchesOfNonVisibleProject_NotFound() throws Exception {
blockRead(project, "refs/*");
assertEquals(HttpStatus.SC_NOT_FOUND,
userSession.get("/projects/" + project.get() + "/branches").getStatusCode());
@@ -106,8 +92,7 @@
}
@Test
- public void listBranchesSomeHidden() throws IOException, GitAPIException,
- ConfigInvalidException, OrmException, JSchException {
+ public void listBranchesSomeHidden() throws Exception {
blockRead(project, "refs/heads/dev");
pushTo("refs/heads/master");
String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
@@ -123,8 +108,7 @@
}
@Test
- public void listBranchesHeadHidden() throws IOException, GitAPIException,
- ConfigInvalidException, OrmException, JSchException {
+ public void listBranchesHeadHidden() throws Exception {
blockRead(project, "refs/heads/master");
pushTo("refs/heads/master");
pushTo("refs/heads/dev");
@@ -139,12 +123,10 @@
return adminSession.get(endpoint);
}
- private void blockRead(Project.NameKey project, String ref)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
+ private void blockRead(Project.NameKey project, String ref) throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
block(cfg, Permission.READ, REGISTERED_USERS, ref);
saveProjectConfig(project, cfg);
- projectCache.evict(cfg.getProject());
}
private static List<BranchInfo> toBranchInfoList(RestResponse r)
@@ -160,13 +142,4 @@
PushOneCommit push = pushFactory.create(db, admin.getIdent());
return push.to(git, ref);
}
-
- private void saveProjectConfig(Project.NameKey p, ProjectConfig cfg) throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
- }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index 923d752..bb3d2e7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -121,6 +121,11 @@
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?p=some&r=.*").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?p=some&m=some").getStatusCode());
+
RestResponse r = GET("/projects/?p=some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
@@ -138,13 +143,22 @@
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?r=[.*some").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?r=.*&p=s").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?r=.*&m=s").getStatusCode());
+
RestResponse r = GET("/projects/?r=.*some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(Arrays.asList(projectAwesome), result.values());
- r = GET("/projects/?r=[.*some");
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ r = GET("/projects/?r=some-project$");
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ result = toProjectInfoMap(r);
+ assertProjects(Arrays.asList(someProject), result.values());
r = GET("/projects/?r=.*");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
@@ -181,6 +195,11 @@
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?m=some&r=.*").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?m=some&p=some").getStatusCode());
+
RestResponse r = GET("/projects/?m=some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
index fb830e4..3b790b1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -16,36 +16,26 @@
import static com.google.gerrit.acceptance.GitUtil.checkout;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.GitUtil.fetch;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.SshSession;
-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.config.AllProjectsNameProvider;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Config;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class ProjectLevelConfigIT extends AbstractDaemonTest {
-
- @Inject
- private SchemaFactory<ReviewDb> reviewDbProvider;
-
@Inject
private ProjectCache projectCache;
@@ -55,27 +45,10 @@
@Inject
private PushOneCommit.Factory pushFactory;
- private ReviewDb db;
- private SshSession sshSession;
- private String project;
- private Git git;
-
@Before
public void setUp() throws Exception {
- sshSession = new SshSession(server, admin);
-
- project = "p";
- createProject(sshSession, project, null, true);
- git = cloneProject(sshSession.getUrl() + "/" + project);
fetch(git, RefNames.REFS_CONFIG + ":refs/heads/config");
checkout(git, "refs/heads/config");
-
- db = reviewDbProvider.open();
- }
-
- @After
- public void cleanup() {
- db.close();
}
@Test
@@ -89,13 +62,13 @@
configName, cfg.toText());
push.to(git, RefNames.REFS_CONFIG);
- ProjectState state = projectCache.get(new Project.NameKey(project));
+ ProjectState state = projectCache.get(project);
assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
}
@Test
public void nonExistingConfig() {
- ProjectState state = projectCache.get(new Project.NameKey(project));
+ ProjectState state = projectCache.get(project);
assertEquals("", state.getConfig("test.config").get().toText());
}
@@ -125,7 +98,7 @@
configName, cfg.toText());
push.to(git, RefNames.REFS_CONFIG);
- ProjectState state = projectCache.get(new Project.NameKey(project));
+ ProjectState state = projectCache.get(project);
Config expectedCfg = new Config();
expectedCfg.setString("s1", null, "k1", "childValue1");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
new file mode 100644
index 0000000..66cd2a0
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -0,0 +1,196 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.change;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.Comment;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CommentsIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config noteDbEnabled() {
+ return NotesMigration.allEnabledConfig();
+ }
+
+ @Test
+ public void createDraft() throws GitAPIException, IOException {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Comment.Side.REVISION, 1, "comment 1");
+ addDraft(changeId, revId, comment);
+ Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
+ assertEquals(1, result.size());
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ }
+
+ @Test
+ public void postComment() throws RestApiException, Exception {
+ String file = "file";
+ String contents = "contents";
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+ "first subject", file, contents);
+ PushOneCommit.Result r = push.to(git, "refs/for/master");
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput input = new ReviewInput();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ file, Comment.Side.REVISION, 1, "comment 1");
+ input.comments = new HashMap<String, List<ReviewInput.CommentInput>>();
+ input.comments.put(comment.path, Lists.newArrayList(comment));
+ revision(r).review(input);
+ Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
+ assertTrue(!result.isEmpty());
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ }
+
+ @Test
+ public void putDraft() throws GitAPIException, IOException {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Comment.Side.REVISION, 1, "comment 1");
+ addDraft(changeId, revId, comment);
+ Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ String uuid = actual.id;
+ comment.message = "updated comment 1";
+ updateDraft(changeId, revId, comment, uuid);
+ result = getDraftComments(changeId, revId);
+ actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ }
+
+ @Test
+ public void getDraft() throws GitAPIException, IOException {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Comment.Side.REVISION, 1, "comment 1");
+ CommentInfo returned = addDraft(changeId, revId, comment);
+ CommentInfo actual = getDraftComment(changeId, revId, returned.id);
+ assertCommentInfo(comment, actual);
+ }
+
+ @Test
+ public void deleteDraft() throws IOException, GitAPIException {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Comment.Side.REVISION, 1, "comment 1");
+ CommentInfo returned = addDraft(changeId, revId, comment);
+ deleteDraft(changeId, revId, returned.id);
+ Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
+ assertTrue(drafts.isEmpty());
+ }
+
+ private CommentInfo addDraft(String changeId, String revId,
+ ReviewInput.CommentInput c) throws IOException {
+ RestResponse r = userSession.put(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts", c);
+ assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ return newGson().fromJson(r.getReader(), CommentInfo.class);
+ }
+
+ private void updateDraft(String changeId, String revId,
+ ReviewInput.CommentInput c, String uuid) throws IOException {
+ RestResponse r = userSession.put(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/" + uuid, c);
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ }
+
+ private void deleteDraft(String changeId, String revId, String uuid)
+ throws IOException {
+ RestResponse r = userSession.delete(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/" + uuid);
+ assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ }
+
+ private Map<String, List<CommentInfo>> getPublishedComments(String changeId,
+ String revId) throws IOException {
+ RestResponse r = userSession.get(
+ "/changes/" + changeId + "/revisions/" + revId + "/comments/");
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ Type mapType = new TypeToken<Map<String, List<CommentInfo>>>() {}.getType();
+ return newGson().fromJson(r.getReader(), mapType);
+ }
+
+ private Map<String, List<CommentInfo>> getDraftComments(String changeId,
+ String revId) throws IOException {
+ RestResponse r = userSession.get(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/");
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ Type mapType = new TypeToken<Map<String, List<CommentInfo>>>() {}.getType();
+ return newGson().fromJson(r.getReader(), mapType);
+ }
+
+ private CommentInfo getDraftComment(String changeId, String revId,
+ String uuid) throws IOException {
+ RestResponse r = userSession.get(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/" + uuid);
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ return newGson().fromJson(r.getReader(), CommentInfo.class);
+ }
+
+ private static void assertCommentInfo(ReviewInput.CommentInput expected,
+ CommentInfo actual) {
+ assertEquals(expected.line, actual.line);
+ assertEquals(expected.message, actual.message);
+ assertEquals(expected.inReplyTo, actual.inReplyTo);
+ if (actual.side == null) {
+ assertEquals(expected.side, Comment.Side.REVISION);
+ }
+ }
+
+ private ReviewInput.CommentInput newCommentInfo(String path,
+ Comment.Side side, int line, String message) {
+ ReviewInput.CommentInput input = new ReviewInput.CommentInput();
+ input.path = path;
+ input.side = side;
+ input.line = line;
+ input.message = message;
+ return input;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 2a20a2c..612a32f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -15,7 +15,6 @@
package com.google.gerrit.acceptance.server.project;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
-import static com.google.gerrit.server.project.Util.allow;
import static com.google.gerrit.server.project.Util.category;
import static com.google.gerrit.server.project.Util.value;
import static org.junit.Assert.assertEquals;
@@ -37,6 +36,7 @@
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
import org.junit.Before;
@@ -64,7 +64,7 @@
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
AccountGroup.UUID anonymousUsers =
SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- allow(cfg, Permission.forLabel(Q.getName()), -1, 1, anonymousUsers,
+ Util.allow(cfg, Permission.forLabel(Q.getName()), -1, 1, anonymousUsers,
"refs/heads/*");
saveProjectConfig(cfg);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
index bc311fd..08c6da9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.inject.Inject;
@@ -42,10 +43,7 @@
public class LabelTypeIT extends AbstractDaemonTest {
@ConfigSuite.Config
public static Config noteDbEnabled() {
- Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
- return cfg;
+ return NotesMigration.allEnabledConfig();
}
@Inject
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
index 74b26ba6..2ea5dec 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
@@ -2,6 +2,5 @@
acceptance_tests(
srcs = glob(['*IT.java']),
- deps = ['//gerrit-acceptance-tests:lib'],
labels = ['ssh'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java
index bfce523..1bd1b71 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java
@@ -42,7 +42,7 @@
String response =
sshSession.exec("gerrit ban-commit " + project.get() + " "
+ c.getCommit().getName());
- assertFalse(sshSession.hasError());
+ assertFalse(sshSession.getError(), sshSession.hasError());
assertFalse(response, response.toLowerCase(Locale.US).contains("error"));
PushResult pushResult = pushHead(git, "refs/heads/master", false);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index 9f859dc7..d32a58f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -76,7 +76,7 @@
String response =
sshSession.exec("gerrit gc \"" + project1.get() + "\" \""
+ project2.get() + "\"");
- assertFalse(sshSession.hasError());
+ assertFalse(sshSession.getError(), sshSession.hasError());
assertNoError(response);
gcAssert.assertHasPackFile(project1, project2);
gcAssert.assertHasNoPackFile(allProjects, project3);
@@ -86,7 +86,7 @@
@UseLocalDisk
public void testGcAll() throws JSchException, IOException {
String response = sshSession.exec("gerrit gc --all");
- assertFalse(sshSession.hasError());
+ assertFalse(sshSession.getError(), sshSession.hasError());
assertNoError(response);
gcAssert.assertHasPackFile(allProjects, project1, project2, project3);
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
index 5a7559d..54a573d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
@@ -23,8 +23,8 @@
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
+import com.google.gwtjsonrpc.common.VoidResult;
import java.util.List;
import java.util.Set;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index d9ad274..fccf3b3 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -38,6 +38,9 @@
/** Can create any account on the server. */
public static final String CREATE_ACCOUNT = "createAccount";
+ /** Can modify any account on the server. */
+ public static final String MODIFY_ACCOUNT = "modifyAccount";
+
/** Can create any group on the server. */
public static final String CREATE_GROUP = "createGroup";
@@ -106,7 +109,9 @@
NAMES_ALL.add(CREATE_PROJECT);
NAMES_ALL.add(EMAIL_REVIEWERS);
NAMES_ALL.add(FLUSH_CACHES);
+ NAMES_ALL.add(GENERATE_HTTP_PASSWORD);
NAMES_ALL.add(KILL_TASK);
+ NAMES_ALL.add(MODIFY_ACCOUNT);
NAMES_ALL.add(PRIORITY);
NAMES_ALL.add(QUERY_LIMIT);
NAMES_ALL.add(RUN_AS);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
index 4a45350..5e80ac5 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
@@ -15,12 +15,12 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.AllowCrossSiteRequest;
+import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
+import com.google.gwtjsonrpc.common.VoidResult;
import java.util.List;
diff --git a/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java b/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
index 7c662ae..816f715 100644
--- a/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
+++ b/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
@@ -14,9 +14,10 @@
package com.google.gerrit.common.data;
-import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
public class EncodePathSeparatorTest {
@Test
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index cca3ab7..a0d9455 100644
--- a/gerrit-extension-api/pom.xml
+++ b/gerrit-extension-api/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-extension-api</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Extension API</name>
<description>API for Gerrit Extensions</description>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
index 749b12a..71a93d3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
@@ -18,7 +18,27 @@
import com.google.gerrit.extensions.restapi.RestApiException;
public interface Accounts {
+ /**
+ * Look up an account by ID.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the account. Methods that
+ * mutate the account do not necessarily re-read the account. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code AccountApi} instances.
+ *
+ * @param id any identifier supported by the REST API, including numeric ID,
+ * email, or username.
+ * @return API for accessing the account.
+ * @throws RestApiException if an error occurred.
+ */
AccountApi id(String id) throws RestApiException;
+
+ /**
+ * Look up the account of the current in-scope user.
+ *
+ * @see #id(String)
+ */
AccountApi self() throws RestApiException;
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 3382b76..4d509e9 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -24,8 +24,32 @@
public interface ChangeApi {
String id();
+ /**
+ * Look up the current revision for the change.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the revision. Methods that
+ * mutate the revision do not necessarily re-read the revision. Therefore,
+ * calling a getter method on an instance after calling a mutation method on
+ * that same instance is not guaranteed to reflect the mutation. It is not
+ * recommended to store references to {@code RevisionApi} instances.
+ *
+ * @return API for accessing the revision.
+ * @throws RestApiException if an error occurred.
+ */
RevisionApi current() throws RestApiException;
+
+ /**
+ * Look up a revision of a change by number.
+ *
+ * @see #current()
+ */
RevisionApi revision(int id) throws RestApiException;
+
+ /**
+ * Look up a revision of a change by commit SHA-1.
+ *
+ * @see #current()
+ */
RevisionApi revision(String id) throws RestApiException;
void abandon() throws RestApiException;
@@ -34,9 +58,23 @@
void restore() throws RestApiException;
void restore(RestoreInput in) throws RestApiException;
+ /**
+ * Create a new change that reverts this change.
+ *
+ * @see Changes#id(int)
+ */
ChangeApi revert() throws RestApiException;
+
+ /**
+ * Create a new change that reverts this change.
+ *
+ * @see Changes#id(int)
+ */
ChangeApi revert(RevertInput in) throws RestApiException;
+ String topic() throws RestApiException;
+ void topic(String topic) throws RestApiException;
+
void addReviewer(AddReviewerInput in) throws RestApiException;
void addReviewer(String in) throws RestApiException;
@@ -103,6 +141,16 @@
}
@Override
+ public String topic() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void topic(String topic) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void addReviewer(AddReviewerInput in) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
index 201a0bd..4084946 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -24,10 +24,40 @@
import java.util.List;
public interface Changes {
+ /**
+ * Look up a change by numeric ID.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the change. Methods that
+ * mutate the change do not necessarily re-read the change. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code ChangeApi} instances.
+ *
+ * @param id change number.
+ * @return API for accessing the change.
+ * @throws RestApiException if an error occurred.
+ */
ChangeApi id(int id) throws RestApiException;
- ChangeApi id(String triplet) throws RestApiException;
+
+ /**
+ * Look up a change by string ID.
+ *
+ * @see #id(int)
+ * @param id any identifier supported by the REST API, including change
+ * number, Change-Id, or project~branch~Change-Id triplet.
+ * @return API for accessing the change.
+ * @throws RestApiException if an error occurred.
+ */
+ ChangeApi id(String id) throws RestApiException;
+
+ /**
+ * Look up a change by project, branch, and change ID.
+ *
+ * @see #id(int)
+ */
ChangeApi id(String project, String branch, String id)
throws RestApiException;
+
ChangeApi create(ChangeInfo in) throws RestApiException;
QueryRequest query();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index d013c5d..07a48a1 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -22,6 +22,19 @@
ProjectApi create() throws RestApiException;
ProjectApi create(ProjectInput in) throws RestApiException;
ProjectInfo get();
+
+ /**
+ * Look up a branch by refname.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the branch. Methods that
+ * mutate the branch do not necessarily re-read the branch. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code BranchApi} instances.
+ *
+ * @param ref branch name, with or without "refs/heads/" prefix.
+ * @return API for accessing the branch.
+ */
BranchApi branch(String ref);
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
index 9c0cfd8..736d375 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -21,6 +21,19 @@
import java.util.List;
public interface Projects {
+ /**
+ * Look up a project by name.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the project. Methods that
+ * mutate the project do not necessarily re-read the project. Therefore,
+ * calling a getter method on an instance after calling a mutation method on
+ * that same instance is not guaranteed to reflect the mutation. It is not
+ * recommended to store references to {@code ProjectApi} instances.
+ *
+ * @param name project name.
+ * @return API for accessing the project.
+ * @throws RestApiException if an error occurred.
+ */
ProjectApi name(String name) throws RestApiException;
ListRequest list();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
similarity index 63%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
index 73db6f5..4946cb9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 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.
@@ -12,14 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.extensions.common;
-import com.google.inject.BindingAnnotation;
+import java.util.Map;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface InstallPlugins {
+public class EditInfo {
+ public CommitInfo commit;
+ public Map<String, FileInfo> files;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java
new file mode 100644
index 0000000..2f615c1
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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.restapi;
+
+
+/**
+ * Optional interface for {@link RestCollection}.
+ * <p>
+ * Collections that implement this interface can accept a {@code DELETE} directly
+ * on the collection itself.
+ */
+public interface AcceptsDelete<P extends RestResource> {
+ /**
+ * Handle deletion of a child resource by DELETE on the collection.
+ *
+ * @param parent parent collection handle.
+ * @param id id of the resource being created (optional).
+ * @return a view to perform the deletion.
+ * @throws RestApiException the view cannot be constructed.
+ */
+ <I> RestModifyView<P, I> delete(P parent, IdString id)
+ throws RestApiException;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
index 89a4f33..4619a06 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
@@ -16,6 +16,9 @@
/** Configures a web UI plugin written using JavaScript. */
public class JavaScriptPlugin extends WebUiPlugin {
+ public static final String INIT_JS = "init.js";
+ public static final String STATIC_INIT_JS = "static/" + INIT_JS;
+
private final String fileName;
/**
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
index 5bb3c1e..eb87e7f 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
@@ -14,9 +14,10 @@
package com.google.gwtexpui.linker.server;
+import static java.util.regex.Pattern.compile;
+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static java.util.regex.Pattern.compile;
import javax.servlet.http.HttpServletRequest;
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
index bc20a9d..182eac3 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
@@ -14,9 +14,10 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
public class RawFindReplaceTest {
@Test
public void testFindReplace() {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
index 75c3745..f89c62b 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_LinkifyTest {
@Test
public void testLinkify_SimpleHttp1() {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
index 71b55a1..4fa6254 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
@@ -14,16 +14,16 @@
package com.google.gwtexpui.safehtml.client;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-
public class SafeHtml_ReplaceTest {
@Test
public void testReplaceEmpty() {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
index 045555a..ea91ee3 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyListTest {
private static final String BEGIN_LIST = "<ul class=\"wikiList\">";
private static final String END_LIST = "</ul>";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
index 605185e..57399dc 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyPreformatTest {
private static final String B = "<span class=\"wikiPreFormat\">";
private static final String E = "</span><br />";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
index d6fba26..f6b6b91 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyQuoteTest {
private static final String B = "<blockquote class=\"wikiQuote\">";
private static final String E = "</blockquote>";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
index 00b29de..3c261d0 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyTest {
@Test
public void testWikify_OneLine1() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
index 275937e..c6da773 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
@@ -32,7 +32,6 @@
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
-import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
@@ -43,6 +42,7 @@
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
+import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
import java.util.HashSet;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
index 25036d3..9adcaf7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
@@ -18,11 +18,11 @@
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index a92b736..c64620f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -135,9 +135,6 @@
String sectionTypeSection();
Map<String, String> sectionNames();
- String pagedProjectListPrev();
- String pagedProjectListNext();
-
- String pagedGroupListPrev();
- String pagedGroupListNext();
+ String pagedListPrev();
+ String pagedListNext();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index ef35e00..df79380 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -99,11 +99,8 @@
errorNoMatchingGroups = No Matching Groups
errorNoGitRepository = No Git Repository
-pagedProjectListPrev = ⇦Prev
-pagedProjectListNext = Next⇨
-
-pagedGroupListPrev = ⇦Prev
-pagedGroupListNext = Next⇨
+pagedListPrev = ⇦Prev
+pagedListNext = Next⇨
addPermission = Add Permission ...
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index 6579f83..0685cb7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -148,10 +148,10 @@
setPageTitle(Util.C.groupListTitle());
initPageHeader();
- prev = new Hyperlink(Util.C.pagedGroupListPrev(), true, "");
+ prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedGroupListNext(), true, "");
+ next = new Hyperlink(Util.C.pagedListNext(), true, "");
next.setVisible(false);
groups = new GroupTable(PageLinks.ADMIN_GROUPS);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index 3bd05e0..fa1a21f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -161,10 +161,10 @@
setPageTitle(Util.C.projectListTitle());
initPageHeader();
- prev = new Hyperlink(Util.C.pagedProjectListPrev(), true, "");
+ prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedProjectListNext(), true, "");
+ next = new Hyperlink(Util.C.pagedListNext(), true, "");
next.setVisible(false);
projects = new ProjectsTable() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
index 3c927fc..a17d648 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
@@ -63,12 +63,12 @@
<g:FlowPanel ui:field='comments'/>
</g:ScrollPanel>
<div class='{res.style.section}' style='position: relative'>
- <ui:msg><g:Button ui:field='post'
+ <g:Button ui:field='post'
title='Post reply (Shortcut: Ctrl-Enter)'
styleName='{res.style.button}'>
<ui:attribute name='title'/>
- <div>Post</div>
- </g:Button></ui:msg>
+ <div><ui:msg>Post</ui:msg></div>
+ </g:Button>
<g:Button ui:field='cancel'
title='Close reply form (Shortcut: Esc)'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 0c1815f..c79c45e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -85,7 +85,7 @@
public final native String branch() /*-{ return this.branch; }-*/;
public final native String topic() /*-{ return this.topic; }-*/;
public final native String change_id() /*-{ return this.change_id; }-*/;
- public final native boolean mergeable() /*-{ return this.mergeable; }-*/;
+ public final native boolean mergeable() /*-{ return this.mergeable || false; }-*/;
public final native int insertions() /*-{ return this.insertions; }-*/;
public final native int deletions() /*-{ return this.deletions; }-*/;
private final native String statusRaw() /*-{ return this.status; }-*/;
@@ -204,6 +204,17 @@
}
}
+ public static class EditInfo extends JavaScriptObject {
+ public final native String name() /*-{ return this.name; }-*/;
+ public final native CommitInfo commit() /*-{ return this.commit; }-*/;
+
+ public final native boolean has_files() /*-{ return this.hasOwnProperty('files') }-*/;
+ public final native NativeMap<FileInfo> files() /*-{ return this.files; }-*/;
+
+ protected EditInfo() {
+ }
+ }
+
public static class RevisionInfo extends JavaScriptObject {
public final native int _number() /*-{ return this._number; }-*/;
public final native String name() /*-{ return this.name; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index 270b9f5..ca7157a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -18,8 +18,8 @@
import static com.google.gerrit.client.FormatUtil.shortFormat;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.ui.AccountLinkPanel;
import com.google.gerrit.client.ui.BranchLink;
import com.google.gerrit.client.ui.ChangeLink;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
index f176372..b57cdac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
@@ -16,9 +16,9 @@
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
index 845537e..ce5c060 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
@@ -19,9 +19,9 @@
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gwt.aria.client.Roles;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Widget;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
index b8fa373..23e1109 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
@@ -16,12 +16,10 @@
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.SmallHeading;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusWidget;
@@ -29,6 +27,7 @@
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtexpui.user.client.AutoCenterDialogBox;
+import com.google.gwtjsonrpc.common.AsyncCallback;
public abstract class CommentedActionDialog<T> extends AutoCenterDialogBox
implements CloseHandler<PopupPanel> {
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
index 5be029c..6705e51 100644
--- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
@@ -14,18 +14,18 @@
package com.google.gerrit.client;
-import static org.junit.Assert.assertEquals;
-import static com.google.gerrit.client.RelativeDateFormatter.YEAR_IN_MILLIS;
-import static com.google.gerrit.client.RelativeDateFormatter.SECOND_IN_MILLIS;
-import static com.google.gerrit.client.RelativeDateFormatter.MINUTE_IN_MILLIS;
-import static com.google.gerrit.client.RelativeDateFormatter.HOUR_IN_MILLIS;
import static com.google.gerrit.client.RelativeDateFormatter.DAY_IN_MILLIS;
-
-import java.util.Date;
+import static com.google.gerrit.client.RelativeDateFormatter.HOUR_IN_MILLIS;
+import static com.google.gerrit.client.RelativeDateFormatter.MINUTE_IN_MILLIS;
+import static com.google.gerrit.client.RelativeDateFormatter.SECOND_IN_MILLIS;
+import static com.google.gerrit.client.RelativeDateFormatter.YEAR_IN_MILLIS;
+import static org.junit.Assert.assertEquals;
import org.eclipse.jgit.util.RelativeDateFormatter;
import org.junit.Test;
+import java.util.Date;
+
public class RelativeDateFormatterTest {
private static void assertFormat(long ageFromNow, long timeUnit,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java
index 992c70a..901b180 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java
@@ -14,12 +14,12 @@
package com.google.gerrit.httpd;
-import javax.servlet.http.HttpServletRequest;
-
import com.google.gerrit.common.Nullable;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import javax.servlet.http.HttpServletRequest;
+
public class CanonicalWebUrl {
private final Provider<String> configured;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
index 044c18c..6c17c87 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
@@ -32,4 +32,7 @@
return CharMatcher.is('/').trimLeadingFrom(Url.decode(encodedToken));
}
}
+
+ private LoginUrlToken() {
+ }
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java
similarity index 63%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
copy to gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java
index 73db6f5..67b97c4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 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.
@@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.httpd;
-import com.google.inject.BindingAnnotation;
+import java.net.URL;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface InstallPlugins {
+public interface ProxyProperties {
+ URL getProxyUrl();
+ String getUsername();
+ String getPassword();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java
new file mode 100644
index 0000000..0e51cc2
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+@Singleton
+class ProxyPropertiesProvider implements Provider<ProxyProperties> {
+
+ private URL proxyUrl;
+ private String proxyUser;
+ private String proxyPassword;
+
+ @Inject
+ ProxyPropertiesProvider(@GerritServerConfig Config config)
+ throws MalformedURLException {
+ String proxyUrlStr = config.getString("http", null, "proxy");
+ if (!Strings.isNullOrEmpty(proxyUrlStr)) {
+ proxyUrl = new URL(proxyUrlStr);
+ proxyUser = config.getString("http", null, "proxyUsername");
+ proxyPassword = config.getString("http", null, "proxyPassword");
+ String userInfo = proxyUrl.getUserInfo();
+ if (userInfo != null) {
+ int c = userInfo.indexOf(':');
+ if (0 < c) {
+ proxyUser = userInfo.substring(0, c);
+ proxyPassword = userInfo.substring(c + 1);
+ } else {
+ proxyUser = userInfo;
+ }
+ }
+ }
+ }
+
+ @Override
+ public ProxyProperties get() {
+ return new ProxyProperties() {
+ @Override
+ public URL getProxyUrl() {
+ return proxyUrl;
+ }
+ @Override
+ public String getUsername() {
+ return proxyUser;
+ }
+ @Override
+ public String getPassword() {
+ return proxyPassword;
+ }
+ };
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 3443968..e2adcd5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -131,6 +131,8 @@
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
HttpRemotePeerProvider.class).in(RequestScoped.class);
+ bind(ProxyProperties.class).toProvider(ProxyPropertiesProvider.class);
+
listener().toInstance(registerInParentInjectors());
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
index d1c617f..0e81a0d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
@@ -14,14 +14,18 @@
package com.google.gerrit.httpd.plugins;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.gerrit.extensions.annotations.Export;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.server.plugins.HttpModuleGenerator;
import com.google.gerrit.server.plugins.InvalidPluginException;
-import com.google.gerrit.server.plugins.ModuleGenerator;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
@@ -33,9 +37,10 @@
import javax.servlet.http.HttpServlet;
class HttpAutoRegisterModuleGenerator extends ServletModule
- implements ModuleGenerator {
+ implements HttpModuleGenerator {
private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
+ private String javascript;
@Override
protected void configureServlets() {
@@ -53,6 +58,10 @@
Annotation n = calculateBindAnnotation(impl);
bind(type).annotatedWith(n).to(impl);
}
+ if (javascript != null) {
+ DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(
+ new JavaScriptPlugin(javascript));
+ }
}
@Override
@@ -80,6 +89,14 @@
}
@Override
+ public void export(String javascript) {
+ checkState(this.javascript == null,
+ "Multiple JavaScript plugins detected: %s, %s", this.javascript,
+ javascript);
+ this.javascript = javascript;
+ }
+
+ @Override
public void listen(TypeLiteral<?> tl, Class<?> clazz) {
listeners.put(tl, clazz);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
index 5dc7e2e..365a222 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
@@ -15,7 +15,7 @@
package com.google.gerrit.httpd.plugins;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.plugins.ModuleGenerator;
+import com.google.gerrit.server.plugins.HttpModuleGenerator;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.inject.internal.UniqueAnnotations;
@@ -37,7 +37,7 @@
.annotatedWith(UniqueAnnotations.create())
.to(HttpPluginServlet.class);
- bind(ModuleGenerator.class)
+ bind(HttpModuleGenerator.class)
.to(HttpAutoRegisterModuleGenerator.class);
install(new CacheModule() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 568c492..f3f1726 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -26,6 +26,7 @@
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
@@ -50,6 +51,7 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AcceptsDelete;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -254,6 +256,10 @@
@SuppressWarnings("unchecked")
AcceptsPost<RestResource> ac = (AcceptsPost<RestResource>) c;
viewData = new ViewData(null, ac.post(rsrc));
+ } else if (c instanceof AcceptsDelete && "DELETE".equals(req.getMethod())) {
+ @SuppressWarnings("unchecked")
+ AcceptsDelete<RestResource> ac = (AcceptsDelete<RestResource>) c;
+ viewData = new ViewData(null, ac.delete(rsrc, null));
} else {
throw new MethodNotAllowedException();
}
@@ -273,6 +279,13 @@
AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) c;
viewData = new ViewData(viewData.pluginName, ac.create(rsrc, id));
status = SC_CREATED;
+ } else if (c instanceof AcceptsDelete
+ && path.isEmpty()
+ && "DELETE".equals(req.getMethod())) {
+ @SuppressWarnings("unchecked")
+ AcceptsDelete<RestResource> ac = (AcceptsDelete<RestResource>) c;
+ viewData = new ViewData(viewData.pluginName, ac.delete(rsrc, id));
+ status = SC_NO_CONTENT;
} else {
throw e;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index b6549ea..622ba6c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc.account;
import com.google.common.base.Strings;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.AccountSecurity;
import com.google.gerrit.common.data.ContributorAgreement;
@@ -27,7 +28,6 @@
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.ContactInformation;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -71,6 +71,7 @@
private final ChangeHooks hooks;
private final GroupCache groupCache;
+ private final AuditService auditService;
@Inject
AccountSecurityImpl(final Provider<ReviewDb> schema,
@@ -82,7 +83,8 @@
final ChangeUserName.CurrentUser changeUserNameFactory,
final DeleteExternalIds.Factory deleteExternalIdsFactory,
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
- final ChangeHooks hooks, final GroupCache groupCache) {
+ final ChangeHooks hooks, final GroupCache groupCache,
+ final AuditService auditService) {
super(schema, currentUser);
contactStore = cs;
realm = r;
@@ -92,6 +94,7 @@
byEmailCache = abec;
accountCache = uac;
accountManager = am;
+ this.auditService = auditService;
useContactInfo = contactStore != null && contactStore.isEnabled();
@@ -198,9 +201,8 @@
AccountGroupMember m = db.accountGroupMembers().get(key);
if (m == null) {
m = new AccountGroupMember(key);
- db.accountGroupMembersAudit().insert(
- Collections.singleton(new AccountGroupMemberAudit(
- m, account.getId(), TimeUtil.nowTs())));
+ auditService.dispatchAddAccountsToGroup(account.getId(), Collections
+ .singleton(m));
db.accountGroupMembers().insert(Collections.singleton(m));
accountCache.evict(m.getAccountId());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index 73cc83a..4a673cb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -170,7 +170,8 @@
// quickly locate where they have pending drafts, and review them.
//
final Account.Id me = ((IdentifiedUser) user).getAccountId();
- for (final PatchLineComment c : db.patchComments().draftByPatchSetAuthor(psIdNew, me)) {
+ for (PatchLineComment c
+ : plcUtil.draftByPatchSetAuthor(db, psIdNew, me, notes)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setDraftCount(p.getDraftCount() + 1);
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
index 26cdd8a..57b089a 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
@@ -14,11 +14,11 @@
package com.google.gerrit.httpd;
-import org.junit.Test;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
public class GitWebConfigTest {
private static final String VALID_CHARACTERS = "*()";
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
index 8533a9c..af90585 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.restapi;
+import static org.junit.Assert.assertEquals;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -22,7 +24,6 @@
import com.google.gson.JsonPrimitive;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
public class ParameterParserTest {
@Test
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 64c7ed5..9ae5802 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
@@ -281,26 +281,6 @@
@SuppressWarnings("unchecked")
@Override
- public void insert(ChangeData cd) throws IOException {
- Term id = QueryBuilder.idTerm(cd);
- Document doc = toDocument(cd);
- try {
- if (cd.change().getStatus().isOpen()) {
- Futures.allAsList(
- closedIndex.delete(id),
- openIndex.insert(doc)).get();
- } else {
- Futures.allAsList(
- openIndex.delete(id),
- closedIndex.insert(doc)).get();
- }
- } catch (OrmException | ExecutionException | InterruptedException e) {
- throw new IOException(e);
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
public void replace(ChangeData cd) throws IOException {
Term id = QueryBuilder.idTerm(cd);
Document doc = toDocument(cd);
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 1681bc2..dc5e102 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
+import com.google.gerrit.httpd.ProxyProperties;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
@@ -55,7 +56,6 @@
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.util.HttpClientFactory;
-import org.openid4java.util.ProxyProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -108,29 +108,18 @@
final Provider<IdentifiedUser> iu,
CanonicalWebUrl up,
@GerritServerConfig final Config config, final AuthConfig ac,
- final AccountManager am) throws ConsumerException, MalformedURLException {
+ final AccountManager am,
+ ProxyProperties proxyProperties)
+ throws ConsumerException, MalformedURLException {
- if (config.getString("http", null, "proxy") != null) {
- final URL proxyUrl = new URL(config.getString("http", null, "proxy"));
- String username = config.getString("http", null, "proxyUsername");
- String password = config.getString("http", null, "proxyPassword");
-
- final String userInfo = proxyUrl.getUserInfo();
- if (userInfo != null) {
- int c = userInfo.indexOf(':');
- if (0 < c) {
- username = userInfo.substring(0, c);
- password = userInfo.substring(c + 1);
- } else {
- username = userInfo;
- }
- }
-
- final ProxyProperties proxy = new ProxyProperties();
- proxy.setProxyHostName(proxyUrl.getHost());
- proxy.setProxyPort(proxyUrl.getPort());
- proxy.setUserName(username);
- proxy.setPassword(password);
+ if (proxyProperties.getProxyUrl() != null) {
+ final org.openid4java.util.ProxyProperties proxy =
+ new org.openid4java.util.ProxyProperties();
+ URL url = proxyProperties.getProxyUrl();
+ proxy.setProxyHostName(url.getHost());
+ proxy.setProxyPort(url.getPort());
+ proxy.setUserName(proxyProperties.getUsername());
+ proxy.setPassword(proxyProperties.getPassword());
HttpClientFactory.setProxyProperties(proxy);
}
diff --git a/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java b/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
index f0ad62a..a2c3dae 100644
--- a/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
+++ b/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
@@ -14,9 +14,10 @@
package org.eclipse.jgit.diff;
-import org.junit.Test;
import static org.junit.Assert.assertNotNull;
+import org.junit.Test;
+
public class EditDeserializerTest {
@Test
public void testDiffDeserializer() {
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 3a89cd0..cffc0c1 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -1,15 +1,7 @@
SRCS = 'src/main/java/com/google/gerrit/pgm/'
+RSRCS = 'src/main/resources/com/google/gerrit/pgm/'
-INIT_API_SRCS = [SRCS + n for n in [
- 'init/AllProjectsConfig.java',
- 'init/AllProjectsNameOnInitProvider.java',
- 'util/ConsoleUI.java',
- 'init/InitFlags.java',
- 'init/InitStep.java',
- 'init/InitStep.java',
- 'init/InstallPlugins.java',
- 'init/Section.java',
-]]
+INIT_API_SRCS = glob([SRCS + 'init/api/*.java'])
java_library(
name = 'init-api',
@@ -33,65 +25,79 @@
visibility = ['PUBLIC'],
)
-INIT_BASE_SRCS = [SRCS + 'BaseInit.java'] + glob(
- [SRCS + n for n in [
- 'init/**/*.java',
- 'util/**/*.java',
- ]],
- excludes = INIT_API_SRCS +
- [SRCS + n for n in [
- 'init/Browser.java',
- 'util/ErrorLogFile.java',
- 'util/GarbageCollectionLogFile.java',
- 'util/LogFileCompressor.java',
- 'util/RuntimeShutdown.java',
- ]]
- )
-
-INIT_BASE_RSRCS = ['src/main/resources/com/google/gerrit/pgm/libraries.config']
-
java_library(
- name = 'init-base',
- srcs = INIT_BASE_SRCS,
- resources = INIT_BASE_RSRCS,
+ name = 'init',
+ srcs = glob([SRCS + 'init/*.java']),
+ resources = glob([RSRCS + 'init/*']),
deps = [
':init-api',
+ ':util',
'//gerrit-common:server',
'//gerrit-extension-api:api',
'//gerrit-lucene:lucene',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-util-cli:cli',
+ '//lib:args4j',
+ '//lib:guava',
+ '//lib:gwtjsonrpc',
+ '//lib:gwtorm',
+ '//lib:h2',
'//lib/commons:dbcp',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/jgit:jgit',
'//lib/mina:sshd',
- '//lib:args4j',
- '//lib:guava',
- '//lib:gwtjsonrpc',
- '//lib:gwtorm',
'//lib/log:api',
],
provided_deps = ['//gerrit-launcher:launcher'],
visibility = [
- '//gerrit-war:',
'//gerrit-acceptance-tests/...',
+ '//gerrit-war:',
+ ],
+)
+
+java_library(
+ name = 'util',
+ srcs = glob([SRCS + 'util/*.java']),
+ deps = [
+ '//gerrit-cache-h2:cache-h2',
+ '//gerrit-common:client',
+ '//gerrit-common:server',
+ '//gerrit-extension-api:api',
+ '//gerrit-reviewdb:server',
+ '//gerrit-server:server',
+ '//gerrit-util-cli:cli',
+ '//lib:args4j',
+ '//lib:guava',
+ '//lib:gwtorm',
+ '//lib/commons:dbcp',
+ '//lib/guice:guice',
+ '//lib/jgit:jgit',
+ '//lib/log:api',
+ '//lib/log:log4j',
+ ],
+ visibility = [
+ '//gerrit-acceptance-tests/...',
+ '//gerrit-war:',
],
)
java_library(
name = 'pgm',
srcs = glob(
- ['src/main/java/**/*.java'],
- excludes = INIT_API_SRCS + INIT_BASE_SRCS
+ [SRCS + n for n in [
+ '*.java',
+ # TODO(dborowitz): Split these into separate rules.
+ 'http/**/*.java',
+ 'shell/**/*.java',
+ ]],
),
- resources = glob(
- ['src/main/resources/**/*'],
- excludes = INIT_BASE_RSRCS),
+ resources = glob([RSRCS + '*']),
deps = [
+ ':init',
':init-api',
- ':init-base',
+ ':util',
'//gerrit-cache-h2:cache-h2',
'//gerrit-common:server',
'//gerrit-extension-api:api',
@@ -136,8 +142,8 @@
name = 'pgm_tests',
srcs = glob(['src/test/java/**/*.java']),
deps = [
+ ':init',
':init-api',
- ':init-base',
':pgm',
'//gerrit-server:server',
'//lib:junit',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index c30507f..f0115ad 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -19,9 +19,10 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.PluginData;
+import com.google.gerrit.pgm.init.BaseInit;
import com.google.gerrit.pgm.init.Browser;
import com.google.gerrit.pgm.init.InitPlugins;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.util.ErrorLogFile;
import com.google.gerrit.pgm.util.IoUtil;
import com.google.gerrit.server.config.GerritServerConfigModule;
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
new file mode 100644
index 0000000..21b7132
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
@@ -0,0 +1,287 @@
+// Copyright (C) 2014 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;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+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.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.util.BatchGitModule;
+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.reviewdb.client.Change;
+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.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MultiProgressMonitor;
+import com.google.gerrit.server.git.MultiProgressMonitor.Task;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.notedb.ChangeRebuilder;
+import com.google.gerrit.server.notedb.NoteDbModule;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class RebuildNotedb extends SiteProgram {
+ private static final Logger log =
+ LoggerFactory.getLogger(RebuildNotedb.class);
+
+ @Option(name = "--threads", usage = "Number of threads to use for indexing")
+ private int threads = Runtime.getRuntime().availableProcessors();
+
+ private Injector dbInjector;
+ private Injector sysInjector;
+
+ @Override
+ public int run() throws Exception {
+ mustHaveValidSite();
+ dbInjector = createDbInjector(MULTI_USER);
+ threads = ThreadLimiter.limitThreads(dbInjector, threads);
+
+ LifecycleManager dbManager = new LifecycleManager();
+ dbManager.add(dbInjector);
+ dbManager.start();
+
+ sysInjector = createSysInjector();
+ LifecycleManager sysManager = new LifecycleManager();
+ sysManager.add(sysInjector);
+ sysManager.start();
+
+ ListeningExecutorService executor = newExecutor();
+ System.out.println("Rebuilding the notedb");
+ ChangeRebuilder rebuilder = sysInjector.getInstance(ChangeRebuilder.class);
+
+ Multimap<Project.NameKey, Change> changesByProject = getChangesByProject();
+ final AtomicBoolean ok = new AtomicBoolean(true);
+ Stopwatch sw = Stopwatch.createStarted();
+ GitRepositoryManager repoManager =
+ sysInjector.getInstance(GitRepositoryManager.class);
+ final Project.NameKey allUsersName =
+ sysInjector.getInstance(AllUsersName.class);
+ final Repository allUsersRepo = repoManager.openRepository(allUsersName);
+ try {
+ deleteDraftRefs(allUsersRepo);
+ for (final Project.NameKey project : changesByProject.keySet()) {
+ final Repository repo = repoManager.openRepository(project);
+ try {
+ final BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ final BatchRefUpdate bruForDrafts =
+ allUsersRepo.getRefDatabase().newBatchUpdate();
+ List<ListenableFuture<?>> futures = Lists.newArrayList();
+
+ // Here, we truncate the project name to 50 characters to ensure that
+ // the whole monitor line for a project fits on one line (<80 chars).
+ final MultiProgressMonitor mpm = new MultiProgressMonitor(System.out,
+ truncateProjectName(project.get()));
+ final Task doneTask =
+ mpm.beginSubTask("done", changesByProject.get(project).size());
+ final Task failedTask =
+ mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
+
+ for (final Change c : changesByProject.get(project)) {
+ final ListenableFuture<?> future = rebuilder.rebuildAsync(c,
+ executor, bru, bruForDrafts, repo, allUsersRepo);
+ futures.add(future);
+ future.addListener(
+ new RebuildListener(c.getId(), future, ok, doneTask, failedTask),
+ MoreExecutors.sameThreadExecutor());
+ }
+
+ mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+ new AsyncFunction<List<?>, Void>() {
+ @Override
+ public ListenableFuture<Void> apply(List<?> input)
+ throws Exception {
+ execute(bru, repo);
+ execute(bruForDrafts, allUsersRepo);
+ mpm.end();
+ return Futures.immediateFuture(null);
+ }
+ }));
+ } catch (Exception e) {
+ log.error("Error rebuilding notedb", e);
+ ok.set(false);
+ break;
+ } finally {
+ repo.close();
+ }
+ }
+ } finally {
+ allUsersRepo.close();
+ }
+
+ double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
+ System.out.format("Rebuild %d changes in %.01fs (%.01f/s)\n",
+ changesByProject.size(), t, changesByProject.size() / t);
+ return ok.get() ? 0 : 1;
+ }
+
+ private static String truncateProjectName(String projectName) {
+ int monitorStringMaxLength = 50;
+ String monitorString = (projectName.length() > monitorStringMaxLength)
+ ? projectName.substring(0, monitorStringMaxLength)
+ : projectName;
+ if (projectName.length() > monitorString.length()) {
+ monitorString = monitorString + "...";
+ }
+ return monitorString;
+ }
+
+ private static void execute(BatchRefUpdate bru, Repository repo)
+ throws IOException {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ } finally {
+ rw.release();
+ }
+ }
+
+ private void deleteDraftRefs(Repository allUsersRepo) throws IOException {
+ RefDatabase refDb = allUsersRepo.getRefDatabase();
+ Map<String, Ref> allRefs = refDb.getRefs(RefNames.REFS_DRAFT_COMMENTS);
+ BatchRefUpdate bru = refDb.newBatchUpdate();
+ for (Map.Entry<String, Ref> ref : allRefs.entrySet()) {
+ bru.addCommand(new ReceiveCommand(ref.getValue().getObjectId(),
+ ObjectId.zeroId(), RefNames.REFS_DRAFT_COMMENTS + ref.getKey()));
+ }
+ execute(bru, allUsersRepo);
+ }
+
+ private Injector createSysInjector() {
+ return dbInjector.createChildInjector(new AbstractModule() {
+ @Override
+ public void configure() {
+ install(dbInjector.getInstance(BatchProgramModule.class));
+ install(new BatchGitModule());
+ install(new NoteDbModule());
+ bind(NotesMigration.class).toInstance(NotesMigration.allEnabled());
+ }
+ });
+ }
+
+ private ListeningExecutorService newExecutor() {
+ if (threads > 0) {
+ return MoreExecutors.listeningDecorator(
+ dbInjector.getInstance(WorkQueue.class)
+ .createQueue(threads, "RebuildChange"));
+ } else {
+ return MoreExecutors.sameThreadExecutor();
+ }
+ }
+
+ private Multimap<Project.NameKey, Change> getChangesByProject()
+ throws OrmException {
+ // Memorize all changes so we can close the db connection and allow
+ // rebuilder threads to use the full connection pool.
+ SchemaFactory<ReviewDb> schemaFactory = sysInjector.getInstance(Key.get(
+ new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
+ ReviewDb db = schemaFactory.open();
+ Multimap<Project.NameKey, Change> changesByProject =
+ ArrayListMultimap.create();
+ try {
+ for (Change c : db.changes().all()) {
+ changesByProject.put(c.getProject(), c);
+ }
+ return changesByProject;
+ } finally {
+ db.close();
+ }
+ }
+
+ private class RebuildListener implements Runnable {
+ private Change.Id changeId;
+ private ListenableFuture<?> future;
+ private AtomicBoolean ok;
+ private Task doneTask;
+ private Task failedTask;
+
+
+ private RebuildListener(Change.Id changeId, ListenableFuture<?> future,
+ AtomicBoolean ok, Task doneTask, Task failedTask) {
+ this.changeId = changeId;
+ this.future = future;
+ this.ok = ok;
+ this.doneTask = doneTask;
+ this.failedTask = failedTask;
+ }
+
+ @Override
+ public void run() {
+ try {
+ future.get();
+ doneTask.update(1);
+ } catch (ExecutionException | InterruptedException e) {
+ fail(e);
+ } catch (RuntimeException e) {
+ failAndThrow(e);
+ } catch (Error e) {
+ // Can't join with RuntimeException because "RuntimeException
+ // | Error" becomes Throwable, which messes with signatures.
+ failAndThrow(e);
+ }
+ }
+
+ private void fail(Throwable t) {
+ log.error("Failed to rebuild change " + changeId, t);
+ ok.set(false);
+ failedTask.update(1);
+ }
+
+ private void failAndThrow(RuntimeException e) {
+ fail(e);
+ throw e;
+ }
+
+ private void failAndThrow(Error e) {
+ fail(e);
+ throw e;
+ }
+ }
+}
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 62896ea..6ade6a9 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
@@ -15,49 +15,26 @@
package com.google.gerrit.pgm;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
-import static com.google.inject.Scopes.SINGLETON;
-import com.google.common.cache.Cache;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.DisabledChangeHooks;
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule;
+import com.google.gerrit.pgm.util.BatchGitModule;
+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.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.rules.PrologModule;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCacheImpl;
-import com.google.gerrit.server.account.AccountCacheImpl;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupCacheImpl;
-import com.google.gerrit.server.account.GroupIncludeCacheImpl;
-import com.google.gerrit.server.cache.CacheRemovalListener;
-import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.ChangeKindCacheImpl;
import com.google.gerrit.server.change.MergeabilityChecker;
import com.google.gerrit.server.change.MergeabilityChecksExecutor;
import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.git.validators.CommitValidationListener;
-import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.group.GroupModule;
import com.google.gerrit.server.index.ChangeBatchIndexer;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.ChangeSchemas;
@@ -66,28 +43,13 @@
import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.notedb.NoteDbModule;
-import com.google.gerrit.server.patch.PatchListCacheImpl;
-import com.google.gerrit.server.project.AccessControlModule;
-import com.google.gerrit.server.project.CommentLinkInfo;
-import com.google.gerrit.server.project.CommentLinkProvider;
-import com.google.gerrit.server.project.ProjectCacheImpl;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.SectionSortCache;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.solr.SolrIndexModule;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
-import com.google.inject.Provider;
import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.Config;
@@ -95,17 +57,12 @@
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.kohsuke.args4j.Option;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class Reindex extends SiteProgram {
- private static final Logger log = LoggerFactory.getLogger(Reindex.class);
-
@Option(name = "--threads", usage = "Number of threads to use for indexing")
private int threads = Runtime.getRuntime().availableProcessors();
@@ -133,7 +90,7 @@
public int run() throws Exception {
mustHaveValidSite();
dbInjector = createDbInjector(MULTI_USER);
- limitThreads();
+ threads = ThreadLimiter.limitThreads(dbInjector, threads);
disableLuceneAutomaticCommit();
if (version == null) {
version = ChangeSchemas.getLatest().getVersion();
@@ -162,24 +119,9 @@
return result;
}
- private void limitThreads() {
- Config cfg =
- dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
- boolean usePool = cfg.getBoolean("database", "connectionpool",
- dbInjector.getInstance(DataSourceType.class).usePool());
- int poolLimit = cfg.getInt("database", "poollimit",
- DataSourceProvider.DEFAULT_POOL_LIMIT);
- if (usePool && threads > poolLimit) {
- log.warn("Limiting reindexing to " + poolLimit
- + " threads due to database.poolLimit");
- threads = poolLimit;
- }
- }
-
private Injector createSysInjector() {
List<Module> modules = Lists.newArrayList();
- modules.add(PatchListCacheImpl.module());
- AbstractModule changeIndexModule;
+ Module changeIndexModule;
switch (IndexModule.getIndexType(dbInjector)) {
case LUCENE:
changeIndexModule = new LuceneIndexModule(version, threads, outputBase);
@@ -191,39 +133,10 @@
throw new IllegalStateException("unsupported index.type");
}
modules.add(changeIndexModule);
- modules.add(new ReviewDbModule());
- modules.add(new FactoryModule() {
- @SuppressWarnings("rawtypes")
+ modules.add(dbInjector.getInstance(BatchProgramModule.class));
+ modules.add(new AbstractModule() {
@Override
protected void configure() {
- // Plugins are not loaded and we're just running through each change
- // once, so don't worry about cache removal.
- bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
- .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
- bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
- .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
- bind(new TypeLiteral<List<CommentLinkInfo>>() {})
- .toProvider(CommentLinkProvider.class).in(SINGLETON);
- bind(String.class).annotatedWith(CanonicalWebUrl.class)
- .toProvider(CanonicalWebUrlProvider.class);
- bind(IdentifiedUser.class)
- .toProvider(Providers. <IdentifiedUser>of(null));
- bind(CurrentUser.class).to(IdentifiedUser.class);
- install(new AccessControlModule());
- install(new DefaultCacheFactory.Module());
- install(new GroupModule());
- install(new PrologModule());
- install(AccountByEmailCacheImpl.module());
- install(AccountCacheImpl.module());
- install(GroupCacheImpl.module());
- install(GroupIncludeCacheImpl.module());
- install(ProjectCacheImpl.module());
- install(SectionSortCache.module());
- install(ChangeKindCacheImpl.module());
- factory(CapabilityControl.Factory.class);
- factory(ChangeData.Factory.class);
- factory(ProjectState.Factory.class);
-
if (recheckMergeable) {
install(new MergeabilityModule());
} else {
@@ -245,61 +158,16 @@
}
}
- private class ReviewDbModule extends LifecycleModule {
- @Override
- protected void configure() {
- final SchemaFactory<ReviewDb> schema = dbInjector.getInstance(
- Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
- final List<ReviewDb> dbs = Collections.synchronizedList(
- Lists.<ReviewDb> newArrayListWithCapacity(threads + 1));
- final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
-
- bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
- @Override
- public ReviewDb get() {
- ReviewDb db = localDb.get();
- if (db == null) {
- try {
- db = schema.open();
- dbs.add(db);
- localDb.set(db);
- } catch (OrmException e) {
- throw new ProvisionException("unable to open ReviewDb", e);
- }
- }
- return db;
- }
- });
- listener().toInstance(new LifecycleListener() {
- @Override
- public void start() {
- // Do nothing.
- }
-
- @Override
- public void stop() {
- for (ReviewDb db : dbs) {
- db.close();
- }
- }
- });
- }
- }
-
private static class MergeabilityModule extends FactoryModule {
@Override
public void configure() {
factory(PatchSetInserter.Factory.class);
- bind(ChangeHooks.class).to(DisabledChangeHooks.class);
bind(ReplacePatchSetSender.Factory.class).toProvider(
Providers.<ReplacePatchSetSender.Factory>of(null));
factory(MergeUtil.Factory.class);
- DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
- DynamicSet.setOf(binder(), CommitValidationListener.class);
- factory(CommitValidators.Factory.class);
- install(new GitModule());
install(new NoteDbModule());
+ install(new BatchGitModule());
}
@Provides
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
index 4c65218a..80aa3b2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
@@ -16,6 +16,7 @@
import com.google.common.base.Objects;
import com.google.common.base.Strings;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.inject.Inject;
import com.google.inject.Provider;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
similarity index 95%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index bacdd2d..b95859e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm;
+package com.google.gerrit.pgm.init;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
import static com.google.inject.Stage.PRODUCTION;
@@ -20,12 +20,9 @@
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Die;
-import com.google.gerrit.pgm.init.InitFlags;
-import com.google.gerrit.pgm.init.InitModule;
-import com.google.gerrit.pgm.init.InstallPlugins;
-import com.google.gerrit.pgm.init.PluginsDistribution;
-import com.google.gerrit.pgm.init.SitePathInitializer;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InstallPlugins;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.SitePath;
@@ -166,8 +163,8 @@
return false;
}
- static class SiteInit {
- final SitePaths site;
+ public static class SiteInit {
+ public final SitePaths site;
final InitFlags flags;
final ConsoleUI ui;
final SitePathInitializer initializer;
@@ -228,10 +225,10 @@
return ConsoleUI.getInstance(false);
}
- static class SiteRun {
- final ConsoleUI ui;
- final SitePaths site;
- final InitFlags flags;
+ public static class SiteRun {
+ public final ConsoleUI ui;
+ public final SitePaths site;
+ public final InitFlags flags;
final SchemaUpdater schemaUpdater;
final SchemaFactory<ReviewDb> schema;
final GitRepositoryManager repositoryManager;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
index 7ac1ed6..c30edf8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm.init;
+import com.google.gerrit.pgm.init.api.Section;
+
/** Abstraction of initializer for the database section */
interface DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
index 0ea3ff0..88bc790 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
@@ -14,6 +14,7 @@
package com.google.gerrit.pgm.init;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index a2037b5..83c4510 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -16,7 +16,9 @@
import static com.google.gerrit.pgm.init.InitUtil.dnOf;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
index 7230c9d..4c026ac 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
@@ -16,6 +16,8 @@
import static com.google.gerrit.pgm.init.InitUtil.die;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
index 92cd46d..0fda842 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -18,7 +18,9 @@
import static com.google.gerrit.pgm.init.InitUtil.username;
import com.google.gerrit.launcher.GerritLauncher;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index 0c64552..668b307 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -18,7 +18,9 @@
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Binding;
import com.google.inject.Guice;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
index dc8a440..264031d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
@@ -16,7 +16,9 @@
import static com.google.gerrit.pgm.init.InitUtil.die;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
index 8929a7b..9370a14 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -20,7 +20,10 @@
import static com.google.gerrit.pgm.init.InitUtil.isAnyAddress;
import static com.google.gerrit.pgm.init.InitUtil.toURI;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index 9966fda..b522d07 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -15,7 +15,10 @@
package com.google.gerrit.pgm.init;
import com.google.gerrit.lucene.LuceneChangeIndex;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.ChangeSchemas;
import com.google.gerrit.server.index.IndexModule.IndexType;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
index 78cfa3b..5f9aade 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
@@ -14,7 +14,9 @@
package com.google.gerrit.pgm.init;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.AllProjectsConfig;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
index 4ce9a24..1dd9df2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
@@ -14,6 +14,9 @@
package com.google.gerrit.pgm.init;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.binder.LinkedBindingBuilder;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index 1372c31..b079aad 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -16,7 +16,8 @@
import com.google.common.base.Objects;
import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.plugins.JarPluginProvider;
import com.google.gerrit.server.plugins.PluginLoader;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index c737c39..c30d3f5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -16,7 +16,9 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.PluginData;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.plugins.JarPluginProvider;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index ae621ae..e9cc1ed 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -17,7 +17,9 @@
import static com.google.gerrit.pgm.init.InitUtil.isLocal;
import static com.google.gerrit.pgm.init.InitUtil.username;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.mail.SmtpEmailSender.Encryption;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index 9003b30..4dc05c5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -18,7 +18,9 @@
import static com.google.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.hostname;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
index 20034c1..73b6396 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.pgm.init.InitUtil.username;
import com.google.common.base.Strings;
+import com.google.gerrit.pgm.init.api.Section;
class JDBCInitializer implements DatabaseConfigInitializer {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index 7209990..ea8f0f1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -33,7 +33,7 @@
@Singleton
class Libraries {
private static final String RESOURCE_FILE =
- "com/google/gerrit/pgm/libraries.config";
+ "com/google/gerrit/pgm/init/libraries.config";
private final Provider<LibraryDownloader> downloadProvider;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index b943ca3..1369861 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -17,7 +17,7 @@
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gerrit.common.Die;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.util.IoUtil;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
index 4d746cc..c3111554 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
@@ -16,6 +16,8 @@
import static com.google.gerrit.pgm.init.InitUtil.username;
+import com.google.gerrit.pgm.init.api.Section;
+
public class MaxDbInitializer implements DatabaseConfigInitializer {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
index fe6a4d9..50d71f3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
@@ -16,6 +16,8 @@
import static com.google.gerrit.pgm.init.InitUtil.username;
+import com.google.gerrit.pgm.init.api.Section;
+
class MySqlInitializer implements DatabaseConfigInitializer {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java
index 180beb0..e7bf99b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java
@@ -16,6 +16,8 @@
import static com.google.gerrit.pgm.init.InitUtil.username;
+import com.google.gerrit.pgm.init.api.Section;
+
public class OracleInitializer implements DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
index 1425663..4f2b802 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
@@ -16,6 +16,8 @@
import static com.google.gerrit.pgm.init.InitUtil.username;
+import com.google.gerrit.pgm.init.api.Section;
+
class PostgreSQLInitializer implements DatabaseConfigInitializer {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 892a8a5..c172e7b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -22,8 +22,9 @@
import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
import static com.google.gerrit.pgm.init.InitUtil.version;
-import com.google.gerrit.pgm.BaseInit;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.mail.OutgoingEmail;
import com.google.inject.Binding;
@@ -85,7 +86,7 @@
savePublic(flags.cfg);
saveSecure(flags.sec);
- extract(site.gerrit_sh, BaseInit.class, "gerrit.sh");
+ extract(site.gerrit_sh, getClass(), "gerrit.sh");
chmod(0755, site.gerrit_sh);
chmod(0700, site.tmp_dir);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
index 97be0c5..7b64308 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -18,7 +18,10 @@
import static com.google.gerrit.pgm.init.InitUtil.savePublic;
import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
similarity index 97%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
index cd4a0b8..dfb2f38 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.SitePaths;
@@ -93,7 +93,7 @@
throw new UnsupportedOperationException();
}
- void save(String message) throws IOException {
+ public void save(String message) throws IOException {
save(new PersonIdent("Gerrit Initialization", "init@gerrit"), message);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsNameOnInitProvider.java
similarity index 96%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsNameOnInitProvider.java
index 1c9415a..3b7c0de 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsNameOnInitProvider.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
similarity index 99%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
index 3cbf047..fb0ef28 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.pgm.init.api;
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
similarity index 91%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
index 267b41a..6ef7071 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
@@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
+import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -41,9 +42,9 @@
public final FileBasedConfig sec;
public final List<String> installPlugins;
-
+ @VisibleForTesting
@Inject
- InitFlags(final SitePaths site,
+ public InitFlags(final SitePaths site,
final @InstallPlugins List<String> installPlugins) throws IOException,
ConfigInvalidException {
this.installPlugins = installPlugins;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
similarity index 95%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
index 5a9a334..250cf59 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
/** A single step in the site initialization process. */
public interface InitStep {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InstallPlugins.java
similarity index 95%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InstallPlugins.java
index 73db6f5..256892a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InstallPlugins.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.inject.BindingAnnotation;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
similarity index 97%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
index d0a89e7..a376bb7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
@@ -50,7 +49,7 @@
this.subsection = subsection;
}
- String get(String name) {
+ public String get(String name) {
return flags.cfg.getString(section, subsection, name);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
new file mode 100644
index 0000000..11ab073
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.DisabledChangeHooks;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.GitModule;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidators;
+
+/** Module for batch programs that need git access. */
+public class BatchGitModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ bind(ChangeHooks.class).to(DisabledChangeHooks.class);
+ DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
+ DynamicSet.setOf(binder(), CommitValidationListener.class);
+ factory(CommitValidators.Factory.class);
+ install(new GitModule());
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
new file mode 100644
index 0000000..ec98465
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2014 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;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+import com.google.common.cache.Cache;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.rules.PrologModule;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountByEmailCacheImpl;
+import com.google.gerrit.server.account.AccountCacheImpl;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupCacheImpl;
+import com.google.gerrit.server.account.GroupIncludeCacheImpl;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.ChangeCache;
+import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.group.GroupModule;
+import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.server.project.AccessControlModule;
+import com.google.gerrit.server.project.CommentLinkInfo;
+import com.google.gerrit.server.project.CommentLinkProvider;
+import com.google.gerrit.server.project.ProjectCacheImpl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SectionSortCache;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.util.Providers;
+
+import java.util.List;
+
+/**
+ * Module for programs that perform batch operations on a site.
+ * <p>
+ * Any program that requires this module likely also requires using
+ * {@link ThreadLimiter} to limit the number of threads accessing the database
+ * concurrently.
+ */
+public class BatchProgramModule extends FactoryModule {
+ private final Module reviewDbModule;
+
+ @Inject
+ BatchProgramModule(PerThreadReviewDbModule reviewDbModule) {
+ this.reviewDbModule = reviewDbModule;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected void configure() {
+ install(reviewDbModule);
+ install(PatchListCacheImpl.module());
+ // Plugins are not loaded and we're just running through each change
+ // once, so don't worry about cache removal.
+ bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
+ .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+ bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
+ .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
+ bind(new TypeLiteral<List<CommentLinkInfo>>() {})
+ .toProvider(CommentLinkProvider.class).in(SINGLETON);
+ bind(String.class).annotatedWith(CanonicalWebUrl.class)
+ .toProvider(CanonicalWebUrlProvider.class);
+ bind(IdentifiedUser.class)
+ .toProvider(Providers.<IdentifiedUser> of(null));
+ bind(CurrentUser.class).to(IdentifiedUser.class);
+ install(new AccessControlModule());
+ install(new DefaultCacheFactory.Module());
+ install(new GroupModule());
+ install(new PrologModule());
+ install(AccountByEmailCacheImpl.module());
+ install(AccountCacheImpl.module());
+ install(GroupCacheImpl.module());
+ install(GroupIncludeCacheImpl.module());
+ install(ProjectCacheImpl.module());
+ install(SectionSortCache.module());
+ install(ChangeKindCacheImpl.module());
+ install(ChangeCache.module());
+ install(TagCache.module());
+ factory(CapabilityControl.Factory.class);
+ factory(ChangeData.Factory.class);
+ factory(ProjectState.Factory.class);
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
new file mode 100644
index 0000000..eb12937
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2014 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;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Module to bind a single {@link ReviewDb} instance per thread.
+ * <p>
+ * New instances are opened on demand, but are closed only at shutdown.
+ */
+class PerThreadReviewDbModule extends LifecycleModule {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ PerThreadReviewDbModule(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ protected void configure() {
+ final List<ReviewDb> dbs = Collections.synchronizedList(
+ Lists.<ReviewDb> newArrayList());
+ final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
+
+ bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
+ @Override
+ public ReviewDb get() {
+ ReviewDb db = localDb.get();
+ if (db == null) {
+ try {
+ db = schema.open();
+ dbs.add(db);
+ localDb.set(db);
+ } catch (OrmException e) {
+ throw new ProvisionException("unable to open ReviewDb", e);
+ }
+ }
+ return db;
+ }
+ });
+ listener().toInstance(new LifecycleListener() {
+ @Override
+ public void start() {
+ // Do nothing.
+ }
+
+ @Override
+ public void stop() {
+ for (ReviewDb db : dbs) {
+ db.close();
+ }
+ }
+ });
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java
new file mode 100644
index 0000000..44361aa
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2014 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;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.schema.DataSourceProvider;
+import com.google.gerrit.server.schema.DataSourceType;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// TODO(dborowitz): Not necessary once we switch to notedb.
+/** Utility to limit threads used by a batch program. */
+public class ThreadLimiter {
+ private static final Logger log =
+ LoggerFactory.getLogger(ThreadLimiter.class);
+
+ public static int limitThreads(Injector dbInjector, int threads) {
+ return limitThreads(
+ dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)),
+ dbInjector.getInstance(DataSourceType.class),
+ threads);
+ }
+
+ private static int limitThreads(Config cfg, DataSourceType dst, int threads) {
+ boolean usePool = cfg.getBoolean("database", "connectionpool",
+ dst.usePool());
+ int poolLimit = cfg.getInt("database", "poollimit",
+ DataSourceProvider.DEFAULT_POOL_LIMIT);
+ if (usePool && threads > poolLimit) {
+ log.warn("Limiting program to " + poolLimit
+ + " threads due to database.poolLimit");
+ return poolLimit;
+ }
+ return threads;
+ }
+
+ private ThreadLimiter() {
+ }
+}
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
similarity index 100%
rename from gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
rename to gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
similarity index 100%
rename from gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
rename to gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
index 37eeda5..a37c97d 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
@@ -17,12 +17,13 @@
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertNotNull;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Provider;
+
import org.junit.Test;
-import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.FileNotFoundException;
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
index 26c4382..c6da0f0 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
@@ -24,7 +24,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import org.eclipse.jgit.errors.ConfigInvalidException;
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 1afb610..40767e0 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/gerrit-plugin-api/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-api</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin API</name>
<description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index 1c321bf..b117b29 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-archetype</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<name>Gerrit Code Review - Plugin Archetype</name>
<description>Maven Archetype for Gerrit Plugins</description>
<url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
index 854d661..a7f2bbf 100644
--- a/gerrit-plugin-gwt-archetype/pom.xml
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-gwt-archetype</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<name>Gerrit Code Review - Web UI GWT Plugin Archetype</name>
<description>Maven Archetype for Gerrit Web UI GWT Plugins</description>
<url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
index 95da330..fd6fc9b 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-gwtui</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin GWT UI</name>
<description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
index 8278280..89bb026 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
@@ -15,8 +15,6 @@
package com.google.gerrit.plugin.rebind;
-import java.io.PrintWriter;
-
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
@@ -27,6 +25,8 @@
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
+import java.io.PrintWriter;
+
/**
* Write the top layer in the Gadget bootstrap sandwich and generate a stub
* manifest that will be completed by the linker.
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
index 35c5154..796df19 100644
--- a/gerrit-plugin-js-archetype/pom.xml
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-js-archetype</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name>
<description>Maven Archetype for Gerrit Web UI JavaScript Plugins</description>
<url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK
index faf80a8..9b1991b 100644
--- a/gerrit-reviewdb/BUCK
+++ b/gerrit-reviewdb/BUCK
@@ -1,4 +1,5 @@
SRC = 'src/main/java/com/google/gerrit/reviewdb/'
+TESTS = 'src/test/java/com/google/gerrit/reviewdb/'
gwt_module(
name = 'client',
@@ -22,3 +23,15 @@
],
visibility = ['PUBLIC'],
)
+
+java_test(
+ name = 'client_tests',
+ srcs = glob([TESTS + 'client/**/*.java']),
+ deps = [
+ ':client',
+ '//lib:gwtorm',
+ '//lib:junit',
+ ],
+ source_under_test = [':client'],
+ visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
index e131f7a..3d156f9 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
@@ -104,6 +104,55 @@
r.fromString(str);
return r;
}
+
+ /**
+ * Parse an Account.Id out of a part of a ref-name.
+ *
+ * @param name a ref name with the following syntax: {@code "34/1234..."}.
+ * We assume that the caller has trimmed any prefix.
+ */
+ public static Id fromRefPart(String name) {
+ if (name == null) {
+ return null;
+ }
+
+ String[] parts = name.split("/");
+ int n = parts.length;
+ if (n < 2) {
+ return null;
+ }
+
+ // Last 2 digits.
+ int le;
+ for (le = 0; le < parts[0].length(); le++) {
+ if (!Character.isDigit(parts[0].charAt(le))) {
+ return null;
+ }
+ }
+ if (le != 2) {
+ return null;
+ }
+
+ // Full ID.
+ int ie;
+ for (ie = 0; ie < parts[1].length(); ie++) {
+ if (!Character.isDigit(parts[1].charAt(ie))) {
+ if (ie == 0) {
+ return null;
+ } else {
+ break;
+ }
+ }
+ }
+
+ int shard = Integer.parseInt(parts[0]);
+ int id = Integer.parseInt(parts[1].substring(0, ie));
+
+ if (id % 100 != shard) {
+ return null;
+ }
+ return new Account.Id(id);
+ }
}
@Column(id = 1)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index dbce048..94d2f64 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -14,6 +14,8 @@
package com.google.gerrit.reviewdb.client;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.IntKey;
import com.google.gwtorm.client.RowVersion;
@@ -128,8 +130,64 @@
return r;
}
- public static Id fromRef(final String ref) {
- return PatchSet.Id.fromRef(ref).getParentKey();
+ public static Id fromRef(String ref) {
+ int cs = startIndex(ref);
+ if (cs < 0) {
+ return null;
+ }
+ int ce = nextNonDigit(ref, cs);
+ if (ref.substring(ce).equals(RefNames.META_SUFFIX)
+ || PatchSet.Id.fromRef(ref, ce) >= 0) {
+ return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
+ }
+ return null;
+ }
+
+ static int startIndex(String ref) {
+ if (ref == null || !ref.startsWith(REFS_CHANGES)) {
+ return -1;
+ }
+
+ // Last 2 digits.
+ int ls = REFS_CHANGES.length();
+ int le = nextNonDigit(ref, ls);
+ if (le - ls != 2 || le >= ref.length() || ref.charAt(le) != '/') {
+ return -1;
+ }
+
+ // Change ID.
+ int cs = le + 1;
+ if (cs >= ref.length() || ref.charAt(cs) == '0') {
+ return -1;
+ }
+ int ce = nextNonDigit(ref, cs);
+ if (ce >= ref.length() || ref.charAt(ce) != '/') {
+ return -1;
+ }
+ switch (ce - cs) {
+ case 0:
+ return -1;
+ case 1:
+ if (ref.charAt(ls) != '0'
+ || ref.charAt(ls + 1) != ref.charAt(cs)) {
+ return -1;
+ }
+ break;
+ default:
+ if (ref.charAt(ls) != ref.charAt(ce - 2)
+ || ref.charAt(ls + 1) != ref.charAt(ce - 1)) {
+ return -1;
+ }
+ break;
+ }
+ return cs;
+ }
+
+ static int nextNonDigit(String s, int i) {
+ while (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9') {
+ i++;
+ }
+ return i;
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
index 613978a..5d6f119 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
@@ -24,28 +24,8 @@
/** A single revision of a {@link Change}. */
public final class PatchSet {
/** Is the reference name a change reference? */
- public static boolean isRef(final String name) {
- if (name == null || !name.startsWith(REFS_CHANGES)) {
- return false;
- }
- boolean accepted = false;
- int numsFound = 0;
- for (int i = name.length() - 1; i >= REFS_CHANGES.length() - 1; i--) {
- char c = name.charAt(i);
- if (c >= '0' && c <= '9') {
- accepted = (c != '0');
- } else if (c == '/') {
- if (accepted) {
- if (++numsFound == 2) {
- return true;
- }
- accepted = false;
- }
- } else {
- return false;
- }
- }
- return false;
+ public static boolean isRef(String name) {
+ return Id.fromRef(name) != null;
}
public static class Id extends IntKey<Change.Id> {
@@ -105,19 +85,33 @@
}
/** Parse a PatchSet.Id from a {@link PatchSet#getRefName()} result. */
- public static Id fromRef(String name) {
- if (!name.startsWith(REFS_CHANGES)) {
- throw new IllegalArgumentException("Not a PatchSet.Id: " + name);
+ public static Id fromRef(String ref) {
+ int cs = Change.Id.startIndex(ref);
+ if (cs < 0) {
+ return null;
}
- final String[] parts = name.substring(REFS_CHANGES.length()).split("/");
- final int n = parts.length;
- if (n < 2) {
- throw new IllegalArgumentException("Not a PatchSet.Id: " + name);
+ int ce = Change.Id.nextNonDigit(ref, cs);
+ int patchSetId = fromRef(ref, ce);
+ if (patchSetId < 0) {
+ return null;
}
- final int changeId = Integer.parseInt(parts[n - 2]);
- final int patchSetId = Integer.parseInt(parts[n - 1]);
+ int changeId = Integer.parseInt(ref.substring(cs, ce));
return new PatchSet.Id(new Change.Id(changeId), patchSetId);
}
+
+ static int fromRef(String ref, int changeIdEnd) {
+ // Patch set ID.
+ int ps = changeIdEnd + 1;
+ if (ps >= ref.length() || ref.charAt(ps) == '0') {
+ return -1;
+ }
+ for (int i = ps; i < ref.length(); i++) {
+ if (ref.charAt(i) < '0' || ref.charAt(i) > '9') {
+ return -1;
+ }
+ }
+ return Integer.parseInt(ref.substring(ps));
+ }
}
@Column(id = 1, name = Column.NONE)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index 968cfde..9fdb560 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -31,6 +31,8 @@
/** Configurations of project-specific dashboards (canned search queries). */
public static final String REFS_DASHBOARDS = "refs/meta/dashboards/";
+ public static final String REFS_DRAFT_COMMENTS = "refs/draft-comments/";
+
/**
* Prefix applied to merge commit base nodes.
* <p>
@@ -43,6 +45,9 @@
*/
public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
+ /** Suffix of a meta ref in the notedb. */
+ public static final String META_SUFFIX = "/meta";
+
public static String refsUsers(Account.Id accountId) {
StringBuilder r = new StringBuilder();
r.append(REFS_USER);
@@ -57,6 +62,22 @@
return r.toString();
}
+ public static String refsDraftComments(Account.Id accountId,
+ Change.Id changeId) {
+ StringBuilder r = new StringBuilder();
+ r.append(REFS_DRAFT_COMMENTS);
+ int n = accountId.get() % 100;
+ if (n < 10) {
+ r.append('0');
+ }
+ r.append(n);
+ r.append('/');
+ r.append(accountId.get());
+ r.append('-');
+ r.append(changeId.get());
+ return r.toString();
+ }
+
private RefNames() {
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java
index ac2c849..47d8971 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java
@@ -14,7 +14,7 @@
package com.google.gerrit.reviewdb.server;
-import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.client.RevId;
@@ -33,7 +33,7 @@
ResultSet<PatchSetAncestor> ancestorsOf(PatchSet.Id id) throws OrmException;
@Query("WHERE key.patchSetId.changeId = ?")
- ResultSet<PatchSetAncestor> byChange(Id id) throws OrmException;
+ ResultSet<PatchSetAncestor> byChange(Change.Id id) throws OrmException;
@Query("WHERE key.patchSetId = ?")
ResultSet<PatchSetAncestor> byPatchSet(PatchSet.Id id) throws OrmException;
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
new file mode 100644
index 0000000..f5c75f0
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2014 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.reviewdb.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class AccountTest {
+ @Test
+ public void parseRefNameParts() {
+ assertRefPart(1, "01/1");
+ assertRefPart(1, "01/1-drafts");
+ assertRefPart(1, "01/1-drafts/2");
+
+ assertNotRefPart(null);
+ assertNotRefPart("");
+
+ // This method assumes that the common prefix "refs/users/" will be removed.
+ assertNotRefPart("refs/users/01/1");
+
+ // Invalid characters.
+ assertNotRefPart("01a/1");
+ assertNotRefPart("01/a1");
+
+ // Mismatched shard.
+ assertNotRefPart("01/23");
+
+ // Shard too short.
+ assertNotRefPart("1/1");
+ }
+
+ private static void assertRefPart(int accountId, String refName) {
+ assertEquals(new Account.Id(accountId), Account.Id.fromRefPart(refName));
+ }
+
+ private static void assertNotRefPart(String refName) {
+ assertNull(Account.Id.fromRefPart(refName));
+ }
+}
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
new file mode 100644
index 0000000..218d04f
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2014 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.reviewdb.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class ChangeTest {
+ @Test
+ public void parseInvalidRefNames() {
+ assertNotRef(null);
+ assertNotRef("");
+ assertNotRef("01/1/1");
+ assertNotRef("HEAD");
+ assertNotRef("refs/tags/v1");
+ }
+
+ @Test
+ public void parsePatchSetRefNames() {
+ assertRef(1, "refs/changes/01/1/1");
+ assertRef(1234, "refs/changes/34/1234/56");
+
+ // Invalid characters.
+ assertNotRef("refs/changes/0x/1/1");
+ assertNotRef("refs/changes/01/x/1");
+ assertNotRef("refs/changes/01/1/x");
+
+ // Truncations.
+ assertNotRef("refs/changes/");
+ assertNotRef("refs/changes/1");
+ assertNotRef("refs/changes/01");
+ assertNotRef("refs/changes/01/");
+ assertNotRef("refs/changes/01/1/");
+ assertNotRef("refs/changes/01/1/1/");
+ assertNotRef("refs/changes/01//1/1");
+
+ // Leading zeroes.
+ assertNotRef("refs/changes/01/01/1");
+ assertNotRef("refs/changes/01/1/01");
+
+ // Mismatched last 2 digits.
+ assertNotRef("refs/changes/35/1234/56");
+
+ // Something other than patch set after change.
+ assertNotRef("refs/changes/34/1234/0");
+ assertNotRef("refs/changes/34/1234/foo");
+ assertNotRef("refs/changes/34/1234|56");
+ assertNotRef("refs/changes/34/1234foo");
+ }
+
+ @Test
+ public void parseChangeMetaRefNames() {
+ assertRef(1, "refs/changes/01/1/meta");
+ assertRef(1234, "refs/changes/34/1234/meta");
+
+ assertNotRef("refs/changes/01/1/met");
+ assertNotRef("refs/changes/01/1/META");
+ assertNotRef("refs/changes/01/1/1/meta");
+ }
+
+ private static void assertRef(int changeId, String refName) {
+ assertEquals(new Change.Id(changeId), Change.Id.fromRef(refName));
+ }
+
+ private static void assertNotRef(String refName) {
+ assertNull(Change.Id.fromRef(refName));
+ }
+}
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java
new file mode 100644
index 0000000..33da24a
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2014 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.reviewdb.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class PatchSetTest {
+ @Test
+ public void parseRefNames() {
+ assertRef(1, 1, "refs/changes/01/1/1");
+ assertRef(1234, 56, "refs/changes/34/1234/56");
+
+ // Not even close.
+ assertNotRef(null);
+ assertNotRef("");
+ assertNotRef("01/1/1");
+ assertNotRef("HEAD");
+ assertNotRef("refs/tags/v1");
+
+ // Invalid characters.
+ assertNotRef("refs/changes/0x/1/1");
+ assertNotRef("refs/changes/01/x/1");
+ assertNotRef("refs/changes/01/1/x");
+
+ // Truncations.
+ assertNotRef("refs/changes/");
+ assertNotRef("refs/changes/1");
+ assertNotRef("refs/changes/01");
+ assertNotRef("refs/changes/01/");
+ assertNotRef("refs/changes/01/1/");
+ assertNotRef("refs/changes/01/1/1/");
+ assertNotRef("refs/changes/01//1/1");
+
+ // Leading zeroes.
+ assertNotRef("refs/changes/01/01/1");
+ assertNotRef("refs/changes/01/1/01");
+
+ // Mismatched last 2 digits.
+ assertNotRef("refs/changes/35/1234/56");
+
+ // Something other than patch set after change.
+ assertNotRef("refs/changes/34/1234/0");
+ assertNotRef("refs/changes/34/1234/foo");
+ assertNotRef("refs/changes/34/1234|56");
+ assertNotRef("refs/changes/34/1234foo");
+ }
+
+ private static void assertRef(int changeId, int psId, String refName) {
+ assertTrue(PatchSet.isRef(refName));
+ assertEquals(new PatchSet.Id(new Change.Id(changeId), psId),
+ PatchSet.Id.fromRef(refName));
+ }
+
+ private static void assertNotRef(String refName) {
+ assertFalse(PatchSet.isRef(refName));
+ assertNull(PatchSet.Id.fromRef(refName));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
index dc870ac..89b51f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
@@ -22,6 +22,7 @@
@Override
protected void configure() {
DynamicSet.setOf(binder(), AuditListener.class);
+ DynamicSet.setOf(binder(), GroupMemberAuditListener.class);
bind(AuditService.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
index a992aa1..4844045 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
@@ -15,16 +15,29 @@
package com.google.gerrit.audit;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
@Singleton
public class AuditService {
+ private static final Logger log = LoggerFactory.getLogger(AuditService.class);
+
private final DynamicSet<AuditListener> auditListeners;
+ private final DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners;
@Inject
- public AuditService(DynamicSet<AuditListener> auditListeners) {
+ public AuditService(DynamicSet<AuditListener> auditListeners,
+ DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners) {
this.auditListeners = auditListeners;
+ this.groupMemberAuditListeners = groupMemberAuditListeners;
}
public void dispatch(AuditEvent action) {
@@ -32,4 +45,48 @@
auditListener.onAuditableAction(action);
}
}
+
+ public void dispatchAddAccountsToGroup(Account.Id actor,
+ Collection<AccountGroupMember> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onAddAccountsToGroup(actor, added);
+ } catch (RuntimeException e) {
+ log.error("failed to log add accounts to group event", e);
+ }
+ }
+ }
+
+ public void dispatchDeleteAccountsFromGroup(Account.Id actor,
+ Collection<AccountGroupMember> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onDeleteAccountsFromGroup(actor, removed);
+ } catch (RuntimeException e) {
+ log.error("failed to log delete accounts from group event", e);
+ }
+ }
+ }
+
+ public void dispatchAddGroupsToGroup(Account.Id actor,
+ Collection<AccountGroupById> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onAddGroupsToGroup(actor, added);
+ } catch (RuntimeException e) {
+ log.error("failed to log add groups to group event", e);
+ }
+ }
+ }
+
+ public void dispatchDeleteGroupsFromGroup(Account.Id actor,
+ Collection<AccountGroupById> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onDeleteGroupsFromGroup(actor, removed);
+ } catch (RuntimeException e) {
+ log.error("failed to log delete groups from group event", e);
+ }
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java
new file mode 100644
index 0000000..1269f4a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 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.audit;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+
+import java.util.Collection;
+
+@ExtensionPoint
+public interface GroupMemberAuditListener {
+
+ void onAddAccountsToGroup(Account.Id actor,
+ Collection<AccountGroupMember> added);
+
+ void onDeleteAccountsFromGroup(Account.Id actor,
+ Collection<AccountGroupMember> removed);
+
+ void onAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added);
+
+ void onDeleteGroupsFromGroup(Account.Id actor,
+ Collection<AccountGroupById> deleted);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index dd68296..d64b7d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -17,8 +17,8 @@
import com.google.gerrit.common.ChangeHookRunner.HookResult;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch.NameKey;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Branch.NameKey;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 900bbdd..d507360 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -119,7 +119,7 @@
*/
public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
ReviewDb db, ChangeNotes notes) throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return getReviewers(db.patchSetApprovals().byChange(notes.getChangeId()));
}
return notes.load().getReviewers();
@@ -137,7 +137,7 @@
public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
ChangeNotes notes, Iterable<PatchSetApproval> allApprovals)
throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return getReviewers(allApprovals);
}
return notes.load().getReviewers();
@@ -269,7 +269,7 @@
public ListMultimap<PatchSet.Id, PatchSetApproval> byChange(ReviewDb db,
ChangeNotes notes) throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
ImmutableListMultimap.Builder<PatchSet.Id, PatchSetApproval> result =
ImmutableListMultimap.builder();
for (PatchSetApproval psa
@@ -283,7 +283,7 @@
public Iterable<PatchSetApproval> byPatchSet(ReviewDb db, ChangeControl ctl,
PatchSet.Id psId) throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return sortApprovals(db.patchSetApprovals().byPatchSet(psId));
}
return copier.getForPatchSet(db, ctl, psId);
@@ -292,7 +292,7 @@
public Iterable<PatchSetApproval> byPatchSetUser(ReviewDb db,
ChangeControl ctl, PatchSet.Id psId, Account.Id accountId)
throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return sortApprovals(
db.patchSetApprovals().byPatchSetUser(psId, accountId));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 72fd1a1..6a44219 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -50,7 +50,7 @@
}
public List<ChangeMessage> byChange(ReviewDb db, ChangeNotes notes) throws OrmException {
- if (!migration.readChangeMessages()) {
+ if (!migration.readChanges()) {
return
sortChangeMessages(db.changeMessages().byChange(notes.getChangeId()));
} else {
@@ -60,7 +60,7 @@
public List<ChangeMessage> byPatchSet(ReviewDb db, ChangeNotes notes,
PatchSet.Id psId) throws OrmException {
- if (!migration.readChangeMessages()) {
+ if (!migration.readChanges()) {
return sortChangeMessages(db.changeMessages().byPatchSet(psId));
}
return notes.load().getChangeMessages().get(psId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 80213f6..22560e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -508,8 +508,8 @@
ReviewDb db = this.db.get();
db.accountPatchReviews().delete(db.accountPatchReviews().byPatchSet(patchSetId));
db.changeMessages().delete(db.changeMessages().byPatchSet(patchSetId));
- db.patchComments().delete(db.patchComments().byPatchSet(patchSetId));
// No need to delete from notedb; draft patch sets will be filtered out.
+ db.patchComments().delete(db.patchComments().byPatchSet(patchSetId));
db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(patchSetId));
db.patchSetAncestors().delete(db.patchSetAncestors().byPatchSet(patchSetId));
@@ -520,7 +520,7 @@
return (IdentifiedUser) userProvider.get();
}
- private static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
+ public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
return new PatchSet.Id(id.getParentKey(), id.get() + 1);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
index d10366e..956a0d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
@@ -32,6 +32,7 @@
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.gerrit.util.cli.OptionHandlers;
+
import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.args4j.spi.OptionHandler;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java b/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java
new file mode 100644
index 0000000..be07bde
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 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;
+
+import com.google.gerrit.extensions.common.GitPerson;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.sql.Timestamp;
+
+/**
+ * Converters to classes in {@code com.google.gerrit.extensions.common}.
+ * <p>
+ * The server frequently needs to convert internal types to types exposed in the
+ * extension API, but the converters themselves are not part of this API. This
+ * class contains such converters as static utility methods.
+ */
+public class CommonConverters {
+ public static GitPerson toGitPerson(PersonIdent ident) {
+ GitPerson result = new GitPerson();
+ result.name = ident.getName();
+ result.email = ident.getEmailAddress();
+ result.date = new Timestamp(ident.getWhen().getTime());
+ result.tz = ident.getTimeZoneOffset();
+ return result;
+ }
+
+ private CommonConverters() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
index 4918546..e8420b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
@@ -12,25 +12,46 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-
package com.google.gerrit.server;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
+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.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.DraftCommentNotes;
import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Utility functions to manipulate PatchLineComments.
@@ -40,44 +61,232 @@
*/
@Singleton
public class PatchLineCommentsUtil {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final DraftCommentNotes.Factory draftFactory;
private final NotesMigration migration;
@VisibleForTesting
@Inject
- public PatchLineCommentsUtil(NotesMigration migration) {
+ public PatchLineCommentsUtil(GitRepositoryManager repoManager,
+ AllUsersNameProvider allUsersProvider,
+ DraftCommentNotes.Factory draftFactory,
+ NotesMigration migration) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsersProvider.get();
+ this.draftFactory = draftFactory;
this.migration = migration;
}
+ public Optional<PatchLineComment> get(ReviewDb db, ChangeNotes notes,
+ PatchLineComment.Key key) throws OrmException {
+ if (!migration.readChanges()) {
+ return Optional.fromNullable(db.patchComments().get(key));
+ }
+ for (PatchLineComment c : publishedByChange(db, notes)) {
+ if (key.equals(c.getKey())) {
+ return Optional.of(c);
+ }
+ }
+ for (PatchLineComment c : draftByChange(db, notes)) {
+ if (key.equals(c.getKey())) {
+ return Optional.of(c);
+ }
+ }
+ return Optional.absent();
+ }
+
+ public List<PatchLineComment> publishedByChange(ReviewDb db,
+ ChangeNotes notes) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(byCommentStatus(
+ db.patchComments().byChange(notes.getChangeId()), Status.PUBLISHED));
+ }
+
+ notes.load();
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(notes.getBaseComments().values());
+ comments.addAll(notes.getPatchSetComments().values());
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByChange(ReviewDb db,
+ ChangeNotes notes) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(byCommentStatus(
+ db.patchComments().byChange(notes.getChangeId()), Status.DRAFT));
+ }
+
+ List<PatchLineComment> comments = Lists.newArrayList();
+ Iterable<String> filtered = getDraftRefs(notes.getChangeId());
+ for (String refName : filtered) {
+ Account.Id account = Account.Id.fromRefPart(refName);
+ if (account != null) {
+ comments.addAll(draftByChangeAuthor(db, notes, account));
+ }
+ }
+ return sort(comments);
+ }
+
+ private static List<PatchLineComment> byCommentStatus(
+ ResultSet<PatchLineComment> comments,
+ final PatchLineComment.Status status) {
+ return Lists.newArrayList(
+ Iterables.filter(comments, new Predicate<PatchLineComment>() {
+ @Override
+ public boolean apply(PatchLineComment input) {
+ return (input.getStatus() == status);
+ }
+ })
+ );
+ }
+
+ public List<PatchLineComment> byPatchSet(ReviewDb db,
+ ChangeNotes notes, PatchSet.Id psId) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(db.patchComments().byPatchSet(psId).toList());
+ }
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(publishedByPatchSet(db, notes, psId));
+
+ Iterable<String> filtered = getDraftRefs(notes.getChangeId());
+ for (String refName : filtered) {
+ Account.Id account = Account.Id.fromRefPart(refName);
+ if (account != null) {
+ comments.addAll(draftByPatchSetAuthor(db, psId, account, notes));
+ }
+ }
+ return sort(comments);
+ }
+
public List<PatchLineComment> publishedByChangeFile(ReviewDb db,
ChangeNotes notes, Change.Id changeId, String file) throws OrmException {
- if (!migration.readPublishedComments()) {
- return db.patchComments().publishedByChangeFile(changeId, file).toList();
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments().publishedByChangeFile(changeId, file).toList());
}
notes.load();
- List<PatchLineComment> commentsOnFile = new ArrayList<PatchLineComment>();
+ List<PatchLineComment> comments = Lists.newArrayList();
- // We must iterate through all comments to find the ones on this file.
- addCommentsInFile(commentsOnFile, notes.getBaseComments().values(), file);
- addCommentsInFile(commentsOnFile, notes.getPatchSetComments().values(),
+ addCommentsOnFile(comments, notes.getBaseComments().values(), file);
+ addCommentsOnFile(comments, notes.getPatchSetComments().values(),
file);
-
- Collections.sort(commentsOnFile, ChangeNotes.PatchLineCommentComparator);
- return commentsOnFile;
+ return sort(comments);
}
public List<PatchLineComment> publishedByPatchSet(ReviewDb db,
ChangeNotes notes, PatchSet.Id psId) throws OrmException {
- if (!migration.readPublishedComments()) {
- return db.patchComments().publishedByPatchSet(psId).toList();
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments().publishedByPatchSet(psId).toList());
}
notes.load();
- List<PatchLineComment> commentsOnPs = new ArrayList<PatchLineComment>();
- commentsOnPs.addAll(notes.getPatchSetComments().get(psId));
- commentsOnPs.addAll(notes.getBaseComments().get(psId));
- return commentsOnPs;
+ List<PatchLineComment> comments = new ArrayList<PatchLineComment>();
+ comments.addAll(notes.getPatchSetComments().get(psId));
+ comments.addAll(notes.getBaseComments().get(psId));
+ return sort(comments);
}
- private static Collection<PatchLineComment> addCommentsInFile(
+ public List<PatchLineComment> draftByPatchSetAuthor(ReviewDb db,
+ PatchSet.Id psId, Account.Id author, ChangeNotes notes)
+ throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments().draftByPatchSetAuthor(psId, author).toList());
+ }
+
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(notes.getDraftBaseComments(author).row(psId).values());
+ comments.addAll(notes.getDraftPsComments(author).row(psId).values());
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByChangeFileAuthor(ReviewDb db,
+ ChangeNotes notes, String file, Account.Id author)
+ throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments()
+ .draftByChangeFileAuthor(notes.getChangeId(), file, author)
+ .toList());
+ }
+ List<PatchLineComment> comments = Lists.newArrayList();
+ addCommentsOnFile(comments, notes.getDraftBaseComments(author).values(),
+ file);
+ addCommentsOnFile(comments, notes.getDraftPsComments(author).values(),
+ file);
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByChangeAuthor(ReviewDb db,
+ ChangeNotes notes, Account.Id author)
+ throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(db.patchComments().byChange(notes.getChangeId()).toList());
+ }
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(notes.getDraftBaseComments(author).values());
+ comments.addAll(notes.getDraftPsComments(author).values());
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByAuthor(ReviewDb db,
+ Account.Id author) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(db.patchComments().draftByAuthor(author).toList());
+ }
+
+ Set<String> refNames =
+ getRefNamesAllUsers(RefNames.REFS_DRAFT_COMMENTS);
+
+ List<PatchLineComment> comments = Lists.newArrayList();
+ for (String refName : refNames) {
+ Account.Id id = Account.Id.fromRefPart(refName);
+ if (!author.equals(id)) {
+ continue;
+ }
+ Change.Id changeId = Change.Id.parse(refName);
+ DraftCommentNotes draftNotes =
+ draftFactory.create(changeId, author).load();
+ comments.addAll(draftNotes.getDraftBaseComments().values());
+ comments.addAll(draftNotes.getDraftPsComments().values());
+ }
+ return sort(comments);
+ }
+
+ public void insertComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.insertComment(c);
+ }
+ db.patchComments().insert(comments);
+ }
+
+ public void upsertComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.upsertComment(c);
+ }
+ db.patchComments().upsert(comments);
+ }
+
+ public void updateComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.updateComment(c);
+ }
+ db.patchComments().update(comments);
+ }
+
+ public void deleteComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.deleteComment(c);
+ }
+ db.patchComments().delete(comments);
+ }
+
+ private static Collection<PatchLineComment> addCommentsOnFile(
Collection<PatchLineComment> commentsOnFile,
Collection<PatchLineComment> allComments,
String file) {
@@ -90,11 +299,53 @@
return commentsOnFile;
}
- public void addPublishedComments(ReviewDb db, ChangeUpdate update,
- Iterable<PatchLineComment> comments) throws OrmException {
- for (PatchLineComment c : comments) {
- update.putComment(c);
+ public static void setCommentRevId(PatchLineComment c,
+ PatchListCache cache, Change change, PatchSet ps) throws OrmException {
+ if (c.getRevId() != null) {
+ return;
}
- db.patchComments().upsert(comments);
+ PatchList patchList;
+ try {
+ patchList = cache.get(change, ps);
+ } catch (PatchListNotAvailableException e) {
+ throw new OrmException(e);
+ }
+ c.setRevId((c.getSide() == (short) 0)
+ ? new RevId(ObjectId.toString(patchList.getOldId()))
+ : new RevId(ObjectId.toString(patchList.getNewId())));
+ }
+
+ private Set<String> getRefNamesAllUsers(String prefix) throws OrmException {
+ Repository repo;
+ try {
+ repo = repoManager.openRepository(allUsers);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ try {
+ RefDatabase refDb = repo.getRefDatabase();
+ return refDb.getRefs(prefix).keySet();
+ } catch (IOException e) {
+ throw new OrmException(e);
+ } finally {
+ repo.close();
+ }
+ }
+
+ private Iterable<String> getDraftRefs(final Change.Id changeId)
+ throws OrmException {
+ Set<String> refNames = getRefNamesAllUsers(RefNames.REFS_DRAFT_COMMENTS);
+ final String suffix = "-" + changeId.get();
+ return Iterables.filter(refNames, new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ return input.endsWith(suffix);
+ }
+ });
+ }
+
+ private static List<PatchLineComment> sort(List<PatchLineComment> comments) {
+ Collections.sort(comments, ChangeNotes.PatchLineCommentComparator);
+ return comments;
}
}
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 77ebe0f..e31e2e6 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
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
@@ -23,7 +24,6 @@
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectCache;
@@ -53,6 +53,7 @@
private final ChangeUserName.Factory changeUserNameFactory;
private final ProjectCache projectCache;
private final AtomicBoolean awaitsFirstAccountCheck;
+ private final AuditService auditService;
@Inject
AccountManager(final SchemaFactory<ReviewDb> schema,
@@ -60,7 +61,8 @@
final Realm accountMapper,
final IdentifiedUser.GenericFactory userFactory,
final ChangeUserName.Factory changeUserNameFactory,
- final ProjectCache projectCache) throws OrmException {
+ final ProjectCache projectCache,
+ final AuditService auditService) throws OrmException {
this.schema = schema;
this.byIdCache = byIdCache;
this.byEmailCache = byEmailCache;
@@ -69,6 +71,7 @@
this.changeUserNameFactory = changeUserNameFactory;
this.projectCache = projectCache;
this.awaitsFirstAccountCheck = new AtomicBoolean(true);
+ this.auditService = auditService;
}
/**
@@ -227,8 +230,7 @@
final AccountGroup.Id adminId = g.getId();
final AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(newId, adminId));
- db.accountGroupMembersAudit().insert(Collections.singleton(
- new AccountGroupMemberAudit(m, newId, TimeUtil.nowTs())));
+ auditService.dispatchAddAccountsToGroup(newId, Collections.singleton(m));
db.accountGroupMembers().insert(Collections.singleton(m));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 3c21d17..58c674c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -63,7 +63,7 @@
public Response<SshKeyInfo> apply(AccountResource rsrc, Input input)
throws AuthException, BadRequestException, OrmException, IOException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to add SSH keys");
}
return apply(rsrc.getUser(), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index f0f22b5..631256a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -110,6 +110,12 @@
|| canAdministrateServer();
}
+ /** @return true if the user can modify an account for another user. */
+ public boolean canModifyAccount() {
+ return canPerform(GlobalCapability.MODIFY_ACCOUNT)
+ || canAdministrateServer();
+ }
+
/** @return true if the user can view all accounts. */
public boolean canViewAllAccounts() {
return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
index 6b68032..1cb27f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
@@ -18,7 +18,6 @@
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.Provider;
import org.slf4j.Logger;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 2d42f0d..7537c70 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import com.google.common.collect.Sets;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.errors.InvalidSshKeyException;
@@ -30,7 +31,6 @@
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
@@ -74,13 +74,14 @@
private final AccountByEmailCache byEmailCache;
private final AccountInfo.Loader.Factory infoLoader;
private final String username;
+ private final AuditService auditService;
@Inject
CreateAccount(ReviewDb db, Provider<IdentifiedUser> currentUser,
GroupsCollection groupsCollection, SshKeyCache sshKeyCache,
AccountCache accountCache, AccountByEmailCache byEmailCache,
AccountInfo.Loader.Factory infoLoader,
- @Assisted String username) {
+ @Assisted String username, AuditService auditService) {
this.db = db;
this.currentUser = currentUser;
this.groupsCollection = groupsCollection;
@@ -89,6 +90,7 @@
this.byEmailCache = byEmailCache;
this.infoLoader = infoLoader;
this.username = username;
+ this.auditService = auditService;
}
@Override
@@ -169,9 +171,8 @@
for (AccountGroup.Id groupId : groups) {
AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
- db.accountGroupMembersAudit().insert(Collections.singleton(
- new AccountGroupMemberAudit(
- m, currentUser.get().getAccountId(), TimeUtil.nowTs())));
+ auditService.dispatchAddAccountsToGroup(currentUser.get().getAccountId(),
+ Collections.singleton(m));
db.accountGroupMembers().insert(Collections.singleton(m));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
index 4be8067..441213d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -23,8 +23,8 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.CreateEmail.Input;
@@ -85,7 +85,7 @@
ResourceNotFoundException, OrmException, EmailException,
MethodNotAllowedException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to add email address");
}
@@ -98,8 +98,8 @@
}
if (input.noConfirmation
- && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("must be administrator to use no_confirmation");
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("not allowed to use no_confirmation");
}
return apply(rsrc.getUser(), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
index 52ab651..abdaf23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
@@ -29,7 +29,7 @@
import java.util.Collections;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class DeleteActive implements RestModifyView<AccountResource, Input> {
public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index 6048586..f1e02bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -55,7 +55,7 @@
throws AuthException, ResourceNotFoundException,
ResourceConflictException, MethodNotAllowedException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to delete email address");
}
return apply(rsrc.getUser(), rsrc.getEmail());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 47047ed..4ac65ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -20,7 +20,9 @@
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
import static com.google.gerrit.common.data.GlobalCapability.EMAIL_REVIEWERS;
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
+import static com.google.gerrit.common.data.GlobalCapability.GENERATE_HTTP_PASSWORD;
import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
+import static com.google.gerrit.common.data.GlobalCapability.MODIFY_ACCOUNT;
import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
import static com.google.gerrit.common.data.GlobalCapability.RUN_GC;
import static com.google.gerrit.common.data.GlobalCapability.STREAM_EVENTS;
@@ -113,7 +115,9 @@
have.put(CREATE_PROJECT, cc.canCreateProject());
have.put(EMAIL_REVIEWERS, cc.canEmailReviewers());
have.put(FLUSH_CACHES, cc.canFlushCaches());
+ have.put(GENERATE_HTTP_PASSWORD, cc.canGenerateHttpPassword());
have.put(KILL_TASK, cc.canKillTask());
+ have.put(MODIFY_ACCOUNT, cc.canModifyAccount());
have.put(RUN_GC, cc.canRunGC());
have.put(STREAM_EVENTS, cc.canStreamEvents());
have.put(VIEW_ALL_ACCOUNTS, cc.canViewAllAccounts());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
index 64991e6..bf9c9ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
@@ -40,7 +40,7 @@
public List<EmailInfo> apply(AccountResource rsrc) throws AuthException,
OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to list email addresses");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
index c49ab98..e080015 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
@@ -36,7 +36,7 @@
public String apply(AccountResource rsrc) throws AuthException,
ResourceNotFoundException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canGenerateHttpPassword()) {
throw new AuthException("not allowed to get http password");
}
AccountState s = rsrc.getUser().state();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index ccc6e48..914f159 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -23,11 +23,11 @@
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
index 9266c3a..6846470 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -45,7 +45,7 @@
public List<SshKeyInfo> apply(AccountResource rsrc) throws AuthException,
OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to get SSH keys");
}
return apply(rsrc.getUser());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
index a54a97b..bd533aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
@@ -14,19 +14,17 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -52,12 +50,13 @@
private final PersonIdent serverIdent;
private final GroupCache groupCache;
private final CreateGroupArgs createGroupArgs;
+ private final AuditService auditService;
@Inject
PerformCreateGroup(ReviewDb db, AccountCache accountCache,
GroupIncludeCache groupIncludeCache, IdentifiedUser currentUser,
@GerritPersonIdent PersonIdent serverIdent, GroupCache groupCache,
- @Assisted CreateGroupArgs createGroupArgs) {
+ @Assisted CreateGroupArgs createGroupArgs, AuditService auditService) {
this.db = db;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
@@ -65,6 +64,7 @@
this.serverIdent = serverIdent;
this.groupCache = groupCache;
this.createGroupArgs = createGroupArgs;
+ this.auditService = auditService;
}
/**
@@ -127,18 +127,13 @@
private void addMembers(final AccountGroup.Id groupId,
final Collection<? extends Account.Id> members) throws OrmException {
List<AccountGroupMember> memberships = new ArrayList<>();
- List<AccountGroupMemberAudit> membershipsAudit = new ArrayList<>();
for (Account.Id accountId : members) {
final AccountGroupMember membership =
new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId));
memberships.add(membership);
-
- final AccountGroupMemberAudit audit = new AccountGroupMemberAudit(
- membership, currentUser.getAccountId(), TimeUtil.nowTs());
- membershipsAudit.add(audit);
}
db.accountGroupMembers().insert(memberships);
- db.accountGroupMembersAudit().insert(membershipsAudit);
+ auditService.dispatchAddAccountsToGroup(currentUser.getAccountId(), memberships);
for (Account.Id accountId : members) {
accountCache.evict(accountId);
@@ -148,18 +143,13 @@
private void addGroups(final AccountGroup.Id groupId,
final Collection<? extends AccountGroup.UUID> groups) throws OrmException {
List<AccountGroupById> includeList = new ArrayList<>();
- List<AccountGroupByIdAud> includesAudit = new ArrayList<>();
for (AccountGroup.UUID includeUUID : groups) {
final AccountGroupById groupInclude =
new AccountGroupById(new AccountGroupById.Key(groupId, includeUUID));
includeList.add(groupInclude);
-
- final AccountGroupByIdAud audit = new AccountGroupByIdAud(
- groupInclude, currentUser.getAccountId(), TimeUtil.nowTs());
- includesAudit.add(audit);
}
db.accountGroupById().insert(includeList);
- db.accountGroupByIdAud().insert(includesAudit);
+ auditService.dispatchAddGroupsToGroup(currentUser.getAccountId(), includeList);
for (AccountGroup.UUID uuid : groups) {
groupIncludeCache.evictMemberIn(uuid);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
index 69d16d8..c7a63e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
@@ -29,7 +29,7 @@
import java.util.Collections;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class PutActive implements RestModifyView<AccountResource, Input> {
public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index 3903050..93b35c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -86,14 +86,14 @@
} else if (input.httpPassword == null) {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canGenerateHttpPassword()) {
throw new AuthException("not allowed to clear HTTP password");
}
newPassword = null;
} else {
- if (!self.get().getCapabilities().canAdministrateServer()) {
+ if (!self.get().getCapabilities().canGenerateHttpPassword()) {
throw new AuthException("not allowed to set HTTP password directly, "
- + "need to be Gerrit administrator");
+ + "requires the Generate HTTP Password permission");
}
newPassword = input.httpPassword;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
index 554bae7..601ee76 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
@@ -64,7 +64,7 @@
throws AuthException, MethodNotAllowedException,
ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to change name");
}
return apply(rsrc.getUser(), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
index 7ac987d..c49e3be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
@@ -52,7 +52,7 @@
public Response<String> apply(AccountResource.Email rsrc, Input input)
throws AuthException, ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to set preferred email address");
}
return apply(rsrc.getUser(), rsrc.getEmail());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index 9b971e4..08386b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -68,8 +68,8 @@
public DiffPreferencesInfo apply(AccountResource rsrc, Input input)
throws AuthException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("restricted to administrator");
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("restricted to members of Modify Accounts");
}
if (input == null) {
input = new Input();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index c3cc636..a5e02d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -28,11 +28,11 @@
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -95,8 +95,8 @@
throws AuthException, ResourceNotFoundException, OrmException,
IOException, ConfigInvalidException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("restricted to administrator");
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("restricted to members of Modify Accounts");
}
if (i == null) {
i = new Input();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
index b94158f..b35c03e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
@@ -55,7 +55,7 @@
public AccountResource.SshKey parse(AccountResource rsrc, IdString id)
throws ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new ResourceNotFoundException();
}
return parse(rsrc.getUser(), id);
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 5f90d34..02c47f4 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
@@ -29,7 +29,9 @@
import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.GetTopic;
import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.change.PutTopic;
import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.Revert;
import com.google.gerrit.server.change.Revisions;
@@ -53,6 +55,8 @@
private final Abandon abandon;
private final Revert revert;
private final Restore restore;
+ private final GetTopic getTopic;
+ private final PutTopic putTopic;
private final Provider<PostReviewers> postReviewers;
private final Provider<ChangeJson> changeJson;
@@ -63,6 +67,8 @@
Abandon abandon,
Revert revert,
Restore restore,
+ GetTopic getTopic,
+ PutTopic putTopic,
Provider<PostReviewers> postReviewers,
Provider<ChangeJson> changeJson,
@Assisted ChangeResource change) {
@@ -72,6 +78,8 @@
this.revisionApi = revisionApi;
this.abandon = abandon;
this.restore = restore;
+ this.getTopic = getTopic;
+ this.putTopic = putTopic;
this.postReviewers = postReviewers;
this.changeJson = changeJson;
this.change = change;
@@ -145,6 +153,22 @@
}
@Override
+ public String topic() throws RestApiException {
+ return getTopic.apply(change);
+ }
+
+ @Override
+ public void topic(String topic) throws RestApiException {
+ PutTopic.Input in = new PutTopic.Input();
+ in.topic = topic;
+ try {
+ putTopic.apply(change, in);
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot set topic", e);
+ }
+ }
+
+ @Override
public void addReviewer(String reviewer) throws RestApiException {
AddReviewerInput in = new AddReviewerInput();
in.reviewer = reviewer;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 5a19814..cc61695 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -17,6 +17,7 @@
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountException;
@@ -37,6 +38,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -58,6 +60,42 @@
@Singleton class Helper {
static final String LDAP_UUID = "ldap:";
+ static private Map<String, String> getPoolProperties(Config config) {
+ if (LdapRealm.optional(config, "useConnectionPooling", false)) {
+ Map<String, String> r = Maps.newHashMap();
+ r.put("com.sun.jndi.ldap.connect.pool", "true");
+
+ String connectTimeout = LdapRealm.optional(config, "connectTimeout");
+ String poolDebug = LdapRealm.optional(config, "poolDebug");
+ String poolTimeout = LdapRealm.optional(config, "poolTimeout");
+
+ if (connectTimeout != null) {
+ r.put("com.sun.jndi.ldap.connect.timeout", Long.toString(ConfigUtil
+ .getTimeUnit(connectTimeout, 0, TimeUnit.MILLISECONDS)));
+ }
+ r.put("com.sun.jndi.ldap.connect.pool.authentication",
+ LdapRealm.optional(config, "poolAuthentication", "none simple"));
+ if (poolDebug != null) {
+ r.put("com.sun.jndi.ldap.connect.pool.debug", poolDebug);
+ }
+ r.put("com.sun.jndi.ldap.connect.pool.initsize",
+ String.valueOf(LdapRealm.optional(config, "poolInitsize", 1)));
+ r.put("com.sun.jndi.ldap.connect.pool.maxsize",
+ String.valueOf(LdapRealm.optional(config, "poolMaxsize", 0)));
+ r.put("com.sun.jndi.ldap.connect.pool.prefsize",
+ String.valueOf(LdapRealm.optional(config, "poolPrefsize", 0)));
+ r.put("com.sun.jndi.ldap.connect.pool.protocol",
+ LdapRealm.optional(config, "poolProtocol", "plain"));
+ if (poolTimeout != null) {
+ r.put("com.sun.jndi.ldap.connect.pool.timeout", Long
+ .toString(ConfigUtil.getTimeUnit(poolTimeout, 0,
+ TimeUnit.MILLISECONDS)));
+ }
+ return r;
+ }
+ return null;
+ }
+
private final Cache<String, ImmutableSet<String>> groupsByInclude;
private final Config config;
private final String server;
@@ -68,6 +106,7 @@
private final String authentication;
private volatile LdapSchema ldapSchema;
private final String readTimeOutMillis;
+ private final Map<String, String> connectionPoolConfig;
@Inject
Helper(@GerritServerConfig final Config config,
@@ -76,10 +115,11 @@
this.config = config;
this.server = LdapRealm.optional(config, "server");
this.username = LdapRealm.optional(config, "username");
- this.password = LdapRealm.optional(config, "password");
- this.referral = LdapRealm.optional(config, "referral");
+ this.password = LdapRealm.optional(config, "password", "");
+ this.referral = LdapRealm.optional(config, "referral", "ignore");
this.sslVerify = config.getBoolean("ldap", "sslverify", true);
- this.authentication = LdapRealm.optional(config, "authentication");
+ this.authentication =
+ LdapRealm.optional(config, "authentication", "simple");
String timeout = LdapRealm.optional(config, "readTimeout");
if (timeout != null) {
readTimeOutMillis =
@@ -89,6 +129,7 @@
readTimeOutMillis = null;
}
this.groupsByInclude = groupsByInclude;
+ this.connectionPoolConfig = getPoolProperties(config);
}
private Properties createContextProperties() {
@@ -107,14 +148,17 @@
DirContext open() throws NamingException, LoginException {
final Properties env = createContextProperties();
- env.put(Context.SECURITY_AUTHENTICATION, authentication != null ? authentication : "simple");
- env.put(Context.REFERRAL, referral != null ? referral : "ignore");
+ if (connectionPoolConfig != null) {
+ env.putAll(connectionPoolConfig);
+ }
+ env.put(Context.SECURITY_AUTHENTICATION, authentication);
+ env.put(Context.REFERRAL, referral);
if ("GSSAPI".equals(authentication)) {
return kerberosOpen(env);
} else {
if (username != null) {
env.put(Context.SECURITY_PRINCIPAL, username);
- env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
+ env.put(Context.SECURITY_CREDENTIALS, password);
}
return new InitialDirContext(env);
}
@@ -146,8 +190,8 @@
final Properties env = createContextProperties();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, dn);
- env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
- env.put(Context.REFERRAL, referral != null ? referral : "ignore");
+ env.put(Context.SECURITY_CREDENTIALS, password);
+ env.put(Context.REFERRAL, referral);
try {
return new InitialDirContext(env);
} catch (NamingException e) {
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 84b5277..22c60b4 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
@@ -106,6 +106,22 @@
return config.getString("ldap", null, name);
}
+ static int optional(Config config, String name, int defaultValue) {
+ return config.getInt("ldap", name, defaultValue);
+ }
+
+ static String optional(Config config, String name, String defaultValue) {
+ final String v = optional(config, name);
+ if (Strings.isNullOrEmpty(v)) {
+ return defaultValue;
+ }
+ return v;
+ }
+
+ static boolean optional(Config config, String name, boolean defaultValue) {
+ return config.getBoolean("ldap", name, defaultValue);
+ }
+
static String required(final Config config, final String name) {
final String v = optional(config, name);
if (v == null || "".equals(v)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
new file mode 100644
index 0000000..c57f5a0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Represents change edit resource, that is actualy two kinds of resources:
+ * <ul>
+ * <li>the change edit itself</li>
+ * <li>a path within the edit</li>
+ * </ul>
+ * distinguished by whether path is null or not.
+ */
+public class ChangeEditResource implements RestResource {
+ public static final TypeLiteral<RestView<ChangeEditResource>> CHANGE_EDIT_KIND =
+ new TypeLiteral<RestView<ChangeEditResource>>() {};
+
+ private final ChangeResource change;
+ private final ChangeEdit edit;
+ private final String path;
+
+ public ChangeEditResource(ChangeResource change, ChangeEdit edit,
+ String path) {
+ this.change = change;
+ this.edit = edit;
+ this.path = path;
+ }
+
+ // TODO(davido): Make this cacheable.
+ // Should just depend on the SHA-1 of the edit itself.
+ public boolean isCacheable() {
+ return false;
+ }
+
+ public ChangeResource getChangeResource() {
+ return change;
+ }
+
+ public ChangeControl getControl() {
+ return getChangeResource().getControl();
+ }
+
+ public Change getChange() {
+ return edit.getChange();
+ }
+
+ public ChangeEdit getChangeEdit() {
+ return edit;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ Account.Id getAccountId() {
+ return getUser().getAccountId();
+ }
+
+ IdentifiedUser getUser() {
+ return edit.getUser();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
new file mode 100644
index 0000000..246f6e6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -0,0 +1,434 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.io.ByteStreams;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AcceptsDelete;
+import com.google.gerrit.extensions.restapi.AcceptsPost;
+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.DefaultInput;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+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.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditJson;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.assistedinject.Assisted;
+
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+
+@Singleton
+public class ChangeEdits implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsCreate<ChangeResource>,
+ AcceptsPost<ChangeResource>,
+ AcceptsDelete<ChangeResource> {
+ private final DynamicMap<RestView<ChangeEditResource>> views;
+ private final Create.Factory createFactory;
+ private final DeleteEdit.Factory deleteEditFactory;
+ private final Provider<Detail> detail;
+ private final ChangeEditUtil editUtil;
+ private final Post post;
+
+ @Inject
+ ChangeEdits(DynamicMap<RestView<ChangeEditResource>> views,
+ Create.Factory createFactory,
+ Provider<Detail> detail,
+ ChangeEditUtil editUtil,
+ Post post,
+ DeleteEdit.Factory deleteEditFactory) {
+ this.views = views;
+ this.createFactory = createFactory;
+ this.detail = detail;
+ this.editUtil = editUtil;
+ this.post = post;
+ this.deleteEditFactory = deleteEditFactory;
+ }
+
+ @Override
+ public DynamicMap<RestView<ChangeEditResource>> views() {
+ return views;
+ }
+
+ @Override
+ public RestView<ChangeResource> list() {
+ return detail.get();
+ }
+
+ @Override
+ public ChangeEditResource parse(ChangeResource rsrc, IdString id)
+ throws ResourceNotFoundException, AuthException, IOException,
+ InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new ChangeEditResource(rsrc, edit.get(), id.get());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Create create(ChangeResource parent, IdString id)
+ throws RestApiException {
+ return createFactory.create(parent.getChange(), id.get());
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Post post(ChangeResource parent) throws RestApiException {
+ return post;
+ }
+
+ /**
+ * Create handler that is activated when collection element is accessed
+ * but doesn't exist, e. g. PUT request with a path was called but
+ * change edit wasn't created yet. Change edit is created and PUT
+ * handler is called.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public DeleteEdit delete(ChangeResource parent, IdString id)
+ throws RestApiException {
+ return deleteEditFactory.create(parent.getChange(),
+ id != null ? id.get() : null);
+ }
+
+ static class Create implements
+ RestModifyView<ChangeResource, Put.Input> {
+
+ interface Factory {
+ Create create(Change change, String path);
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+ private final Put putEdit;
+ private final Change change;
+ private final String path;
+
+ @Inject
+ Create(Provider<ReviewDb> db,
+ ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier,
+ Put putEdit,
+ @Assisted Change change,
+ @Assisted @Nullable String path) {
+ this.db = db;
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ this.putEdit = putEdit;
+ this.change = change;
+ this.path = path;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource resource, Put.Input input)
+ throws AuthException, IOException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ if (edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "edit already exists for the change %s",
+ resource.getChange().getChangeId()));
+ }
+ edit = createEdit();
+ if (!Strings.isNullOrEmpty(path)) {
+ putEdit.apply(new ChangeEditResource(resource, edit.get(), path),
+ input);
+ }
+ return Response.none();
+ }
+
+ private Optional<ChangeEdit> createEdit() throws AuthException,
+ IOException, ResourceConflictException, OrmException,
+ InvalidChangeOperationException {
+ editModifier.createEdit(change,
+ db.get().patchSets().get(change.currentPatchSetId()));
+ return editUtil.byChange(change);
+ }
+ }
+
+ static class DeleteEdit implements
+ RestModifyView<ChangeResource, DeleteEdit.Input> {
+ public static class Input {
+ }
+
+ interface Factory {
+ DeleteEdit create(Change change, String path);
+ }
+
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+ private final Provider<ReviewDb> db;
+ private final String path;
+
+ @Inject
+ DeleteEdit(ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier,
+ Provider<ReviewDb> db,
+ @Assisted @Nullable String path) {
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ this.db = db;
+ this.path = path;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, DeleteEdit.Input in)
+ throws IOException, AuthException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException, BadRequestException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (edit.isPresent() && path == null) {
+ // Edit is wiped out
+ editUtil.delete(edit.get());
+ } else if (!edit.isPresent() && path != null) {
+ // Edit is created on top of current patch set by deleting path.
+ // Even if the latest patch set changed since the user triggered
+ // the operation, deleting the whole file is probably still what
+ // they intended.
+ editModifier.createEdit(rsrc.getChange(), db.get().patchSets().get(
+ rsrc.getChange().currentPatchSetId()));
+ edit = editUtil.byChange(rsrc.getChange());
+ editModifier.deleteFile(edit.get(), path);
+ } else {
+ // Bad request
+ throw new BadRequestException(
+ "change edit doesn't exist and no path was provided");
+ }
+ return Response.none();
+ }
+ }
+
+ static class Detail implements RestReadView<ChangeResource> {
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditJson editJson;
+ private final FileInfoJson fileInfoJson;
+ private final Revisions revisions;
+
+ @Option(name = "--base", metaVar = "revision-id")
+ String base;
+
+ @Option(name = "--list", metaVar = "LIST")
+ boolean list;
+
+ @Inject
+ Detail(ChangeEditUtil editUtil,
+ ChangeEditJson editJson,
+ FileInfoJson fileInfoJson,
+ Revisions revisions) {
+ this.editJson = editJson;
+ this.editUtil = editUtil;
+ this.fileInfoJson = fileInfoJson;
+ this.revisions = revisions;
+ }
+
+ @Override
+ public Response<EditInfo> apply(ChangeResource rsrc) throws AuthException,
+ IOException, InvalidChangeOperationException,
+ ResourceNotFoundException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ return Response.none();
+ }
+
+ EditInfo editInfo = editJson.toEditInfo(edit.get());
+ if (list) {
+ PatchSet basePatchSet = null;
+ if (base != null) {
+ RevisionResource baseResource = revisions.parse(
+ rsrc, IdString.fromDecoded(base));
+ basePatchSet = baseResource.getPatchSet();
+ }
+ try {
+ editInfo.files =
+ fileInfoJson.toFileInfoMap(
+ rsrc.getChange(),
+ edit.get().getRevision(),
+ basePatchSet);
+ } catch (PatchListNotAvailableException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+ }
+ return Response.ok(editInfo);
+ }
+ }
+
+ /**
+ * Post to edit collection resource. Two different operations are
+ * supported:
+ * <ul>
+ * <li>Create non existing change edit</li>
+ * <li>Restore path in existing change edit</li>
+ * </ul>
+ * The combination of two operations in one request is supported.
+ */
+ @Singleton
+ public static class Post implements
+ RestModifyView<ChangeResource, Post.Input> {
+ public static class Input {
+ public String restorePath;
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ Post(Provider<ReviewDb> db,
+ ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier) {
+ this.db = db;
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource resource, Post.Input input)
+ throws AuthException, InvalidChangeOperationException, IOException,
+ ResourceConflictException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(resource.getChange());
+ if (!edit.isPresent()) {
+ edit = createEdit(resource.getChange());
+ }
+
+ if (input != null && !Strings.isNullOrEmpty(input.restorePath)) {
+ editModifier.restoreFile(edit.get(), input.restorePath);
+ }
+ return Response.none();
+ }
+
+ private Optional<ChangeEdit> createEdit(Change change)
+ throws AuthException, IOException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException {
+ editModifier.createEdit(change,
+ db.get().patchSets().get(change.currentPatchSetId()));
+ return editUtil.byChange(change);
+ }
+ }
+
+ /**
+ * Put handler that is activated when PUT request is called on
+ * collection element.
+ */
+ @Singleton
+ public static class Put implements
+ RestModifyView<ChangeEditResource, Put.Input> {
+ public static class Input {
+ @DefaultInput
+ public RawInput content;
+ }
+
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ Put(ChangeEditModifier editModifier) {
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc, Input input)
+ throws AuthException, ResourceConflictException, IOException {
+ try {
+ editModifier.modifyFile(rsrc.getChangeEdit(), rsrc.getPath(),
+ ByteStreams.toByteArray(input.content.getInputStream()));
+ } catch(InvalidChangeOperationException | IOException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ return Response.none();
+ }
+ }
+
+ /**
+ * Handler to delete a file.
+ * <p>
+ * This deletes the file from the repository completely. This is not the same
+ * as reverting or restoring a file to its previous contents.
+ */
+ @Singleton
+ static class DeleteContent implements
+ RestModifyView<ChangeEditResource, DeleteContent.Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ DeleteContent(ChangeEditModifier editModifier) {
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc, DeleteContent.Input input)
+ throws AuthException, ResourceConflictException {
+ try {
+ editModifier.deleteFile(rsrc.getChangeEdit(), rsrc.getPath());
+ } catch(InvalidChangeOperationException | IOException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ return Response.none();
+ }
+ }
+
+ @Singleton
+ static class Get implements RestReadView<ChangeEditResource> {
+ private final FileContentUtil fileContentUtil;
+
+ @Inject
+ Get(FileContentUtil fileContentUtil) {
+ this.fileContentUtil = fileContentUtil;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc)
+ throws ResourceNotFoundException, IOException {
+ try {
+ return Response.ok(fileContentUtil.getContent(
+ rsrc.getChangeEdit().getChange().getProject(),
+ rsrc.getChangeEdit().getRevision().get(),
+ rsrc.getPath()));
+ } catch (ResourceNotFoundException rnfe) {
+ return Response.none();
+ }
+ }
+ }
+}
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 586c294..fb80984 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
@@ -77,6 +77,7 @@
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.extensions.webui.UiActions;
@@ -127,6 +128,7 @@
private final Provider<WebLinks> webLinks;
private final EnumSet<ListChangesOption> options;
private final ChangeMessagesUtil cmUtil;
+ private final PatchLineCommentsUtil plcUtil;
private AccountInfo.Loader accountLoader;
@@ -147,7 +149,8 @@
DynamicMap<RestView<ChangeResource>> changeViews,
Revisions revisions,
Provider<WebLinks> webLinks,
- ChangeMessagesUtil cmUtil) {
+ ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil) {
this.db = db;
this.labelNormalizer = ln;
this.userProvider = user;
@@ -163,6 +166,7 @@
this.revisions = revisions;
this.webLinks = webLinks;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
options = EnumSet.noneOf(ListChangesOption.class);
}
@@ -821,9 +825,8 @@
&& userProvider.get().isIdentifiedUser()) {
IdentifiedUser user = (IdentifiedUser)userProvider.get();
out.hasDraftComments =
- db.get().patchComments()
- .draftByPatchSetAuthor(in.getId(), user.getAccountId())
- .iterator().hasNext()
+ plcUtil.draftByPatchSetAuthor(db.get(), in.getId(),
+ user.getAccountId(), ctl.getNotes()).iterator().hasNext()
? true
: null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 8e2b6ac..82e0c91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -218,7 +218,7 @@
// having the same tree as would exist when the prior commit is
// cherry-picked onto the next commit's new first parent.
ThreeWayMerger merger = MergeUtil.newThreeWayMerger(
- key.repo, MergeUtil.createDryRunInserter(), key.strategyName);
+ key.repo, MergeUtil.createDryRunInserter(key.repo), key.strategyName);
merger.setBase(prior.getParent(0));
if (merger.merge(next.getParent(0), prior)
&& merger.getResultTreeId().equals(next.getTree())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index d034a10..889ae1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -91,7 +91,7 @@
return json.format(cherryPickedChangeId);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
- } catch (MergeException e) {
+ } catch (MergeException e) {
throw new ResourceConflictException(e.getMessage());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index b1f6d8c..158f569 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -27,7 +27,9 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeConflictException;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
@@ -153,14 +155,12 @@
cherryPickCommit =
mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
commitToCherryPick, committerIdent, commitMessage, revWalk);
+ } catch (MergeIdenticalTreeException | MergeConflictException e) {
+ throw new MergeException("Cherry pick failed: " + e.getMessage());
} finally {
oi.release();
}
- if (cherryPickCommit == null) {
- throw new MergeException("Cherry pick failed");
- }
-
Change.Key changeKey;
final List<String> idList = cherryPickCommit.getFooterLines(CHANGE_ID);
if (!idList.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
index adc1644..4c5d848 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
@@ -17,8 +17,8 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.changes.Side;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.CommentRange;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.server.account.AccountInfo;
import java.sql.Timestamp;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
index 1d2fa406..b025e65 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+
import com.google.common.base.Strings;
import com.google.gerrit.common.changes.Side;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -24,27 +26,41 @@
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.change.PutDraft.Input;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
+import java.sql.Timestamp;
import java.util.Collections;
@Singleton
class CreateDraft implements RestModifyView<RevisionResource, Input> {
private final Provider<ReviewDb> db;
+ private final ChangeUpdate.Factory updateFactory;
+ private final PatchLineCommentsUtil plcUtil;
+ private final PatchListCache patchListCache;
@Inject
- CreateDraft(Provider<ReviewDb> db) {
+ CreateDraft(Provider<ReviewDb> db,
+ ChangeUpdate.Factory updateFactory,
+ PatchLineCommentsUtil plcUtil,
+ PatchListCache patchListCache) {
this.db = db;
+ this.updateFactory = updateFactory;
+ this.plcUtil = plcUtil;
+ this.patchListCache = patchListCache;
}
@Override
public Response<CommentInfo> apply(RevisionResource rsrc, Input in)
- throws BadRequestException, OrmException {
+ throws BadRequestException, OrmException, IOException {
if (Strings.isNullOrEmpty(in.path)) {
throw new BadRequestException("path must be non-empty");
} else if (in.message == null || in.message.trim().isEmpty()) {
@@ -59,15 +75,20 @@
? in.line
: in.range != null ? in.range.getEndLine() : 0;
+ Timestamp now = TimeUtil.nowTs();
+ ChangeUpdate update = updateFactory.create(rsrc.getControl(), now);
+
PatchLineComment c = new PatchLineComment(
new PatchLineComment.Key(
new Patch.Key(rsrc.getPatchSet().getId(), in.path),
ChangeUtil.messageUUID(db.get())),
- line, rsrc.getAccountId(), Url.decode(in.inReplyTo), TimeUtil.nowTs());
+ line, rsrc.getAccountId(), Url.decode(in.inReplyTo), now);
c.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
c.setMessage(in.message.trim());
c.setRange(in.range);
- db.get().patchComments().insert(Collections.singleton(c));
+ setCommentRevId(c, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ plcUtil.insertComments(db.get(), update, Collections.singleton(c));
+ update.commit();
return Response.created(new CommentInfo(c, null));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
index 46ae834..f7fb300 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
@@ -14,15 +14,22 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.change.DeleteDraft.Input;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.util.Collections;
@Singleton
@@ -31,16 +38,30 @@
}
private final Provider<ReviewDb> db;
+ private final PatchLineCommentsUtil plcUtil;
+ private final ChangeUpdate.Factory updateFactory;
+ private final PatchListCache patchListCache;
@Inject
- DeleteDraft(Provider<ReviewDb> db) {
+ DeleteDraft(Provider<ReviewDb> db,
+ PatchLineCommentsUtil plcUtil,
+ ChangeUpdate.Factory updateFactory,
+ PatchListCache patchListCache) {
this.db = db;
+ this.plcUtil = plcUtil;
+ this.updateFactory = updateFactory;
+ this.patchListCache = patchListCache;
}
@Override
public Response<CommentInfo> apply(DraftResource rsrc, Input input)
- throws OrmException {
- db.get().patchComments().delete(Collections.singleton(rsrc.getComment()));
+ throws OrmException, IOException {
+ ChangeUpdate update = updateFactory.create(rsrc.getControl());
+
+ PatchLineComment c = rsrc.getComment();
+ setCommentRevId(c, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ plcUtil.deleteComments(db.get(), update, Collections.singleton(c));
+ update.commit();
return Response.none();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java
index 322faea..b0ed7d0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -34,16 +35,19 @@
private final Provider<CurrentUser> user;
private final ListDrafts list;
private final Provider<ReviewDb> dbProvider;
+ private final PatchLineCommentsUtil plcUtil;
@Inject
Drafts(DynamicMap<RestView<DraftResource>> views,
Provider<CurrentUser> user,
ListDrafts list,
- Provider<ReviewDb> dbProvider) {
+ Provider<ReviewDb> dbProvider,
+ PatchLineCommentsUtil plcUtil) {
this.views = views;
this.user = user;
this.list = list;
this.dbProvider = dbProvider;
+ this.plcUtil = plcUtil;
}
@Override
@@ -62,10 +66,8 @@
throws ResourceNotFoundException, OrmException, AuthException {
checkIdentifiedUser();
String uuid = id.get();
- for (PatchLineComment c : dbProvider.get().patchComments()
- .draftByPatchSetAuthor(
- rev.getPatchSet().getId(),
- rev.getAccountId())) {
+ for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(dbProvider.get(),
+ rev.getPatchSet().getId(), rev.getAccountId(), rev.getNotes())) {
if (uuid.equals(c.getKey().get())) {
return new DraftResource(rev, c);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
new file mode 100644
index 0000000..9a817e3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -0,0 +1,71 @@
+// 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.change;
+
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+@Singleton
+public class FileContentUtil {
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ FileContentUtil(GitRepositoryManager repoManager) {
+ this.repoManager = repoManager;
+ }
+
+ public BinaryResult getContent(Project.NameKey project, String revstr,
+ String path) throws ResourceNotFoundException, IOException {
+ Repository repo = repoManager.openRepository(project);
+ try {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ RevCommit commit = rw.parseCommit(repo.resolve(revstr));
+ TreeWalk tw =
+ TreeWalk.forPath(rw.getObjectReader(), path,
+ commit.getTree().getId());
+ if (tw == null) {
+ throw new ResourceNotFoundException();
+ }
+ final ObjectLoader object = repo.open(tw.getObjectId(0));
+ @SuppressWarnings("resource")
+ BinaryResult result = new BinaryResult() {
+ @Override
+ public void writeTo(OutputStream os) throws IOException {
+ object.copyTo(os);
+ }
+ };
+ return result.setContentLength(object.getSize()).base64();
+ } finally {
+ rw.release();
+ }
+ } finally {
+ repo.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
index 6bb7236..6ae87a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -21,6 +21,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -44,15 +45,15 @@
Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
- return toFileInfoMap(change, patchSet, null);
+ return toFileInfoMap(change, patchSet.getRevision(), null);
}
- Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet, @Nullable PatchSet base)
+ Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, @Nullable PatchSet base)
throws PatchListNotAvailableException {
ObjectId a = (base == null)
? null
: ObjectId.fromString(base.getRevision().get());
- ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
+ ObjectId b = ObjectId.fromString(revision.get());
PatchList list = patchListCache.get(
new PatchListKey(change.getProject(), a, b, Whitespace.IGNORE_NONE));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 74659b8..3035ce1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -141,7 +141,7 @@
try {
Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
- resource.getPatchSet(),
+ resource.getPatchSet().getRevision(),
basePatchSet));
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index bfc1df9..5a73e86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -17,69 +17,26 @@
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.treewalk.TreeWalk;
-
import java.io.IOException;
-import java.io.OutputStream;
@Singleton
public class GetContent implements RestReadView<FileResource> {
- private final GitRepositoryManager repoManager;
+ private final FileContentUtil fileContentUtil;
@Inject
- GetContent(GitRepositoryManager repoManager) {
- this.repoManager = repoManager;
+ GetContent(FileContentUtil fileContentUtil) {
+ this.fileContentUtil = fileContentUtil;
}
@Override
public BinaryResult apply(FileResource rsrc)
throws ResourceNotFoundException, IOException {
- return apply(rsrc.getRevision().getControl().getProject().getNameKey(),
+ return fileContentUtil.getContent(
+ rsrc.getRevision().getControl().getProject().getNameKey(),
rsrc.getRevision().getPatchSet().getRevision().get(),
rsrc.getPatchKey().get());
}
-
- public BinaryResult apply(Project.NameKey project, String revstr, String path)
- throws ResourceNotFoundException, IOException {
- Repository repo = repoManager.openRepository(project);
- try {
- RevWalk rw = new RevWalk(repo);
- try {
- RevCommit commit =
- rw.parseCommit(repo.resolve(revstr));
- TreeWalk tw =
- TreeWalk.forPath(rw.getObjectReader(), path,
- commit.getTree().getId());
- if (tw == null) {
- throw new ResourceNotFoundException();
- }
- try {
- final ObjectLoader object = repo.open(tw.getObjectId(0));
- @SuppressWarnings("resource")
- BinaryResult result = new BinaryResult() {
- @Override
- public void writeTo(OutputStream os) throws IOException {
- object.copyTo(os);
- }
- };
- return result.setContentLength(object.getSize()).base64();
- } finally {
- tw.release();
- }
- } finally {
- rw.release();
- }
- } finally {
- repo.close();
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
index 0ccb15c..fd727c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
@@ -21,12 +21,12 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
@@ -39,7 +39,6 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -50,7 +49,6 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
@@ -272,15 +270,6 @@
return r;
}
- private static GitPerson toGitPerson(PersonIdent id) {
- GitPerson p = new GitPerson();
- p.name = id.getName();
- p.email = id.getEmailAddress();
- p.date = new Timestamp(id.getWhen().getTime());
- p.tz = id.getTimeZoneOffset();
- return p;
- }
-
public static class RelatedInfo {
public List<ChangeAndCommit> changes;
}
@@ -309,7 +298,7 @@
p.commit = c.getParent(i).name();
commit.parents.add(p);
}
- commit.author = toGitPerson(c.getAuthorIdent());
+ commit.author = CommonConverters.toGitPerson(c.getAuthorIdent());
commit.subject = c.getShortMessage();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
index 3a2f7e7..0746588 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
@@ -19,7 +19,7 @@
import com.google.inject.Singleton;
@Singleton
-class GetTopic implements RestReadView<ChangeResource> {
+public class GetTopic implements RestReadView<ChangeResource> {
@Override
public String apply(ChangeResource rsrc) {
return Strings.nullToEmpty(rsrc.getChange().getTopic());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
index 201ee14..fcac76d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -49,12 +49,26 @@
public static IncludedInDetail resolve(final Repository repo,
final RevWalk rw, final RevCommit commit) throws IOException {
- return new IncludedInResolver(repo, rw, commit).resolve();
+ RevFlag flag = newFlag(rw);
+ try {
+ return new IncludedInResolver(repo, rw, commit, flag).resolve();
+ } finally {
+ rw.disposeFlag(flag);
+ }
}
public static boolean includedInOne(final Repository repo, final RevWalk rw,
final RevCommit commit, final Collection<Ref> refs) throws IOException {
- return new IncludedInResolver(repo, rw, commit).includedInOne(refs);
+ RevFlag flag = newFlag(rw);
+ try {
+ return new IncludedInResolver(repo, rw, commit, flag).includedInOne(refs);
+ } finally {
+ rw.disposeFlag(flag);
+ }
+ }
+
+ private static RevFlag newFlag(RevWalk rw) {
+ return rw.newFlag("CONTAINS_TARGET");
}
private final Repository repo;
@@ -65,12 +79,12 @@
private Multimap<RevCommit, String> commitToRef;
private List<RevCommit> tipsByCommitTime;
- private IncludedInResolver(final Repository repo, final RevWalk rw,
- final RevCommit target) {
+ private IncludedInResolver(Repository repo, RevWalk rw, RevCommit target,
+ RevFlag containsTarget) {
this.repo = repo;
this.rw = rw;
this.target = target;
- this.containsTarget = rw.newFlag("CONTAINS_TARGET");
+ this.containsTarget = containsTarget;
}
private IncludedInDetail resolve() throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java
index f4d7b49..146ded8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java
@@ -26,13 +26,10 @@
@Singleton
class ListComments extends ListDrafts {
- private final PatchLineCommentsUtil plcUtil;
-
@Inject
ListComments(Provider<ReviewDb> db, AccountInfo.Loader.Factory alf,
PatchLineCommentsUtil plcUtil) {
- super(db, alf);
- this.plcUtil = plcUtil;
+ super(db, alf, plcUtil);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
index bd3aa04..1a26898 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
@@ -22,6 +22,7 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -36,20 +37,21 @@
@Singleton
class ListDrafts implements RestReadView<RevisionResource> {
protected final Provider<ReviewDb> db;
+ protected final PatchLineCommentsUtil plcUtil;
private final AccountInfo.Loader.Factory accountLoaderFactory;
@Inject
- ListDrafts(Provider<ReviewDb> db, AccountInfo.Loader.Factory alf) {
+ ListDrafts(Provider<ReviewDb> db, AccountInfo.Loader.Factory alf,
+ PatchLineCommentsUtil plcUtil) {
this.db = db;
this.accountLoaderFactory = alf;
+ this.plcUtil = plcUtil;
}
protected Iterable<PatchLineComment> listComments(RevisionResource rsrc)
throws OrmException {
- return db.get().patchComments()
- .draftByPatchSetAuthor(
- rsrc.getPatchSet().getId(),
- rsrc.getAccountId());
+ return plcUtil.draftByPatchSetAuthor(db.get(), rsrc.getPatchSet().getId(),
+ rsrc.getAccountId(), rsrc.getNotes());
}
protected boolean includeAuthorInfo() {
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 6880ca2..a916430 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
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.change.ChangeEditResource.CHANGE_EDIT_KIND;
import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
import static com.google.gerrit.server.change.CommentResource.COMMENT_KIND;
import static com.google.gerrit.server.change.DraftResource.DRAFT_KIND;
@@ -44,6 +45,7 @@
DynamicMap.mapOf(binder(), FILE_KIND);
DynamicMap.mapOf(binder(), REVIEWER_KIND);
DynamicMap.mapOf(binder(), REVISION_KIND);
+ DynamicMap.mapOf(binder(), CHANGE_EDIT_KIND);
get(CHANGE_KIND).to(GetChange.class);
get(CHANGE_KIND, "detail").to(GetDetail.class);
@@ -99,6 +101,13 @@
get(FILE_KIND, "content").to(GetContent.class);
get(FILE_KIND, "diff").to(GetDiff.class);
+ child(CHANGE_KIND, "edit").to(ChangeEdits.class);
+ child(CHANGE_KIND, "publish_edit").to(PublishChangeEdit.class);
+ child(CHANGE_KIND, "rebase_edit").to(RebaseChangeEdit.class);
+ put(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Put.class);
+ delete(CHANGE_EDIT_KIND).to(ChangeEdits.DeleteContent.class);
+ get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
+
install(new FactoryModule() {
@Override
protected void configure() {
@@ -107,6 +116,8 @@
factory(EmailReviewComments.Factory.class);
factory(ChangeInserter.Factory.class);
factory(PatchSetInserter.Factory.class);
+ factory(ChangeEdits.Create.Factory.class);
+ factory(ChangeEdits.DeleteEdit.Factory.class);
}
});
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 044e899..21f5a69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
@@ -54,6 +55,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
@@ -358,7 +360,7 @@
}
}
- private void validate() throws InvalidChangeOperationException {
+ private void validate() throws InvalidChangeOperationException, IOException {
CommitValidators cv =
commitValidatorsFactory.create(ctl.getRefControl(), sshInfo, git);
@@ -374,7 +376,8 @@
try {
switch (validatePolicy) {
case RECEIVE_COMMITS:
- cv.validateForReceiveCommits(event);
+ NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(git, revWalk);
+ cv.validateForReceiveCommits(event, rejectCommits);
break;
case GERRIT:
cv.validateForGerritCommits(event);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 27ef005..4b6e72c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
@@ -44,7 +45,6 @@
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -54,9 +54,7 @@
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.LabelVote;
@@ -65,7 +63,6 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -346,15 +343,6 @@
List<PatchLineComment> del = Lists.newArrayList();
List<PatchLineComment> ups = Lists.newArrayList();
- PatchList patchList = null;
- try {
- patchList = patchListCache.get(rsrc.getChange(), rsrc.getPatchSet());
- } catch (PatchListNotAvailableException e) {
- throw new OrmException("could not load PatchList for this patchset", e);
- }
- RevId patchSetCommit = new RevId(ObjectId.toString(patchList.getNewId()));
- RevId baseCommit = new RevId(ObjectId.toString(patchList.getOldId()));;
-
for (Map.Entry<String, List<CommentInput>> ent : in.entrySet()) {
String path = ent.getKey();
for (CommentInput c : ent.getValue()) {
@@ -374,7 +362,8 @@
e.setStatus(PatchLineComment.Status.PUBLISHED);
e.setWrittenOn(timestamp);
e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
- e.setRevId(c.side == Side.PARENT ? baseCommit : patchSetCommit);
+ setCommentRevId(e, patchListCache, rsrc.getChange(),
+ rsrc.getPatchSet());
e.setMessage(c.message);
if (c.range != null) {
e.setRange(new CommentRange(
@@ -399,13 +388,14 @@
for (PatchLineComment e : drafts.values()) {
e.setStatus(PatchLineComment.Status.PUBLISHED);
e.setWrittenOn(timestamp);
- e.setRevId(e.getSide() == (short) 0 ? baseCommit : patchSetCommit);
+ setCommentRevId(e, patchListCache, rsrc.getChange(),
+ rsrc.getPatchSet());
ups.add(e);
}
break;
}
- db.get().patchComments().delete(del);
- plcUtil.addPublishedComments(db.get(), update, ups);
+ plcUtil.deleteComments(db.get(), update, del);
+ plcUtil.upsertComments(db.get(), update, ups);
comments.addAll(ups);
return !del.isEmpty() || !ups.isEmpty();
}
@@ -413,9 +403,8 @@
private Map<String, PatchLineComment> scanDraftComments(
RevisionResource rsrc) throws OrmException {
Map<String, PatchLineComment> drafts = Maps.newHashMap();
- for (PatchLineComment c : db.get().patchComments().draftByPatchSetAuthor(
- rsrc.getPatchSet().getId(),
- rsrc.getAccountId())) {
+ for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(db.get(),
+ rsrc.getPatchSet().getId(), rsrc.getAccountId(), rsrc.getNotes())) {
drafts.put(c.getKey().get(), c);
}
return drafts;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
new file mode 100644
index 0000000..b511c20
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
@@ -0,0 +1,99 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Optional;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsPost;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class PublishChangeEdit implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsPost<ChangeResource> {
+
+ private final Publish publish;
+
+ @Inject
+ PublishChangeEdit(Publish publish) {
+ this.publish = publish;
+ }
+
+ @Override
+ public DynamicMap<RestView<ChangeEditResource>> views() {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ @Override
+ public RestView<ChangeResource> list() {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ @Override
+ public ChangeEditResource parse(ChangeResource parent, IdString id)
+ throws ResourceNotFoundException, Exception {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Publish post(ChangeResource parent) throws RestApiException {
+ return publish;
+ }
+
+ @Singleton
+ public static class Publish implements RestModifyView<ChangeResource, Publish.Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditUtil editUtil;
+
+ @Inject
+ Publish(ChangeEditUtil editUtil) {
+ this.editUtil = editUtil;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, Publish.Input in)
+ throws AuthException, ResourceConflictException, NoSuchChangeException,
+ IOException, InvalidChangeOperationException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "no edit exists for change %s",
+ rsrc.getChange().getChangeId()));
+ }
+ editUtil.publish(edit.get());
+ return Response.none();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
index c1fb304..a3b0614 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+
import com.google.gerrit.common.changes.Side;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -24,13 +26,17 @@
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.change.PutDraft.Input;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.Collections;
@@ -51,17 +57,28 @@
private final Provider<ReviewDb> db;
private final DeleteDraft delete;
+ private final PatchLineCommentsUtil plcUtil;
+ private final ChangeUpdate.Factory updateFactory;
+ private final PatchListCache patchListCache;
@Inject
- PutDraft(Provider<ReviewDb> db, DeleteDraft delete) {
+ PutDraft(Provider<ReviewDb> db,
+ DeleteDraft delete,
+ PatchLineCommentsUtil plcUtil,
+ ChangeUpdate.Factory updateFactory,
+ PatchListCache patchListCache) {
this.db = db;
this.delete = delete;
+ this.plcUtil = plcUtil;
+ this.updateFactory = updateFactory;
+ this.patchListCache = patchListCache;
}
@Override
public Response<CommentInfo> apply(DraftResource rsrc, Input in) throws
- BadRequestException, OrmException {
+ BadRequestException, OrmException, IOException {
PatchLineComment c = rsrc.getComment();
+ ChangeUpdate update = updateFactory.create(rsrc.getControl());
if (in == null || in.message == null || in.message.trim().isEmpty()) {
return delete.apply(rsrc, null);
} else if (in.id != null && !rsrc.getId().equals(in.id)) {
@@ -76,7 +93,8 @@
&& !in.path.equals(c.getKey().getParentKey().getFileName())) {
// Updating the path alters the primary key, which isn't possible.
// Delete then recreate the comment instead of an update.
- db.get().patchComments().delete(Collections.singleton(c));
+
+ plcUtil.deleteComments(db.get(), update, Collections.singleton(c));
c = new PatchLineComment(
new PatchLineComment.Key(
new Patch.Key(rsrc.getPatchSet().getId(), in.path),
@@ -84,10 +102,18 @@
c.getLine(),
rsrc.getAuthorId(),
c.getParentUuid(), TimeUtil.nowTs());
- db.get().patchComments().insert(Collections.singleton(update(c, in)));
+ setCommentRevId(c, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ plcUtil.insertComments(db.get(), update,
+ Collections.singleton(update(c, in)));
} else {
- db.get().patchComments().update(Collections.singleton(update(c, in)));
+ if (c.getRevId() == null) {
+ setCommentRevId(c, patchListCache, rsrc.getChange(),
+ rsrc.getPatchSet());
+ }
+ plcUtil.updateComments(db.get(), update,
+ Collections.singleton(update(c, in)));
}
+ update.commit();
return Response.ok(new CommentInfo(c, null));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 9676762..3e01444 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -42,7 +42,7 @@
import java.io.IOException;
@Singleton
-class PutTopic implements RestModifyView<ChangeResource, Input>,
+public class PutTopic implements RestModifyView<ChangeResource, Input>,
UiAction<ChangeResource> {
private final Provider<ReviewDb> dbProvider;
private final ChangeIndexer indexer;
@@ -50,9 +50,9 @@
private final ChangeUpdate.Factory updateFactory;
private final ChangeMessagesUtil cmUtil;
- static class Input {
+ public static class Input {
@DefaultInput
- String topic;
+ public String topic;
}
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
new file mode 100644
index 0000000..8fccfb8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
@@ -0,0 +1,117 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Optional;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsPost;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class RebaseChangeEdit implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsPost<ChangeResource> {
+
+ private final Rebase rebase;
+
+ @Inject
+ RebaseChangeEdit(Rebase rebase) {
+ this.rebase = rebase;
+ }
+
+ @Override
+ public DynamicMap<RestView<ChangeEditResource>> views() {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ @Override
+ public RestView<ChangeResource> list() {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ @Override
+ public ChangeEditResource parse(ChangeResource parent, IdString id)
+ throws ResourceNotFoundException, Exception {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Rebase post(ChangeResource parent) throws RestApiException {
+ return rebase;
+ }
+
+ @Singleton
+ public static class Rebase implements RestModifyView<ChangeResource, Publish.Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditModifier editModifier;
+ private final ChangeEditUtil editUtil;
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ Rebase(ChangeEditModifier editModifier,
+ ChangeEditUtil editUtil,
+ Provider<ReviewDb> db) {
+ this.editModifier = editModifier;
+ this.editUtil = editUtil;
+ this.db = db;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, Publish.Input in)
+ throws AuthException, ResourceConflictException, IOException,
+ InvalidChangeOperationException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "no edit exists for change %s",
+ rsrc.getChange().getChangeId()));
+ }
+
+ PatchSet current = db.get().patchSets().get(
+ rsrc.getChange().currentPatchSetId());
+ if (current.getId().equals(edit.get().getBasePatchSet().getId())) {
+ throw new ResourceConflictException(String.format(
+ "edit for change %s is already on latest patch set: %s",
+ rsrc.getChange().getChangeId(),
+ current.getId()));
+ }
+ editModifier.rebaseEdit(edit.get(), current);
+ return Response.none();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
index 289173b..3805a0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -29,7 +29,9 @@
public String createProject;
public String emailReviewers;
public String flushCaches;
+ public String generateHttpPassword;
public String killTask;
+ public String modifyAccount;
public String priority;
public String queryLimit;
public String runAs;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index ab290cb..c54e193 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -17,6 +17,7 @@
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
import org.eclipse.jgit.lib.Config;
+
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
new file mode 100644
index 0000000..6f8b3a0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2014 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.edit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.IdentifiedUser;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * A single user's edit for a change.
+ * <p>
+ * There is max. one edit per user per change. Edits are stored on refs:
+ * refs/users/UU/UUUU/edit-CCCC where UU/UUUU is sharded representation
+ * of user account and CCCC is change number.
+ */
+public class ChangeEdit {
+ private final IdentifiedUser user;
+ private final Change change;
+ private final Ref ref;
+ private final RevCommit editCommit;
+ private final PatchSet basePatchSet;
+
+ public ChangeEdit(IdentifiedUser user, Change change, Ref ref,
+ RevCommit editCommit, PatchSet basePatchSet) {
+ checkNotNull(user);
+ checkNotNull(change);
+ checkNotNull(ref);
+ checkNotNull(editCommit);
+ checkNotNull(basePatchSet);
+ this.user = user;
+ this.change = change;
+ this.ref = ref;
+ this.editCommit = editCommit;
+ this.basePatchSet = basePatchSet;
+ }
+
+ public Change getChange() {
+ return change;
+ }
+
+ public IdentifiedUser getUser() {
+ return user;
+ }
+
+ public Ref getRef() {
+ return ref;
+ }
+
+ public RevId getRevision() {
+ return new RevId(ObjectId.toString(ref.getObjectId()));
+ }
+
+ public String getRefName() {
+ return ChangeEditUtil.editRefName(user.getAccountId(), change.getId());
+ }
+
+ public RevCommit getEditCommit() {
+ return editCommit;
+ }
+
+ public PatchSet getBasePatchSet() {
+ return basePatchSet;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
new file mode 100644
index 0000000..be88004
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2014 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.edit;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.server.CommonConverters;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+
+@Singleton
+public class ChangeEditJson {
+ public EditInfo toEditInfo(ChangeEdit edit) throws IOException {
+ EditInfo out = new EditInfo();
+ out.commit = fillCommit(edit.getEditCommit());
+ return out;
+ }
+
+ private static CommitInfo fillCommit(RevCommit editCommit) throws IOException {
+ CommitInfo commit = new CommitInfo();
+ commit.commit = editCommit.toObjectId().getName();
+ commit.parents = Lists.newArrayListWithCapacity(1);
+ commit.author = CommonConverters.toGitPerson(editCommit.getAuthorIdent());
+ commit.committer = CommonConverters.toGitPerson(
+ editCommit.getCommitterIdent());
+ commit.subject = editCommit.getShortMessage();
+ commit.message = editCommit.getFullMessage();
+
+ CommitInfo i = new CommitInfo();
+ i.commit = editCommit.getParent(0).toObjectId().getName();
+ commit.parents.add(i);
+
+ return commit;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
new file mode 100644
index 0000000..7ea324f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -0,0 +1,383 @@
+// Copyright (C) 2014 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.edit;
+
+import static com.google.gerrit.server.edit.ChangeEditUtil.editRefName;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
+import java.util.TimeZone;
+
+/**
+ * Utility functions to manipulate change edits.
+ * <p>
+ * This class contains methods to modify edit's content.
+ * For retrieving, publishing and deleting edit see
+ * {@link ChangeEditUtil}.
+ * <p>
+ */
+@Singleton
+public class ChangeEditModifier {
+
+ private static enum TreeOperation {
+ CHANGE_ENTRY,
+ DELETE_ENTRY,
+ RESTORE_ENTRY
+ }
+ private final TimeZone tz;
+ private final GitRepositoryManager gitManager;
+ private final Provider<CurrentUser> currentUser;
+
+ @Inject
+ ChangeEditModifier(@GerritPersonIdent PersonIdent gerritIdent,
+ GitRepositoryManager gitManager,
+ Provider<ReviewDb> dbProvider,
+ Provider<CurrentUser> currentUser) {
+ this.gitManager = gitManager;
+ this.currentUser = currentUser;
+ this.tz = gerritIdent.getTimeZone();
+ }
+
+ /**
+ * Create new change edit.
+ *
+ * @param change to create change edit for
+ * @param ps patch set to create change edit on
+ * @return result
+ * @throws AuthException
+ * @throws IOException
+ * @throws ResourceConflictException When change edit already
+ * exists for the change
+ */
+ public RefUpdate.Result createEdit(Change change, PatchSet ps)
+ throws AuthException, IOException, ResourceConflictException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ Repository repo = gitManager.openRepository(change.getProject());
+ String refName = editRefName(me.getAccountId(), change.getId());
+
+ try {
+ if (repo.getRefDatabase().getRef(refName) != null) {
+ throw new ResourceConflictException("edit already exists");
+ }
+
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter();
+ try {
+ RevCommit base = rw.parseCommit(ObjectId.fromString(
+ ps.getRevision().get()));
+ ObjectId commit = createCommit(me, inserter, base, base, base.getTree());
+ inserter.flush();
+ return update(repo, me, refName, rw, ObjectId.zeroId(), commit);
+ } finally {
+ rw.release();
+ inserter.release();
+ }
+ } finally {
+ repo.close();
+ }
+ }
+
+ /**
+ * Rebase change edit on latest patch set
+ *
+ * @param edit change edit that contains edit to rebase
+ * @param current patch set to rebase the edit on
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result rebaseEdit(ChangeEdit edit, PatchSet current)
+ throws AuthException, InvalidChangeOperationException, IOException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ Change change = edit.getChange();
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ String refName = editRefName(me.getAccountId(), change.getId());
+ Repository repo = gitManager.openRepository(change.getProject());
+ try {
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter();
+ try {
+ RevCommit editCommit = edit.getEditCommit();
+ RevCommit mergeTip = rw.parseCommit(ObjectId.fromString(
+ current.getRevision().get()));
+ ThreeWayMerger m = MergeStrategy.RESOLVE.newMerger(repo, true);
+ m.setObjectInserter(inserter);
+ m.setBase(editCommit.getParent(0));
+ if (m.merge(mergeTip, editCommit)) {
+ ObjectId tree = m.getResultTreeId();
+
+ CommitBuilder mergeCommit = new CommitBuilder();
+ mergeCommit.setTreeId(tree);
+ mergeCommit.setParentId(mergeTip);
+ mergeCommit.setAuthor(editCommit.getAuthorIdent());
+ mergeCommit.setCommitter(new PersonIdent(
+ editCommit.getCommitterIdent(), TimeUtil.nowTs()));
+ mergeCommit.setMessage(editCommit.getFullMessage());
+ ObjectId newEdit = inserter.insert(mergeCommit);
+ inserter.flush();
+ return update(repo, me, refName, rw, editCommit, newEdit);
+ } else {
+ // TODO(davido): Allow to resolve conflicts inline
+ throw new InvalidChangeOperationException("merge conflict");
+ }
+ } finally {
+ rw.release();
+ inserter.release();
+ }
+ } finally {
+ repo.close();
+ }
+ }
+
+ /**
+ * Modify file in existing change edit from its base commit.
+ *
+ * @param edit change edit
+ * @param file path to modify
+ * @param content new content
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result modifyFile(ChangeEdit edit,
+ String file, byte[] content) throws AuthException,
+ InvalidChangeOperationException, IOException {
+ return modify(TreeOperation.CHANGE_ENTRY, edit, file, content);
+ }
+
+ /**
+ * Delete file in existing change edit.
+ *
+ * @param edit change edit
+ * @param file path to delete
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result deleteFile(ChangeEdit edit,
+ String file) throws AuthException, InvalidChangeOperationException,
+ IOException {
+ return modify(TreeOperation.DELETE_ENTRY, edit, file, null);
+ }
+
+ /**
+ * Restore file in existing change edit.
+ *
+ * @param edit change edit
+ * @param file path to restore
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result restoreFile(ChangeEdit edit,
+ String file) throws AuthException, InvalidChangeOperationException,
+ IOException {
+ return modify(TreeOperation.RESTORE_ENTRY, edit, file, null);
+ }
+
+ private RefUpdate.Result modify(TreeOperation op,
+ ChangeEdit edit, String file, byte[] content)
+ throws AuthException, IOException, InvalidChangeOperationException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ Repository repo = gitManager.openRepository(edit.getChange().getProject());
+ try {
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter();
+ ObjectReader reader = repo.newObjectReader();
+ try {
+ String refName = edit.getRefName();
+ RevCommit prevEdit = rw.parseCommit(edit.getRef().getObjectId());
+ PatchSet basePs = edit.getBasePatchSet();
+
+ RevCommit base = rw.parseCommit(ObjectId.fromString(
+ basePs.getRevision().get()));
+ ObjectId newTree = writeNewTree(op, repo, rw, inserter,
+ prevEdit, reader, file, content, base);
+ if (ObjectId.equals(newTree, prevEdit.getTree())) {
+ throw new InvalidChangeOperationException("no changes were made");
+ }
+
+ ObjectId commit = createCommit(me, inserter, prevEdit, base, newTree);
+ inserter.flush();
+ return update(repo, me, refName, rw, prevEdit, commit);
+ } finally {
+ rw.release();
+ inserter.release();
+ reader.release();
+ }
+ } finally {
+ repo.close();
+ }
+ }
+
+ private ObjectId createCommit(IdentifiedUser me, ObjectInserter inserter,
+ RevCommit prevEdit, RevCommit base, ObjectId tree) throws IOException {
+ CommitBuilder builder = new CommitBuilder();
+ builder.setTreeId(tree);
+ builder.setParentIds(base);
+ builder.setAuthor(prevEdit.getAuthorIdent());
+ builder.setCommitter(getCommitterIdent(me));
+ builder.setMessage(prevEdit.getFullMessage());
+ return inserter.insert(builder);
+ }
+
+ private RefUpdate.Result update(Repository repo, IdentifiedUser me,
+ String refName, RevWalk rw, ObjectId oldObjectId, ObjectId newEdit)
+ throws IOException {
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setExpectedOldObjectId(oldObjectId);
+ ru.setNewObjectId(newEdit);
+ ru.setRefLogIdent(getRefLogIdent(me));
+ ru.setForceUpdate(true);
+ RefUpdate.Result res = ru.update(rw);
+ if (res != RefUpdate.Result.NEW &&
+ res != RefUpdate.Result.FORCED) {
+ throw new IOException("update failed: " + ru);
+ }
+ return res;
+ }
+
+ private static ObjectId writeNewTree(TreeOperation op, Repository repo, RevWalk rw,
+ ObjectInserter ins, RevCommit prevEdit, ObjectReader reader,
+ String fileName, byte[] content, RevCommit base)
+ throws IOException, InvalidChangeOperationException {
+ DirCache newTree = createTree(reader, prevEdit);
+ editTree(
+ op,
+ repo,
+ rw,
+ base,
+ newTree.editor(),
+ ins,
+ fileName,
+ content);
+ return newTree.writeTree(ins);
+ }
+
+ private static void editTree(TreeOperation op, Repository repo, RevWalk rw,
+ RevCommit base, DirCacheEditor dce, ObjectInserter ins, String path,
+ byte[] content) throws IOException, InvalidChangeOperationException {
+ switch (op) {
+ case DELETE_ENTRY:
+ dce.add(new DeletePath(path));
+ break;
+ case CHANGE_ENTRY:
+ case RESTORE_ENTRY:
+ dce.add(getPathEdit(op, repo, rw, base, path, ins, content));
+ break;
+ }
+ dce.finish();
+ }
+
+ private static PathEdit getPathEdit(TreeOperation op, Repository repo, RevWalk rw,
+ RevCommit base, String path, ObjectInserter ins, byte[] content)
+ throws IOException, InvalidChangeOperationException {
+ final ObjectId oid = op == TreeOperation.CHANGE_ENTRY
+ ? ins.insert(Constants.OBJ_BLOB, content)
+ : getObjectIdForRestoreOperation(repo, rw, base, path);
+ return new PathEdit(path) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.REGULAR_FILE);
+ ent.setObjectId(oid);
+ }
+ };
+ }
+
+ private static ObjectId getObjectIdForRestoreOperation(Repository repo,
+ RevWalk rw, RevCommit base, String path)
+ throws IOException, InvalidChangeOperationException {
+ TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), path,
+ base.getTree().getId());
+ // If the file does not exist in the base commit, try to restore it
+ // from the base's parent commit.
+ if (tw == null && base.getParentCount() == 1) {
+ tw = TreeWalk.forPath(rw.getObjectReader(), path,
+ rw.parseCommit(base.getParent(0)).getTree().getId());
+ }
+ if (tw == null) {
+ throw new InvalidChangeOperationException(String.format(
+ "cannot restore path %s: missing in base revision %s",
+ path, base.abbreviate(8).name()));
+ }
+ return tw.getObjectId(0);
+ }
+
+ private static DirCache createTree(ObjectReader reader, RevCommit prevEdit)
+ throws IOException {
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, prevEdit.getTree()
+ .getId());
+ b.finish();
+ return dc;
+ }
+
+ private PersonIdent getCommitterIdent(IdentifiedUser user) {
+ return user.newCommitterIdent(TimeUtil.nowTs(), tz);
+ }
+
+ private PersonIdent getRefLogIdent(IdentifiedUser user) {
+ return user.newRefLogIdent(TimeUtil.nowTs(), tz);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
new file mode 100644
index 0000000..ba50776
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -0,0 +1,293 @@
+// Copyright (C) 2014 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.edit;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+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.IdentifiedUser;
+import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Utility functions to manipulate change edits.
+ * <p>
+ * This class contains methods to retrieve, publish and delete edits.
+ * For changing edits see {@link ChangeEditModifier}.
+ */
+@Singleton
+public class ChangeEditUtil {
+ private final GitRepositoryManager gitManager;
+ private final PatchSetInserter.Factory patchSetInserterFactory;
+ private final ChangeControl.GenericFactory changeControlFactory;
+ private final Provider<ReviewDb> db;
+ private final Provider<IdentifiedUser> user;
+
+ @Inject
+ ChangeEditUtil(GitRepositoryManager gitManager,
+ PatchSetInserter.Factory patchSetInserterFactory,
+ ChangeControl.GenericFactory changeControlFactory,
+ Provider<ReviewDb> db,
+ Provider<IdentifiedUser> user) {
+ this.gitManager = gitManager;
+ this.patchSetInserterFactory = patchSetInserterFactory;
+ this.changeControlFactory = changeControlFactory;
+ this.db = db;
+ this.user = user;
+ }
+
+ /**
+ * Retrieve edits for a change and user. Max. one change edit can
+ * exist per user and change.
+ *
+ * @param change
+ * @return edit for this change for this user, if present.
+ * @throws AuthException
+ * @throws IOException
+ */
+ public Optional<ChangeEdit> byChange(Change change)
+ throws AuthException, IOException, InvalidChangeOperationException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ Repository repo = gitManager.openRepository(change.getProject());
+ try {
+ IdentifiedUser me = (IdentifiedUser) user.get();
+ Ref ref = repo.getRefDatabase().getRef(editRefName(
+ me.getAccountId(), change.getId()));
+ if (ref == null) {
+ return Optional.absent();
+ }
+ RevWalk rw = new RevWalk(repo);
+ try {
+ RevCommit commit = rw.parseCommit(ref.getObjectId());
+ PatchSet basePs = getBasePatchSet(change, commit);
+ return Optional.of(new ChangeEdit(me, change, ref, commit, basePs));
+ } finally {
+ rw.release();
+ }
+ } finally {
+ repo.close();
+ }
+ }
+
+ /**
+ * Promote change edit to patch set, by squashing the edit into
+ * its parent.
+ *
+ * @param edit change edit to publish
+ * @throws AuthException
+ * @throws NoSuchChangeException
+ * @throws IOException
+ * @throws InvalidChangeOperationException
+ * @throws OrmException
+ * @throws ResourceConflictException
+ */
+ public void publish(ChangeEdit edit) throws AuthException,
+ NoSuchChangeException, IOException, InvalidChangeOperationException,
+ OrmException, ResourceConflictException {
+ Change change = edit.getChange();
+ Repository repo = gitManager.openRepository(change.getProject());
+ try {
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter();
+ try {
+
+ PatchSet basePatchSet = edit.getBasePatchSet();
+ if (!basePatchSet.getId().equals(change.currentPatchSetId())) {
+ throw new ResourceConflictException(
+ "only edit for current patch set can be published");
+ }
+
+ insertPatchSet(edit, change, repo, rw, basePatchSet,
+ squashEdit(repo, rw, inserter, edit.getEditCommit(),
+ basePatchSet));
+ } finally {
+ inserter.release();
+ rw.release();
+ }
+
+ // TODO(davido): This should happen in the same BatchRefUpdate.
+ deleteRef(repo, edit);
+ } finally {
+ repo.close();
+ }
+ }
+
+ /**
+ * Delete change edit.
+ *
+ * @param edit change edit to delete
+ * @throws IOException
+ */
+ public void delete(ChangeEdit edit)
+ throws IOException {
+ Change change = edit.getChange();
+ Repository repo = gitManager.openRepository(change.getProject());
+ try {
+ deleteRef(repo, edit);
+ } finally {
+ repo.close();
+ }
+ }
+
+ private PatchSet getBasePatchSet(Change change, RevCommit commit)
+ throws IOException, InvalidChangeOperationException {
+ if (commit.getParentCount() != 1) {
+ throw new InvalidChangeOperationException(
+ "change edit commit has multiple parents");
+ }
+ RevCommit parentCommit = commit.getParent(0);
+ ObjectId rev = parentCommit.getId();
+ RevId parentRev = new RevId(ObjectId.toString(rev));
+ try {
+ List<PatchSet> r = db.get().patchSets().byRevision(parentRev).toList();
+ if (r.isEmpty()) {
+ throw new InvalidChangeOperationException(String.format(
+ "patch set %s change edit is based on doesn't exist",
+ rev.abbreviate(8).name()));
+ }
+ if (r.size() > 1) {
+ throw new InvalidChangeOperationException(String.format(
+ "multiple patch sets for change edit parent %s",
+ rev.abbreviate(8).name()));
+ }
+ PatchSet parentPatchSet = Iterables.getOnlyElement(r);
+ if (!change.getId().equals(
+ parentPatchSet.getId().getParentKey())) {
+ throw new InvalidChangeOperationException(String.format(
+ "different change edit ID %d and its parent patch set %d",
+ change.getId().get(),
+ parentPatchSet.getId().getParentKey().get()));
+ }
+ return parentPatchSet;
+ } catch (OrmException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Returns reference for this change edit with sharded user and change number:
+ * refs/users/UU/UUUU/edit-CCCC.
+ *
+ * @param accountId accout id
+ * @param changeId change number
+ * @return reference for this change edit
+ */
+ static String editRefName(Account.Id accountId, Change.Id changeId) {
+ return String.format("%s/edit-%d",
+ RefNames.refsUsers(accountId),
+ changeId.get());
+ }
+
+ private RevCommit squashEdit(Repository repo, RevWalk rw,
+ ObjectInserter inserter, RevCommit edit, PatchSet basePatchSet)
+ throws IOException, ResourceConflictException {
+ RevCommit parent = rw.parseCommit(ObjectId.fromString(
+ basePatchSet.getRevision().get()));
+ if (parent.getTree().equals(edit.getTree())) {
+ throw new ResourceConflictException("identical tree");
+ }
+ return writeSquashedCommit(rw, inserter, parent, edit);
+ }
+
+ private void insertPatchSet(ChangeEdit edit, Change change,
+ Repository repo, RevWalk rw, PatchSet basePatchSet, RevCommit squashed)
+ throws NoSuchChangeException, InvalidChangeOperationException,
+ OrmException, IOException {
+ PatchSet ps = new PatchSet(
+ ChangeUtil.nextPatchSetId(change.currentPatchSetId()));
+ ps.setRevision(new RevId(ObjectId.toString(squashed)));
+ ps.setUploader(edit.getUser().getAccountId());
+ ps.setCreatedOn(TimeUtil.nowTs());
+
+ PatchSetInserter insr =
+ patchSetInserterFactory.create(repo, rw,
+ changeControlFactory.controlFor(change, edit.getUser()),
+ squashed);
+ insr.setPatchSet(ps)
+ .setMessage(
+ String.format("Patch Set %d: Published edit on patch set %d",
+ ps.getPatchSetId(),
+ basePatchSet.getPatchSetId()))
+ .insert();
+ }
+
+ private static void deleteRef(Repository repo, ChangeEdit edit)
+ throws IOException {
+ String refName = edit.getRefName();
+ RefUpdate ru = repo.updateRef(refName, true);
+ ru.setExpectedOldObjectId(edit.getRef().getObjectId());
+ ru.setForceUpdate(true);
+ RefUpdate.Result result = ru.delete();
+ switch (result) {
+ case FORCED:
+ case NEW:
+ case NO_CHANGE:
+ break;
+ default:
+ throw new IOException(String.format("Failed to delete ref %s: %s",
+ refName, result));
+ }
+ }
+
+ private static RevCommit writeSquashedCommit(RevWalk rw,
+ ObjectInserter inserter, RevCommit parent, RevCommit edit)
+ throws IOException {
+ CommitBuilder mergeCommit = new CommitBuilder();
+ mergeCommit.setParentIds(parent.getParent(0));
+ mergeCommit.setAuthor(parent.getAuthorIdent());
+ mergeCommit.setMessage(parent.getFullMessage());
+ mergeCommit.setCommitter(edit.getCommitterIdent());
+ mergeCommit.setTreeId(edit.getTree());
+
+ return rw.parseCommit(commit(inserter, mergeCommit));
+ }
+
+ private static ObjectId commit(ObjectInserter inserter,
+ CommitBuilder mergeCommit) throws IOException {
+ ObjectId id = inserter.insert(mergeCommit);
+ inserter.flush();
+ return id;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
index a9f161e..08bdd4b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
@@ -20,13 +20,13 @@
import com.google.gerrit.server.git.WorkQueue.Executor;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.Inject;
-import com.google.inject.name.Named;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.name.Named;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
index 7ff91ef..b97ddb5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -48,15 +48,15 @@
@Singleton
public class BanCommit {
-
/**
* Loads a list of commits to reject from {@code refs/meta/reject-commits}.
*
* @param repo repository from which the rejected commits should be loaded
+ * @param walk open revwalk on repo.
* @return NoteMap of commits to be rejected, null if there are none.
* @throws IOException the map cannot be loaded.
*/
- public static NoteMap loadRejectCommitsMap(Repository repo)
+ public static NoteMap loadRejectCommitsMap(Repository repo, RevWalk walk)
throws IOException {
try {
Ref ref = repo.getRef(RefNames.REFS_REJECT_COMMITS);
@@ -64,13 +64,8 @@
return NoteMap.newEmptyMap();
}
- RevWalk rw = new RevWalk(repo);
- try {
- RevCommit map = rw.parseCommit(ref.getObjectId());
- return NoteMap.read(rw.getObjectReader(), map);
- } finally {
- rw.release();
- }
+ RevCommit map = walk.parseCommit(ref.getObjectId());
+ return NoteMap.read(walk.getObjectReader(), map);
} catch (IOException badMap) {
throw new IOException("Cannot load " + RefNames.REFS_REJECT_COMMITS,
badMap);
@@ -79,7 +74,7 @@
private final Provider<IdentifiedUser> currentUser;
private final GitRepositoryManager repoManager;
- private final PersonIdent gerritIdent;
+ private final TimeZone tz;
private NotesBranchUtil.Factory notesBranchUtilFactory;
@Inject
@@ -89,8 +84,8 @@
final NotesBranchUtil.Factory notesBranchUtilFactory) {
this.currentUser = currentUser;
this.repoManager = repoManager;
- this.gerritIdent = gerritIdent;
this.notesBranchUtilFactory = notesBranchUtilFactory;
+ this.tz = gerritIdent.getTimeZone();
}
public BanCommitResult ban(final ProjectControl projectControl,
@@ -99,30 +94,33 @@
MergeException, ConcurrentRefUpdateException {
if (!projectControl.isOwner()) {
throw new PermissionDeniedException(
- "No project owner: not permitted to ban commits");
+ "Not project owner: not permitted to ban commits");
}
final BanCommitResult result = new BanCommitResult();
NoteMap banCommitNotes = NoteMap.newEmptyMap();
- // add a note for each banned commit to notes
+ // Add a note for each banned commit to notes.
final Project.NameKey project = projectControl.getProject().getNameKey();
final Repository repo = repoManager.openRepository(project);
try {
final RevWalk revWalk = new RevWalk(repo);
final ObjectInserter inserter = repo.newObjectInserter();
try {
+ ObjectId noteId = null;
for (final ObjectId commitToBan : commitsToBan) {
try {
revWalk.parseCommit(commitToBan);
} catch (MissingObjectException e) {
- // ignore exception, also not existing commits can be banned
+ // Ignore exception, non-existing commits can be banned.
} catch (IncorrectObjectTypeException e) {
result.notACommit(commitToBan, e.getMessage());
continue;
}
- banCommitNotes.set(commitToBan, createNoteContent(reason, inserter));
+ if (noteId == null) {
+ noteId = createNoteContent(reason, inserter);
+ }
+ banCommitNotes.set(commitToBan, noteId);
}
- inserter.flush();
NotesBranchUtil notesBranchUtil =
notesBranchUtilFactory.create(project, repo, inserter);
NoteMap newlyCreated =
@@ -157,7 +155,6 @@
private PersonIdent createPersonIdent() {
Date now = new Date();
- TimeZone tz = gerritIdent.getTimeZone();
return currentUser.get().newCommitterIdent(now, tz);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
index baae629..c1afb6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
@@ -16,17 +16,13 @@
import org.eclipse.jgit.lib.ObjectId;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
public class BanCommitResult {
-
- private final List<ObjectId> newlyBannedCommits = new LinkedList<>();
- private final List<ObjectId> alreadyBannedCommits = new LinkedList<>();
- private final List<ObjectId> ignoredObjectIds = new LinkedList<>();
-
- public BanCommitResult() {
- }
+ private final List<ObjectId> newlyBannedCommits = new ArrayList<>(4);
+ private final List<ObjectId> alreadyBannedCommits = new ArrayList<>(4);
+ private final List<ObjectId> ignoredObjectIds = new ArrayList<>(4);
public void commitBanned(final ObjectId commitId) {
newlyBannedCommits.add(commitId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index ffb91ce..dee2df0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -46,6 +46,9 @@
/**
* Create (and open) a repository by name.
+ * <p>
+ * If the implementation supports separate metadata repositories, this method
+ * must also create the metadata repository, but does not open it.
*
* @param name the repository name, relative to the base directory.
* @return the cached Repository instance. Caller must call {@code close()}
@@ -59,6 +62,23 @@
throws RepositoryCaseMismatchException, RepositoryNotFoundException,
IOException;
+ /**
+ * Open the repository storing metadata for the given project.
+ * <p>
+ * This includes any project-specific metadata <em>except</em> what is stored
+ * in {@code refs/meta/config}. Implementations may choose to store all
+ * metadata in the original project.
+ *
+ * @param name the base project name name.
+ * @return the cached metadata Repository instance. Caller must call
+ * {@code close()} when done to decrement the resource handle.
+ * @throws RepositoryNotFoundException the name does not denote an existing
+ * repository.
+ * @throws IOException the name cannot be read as a repository.
+ */
+ public abstract Repository openMetadataRepository(Project.NameKey name)
+ throws RepositoryNotFoundException, IOException;
+
/** @return set of all known projects, sorted by natural NameKey order. */
public abstract SortedSet<Project.NameKey> list();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 196d3e9..f2c77cc4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.git;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Objects;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -124,16 +128,25 @@
}
private final File basePath;
+ private final File noteDbPath;
private final Lock namesUpdateLock;
private volatile SortedSet<Project.NameKey> names;
@Inject
- LocalDiskRepositoryManager(final SitePaths site,
- @GerritServerConfig final Config cfg) {
+ LocalDiskRepositoryManager(SitePaths site,
+ @GerritServerConfig Config cfg,
+ NotesMigration notesMigration) {
basePath = site.resolve(cfg.getString("gerrit", null, "basePath"));
if (basePath == null) {
throw new IllegalStateException("gerrit.basePath must be configured");
}
+
+ if (notesMigration.enabled()) {
+ noteDbPath = site.resolve(Objects.firstNonNull(
+ cfg.getString("gerrit", null, "noteDbPath"), "notedb"));
+ } else {
+ noteDbPath = null;
+ }
namesUpdateLock = new ReentrantLock(true /* fair */);
names = list();
}
@@ -143,28 +156,30 @@
return basePath;
}
- private File gitDirOf(Project.NameKey name) {
- return new File(getBasePath(), name.get());
+ public Repository openRepository(Project.NameKey name)
+ throws RepositoryNotFoundException {
+ return openRepository(basePath, name);
}
- public Repository openRepository(Project.NameKey name)
+ private Repository openRepository(File path, Project.NameKey name)
throws RepositoryNotFoundException {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
+ File gitDir = new File(path, name.get());
if (!names.contains(name)) {
// The this.names list does not hold the project-name but it can still exist
// on disk; for instance when the project has been created directly on the
// file-system through replication.
//
if (!name.get().endsWith(Constants.DOT_GIT_EXT)) {
- if (FileKey.resolve(gitDirOf(name), FS.DETECTED) != null) {
+ if (FileKey.resolve(gitDir, FS.DETECTED) != null) {
onCreateProject(name);
} else {
- throw new RepositoryNotFoundException(gitDirOf(name));
+ throw new RepositoryNotFoundException(gitDir);
}
} else {
- final File directory = gitDirOf(name);
+ final File directory = gitDir;
if (FileKey.isGitRepository(new File(directory, Constants.DOT_GIT),
FS.DETECTED)) {
onCreateProject(name);
@@ -172,11 +187,11 @@
directory.getName() + Constants.DOT_GIT_EXT), FS.DETECTED)) {
onCreateProject(name);
} else {
- throw new RepositoryNotFoundException(gitDirOf(name));
+ throw new RepositoryNotFoundException(gitDir);
}
}
}
- final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
+ final FileKey loc = FileKey.lenient(gitDir, FS.DETECTED);
try {
return RepositoryCache.open(loc);
} catch (IOException e1) {
@@ -187,13 +202,22 @@
}
}
- public Repository createRepository(final Project.NameKey name)
+ public Repository createRepository(Project.NameKey name)
+ throws RepositoryNotFoundException, RepositoryCaseMismatchException {
+ Repository repo = createRepository(basePath, name);
+ if (noteDbPath != null) {
+ createRepository(noteDbPath, name);
+ }
+ return repo;
+ }
+
+ private Repository createRepository(File path, Project.NameKey name)
throws RepositoryNotFoundException, RepositoryCaseMismatchException {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
- File dir = FileKey.resolve(gitDirOf(name), FS.DETECTED);
+ File dir = FileKey.resolve(new File(path, name.get()), FS.DETECTED);
FileKey loc;
if (dir != null) {
// Already exists on disk, use the repository we found.
@@ -208,7 +232,7 @@
// of the repository name, so prefer the standard bare name.
//
String n = name.get() + Constants.DOT_GIT_EXT;
- loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
+ loc = FileKey.exact(new File(path, n), FS.DETECTED);
}
try {
@@ -231,6 +255,17 @@
}
}
+ @Override
+ public Repository openMetadataRepository(Project.NameKey name)
+ throws RepositoryNotFoundException, IOException {
+ checkState(noteDbPath != null, "notedb disabled");
+ try {
+ return openRepository(noteDbPath, name);
+ } catch (RepositoryNotFoundException e) {
+ return createRepository(noteDbPath, name);
+ }
+ }
+
private void onCreateProject(final Project.NameKey newProjectName) {
namesUpdateLock.lock();
try {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
similarity index 61%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
index 73db6f5..02bc8dc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 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.
@@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.server.git;
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface InstallPlugins {
+/** Indicates that the commit cannot be merged without conflicts. */
+public class MergeConflictException extends Exception {
+ private static final long serialVersionUID = 1L;
+ public MergeConflictException(String msg) {
+ super(msg, null);
+ }
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
similarity index 60%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
index 73db6f5..109fa76 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 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.
@@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.server.git;
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface InstallPlugins {
+/** Indicates that the commit is already contained in destination banch. */
+public class MergeIdenticalTreeException extends Exception {
+ private static final long serialVersionUID = 1L;
+ public MergeIdenticalTreeException(String msg) {
+ super(msg, null);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index b4a1f0c..18ec7c6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -52,6 +52,7 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.Merger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.FooterLine;
@@ -89,6 +90,12 @@
return cfg.getBoolean("core", null, "useRecursiveMerge", true);
}
+ public static ThreeWayMergeStrategy getMergeStrategy(Config cfg) {
+ return useRecursiveMerge(cfg)
+ ? MergeStrategy.RECURSIVE
+ : MergeStrategy.RESOLVE;
+ }
+
public static interface Factory {
MergeUtil create(ProjectState project);
MergeUtil create(ProjectState project, boolean useContentMerge);
@@ -173,7 +180,8 @@
public RevCommit createCherryPickFromCommit(Repository repo,
ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
PersonIdent cherryPickCommitterIdent, String commitMsg, RevWalk rw)
- throws MissingObjectException, IncorrectObjectTypeException, IOException {
+ throws MissingObjectException, IncorrectObjectTypeException, IOException,
+ MergeIdenticalTreeException, MergeConflictException {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
@@ -181,7 +189,7 @@
if (m.merge(mergeTip, originalCommit)) {
ObjectId tree = m.getResultTreeId();
if (tree.equals(mergeTip.getTree())) {
- return null;
+ throw new MergeIdenticalTreeException("identical tree");
}
CommitBuilder mergeCommit = new CommitBuilder();
@@ -192,7 +200,7 @@
mergeCommit.setMessage(commitMsg);
return rw.parseCommit(commit(inserter, mergeCommit));
} else {
- return null;
+ throw new MergeConflictException("merge conflict");
}
}
@@ -393,7 +401,7 @@
return false;
}
- final ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter());
+ ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
try {
return m.merge(new AnyObjectId[] {mergeTip, toMerge});
} catch (LargeObjectException e) {
@@ -442,8 +450,7 @@
// that on the current merge tip.
//
try {
- final ThreeWayMerger m =
- newThreeWayMerger(repo, createDryRunInserter());
+ ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
m.setBase(toMerge.getParent(0));
return m.merge(mergeTip, toMerge);
} catch (IOException e) {
@@ -470,12 +477,12 @@
}
}
- public static ObjectInserter createDryRunInserter() {
- return new ObjectInserter() {
+ public static ObjectInserter createDryRunInserter(Repository db) {
+ final ObjectInserter delegate = db.newObjectInserter();
+ return new ObjectInserter.Filter() {
@Override
- public ObjectId insert(int objectType, long length, InputStream in)
- throws IOException {
- return idFor(objectType, length, in);
+ protected ObjectInserter delegate() {
+ return delegate;
}
@Override
@@ -487,11 +494,6 @@
public void flush() throws IOException {
// Do nothing.
}
-
- @Override
- public void release() {
- // Do nothing.
- }
};
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index b48028e..b65cc85 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
@@ -21,8 +22,10 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@@ -59,7 +62,24 @@
public MetaDataUpdate create(Project.NameKey name, IdentifiedUser user)
throws RepositoryNotFoundException, IOException {
- MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
+ return create(name, user, null);
+ }
+
+ /**
+ * Create an update using an existing batch ref update.
+ * <p>
+ * This allows batching together updates to multiple metadata refs. For making
+ * multiple commits to a single metadata ref, see
+ * {@link VersionedMetaData#openUpdate(MetaDataUpdate)}.
+ *
+ * @param name project name.
+ * @param user user for the update.
+ * @param batch batch update to use; the caller is responsible for committing
+ * the update.
+ */
+ public MetaDataUpdate create(Project.NameKey name, IdentifiedUser user,
+ BatchRefUpdate batch) throws RepositoryNotFoundException, IOException {
+ MetaDataUpdate md = factory.create(name, mgr.openRepository(name), batch);
md.getCommitBuilder().setAuthor(createPersonIdent(user));
md.getCommitBuilder().setCommitter(serverIdent);
return md;
@@ -86,7 +106,13 @@
public MetaDataUpdate create(Project.NameKey name)
throws RepositoryNotFoundException, IOException {
- MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
+ return create(name, null);
+ }
+
+ /** @see User#create(Project.NameKey, IdentifiedUser, BatchRefUpdate) */
+ public MetaDataUpdate create(Project.NameKey name, BatchRefUpdate batch)
+ throws RepositoryNotFoundException, IOException {
+ MetaDataUpdate md = factory.create(name, mgr.openRepository(name), batch);
md.getCommitBuilder().setAuthor(serverIdent);
md.getCommitBuilder().setCommitter(serverIdent);
return md;
@@ -95,24 +121,32 @@
interface InternalFactory {
MetaDataUpdate create(@Assisted Project.NameKey projectName,
- @Assisted Repository db);
+ @Assisted Repository db, @Assisted @Nullable BatchRefUpdate batch);
}
private final GitReferenceUpdated gitRefUpdated;
private final Project.NameKey projectName;
private final Repository db;
+ private final BatchRefUpdate batch;
private final CommitBuilder commit;
private boolean allowEmpty;
- @Inject
+ @AssistedInject
public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
- @Assisted Project.NameKey projectName, @Assisted Repository db) {
+ @Assisted Project.NameKey projectName, @Assisted Repository db,
+ @Assisted @Nullable BatchRefUpdate batch) {
this.gitRefUpdated = gitRefUpdated;
this.projectName = projectName;
this.db = db;
+ this.batch = batch;
this.commit = new CommitBuilder();
}
+ public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
+ Project.NameKey projectName, Repository db) {
+ this(gitRefUpdated, projectName, db, null);
+ }
+
/** Set the commit message used when committing the update. */
public void setMessage(String message) {
getCommitBuilder().setMessage(message);
@@ -128,6 +162,11 @@
this.allowEmpty = allowEmpty;
}
+ /** @return batch in which to run the update, or {@code null} for no batch. */
+ BatchRefUpdate getBatch() {
+ return batch;
+ }
+
/** Close the cached Repository handle. */
public void close() {
getRepository().close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index 4279b31..d081fe6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -25,14 +25,13 @@
import java.io.IOException;
import java.io.OutputStream;
-
+import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.List;
/**
* Progress reporting interface that multiplexes multiple sub-tasks.
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 a905385..7292cc4 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
@@ -220,6 +220,10 @@
this.projectName = projectName;
}
+ public Project.NameKey getName() {
+ return projectName;
+ }
+
public Project getProject() {
return project;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 9005bdf..45c0fc7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -33,6 +33,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
@@ -40,6 +41,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.CheckedFuture;
@@ -415,7 +417,7 @@
this.project = projectControl.getProject();
this.repo = repo;
this.rp = new ReceivePack(repo);
- this.rejectCommits = BanCommit.loadRejectCommitsMap(repo);
+ this.rejectCommits = BanCommit.loadRejectCommitsMap(repo, rp.getRevWalk());
this.subOpFactory = subOpFactory;
this.submitProvider = submitProvider;
@@ -643,7 +645,10 @@
closeProgress.end();
commandProgress.end();
progress.end();
+ reportMessages();
+ }
+ private void reportMessages() {
Iterable<CreateRequest> created =
Iterables.filter(newChanges, new Predicate<CreateRequest>() {
@Override
@@ -660,15 +665,22 @@
addMessage("");
}
- Iterable<ReplaceRequest> updated =
- Iterables.filter(replaceByChange.values(),
- new Predicate<ReplaceRequest>() {
+ List<ReplaceRequest> updated = FluentIterable
+ .from(replaceByChange.values())
+ .filter(new Predicate<ReplaceRequest>() {
+ @Override
+ public boolean apply(ReplaceRequest input) {
+ return !input.skip && input.inputCommand.getResult() == OK;
+ }
+ })
+ .toSortedList(Ordering.natural().onResultOf(
+ new Function<ReplaceRequest, Integer>() {
@Override
- public boolean apply(ReplaceRequest input) {
- return !input.skip && input.inputCommand.getResult() == OK;
+ public Integer apply(ReplaceRequest in) {
+ return in.change.getId().get();
}
- });
- if (!Iterables.isEmpty(updated)) {
+ }));
+ if (!updated.isEmpty()) {
addMessage("");
addMessage("Updated Changes:");
for (ReplaceRequest u : updated) {
@@ -991,7 +1003,8 @@
}
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
- if (ctl.canCreate(rp.getRevWalk(), obj, allRefs.values().contains(obj))) {
+ rp.getRevWalk().reset();
+ if (ctl.canCreate(db, rp.getRevWalk(), obj)) {
validateNewCommits(ctl, cmd);
batch.addCommand(cmd);
} else {
@@ -2130,8 +2143,11 @@
allRefs.size() / estRefsPerChange,
estRefsPerChange);
for (Ref ref : allRefs.values()) {
- if (ref.getObjectId() != null && PatchSet.isRef(ref.getName())) {
- refsByChange.put(Change.Id.fromRef(ref.getName()), ref);
+ if (ref.getObjectId() != null) {
+ PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
+ if (psId != null) {
+ refsByChange.put(psId.getParentKey(), ref);
+ }
}
}
}
@@ -2267,7 +2283,8 @@
commitValidatorsFactory.create(ctl, sshInfo, repo);
try {
- messages.addAll(commitValidators.validateForReceiveCommits(receiveEvent));
+ messages.addAll(commitValidators.validateForReceiveCommits(
+ receiveEvent, rejectCommits));
} catch (CommitValidationException e) {
messages.addAll(e.getMessages());
reject(cmd, e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 7828973..1da2f7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -26,6 +26,7 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -40,6 +41,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.RawParseUtils;
@@ -175,11 +177,27 @@
void write(CommitBuilder commit) throws IOException;
void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
RevCommit createRef(String refName) throws IOException;
+ void removeRef(String refName) throws IOException;
RevCommit commit() throws IOException;
RevCommit commitAt(ObjectId revision) throws IOException;
void close();
}
+ /**
+ * Open a batch of updates to the same metadata ref.
+ * <p>
+ * This allows making multiple commits to a single metadata ref, at the end of
+ * which is a single ref update. For batching together updates to multiple
+ * refs (each consisting of one or more commits against their respective
+ * refs), create the {@link MetaDataUpdate} with a {@link BatchRefUpdate}.
+ * <p>
+ * A ref update produced by this {@link BatchMetaDataUpdate} is only committed
+ * if there is no associated {@link BatchRefUpdate}. As a result, the
+ * configured ref updated event is not fired if there is an associated batch.
+ *
+ * @param update helper info about the update.
+ * @throws IOException if the update failed.
+ */
public BatchMetaDataUpdate openUpdate(final MetaDataUpdate update) throws IOException {
final Repository db = update.getRepository();
@@ -222,13 +240,23 @@
return;
}
- ObjectId res = newTree.writeTree(inserter);
+ // Reuse tree from parent commit unless there are contents in newTree or
+ // there is no tree for a parent commit.
+ ObjectId res = newTree.getEntryCount() != 0 || srcTree == null
+ ? newTree.writeTree(inserter) : srcTree.copy();
if (res.equals(srcTree) && !update.allowEmpty()
&& (commit.getTreeId() == null)) {
// If there are no changes to the content, don't create the commit.
return;
}
+ // If changes are made to the DirCache and those changes are written as
+ // a commit and then the tree ID is set for the CommitBuilder, then
+ // those previous DirCache changes will be ignored and the commit's
+ // tree will be replaced with the ID in the CommitBuilder. The same is
+ // true if you explicitly set tree ID in a commit and then make changes
+ // to the DirCache; that tree ID will be ignored and replaced by that of
+ // the tree for the updated DirCache.
if (commit.getTreeId() == null) {
commit.setTreeId(res);
} else {
@@ -249,20 +277,23 @@
if (Objects.equal(src, revision)) {
return revision;
}
+ return updateRef(ObjectId.zeroId(), src, refName);
+ }
+ @Override
+ public void removeRef(String refName) throws IOException {
RefUpdate ru = db.updateRef(refName);
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(src);
- ru.disableRefLog();
- inserter.flush();
- RefUpdate.Result result = ru.update();
+ ru.setForceUpdate(true);
+ if (revision != null) {
+ ru.setExpectedOldObjectId(revision);
+ }
+ RefUpdate.Result result = ru.delete();
switch (result) {
- case NEW:
- revision = rw.parseCommit(ru.getNewObjectId());
+ case FORCED:
update.fireGitRefUpdatedEvent(ru);
- return revision;
+ return;
default:
- throw new IOException("Cannot update " + ru.getName() + " in "
+ throw new IOException("Cannot delete " + ru.getName() + " in "
+ db.getDirectory() + ": " + ru.getResult());
}
}
@@ -277,28 +308,8 @@
if (Objects.equal(src, expected)) {
return revision;
}
-
- RefUpdate ru = db.updateRef(getRefName());
- if (expected != null) {
- ru.setExpectedOldObjectId(expected);
- } else {
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- }
- ru.setNewObjectId(src);
- ru.disableRefLog();
- inserter.flush();
-
- switch (ru.update(rw)) {
- case NEW:
- case FAST_FORWARD:
- revision = rw.parseCommit(ru.getNewObjectId());
- update.fireGitRefUpdatedEvent(ru);
- return revision;
-
- default:
- throw new IOException("Cannot update " + ru.getName() + " in "
- + db.getDirectory() + ": " + ru.getResult());
- }
+ return updateRef(Objects.firstNonNull(expected, ObjectId.zeroId()), src,
+ getRefName());
}
@Override
@@ -315,6 +326,35 @@
reader = null;
}
}
+
+ private RevCommit updateRef(AnyObjectId oldId, AnyObjectId newId,
+ String refName) throws IOException {
+ BatchRefUpdate bru = update.getBatch();
+ if (bru != null) {
+ bru.addCommand(new ReceiveCommand(
+ oldId.toObjectId(), newId.toObjectId(), refName));
+ inserter.flush();
+ revision = rw.parseCommit(newId);
+ return revision;
+ }
+
+ RefUpdate ru = db.updateRef(refName);
+ ru.setExpectedOldObjectId(oldId);
+ ru.setNewObjectId(src);
+ ru.disableRefLog();
+ inserter.flush();
+ RefUpdate.Result result = ru.update();
+ switch (result) {
+ case NEW:
+ case FAST_FORWARD:
+ revision = rw.parseCommit(ru.getNewObjectId());
+ update.fireGitRefUpdatedEvent(ru);
+ return revision;
+ default:
+ throw new IOException("Cannot update " + ru.getName() + " in "
+ + db.getDirectory() + ": " + ru.getResult());
+ }
+ }
};
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 62d80fa..30702c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -17,7 +17,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.gerrit.reviewdb.client.Change;
-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.server.ReviewDb;
@@ -69,10 +68,11 @@
}
public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeperately) {
- if (projectCtl.allRefsAreVisibleExcept(
- ImmutableSet.of(RefNames.REFS_CONFIG))) {
+ if (projectCtl.allRefsAreVisible(ImmutableSet.of(RefNames.REFS_CONFIG))) {
Map<String, Ref> r = Maps.newHashMap(refs);
- r.remove(RefNames.REFS_CONFIG);
+ if (!projectCtl.controlForRef(RefNames.REFS_CONFIG).isVisible()) {
+ r.remove(RefNames.REFS_CONFIG);
+ }
return r;
}
@@ -81,12 +81,13 @@
final List<Ref> deferredTags = new ArrayList<>();
for (Ref ref : refs.values()) {
+ Change.Id changeId;
if (ref.getName().startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
continue;
- } else if (PatchSet.isRef(ref.getName())) {
- // Reference to a patch set is visible if the change is visible.
+ } else if ((changeId = Change.Id.fromRef(ref.getName())) != null) {
+ // Reference related to a change is visible if the change is visible.
//
- if (showChanges && visibleChanges.contains(Change.Id.fromRef(ref.getName()))) {
+ if (showChanges && visibleChanges.contains(changeId)) {
result.put(ref.getName(), ref);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 3ff36ab..c1ae440 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -26,7 +26,9 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeConflictException;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.util.TimeUtil;
@@ -84,15 +86,16 @@
// taking the delta relative to that one parent and redoing
// that on the current merge tip.
//
-
- mergeTip = writeCherryPickCommit(mergeTip, n);
-
- if (mergeTip != null) {
+ try {
+ mergeTip = writeCherryPickCommit(mergeTip, n);
newCommits.put(mergeTip.getPatchsetId().getParentKey(), mergeTip);
- } else {
+ } catch (MergeConflictException mce) {
n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
+ mergeTip = null;
+ } catch (MergeIdenticalTreeException mie) {
+ n.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
+ mergeTip = null;
}
-
} else {
// There are multiple parents, so this is a merge commit. We
// don't want to cherry-pick the merge as clients can't easily
@@ -131,7 +134,8 @@
private CodeReviewCommit writeCherryPickCommit(CodeReviewCommit mergeTip,
CodeReviewCommit n) throws IOException, OrmException,
- NoSuchChangeException {
+ NoSuchChangeException, MergeConflictException,
+ MergeIdenticalTreeException {
args.rw.parseBody(n);
@@ -156,10 +160,6 @@
args.inserter, mergeTip, n, cherryPickCommitterIdent,
cherryPickCmtMsg, args.rw);
- if (newCommit == null) {
- return null;
- }
-
PatchSet.Id id =
ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
final PatchSet ps = new PatchSet(id);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 9d0eb66..cad5174 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -24,7 +24,6 @@
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.CommitReceivedEvent;
-import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ReceiveCommits;
import com.google.gerrit.server.git.ValidationError;
@@ -93,7 +92,8 @@
}
public List<CommitValidationMessage> validateForReceiveCommits(
- CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ CommitReceivedEvent receiveEvent, NoteMap rejectCommits)
+ throws CommitValidationException {
List<CommitValidationListener> validators = new LinkedList<>();
@@ -110,7 +110,7 @@
installCommitMsgHookCommand, sshInfo));
}
validators.add(new ConfigValidator(refControl, repo));
- validators.add(new BannedCommitsValidator(repo));
+ validators.add(new BannedCommitsValidator(rejectCommits));
validators.add(new PluginCommitValidationListener(commitValidationListeners));
List<CommitValidationMessage> messages = new LinkedList<>();
@@ -522,24 +522,25 @@
/** Reject banned commits. */
public static class BannedCommitsValidator implements
CommitValidationListener {
- private final Repository repo;
+ private final NoteMap rejectCommits;
- public BannedCommitsValidator(Repository repo) {
- this.repo = repo;
+ public BannedCommitsValidator(NoteMap rejectCommits) {
+ this.rejectCommits = rejectCommits;
}
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
try {
- NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo);
if (rejectCommits.contains(receiveEvent.commit)) {
throw new CommitValidationException("contains banned commit "
+ receiveEvent.commit.getName());
}
return Collections.emptyList();
} catch (IOException e) {
- throw new CommitValidationException(e.getMessage(), e);
+ String m = "error checking banned commits";
+ log.warn(m, e);
+ throw new CommitValidationException(m, e);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 76998b7..6f70d46 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -16,8 +16,8 @@
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
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 614138a..361a773 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
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -27,14 +28,12 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.group.AddIncludedGroups.Input;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -75,15 +74,17 @@
private final GroupIncludeCache groupIncludeCache;
private final Provider<ReviewDb> db;
private final GroupJson json;
+ private final AuditService auditService;
@Inject
public AddIncludedGroups(GroupsCollection groupsCollection,
- GroupIncludeCache groupIncludeCache,
- Provider<ReviewDb> db, GroupJson json) {
+ GroupIncludeCache groupIncludeCache, Provider<ReviewDb> db,
+ GroupJson json, AuditService auditService) {
this.groupsCollection = groupsCollection;
this.groupIncludeCache = groupIncludeCache;
this.db = db;
this.json = json;
+ this.auditService = auditService;
}
@Override
@@ -98,7 +99,6 @@
GroupControl control = resource.getControl();
Map<AccountGroup.UUID, AccountGroupById> newIncludedGroups = Maps.newHashMap();
- List<AccountGroupByIdAud> newIncludedGroupsAudits = Lists.newLinkedList();
List<GroupInfo> result = Lists.newLinkedList();
Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId();
@@ -117,15 +117,13 @@
if (agi == null) {
agi = new AccountGroupById(agiKey);
newIncludedGroups.put(d.getGroupUUID(), agi);
- newIncludedGroupsAudits.add(
- new AccountGroupByIdAud(agi, me, TimeUtil.nowTs()));
}
}
result.add(json.format(d));
}
if (!newIncludedGroups.isEmpty()) {
- db.get().accountGroupByIdAud().insert(newIncludedGroupsAudits);
+ auditService.dispatchAddGroupsToGroup(me, newIncludedGroups.values());
db.get().accountGroupById().insert(newIncludedGroups.values());
for (AccountGroupById agi : newIncludedGroups.values()) {
groupIncludeCache.evictMemberIn(agi.getIncludeUUID());
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 df58c8f..aaeffd9 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
@@ -17,6 +17,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -25,7 +26,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
@@ -39,7 +39,6 @@
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.group.AddMembers.Input;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -82,6 +81,7 @@
private final AccountCache accountCache;
private final AccountInfo.Loader.Factory infoFactory;
private final Provider<ReviewDb> db;
+ private final AuditService auditService;
@Inject
AddMembers(AccountManager accountManager,
@@ -90,8 +90,10 @@
AccountResolver accountResolver,
AccountCache accountCache,
AccountInfo.Loader.Factory infoFactory,
- Provider<ReviewDb> db) {
+ Provider<ReviewDb> db,
+ AuditService auditService) {
this.accountManager = accountManager;
+ this.auditService = auditService;
this.authType = authConfig.getAuthType();
this.accounts = accounts;
this.accountResolver = accountResolver;
@@ -112,7 +114,6 @@
GroupControl control = resource.getControl();
Map<Account.Id, AccountGroupMember> newAccountGroupMembers = Maps.newHashMap();
- List<AccountGroupMemberAudit> newAccountGroupMemberAudits = Lists.newLinkedList();
List<AccountInfo> result = Lists.newLinkedList();
Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId();
AccountInfo.Loader loader = infoFactory.create(true);
@@ -135,14 +136,12 @@
if (m == null) {
m = new AccountGroupMember(key);
newAccountGroupMembers.put(m.getAccountId(), m);
- newAccountGroupMemberAudits.add(
- new AccountGroupMemberAudit(m, me, TimeUtil.nowTs()));
}
}
result.add(loader.get(a.getId()));
}
- db.get().accountGroupMembersAudit().insert(newAccountGroupMemberAudits);
+ auditService.dispatchAddAccountsToGroup(me, newAccountGroupMembers.values());
db.get().accountGroupMembers().insert(newAccountGroupMembers.values());
for (AccountGroupMember m : newAccountGroupMembers.values()) {
accountCache.evict(m.getAccountId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
new file mode 100644
index 0000000..b921224
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
@@ -0,0 +1,201 @@
+// Copyright (C) 2014 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.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.gerrit.audit.GroupMemberAuditListener;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.UniversalGroupBackend;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.slf4j.Logger;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+class DbGroupMemberAuditListener implements GroupMemberAuditListener {
+ private static final Logger log = org.slf4j.LoggerFactory
+ .getLogger(DbGroupMemberAuditListener.class);
+
+ private final Provider<ReviewDb> db;
+ private final AccountCache accountCache;
+ private final GroupCache groupCache;
+ private final UniversalGroupBackend groupBackend;
+
+ @Inject
+ public DbGroupMemberAuditListener(Provider<ReviewDb> db,
+ AccountCache accountCache, GroupCache groupCache,
+ UniversalGroupBackend groupBackend) {
+ this.db = db;
+ this.accountCache = accountCache;
+ this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
+ }
+
+ @Override
+ public void onAddAccountsToGroup(Account.Id me,
+ Collection<AccountGroupMember> added) {
+ List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
+ for (AccountGroupMember m : added) {
+ AccountGroupMemberAudit audit =
+ new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
+ auditInserts.add(audit);
+ }
+ try {
+ db.get().accountGroupMembersAudit().insert(auditInserts);
+ } catch (OrmException e) {
+ logOrmExceptionForAccounts(
+ "Cannot log add accounts to group event performed by user", me,
+ added, e);
+ }
+ }
+
+ @Override
+ public void onDeleteAccountsFromGroup(Account.Id me,
+ Collection<AccountGroupMember> removed) {
+ List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
+ List<AccountGroupMemberAudit> auditUpdates = Lists.newLinkedList();
+ ReviewDb reviewDB = db.get();
+ try {
+ for (AccountGroupMember m : removed) {
+ AccountGroupMemberAudit audit = null;
+ for (AccountGroupMemberAudit a : reviewDB.accountGroupMembersAudit()
+ .byGroupAccount(m.getAccountGroupId(), m.getAccountId())) {
+ if (a.isActive()) {
+ audit = a;
+ break;
+ }
+ }
+
+ if (audit != null) {
+ audit.removed(me, TimeUtil.nowTs());
+ auditUpdates.add(audit);
+ } else {
+ audit = new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
+ audit.removedLegacy();
+ auditInserts.add(audit);
+ }
+ }
+ reviewDB.accountGroupMembersAudit().update(auditUpdates);
+ reviewDB.accountGroupMembersAudit().insert(auditInserts);
+ } catch (OrmException e) {
+ logOrmExceptionForAccounts(
+ "Cannot log delete accounts from group event performed by user", me,
+ removed, e);
+ }
+ }
+
+ @Override
+ public void onAddGroupsToGroup(Account.Id me,
+ Collection<AccountGroupById> added) {
+ List<AccountGroupByIdAud> includesAudit = new ArrayList<>();
+ for (AccountGroupById groupInclude : added) {
+ AccountGroupByIdAud audit =
+ new AccountGroupByIdAud(groupInclude, me, TimeUtil.nowTs());
+ includesAudit.add(audit);
+ }
+ try {
+ db.get().accountGroupByIdAud().insert(includesAudit);
+ } catch (OrmException e) {
+ logOrmExceptionForGroups(
+ "Cannot log add groups to group event performed by user", me, added,
+ e);
+ }
+ }
+
+ @Override
+ public void onDeleteGroupsFromGroup(Account.Id me,
+ Collection<AccountGroupById> removed) {
+ final List<AccountGroupByIdAud> auditUpdates = Lists.newLinkedList();
+ try {
+ for (final AccountGroupById g : removed) {
+ AccountGroupByIdAud audit = null;
+ for (AccountGroupByIdAud a : db.get().accountGroupByIdAud()
+ .byGroupInclude(g.getGroupId(), g.getIncludeUUID())) {
+ if (a.isActive()) {
+ audit = a;
+ break;
+ }
+ }
+
+ if (audit != null) {
+ audit.removed(me, TimeUtil.nowTs());
+ auditUpdates.add(audit);
+ }
+ }
+ db.get().accountGroupByIdAud().update(auditUpdates);
+ } catch (OrmException e) {
+ logOrmExceptionForGroups(
+ "Cannot log delete groups from group event performed by user", me,
+ removed, e);
+ }
+ }
+
+ private void logOrmExceptionForAccounts(String header, Account.Id me,
+ Collection<AccountGroupMember> values, OrmException e) {
+ List<String> descriptions = new ArrayList<>();
+ for (AccountGroupMember m : values) {
+ Account.Id accountId = m.getAccountId();
+ String userName = accountCache.get(accountId).getUserName();
+ AccountGroup.Id groupId = m.getAccountGroupId();
+ String groupName = groupCache.get(groupId).getName();
+
+ descriptions.add(MessageFormat.format("account {0}/{1}, group {2}/{3}",
+ accountId, userName, groupId, groupName));
+ }
+ logOrmException(header, me, descriptions, e);
+ }
+
+ private void logOrmExceptionForGroups(String header, Account.Id me,
+ Collection<AccountGroupById> values, OrmException e) {
+ List<String> descriptions = new ArrayList<>();
+ for (AccountGroupById m : values) {
+ AccountGroup.UUID groupUuid = m.getIncludeUUID();
+ String groupName = groupBackend.get(groupUuid).getName();
+ AccountGroup.Id targetGroupId = m.getGroupId();
+ String targetGroupName = groupCache.get(targetGroupId).getName();
+
+ descriptions.add(MessageFormat.format("group {0}/{1}, group {2}/{3}",
+ groupUuid, groupName, targetGroupId, targetGroupName));
+ }
+ logOrmException(header, me, descriptions, e);
+ }
+
+ private void logOrmException(String header, Account.Id me,
+ Iterable<?> values, OrmException e) {
+ StringBuilder message = new StringBuilder(header);
+ message.append(" ");
+ message.append(me);
+ message.append("/");
+ message.append(accountCache.get(me).getUserName());
+ message.append(": ");
+ message.append(Joiner.on("; ").join(values));
+ log.error(message.toString(), e);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
index 555744e..74d5946 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -26,14 +27,12 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.group.AddIncludedGroups.Input;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -48,16 +47,17 @@
private final GroupIncludeCache groupIncludeCache;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
+ private final AuditService auditService;
@Inject
DeleteIncludedGroups(GroupsCollection groupsCollection,
- GroupIncludeCache groupIncludeCache,
- Provider<ReviewDb> db,
- Provider<CurrentUser> self) {
+ GroupIncludeCache groupIncludeCache, Provider<ReviewDb> db,
+ Provider<CurrentUser> self, AuditService auditService) {
this.groupsCollection = groupsCollection;
this.groupIncludeCache = groupIncludeCache;
this.db = db;
this.self = self;
+ this.auditService = auditService;
}
@Override
@@ -109,27 +109,10 @@
return groups;
}
- private void writeAudits(final List<AccountGroupById> toBeRemoved)
+ private void writeAudits(final List<AccountGroupById> toRemoved)
throws OrmException {
final Account.Id me = ((IdentifiedUser) self.get()).getAccountId();
- final List<AccountGroupByIdAud> auditUpdates = Lists.newLinkedList();
- for (final AccountGroupById g : toBeRemoved) {
- AccountGroupByIdAud audit = null;
- for (AccountGroupByIdAud a : db.get()
- .accountGroupByIdAud().byGroupInclude(g.getGroupId(),
- g.getIncludeUUID())) {
- if (a.isActive()) {
- audit = a;
- break;
- }
- }
-
- if (audit != null) {
- audit.removed(me, TimeUtil.nowTs());
- auditUpdates.add(audit);
- }
- }
- db.get().accountGroupByIdAud().update(auditUpdates);
+ auditService.dispatchDeleteGroupsFromGroup(me, toRemoved);
}
@Singleton
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
index 654ad88..605933b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
@@ -24,7 +25,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -32,7 +32,6 @@
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.AddMembers.Input;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -47,15 +46,18 @@
private final AccountCache accountCache;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
+ private final AuditService auditService;
@Inject
DeleteMembers(AccountsCollection accounts,
AccountCache accountCache, Provider<ReviewDb> db,
- Provider<CurrentUser> self) {
+ Provider<CurrentUser> self,
+ AuditService auditService) {
this.accounts = accounts;
this.accountCache = accountCache;
this.db = db;
this.self = self;
+ this.auditService = auditService;
}
@Override
@@ -94,32 +96,9 @@
return Response.none();
}
- private void writeAudits(final List<AccountGroupMember> toBeRemoved)
- throws OrmException {
+ private void writeAudits(final List<AccountGroupMember> toRemove) {
final Account.Id me = ((IdentifiedUser) self.get()).getAccountId();
- final List<AccountGroupMemberAudit> auditUpdates = Lists.newLinkedList();
- final List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
- for (final AccountGroupMember m : toBeRemoved) {
- AccountGroupMemberAudit audit = null;
- for (AccountGroupMemberAudit a : db.get().accountGroupMembersAudit()
- .byGroupAccount(m.getAccountGroupId(), m.getAccountId())) {
- if (a.isActive()) {
- audit = a;
- break;
- }
- }
-
- if (audit != null) {
- audit.removed(me, TimeUtil.nowTs());
- auditUpdates.add(audit);
- } else {
- audit = new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
- audit.removedLegacy();
- auditInserts.add(audit);
- }
- }
- db.get().accountGroupMembersAudit().update(auditUpdates);
- db.get().accountGroupMembersAudit().insert(auditInserts);
+ auditService.dispatchDeleteAccountsFromGroup(me, toRemove);
}
private Map<Account.Id, AccountGroupMember> getMembers(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
index 97338f1..9b5d9ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
@@ -18,7 +18,9 @@
import static com.google.gerrit.server.group.IncludedGroupResource.INCLUDED_GROUP_KIND;
import static com.google.gerrit.server.group.MemberResource.MEMBER_KIND;
+import com.google.gerrit.audit.GroupMemberAuditListener;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.group.AddIncludedGroups.UpdateIncludedGroup;
import com.google.gerrit.server.group.AddMembers.UpdateMember;
@@ -65,5 +67,9 @@
delete(INCLUDED_GROUP_KIND).to(DeleteIncludedGroup.class);
install(new FactoryModuleBuilder().build(CreateGroup.Factory.class));
+
+ DynamicSet.bind(binder(), GroupMemberAuditListener.class).to(
+ DbGroupMemberAuditListener.class);
+
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
index 5ee240b..a82527f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
@@ -32,7 +32,9 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.change.MergeabilityChecker;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.patch.PatchListLoader;
@@ -43,13 +45,14 @@
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
@@ -112,6 +115,7 @@
private final ListeningExecutorService executor;
private final ChangeIndexer.Factory indexerFactory;
private final MergeabilityChecker mergeabilityChecker;
+ private final ThreeWayMergeStrategy mergeStrategy;
@Inject
ChangeBatchIndexer(SchemaFactory<ReviewDb> schemaFactory,
@@ -119,6 +123,7 @@
GitRepositoryManager repoManager,
@IndexExecutor ListeningExecutorService executor,
ChangeIndexer.Factory indexerFactory,
+ @GerritServerConfig Config config,
@Nullable MergeabilityChecker mergeabilityChecker) {
this.schemaFactory = schemaFactory;
this.changeDataFactory = changeDataFactory;
@@ -126,6 +131,7 @@
this.executor = executor;
this.indexerFactory = indexerFactory;
this.mergeabilityChecker = mergeabilityChecker;
+ this.mergeStrategy = MergeUtil.getMergeStrategy(config);
}
public Result indexAll(ChangeIndex index, Iterable<Project.NameKey> projects,
@@ -162,13 +168,13 @@
public void run() {
try {
future.get();
- } catch (InterruptedException e) {
- fail(project, e);
- } catch (ExecutionException e) {
+ } catch (ExecutionException | InterruptedException e) {
fail(project, e);
} catch (RuntimeException e) {
failAndThrow(project, e);
} catch (Error e) {
+ // Can't join with RuntimeException because "RuntimeException |
+ // Error" becomes Throwable, which messes with signatures.
failAndThrow(project, e);
} finally {
projTask.update(1);
@@ -239,8 +245,13 @@
byId.put(r.getObjectId(), changeDataFactory.create(db, c));
}
}
- new ProjectIndexer(indexer, byId, repo, done, failed, verboseWriter)
- .call();
+ new ProjectIndexer(indexer,
+ mergeStrategy,
+ byId,
+ repo,
+ done,
+ failed,
+ verboseWriter).call();
} catch (RepositoryNotFoundException rnfe) {
log.error(rnfe.getMessage());
} finally {
@@ -259,8 +270,9 @@
};
}
- public static class ProjectIndexer implements Callable<Void> {
+ private static class ProjectIndexer implements Callable<Void> {
private final ChangeIndexer indexer;
+ private final ThreeWayMergeStrategy mergeStrategy;
private final Multimap<ObjectId, ChangeData> byId;
private final ProgressMonitor done;
private final ProgressMonitor failed;
@@ -268,16 +280,15 @@
private final Repository repo;
private RevWalk walk;
- public ProjectIndexer(ChangeIndexer indexer,
- Multimap<ObjectId, ChangeData> changesByCommitId, Repository repo) {
- this(indexer, changesByCommitId, repo,
- NullProgressMonitor.INSTANCE, NullProgressMonitor.INSTANCE, null);
- }
-
- ProjectIndexer(ChangeIndexer indexer,
- Multimap<ObjectId, ChangeData> changesByCommitId, Repository repo,
- ProgressMonitor done, ProgressMonitor failed, PrintWriter verboseWriter) {
+ private ProjectIndexer(ChangeIndexer indexer,
+ ThreeWayMergeStrategy mergeStrategy,
+ Multimap<ObjectId, ChangeData> changesByCommitId,
+ Repository repo,
+ ProgressMonitor done,
+ ProgressMonitor failed,
+ PrintWriter verboseWriter) {
this.indexer = indexer;
+ this.mergeStrategy = mergeStrategy;
this.byId = changesByCommitId;
this.repo = repo;
this.done = done;
@@ -376,7 +387,7 @@
walk.parseBody(a);
return walk.parseTree(a.getTree());
case 2:
- return PatchListLoader.automerge(repo, walk, b);
+ return PatchListLoader.automerge(repo, walk, b, mergeStrategy);
default:
return null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index 41dfba5..2e82bdc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -27,9 +27,9 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
-import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.OrmException;
@@ -408,7 +408,7 @@
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
Set<String> r = Sets.newHashSet();
- for (PatchLineComment c : input.comments()) {
+ for (PatchLineComment c : input.publishedComments()) {
r.add(c.getMessage());
}
for (ChangeMessage m : input.messages()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
index a710a10..eb74928 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -39,23 +39,12 @@
public void close();
/**
- * Insert a change document into the index.
- * <p>
- * Results may not be immediately visible to searchers, but should be visible
- * within a reasonable amount of time.
- *
- * @param cd change document
- *
- * @throws IOException if the change could not be inserted.
- */
- public void insert(ChangeData cd) throws IOException;
-
- /**
* Update a change document in the index.
* <p>
* Semantically equivalent to deleting the document and reinserting it with
- * new field values. Results may not be immediately visible to searchers, but
- * should be visible within a reasonable amount of time.
+ * new field values. A document that does not already exist is created. Results
+ * may not be immediately visible to searchers, but should be visible within a
+ * reasonable amount of time.
*
* @param cd change document
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 2beb49f..5f2ffcb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.errors.EmailException;
@@ -24,6 +25,7 @@
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -53,13 +55,16 @@
private final NotifyHandling notify;
private List<PatchLineComment> inlineComments = Collections.emptyList();
+ private final PatchLineCommentsUtil plcUtil;
@Inject
public CommentSender(EmailArguments ea,
@Assisted NotifyHandling notify,
- @Assisted Change c) {
+ @Assisted Change c,
+ PatchLineCommentsUtil plcUtil) {
super(ea, c, "comment");
this.notify = notify;
+ this.plcUtil = plcUtil;
}
public void setPatchLineComments(final List<PatchLineComment> plc)
@@ -232,17 +237,19 @@
private void appendQuotedParent(StringBuilder out, PatchLineComment child) {
if (child.getParentUuid() != null) {
- PatchLineComment parent;
+ Optional<PatchLineComment> parent;
+ PatchLineComment.Key key = new PatchLineComment.Key(
+ child.getKey().getParentKey(),
+ child.getParentUuid());
try {
- parent = args.db.get().patchComments().get(
- new PatchLineComment.Key(
- child.getKey().getParentKey(),
- child.getParentUuid()));
+ parent = plcUtil.get(args.db.get(), changeData.notes(), key);
} catch (OrmException e) {
- parent = null;
+ log.warn("Could not find the parent of this comment: "
+ + child.toString());
+ parent = Optional.absent();
}
- if (parent != null) {
- String msg = parent.getMessage().trim();
+ if (parent.isPresent()) {
+ String msg = parent.get().getMessage().trim();
if (msg.length() > 75) {
msg = msg.substring(0, 75);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 37094fd..9d93d89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -29,26 +29,22 @@
public abstract class AbstractChangeNotes<T> extends VersionedMetaData {
private boolean loaded;
protected final GitRepositoryManager repoManager;
- private final Change change;
+ private final Change.Id changeId;
- AbstractChangeNotes(GitRepositoryManager repoManager, Change change) {
+ AbstractChangeNotes(GitRepositoryManager repoManager, Change.Id changeId) {
this.repoManager = repoManager;
- this.change = new Change(change);
+ this.changeId = changeId;
}
public Change.Id getChangeId() {
- return change.getId();
- }
-
- public Change getChange() {
- return change;
+ return changeId;
}
public T load() throws OrmException {
if (!loaded) {
Repository repo;
try {
- repo = repoManager.openRepository(getProjectName());
+ repo = repoManager.openMetadataRepository(getProjectName());
} catch (IOException e) {
throw new OrmException(e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index 0d637f5..24a8d16 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -26,10 +25,13 @@
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.VersionedMetaData;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -43,6 +45,7 @@
protected final GitRepositoryManager repoManager;
protected final MetaDataUpdate.User updateFactory;
protected final ChangeControl ctl;
+ protected final String anonymousCowardName;
protected final PersonIdent serverIdent;
protected final Date when;
protected PatchSet.Id psId;
@@ -50,15 +53,22 @@
AbstractChangeUpdate(NotesMigration migration,
GitRepositoryManager repoManager,
MetaDataUpdate.User updateFactory, ChangeControl ctl,
- PersonIdent serverIdent, Date when) {
+ PersonIdent serverIdent,
+ String anonymousCowardName,
+ Date when) {
this.migration = migration;
this.repoManager = repoManager;
this.updateFactory = updateFactory;
this.ctl = ctl;
this.serverIdent = serverIdent;
+ this.anonymousCowardName = anonymousCowardName;
this.when = when;
}
+ public ChangeNotes getChangeNotes() {
+ return ctl.getNotes();
+ }
+
public Change getChange() {
return ctl.getChange();
}
@@ -71,6 +81,10 @@
return (IdentifiedUser) ctl.getCurrentUser();
}
+ public PatchSet.Id getPatchSetId() {
+ return psId;
+ }
+
public void setPatchSetId(PatchSet.Id psId) {
checkArgument(psId == null
|| psId.getParentKey().equals(getChange().getId()));
@@ -78,8 +92,8 @@
}
private void load() throws IOException {
- if (migration.write() && getRevision() == null) {
- Repository repo = repoManager.openRepository(getProjectName());
+ if (migration.writeChanges() && getRevision() == null) {
+ Repository repo = repoManager.openMetadataRepository(getProjectName());
try {
load(repo);
} catch (ConfigInvalidException e) {
@@ -90,16 +104,25 @@
}
}
+ public void setInserter(ObjectInserter inserter) {
+ this.inserter = inserter;
+ }
+
@Override
public BatchMetaDataUpdate openUpdate(MetaDataUpdate update) throws IOException {
throw new UnsupportedOperationException("use openUpdate()");
}
public BatchMetaDataUpdate openUpdate() throws IOException {
- if (migration.write()) {
+ return openUpdateInBatch(null);
+ }
+
+ public BatchMetaDataUpdate openUpdateInBatch(BatchRefUpdate bru)
+ throws IOException {
+ if (migration.writeChanges()) {
load();
MetaDataUpdate md =
- updateFactory.create(getProjectName(), getUser());
+ updateFactory.create(getProjectName(), getUser(), bru);
md.setAllowEmpty(true);
return super.openUpdate(md);
}
@@ -120,6 +143,11 @@
}
@Override
+ public void removeRef(String refName) {
+ // Do nothing.
+ }
+
+ @Override
public RevCommit commit() {
return null;
}
@@ -147,12 +175,14 @@
}
protected PersonIdent newIdent(Account author, Date when) {
- return new PersonIdent(
- author.getFullName(),
- author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
- when, serverIdent.getTimeZone());
+ return ChangeNoteUtil.newIdent(author, when, serverIdent,
+ anonymousCowardName);
}
+ /** Writes commit to a BatchMetaDataUpdate without committing the batch. */
+ abstract public void writeCommit(BatchMetaDataUpdate batch)
+ throws OrmException, IOException;
+
/**
* @return the NameKey for the project where the update will be stored,
* which is not necessarily the same as the change's project.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
new file mode 100644
index 0000000..eb7f6c4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -0,0 +1,319 @@
+// Copyright (C) 2014 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.notedb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Table;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
+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.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A single delta to apply atomically to a change.
+ * <p>
+ * This delta contains only draft comments on a single patch set of a change by
+ * a single author. This delta will become a single commit in the All-Users
+ * repository.
+ * <p>
+ * This class is not thread safe.
+ */
+public class ChangeDraftUpdate extends AbstractChangeUpdate {
+ public interface Factory {
+ ChangeDraftUpdate create(ChangeControl ctl, Date when);
+ }
+
+ private final AllUsersName draftsProject;
+ private final Account.Id accountId;
+ private final CommentsInNotesUtil commentsUtil;
+ private final ChangeNotes changeNotes;
+ private final DraftCommentNotes draftNotes;
+
+ private List<PatchLineComment> upsertComments;
+ private List<PatchLineComment> deleteComments;
+
+ @AssistedInject
+ private ChangeDraftUpdate(
+ @GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
+ GitRepositoryManager repoManager,
+ NotesMigration migration,
+ MetaDataUpdate.User updateFactory,
+ DraftCommentNotes.Factory draftNotesFactory,
+ AllUsersName allUsers,
+ CommentsInNotesUtil commentsUtil,
+ @Assisted ChangeControl ctl,
+ @Assisted Date when) throws OrmException {
+ super(migration, repoManager, updateFactory, ctl, serverIdent,
+ anonymousCowardName, when);
+ this.draftsProject = allUsers;
+ this.commentsUtil = commentsUtil;
+ checkState(ctl.getCurrentUser().isIdentifiedUser(),
+ "Current user must be identified");
+ IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+ this.accountId = user.getAccountId();
+ this.changeNotes = getChangeNotes().load();
+ this.draftNotes = draftNotesFactory.create(ctl.getChange().getId(),
+ user.getAccountId());
+
+ this.upsertComments = Lists.newArrayList();
+ this.deleteComments = Lists.newArrayList();
+ }
+
+ public void insertComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ checkArgument(c.getStatus() == Status.DRAFT,
+ "Cannot insert a published comment into a ChangeDraftUpdate");
+ if (migration.readChanges()) {
+ checkArgument(!changeNotes.containsComment(c),
+ "A comment already exists with the same key,"
+ + " so the following comment cannot be inserted: %s", c);
+ }
+ upsertComments.add(c);
+ }
+
+ public void upsertComment(PatchLineComment c) {
+ verifyComment(c);
+ checkArgument(c.getStatus() == Status.DRAFT,
+ "Cannot upsert a published comment into a ChangeDraftUpdate");
+ upsertComments.add(c);
+ }
+
+ public void updateComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ checkArgument(c.getStatus() == Status.DRAFT,
+ "Cannot update a published comment into a ChangeDraftUpdate");
+ // Here, we check to see if this comment existed previously as a draft.
+ // However, this could cause a race condition if there is a delete and an
+ // update operation happening concurrently (or two deletes) and they both
+ // believe that the comment exists. If a delete happens first, then
+ // the update will fail. However, this is an acceptable risk since the
+ // caller wanted the comment deleted anyways, so the end result will be the
+ // same either way.
+ if (migration.readChanges()) {
+ checkArgument(draftNotes.load().containsComment(c),
+ "Cannot update this comment because it didn't exist previously");
+ }
+ upsertComments.add(c);
+ }
+
+ public void deleteComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ // See the comment above about potential race condition.
+ if (migration.readChanges()) {
+ checkArgument(draftNotes.load().containsComment(c),
+ "Cannot delete this comment because it didn't previously exist as a"
+ + " draft");
+ }
+ if (migration.writeChanges()) {
+ if (draftNotes.load().containsComment(c)) {
+ deleteComments.add(c);
+ }
+ }
+ }
+
+ /**
+ * Deletes a PatchLineComment from the list of drafts only if it existed
+ * previously as a draft. If it wasn't a draft previously, this is a no-op.
+ */
+ public void deleteCommentIfPresent(PatchLineComment c) throws OrmException {
+ if (draftNotes.load().containsComment(c)) {
+ verifyComment(c);
+ deleteComments.add(c);
+ }
+ }
+
+ private void verifyComment(PatchLineComment comment) {
+ checkState(psId != null,
+ "setPatchSetId must be called first");
+ checkArgument(getCommentPsId(comment).equals(psId),
+ "Comment on %s does not match configured patch set %s",
+ getCommentPsId(comment), psId);
+ if (migration.writeChanges()) {
+ checkArgument(comment.getRevId() != null);
+ }
+ checkArgument(comment.getAuthor().equals(accountId),
+ "The author for the following comment does not match the author of"
+ + " this ChangeDraftUpdate (%s): %s", accountId, comment);
+ }
+
+ /** @return the tree id for the updated tree */
+ private ObjectId storeCommentsInNotes(AtomicBoolean removedAllComments)
+ throws OrmException, IOException {
+ if (isEmpty()) {
+ return null;
+ }
+
+ NoteMap noteMap = draftNotes.load().getNoteMap();
+ if (noteMap == null) {
+ noteMap = NoteMap.newEmptyMap();
+ }
+
+ Table<PatchSet.Id, String, PatchLineComment> baseDrafts =
+ draftNotes.getDraftBaseComments();
+ Table<PatchSet.Id, String, PatchLineComment> psDrafts =
+ draftNotes.getDraftPsComments();
+
+ boolean draftsEmpty = baseDrafts.isEmpty() && psDrafts.isEmpty();
+
+ // There is no need to rewrite the note for one of the sides of the patch
+ // set if all of the modifications were made to the comments of one side,
+ // so we set these flags to potentially save that extra work.
+ boolean baseSideChanged = false;
+ boolean revisionSideChanged = false;
+
+ // We must define these RevIds so that if this update deletes all
+ // remaining comments on a given side, then we can remove that note.
+ // However, if this update doesn't delete any comments, it is okay for these
+ // to be null because they won't be used.
+ RevId baseRevId = null;
+ RevId psRevId = null;
+
+ for (PatchLineComment c : deleteComments) {
+ if (c.getSide() == (short) 0) {
+ baseSideChanged = true;
+ baseRevId = c.getRevId();
+ baseDrafts.remove(psId, c.getKey().get());
+ } else {
+ revisionSideChanged = true;
+ psRevId = c.getRevId();
+ psDrafts.remove(psId, c.getKey().get());
+ }
+ }
+
+ for (PatchLineComment c : upsertComments) {
+ if (c.getSide() == (short) 0) {
+ baseSideChanged = true;
+ baseDrafts.put(psId, c.getKey().get(), c);
+ } else {
+ revisionSideChanged = true;
+ psDrafts.put(psId, c.getKey().get(), c);
+ }
+ }
+
+ List<PatchLineComment> newBaseDrafts =
+ Lists.newArrayList(baseDrafts.row(psId).values());
+ List<PatchLineComment> newPsDrafts =
+ Lists.newArrayList(psDrafts.row(psId).values());
+
+ updateNoteMap(baseSideChanged, noteMap, newBaseDrafts,
+ baseRevId);
+ updateNoteMap(revisionSideChanged, noteMap, newPsDrafts,
+ psRevId);
+
+ removedAllComments.set(
+ baseDrafts.isEmpty() && psDrafts.isEmpty() && !draftsEmpty);
+
+ return noteMap.writeTree(inserter);
+ }
+
+ private void updateNoteMap(boolean changed, NoteMap noteMap,
+ List<PatchLineComment> comments, RevId commitId)
+ throws IOException, OrmException {
+ if (changed) {
+ if (comments.isEmpty()) {
+ commentsUtil.removeNote(noteMap, commitId);
+ } else {
+ commentsUtil.writeCommentsToNoteMap(noteMap, comments, inserter);
+ }
+ }
+ }
+
+ public RevCommit commit() throws IOException {
+ BatchMetaDataUpdate batch = openUpdate();
+ try {
+ writeCommit(batch);
+ return batch.commit();
+ } catch (OrmException e) {
+ throw new IOException(e);
+ } finally {
+ batch.close();
+ }
+ }
+
+ @Override
+ public void writeCommit(BatchMetaDataUpdate batch)
+ throws OrmException, IOException {
+ CommitBuilder builder = new CommitBuilder();
+ if (migration.writeChanges()) {
+ AtomicBoolean removedAllComments = new AtomicBoolean();
+ ObjectId treeId = storeCommentsInNotes(removedAllComments);
+ if (treeId != null) {
+ if (removedAllComments.get()) {
+ batch.removeRef(getRefName());
+ } else {
+ builder.setTreeId(treeId);
+ batch.write(builder);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected Project.NameKey getProjectName() {
+ return draftsProject;
+ }
+
+ @Override
+ protected String getRefName() {
+ return RefNames.refsDraftComments(accountId, getChange().getId());
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException {
+ if (isEmpty()) {
+ return false;
+ }
+ commit.setAuthor(newIdent(getUser().getAccount(), when));
+ commit.setCommitter(new PersonIdent(serverIdent, when));
+ commit.setMessage(String.format("Comment on patch set %d", psId.get()));
+ return true;
+ }
+
+ private boolean isEmpty() {
+ return deleteComments.isEmpty()
+ && upsertComments.isEmpty();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 7204735..6286332 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -14,11 +14,16 @@
package com.google.gerrit.server.notedb;
+import com.google.gerrit.common.data.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.FooterKey;
+import java.util.Date;
+
public class ChangeNoteUtil {
static final String GERRIT_PLACEHOLDER_HOST = "gerrit";
@@ -39,10 +44,18 @@
r.append(m);
r.append('/');
r.append(n);
- r.append("/meta");
+ r.append(RefNames.META_SUFFIX);
return r.toString();
}
+ static PersonIdent newIdent(Account author, Date when,
+ PersonIdent serverIdent, String anonymousCowardName) {
+ return new PersonIdent(
+ new AccountInfo(author).getName(anonymousCowardName),
+ author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
+ when, serverIdent.getTimeZone());
+ }
+
private ChangeNoteUtil() {
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index b242d7c..425d310 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -14,29 +14,17 @@
package com.google.gerrit.server.notedb;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
+import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Enums;
import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Table;
-import com.google.common.collect.Tables;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
@@ -44,42 +32,32 @@
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet.Id;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.util.LabelVote;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.FooterKey;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.RawParseUtils;
import java.io.IOException;
-import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.text.ParseException;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
/** View of a single {@link Change} based on the log of its notes branch. */
public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
- private static final Ordering<PatchSetApproval> PSA_BY_TIME =
+ static final Ordering<PatchSetApproval> PSA_BY_TIME =
Ordering.natural().onResultOf(
new Function<PatchSetApproval, Timestamp>() {
@Override
@@ -134,367 +112,44 @@
@Singleton
public static class Factory {
private final GitRepositoryManager repoManager;
+ private final AllUsersNameProvider allUsersProvider;
@VisibleForTesting
@Inject
- public Factory(GitRepositoryManager repoManager) {
+ public Factory(GitRepositoryManager repoManager, AllUsersNameProvider allUsersProvider) {
this.repoManager = repoManager;
+ this.allUsersProvider = allUsersProvider;
}
public ChangeNotes create(Change change) {
- return new ChangeNotes(repoManager, change);
+ return new ChangeNotes(repoManager, allUsersProvider, change);
}
}
- private static class Parser {
- private final Change.Id changeId;
- private final ObjectId tip;
- private final RevWalk walk;
- private final Repository repo;
- private final Map<PatchSet.Id,
- Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
- private final Map<Account.Id, ReviewerState> reviewers;
- private final List<SubmitRecord> submitRecords;
- private final Multimap<PatchSet.Id, ChangeMessage> changeMessages;
- private final Multimap<Id, PatchLineComment> commentsForPs;
- private final Multimap<PatchSet.Id, PatchLineComment> commentsForBase;
- private NoteMap commentNoteMap;
- private Change.Status status;
-
- private Parser(Change change, ObjectId tip, RevWalk walk,
- GitRepositoryManager repoManager) throws RepositoryNotFoundException,
- IOException {
- this.changeId = change.getId();
- this.tip = tip;
- this.walk = walk;
- this.repo = repoManager.openRepository(getProjectName(change));
- approvals = Maps.newHashMap();
- reviewers = Maps.newLinkedHashMap();
- submitRecords = Lists.newArrayListWithExpectedSize(1);
- changeMessages = LinkedListMultimap.create();
- commentsForPs = ArrayListMultimap.create();
- commentsForBase = ArrayListMultimap.create();
- }
-
- private void parseAll() throws ConfigInvalidException, IOException, ParseException {
- walk.markStart(walk.parseCommit(tip));
- for (RevCommit commit : walk) {
- parse(commit);
- }
- parseComments();
- pruneReviewers();
- }
-
- private ImmutableListMultimap<PatchSet.Id, PatchSetApproval>
- buildApprovals() {
- Multimap<PatchSet.Id, PatchSetApproval> result =
- ArrayListMultimap.create(approvals.keySet().size(), 3);
- for (Table<?, ?, Optional<PatchSetApproval>> curr
- : approvals.values()) {
- for (PatchSetApproval psa : Optional.presentInstances(curr.values())) {
- result.put(psa.getPatchSetId(), psa);
- }
- }
- for (Collection<PatchSetApproval> v : result.asMap().values()) {
- Collections.sort((List<PatchSetApproval>) v, PSA_BY_TIME);
- }
- return ImmutableListMultimap.copyOf(result);
- }
-
- private ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessages() {
- for (Collection<ChangeMessage> v : changeMessages.asMap().values()) {
- Collections.sort((List<ChangeMessage>) v, MESSAGE_BY_TIME);
- }
- return ImmutableListMultimap.copyOf(changeMessages);
- }
-
- private void parse(RevCommit commit) throws ConfigInvalidException, IOException {
- if (status == null) {
- status = parseStatus(commit);
- }
- PatchSet.Id psId = parsePatchSetId(commit);
- Account.Id accountId = parseIdent(commit);
- parseChangeMessage(psId, accountId, commit);
-
-
- if (submitRecords.isEmpty()) {
- // Only parse the most recent set of submit records; any older ones are
- // still there, but not currently used.
- parseSubmitRecords(commit.getFooterLines(FOOTER_SUBMITTED_WITH));
- }
-
- for (String line : commit.getFooterLines(FOOTER_LABEL)) {
- parseApproval(psId, accountId, commit, line);
- }
-
- for (ReviewerState state : ReviewerState.values()) {
- for (String line : commit.getFooterLines(state.getFooterKey())) {
- parseReviewer(state, line);
- }
- }
- }
-
- private Change.Status parseStatus(RevCommit commit)
- throws ConfigInvalidException {
- List<String> statusLines = commit.getFooterLines(FOOTER_STATUS);
- if (statusLines.isEmpty()) {
- return null;
- } else if (statusLines.size() > 1) {
- throw expectedOneFooter(FOOTER_STATUS, statusLines);
- }
- Optional<Change.Status> status = Enums.getIfPresent(
- Change.Status.class, statusLines.get(0).toUpperCase());
- if (!status.isPresent()) {
- throw invalidFooter(FOOTER_STATUS, statusLines.get(0));
- }
- return status.get();
- }
-
- private PatchSet.Id parsePatchSetId(RevCommit commit)
- throws ConfigInvalidException {
- List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET);
- if (psIdLines.size() != 1) {
- throw expectedOneFooter(FOOTER_PATCH_SET, psIdLines);
- }
- Integer psId = Ints.tryParse(psIdLines.get(0));
- if (psId == null) {
- throw invalidFooter(FOOTER_PATCH_SET, psIdLines.get(0));
- }
- return new PatchSet.Id(changeId, psId);
- }
-
- private void parseChangeMessage(PatchSet.Id psId, Account.Id accountId,
- RevCommit commit) {
- byte[] raw = commit.getRawBuffer();
- int size = raw.length;
- Charset enc = RawParseUtils.parseEncoding(raw);
-
- int subjectStart = RawParseUtils.commitMessage(raw, 0);
- if (subjectStart < 0 || subjectStart >= size) {
- return;
- }
-
- int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
- if (subjectEnd == size) {
- return;
- }
-
- int changeMessageStart;
-
- if (raw[subjectEnd] == '\n') {
- changeMessageStart = subjectEnd + 2; //\n\n ends paragraph
- } else if (raw[subjectEnd] == '\r') {
- changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
- } else {
- return;
- }
-
- int ptr = size - 1;
- int changeMessageEnd = -1;
- while(ptr > changeMessageStart) {
- ptr = RawParseUtils.prevLF(raw, ptr, '\r');
- if (ptr == -1) {
- break;
- }
- if (raw[ptr] == '\n') {
- changeMessageEnd = ptr - 1;
- break;
- } else if (raw[ptr] == '\r') {
- changeMessageEnd = ptr - 3;
- break;
- }
- }
-
- if (ptr <= changeMessageStart) {
- return;
- }
-
- String changeMsgString = RawParseUtils.decode(enc, raw,
- changeMessageStart, changeMessageEnd + 1);
- ChangeMessage changeMessage = new ChangeMessage(
- new ChangeMessage.Key(psId.getParentKey(), commit.name()),
- accountId,
- new Timestamp(commit.getCommitterIdent().getWhen().getTime()),
- psId);
- changeMessage.setMessage(changeMsgString);
- changeMessages.put(psId, changeMessage);
- }
-
- private void parseComments()
- throws IOException, ConfigInvalidException, ParseException {
- commentNoteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
- ChangeNoteUtil.changeRefName(changeId), walk, changeId,
- commentsForBase, commentsForPs, Status.PUBLISHED);
- }
-
- private void parseApproval(PatchSet.Id psId, Account.Id accountId,
- RevCommit commit, String line) throws ConfigInvalidException {
- Table<Account.Id, String, Optional<PatchSetApproval>> curr =
- approvals.get(psId);
- if (curr == null) {
- curr = Tables.newCustomTable(
- Maps.<Account.Id, Map<String, Optional<PatchSetApproval>>>
- newHashMapWithExpectedSize(2),
- new Supplier<Map<String, Optional<PatchSetApproval>>>() {
- @Override
- public Map<String, Optional<PatchSetApproval>> get() {
- return Maps.newLinkedHashMap();
- }
- });
- approvals.put(psId, curr);
- }
-
- if (line.startsWith("-")) {
- String label = line.substring(1);
- if (!curr.contains(accountId, label)) {
- curr.put(accountId, label, Optional.<PatchSetApproval> absent());
- }
- } else {
- LabelVote l;
- try {
- l = LabelVote.parseWithEquals(line);
- } catch (IllegalArgumentException e) {
- ConfigInvalidException pe =
- parseException("invalid %s: %s", FOOTER_LABEL, line);
- pe.initCause(e);
- throw pe;
- }
- if (!curr.contains(accountId, l.getLabel())) {
- curr.put(accountId, l.getLabel(), Optional.of(new PatchSetApproval(
- new PatchSetApproval.Key(
- psId,
- accountId,
- new LabelId(l.getLabel())),
- l.getValue(),
- new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
- }
- }
- }
-
- private void parseSubmitRecords(List<String> lines)
- throws ConfigInvalidException {
- SubmitRecord rec = null;
-
- for (String line : lines) {
- int c = line.indexOf(": ");
- if (c < 0) {
- rec = new SubmitRecord();
- submitRecords.add(rec);
- int s = line.indexOf(' ');
- String statusStr = s >= 0 ? line.substring(0, s) : line;
- Optional<SubmitRecord.Status> status =
- Enums.getIfPresent(SubmitRecord.Status.class, statusStr);
- checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
- rec.status = status.get();
- if (s >= 0) {
- rec.errorMessage = line.substring(s);
- }
- } else {
- checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
- SubmitRecord.Label label = new SubmitRecord.Label();
- if (rec.labels == null) {
- rec.labels = Lists.newArrayList();
- }
- rec.labels.add(label);
-
- Optional<SubmitRecord.Label.Status> status = Enums.getIfPresent(
- SubmitRecord.Label.Status.class, line.substring(0, c));
- checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
- label.status = status.get();
- int c2 = line.indexOf(": ", c + 2);
- if (c2 >= 0) {
- label.label = line.substring(c + 2, c2);
- PersonIdent ident =
- RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
- checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
- label.appliedBy = parseIdent(ident);
- } else {
- label.label = line.substring(c + 2);
- }
- }
- }
- }
-
- private Account.Id parseIdent(RevCommit commit)
- throws ConfigInvalidException {
- return parseIdent(commit.getAuthorIdent());
- }
-
- private Account.Id parseIdent(PersonIdent ident)
- throws ConfigInvalidException {
- String email = ident.getEmailAddress();
- int at = email.indexOf('@');
- if (at >= 0) {
- String host = email.substring(at + 1, email.length());
- Integer id = Ints.tryParse(email.substring(0, at));
- if (id != null && host.equals(GERRIT_PLACEHOLDER_HOST)) {
- return new Account.Id(id);
- }
- }
- throw parseException("invalid identity, expected <id>@%s: %s",
- GERRIT_PLACEHOLDER_HOST, email);
- }
-
- private void parseReviewer(ReviewerState state, String line)
- throws ConfigInvalidException {
- PersonIdent ident = RawParseUtils.parsePersonIdent(line);
- if (ident == null) {
- throw invalidFooter(state.getFooterKey(), line);
- }
- Account.Id accountId = parseIdent(ident);
- if (!reviewers.containsKey(accountId)) {
- reviewers.put(accountId, state);
- }
- }
-
- private void pruneReviewers() {
- Iterator<Map.Entry<Account.Id, ReviewerState>> rit =
- reviewers.entrySet().iterator();
- while (rit.hasNext()) {
- Map.Entry<Account.Id, ReviewerState> e = rit.next();
- if (e.getValue() == ReviewerState.REMOVED) {
- rit.remove();
- for (Table<Account.Id, ?, ?> curr : approvals.values()) {
- curr.rowKeySet().remove(e.getKey());
- }
- }
- }
- }
-
- private ConfigInvalidException expectedOneFooter(FooterKey footer,
- List<String> actual) {
- return parseException("missing or multiple %s: %s",
- footer.getName(), actual);
- }
-
- private ConfigInvalidException invalidFooter(FooterKey footer,
- String actual) {
- return parseException("invalid %s: %s", footer.getName(), actual);
- }
-
- private void checkFooter(boolean expr, FooterKey footer, String actual)
- throws ConfigInvalidException {
- if (!expr) {
- throw invalidFooter(footer, actual);
- }
- }
-
- private ConfigInvalidException parseException(String fmt, Object... args) {
- return ChangeNotes.parseException(changeId, fmt, args);
- }
- }
-
+ private final Change change;
private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
private ImmutableSetMultimap<ReviewerState, Account.Id> reviewers;
+ private ImmutableList<Account.Id> allPastReviewers;
private ImmutableList<SubmitRecord> submitRecords;
private ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessages;
private ImmutableListMultimap<PatchSet.Id, PatchLineComment> commentsForBase;
private ImmutableListMultimap<PatchSet.Id, PatchLineComment> commentsForPS;
NoteMap noteMap;
+ private final AllUsersName allUsers;
+ private DraftCommentNotes draftCommentNotes;
+
+ @Inject
@VisibleForTesting
- public ChangeNotes(GitRepositoryManager repoManager, Change change) {
- super(repoManager, change);
+ public ChangeNotes(GitRepositoryManager repoManager,
+ AllUsersNameProvider allUsersProvider, Change change) {
+ super(repoManager, change.getId());
+ this.allUsers = allUsersProvider.get();
+ this.change = new Change(change);
+ }
+
+ public Change getChange() {
+ return change;
}
public ImmutableListMultimap<PatchSet.Id, PatchSetApproval> getApprovals() {
@@ -506,6 +161,13 @@
}
/**
+ * @return a list of all users who have ever been a reviewer on this change.
+ */
+ public ImmutableList<Account.Id> getAllPastReviewers() {
+ return allPastReviewers;
+ }
+
+ /**
* @return submit records stored during the most recent submit; only for
* changes that were actually submitted.
*/
@@ -530,6 +192,55 @@
return commentsForPS;
}
+ public Table<PatchSet.Id, String, PatchLineComment> getDraftBaseComments(
+ Account.Id author) throws OrmException {
+ loadDraftComments(author);
+ return draftCommentNotes.getDraftBaseComments();
+ }
+
+ public Table<PatchSet.Id, String, PatchLineComment> getDraftPsComments(
+ Account.Id author) throws OrmException {
+ loadDraftComments(author);
+ return draftCommentNotes.getDraftPsComments();
+ }
+
+ /**
+ * If draft comments have already been loaded for this author, then they will
+ * not be reloaded. However, this method will load the comments if no draft
+ * comments have been loaded or if the caller would like the drafts for
+ * another author.
+ */
+ private void loadDraftComments(Account.Id author)
+ throws OrmException {
+ if (draftCommentNotes == null ||
+ !author.equals(draftCommentNotes.getAuthor())) {
+ draftCommentNotes = new DraftCommentNotes(repoManager, allUsers,
+ getChangeId(), author);
+ draftCommentNotes.load();
+ }
+ }
+
+ public boolean containsComment(PatchLineComment c) throws OrmException {
+ if (containsCommentPublished(c)) {
+ return true;
+ }
+ loadDraftComments(c.getAuthor());
+ return draftCommentNotes.containsComment(c);
+ }
+
+ public boolean containsCommentPublished(PatchLineComment c) {
+ PatchSet.Id psId = getCommentPsId(c);
+ List<PatchLineComment> list = (c.getSide() == (short) 0)
+ ? getBaseComments().get(psId)
+ : getPatchSetComments().get(psId);
+ for (PatchLineComment l : list) {
+ if (c.getKey().equals(l.getKey())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** @return the NoteMap */
NoteMap getNoteMap() {
return noteMap;
@@ -548,9 +259,8 @@
return;
}
RevWalk walk = new RevWalk(reader);
- try {
- Change change = getChange();
- Parser parser = new Parser(change, rev, walk, repoManager);
+ try (ChangeNotesParser parser =
+ new ChangeNotesParser(change, rev, walk, repoManager)) {
parser.parseAll();
if (parser.status != null) {
@@ -569,6 +279,7 @@
reviewers.put(e.getValue(), e.getKey());
}
this.reviewers = reviewers.build();
+ this.allPastReviewers = ImmutableList.copyOf(parser.allPastReviewers);
submitRecords = ImmutableList.copyOf(parser.submitRecords);
} catch (ParseException e1) {
@@ -594,7 +305,7 @@
getClass().getSimpleName() + " is read-only");
}
- private static Project.NameKey getProjectName(Change change) {
+ static Project.NameKey getProjectName(Change change) {
return change.getProject();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
new file mode 100644
index 0000000..83cdc68
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -0,0 +1,413 @@
+// Copyright (C) 2014 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.notedb;
+
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
+
+import com.google.common.base.Enums;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Table;
+import com.google.common.collect.Tables;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+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.client.PatchSetApproval.LabelId;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.LabelVote;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+class ChangeNotesParser implements AutoCloseable {
+ final Map<Account.Id, ReviewerState> reviewers;
+ final List<Account.Id> allPastReviewers;
+ final List<SubmitRecord> submitRecords;
+ final Multimap<PatchSet.Id, PatchLineComment> commentsForPs;
+ final Multimap<PatchSet.Id, PatchLineComment> commentsForBase;
+ NoteMap commentNoteMap;
+ Change.Status status;
+
+ private final Change.Id changeId;
+ private final ObjectId tip;
+ private final RevWalk walk;
+ private final Repository repo;
+ private final Map<PatchSet.Id,
+ Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
+ private final Multimap<PatchSet.Id, ChangeMessage> changeMessages;
+
+ ChangeNotesParser(Change change, ObjectId tip, RevWalk walk,
+ GitRepositoryManager repoManager) throws RepositoryNotFoundException,
+ IOException {
+ this.changeId = change.getId();
+ this.tip = tip;
+ this.walk = walk;
+ this.repo =
+ repoManager.openMetadataRepository(ChangeNotes.getProjectName(change));
+ approvals = Maps.newHashMap();
+ reviewers = Maps.newLinkedHashMap();
+ allPastReviewers = Lists.newArrayList();
+ submitRecords = Lists.newArrayListWithExpectedSize(1);
+ changeMessages = LinkedListMultimap.create();
+ commentsForPs = ArrayListMultimap.create();
+ commentsForBase = ArrayListMultimap.create();
+ }
+
+ @Override
+ public void close() {
+ repo.close();
+ }
+
+ void parseAll() throws ConfigInvalidException, IOException, ParseException {
+ walk.markStart(walk.parseCommit(tip));
+ for (RevCommit commit : walk) {
+ parse(commit);
+ }
+ parseComments();
+ allPastReviewers.addAll(reviewers.keySet());
+ pruneReviewers();
+ }
+
+ ImmutableListMultimap<PatchSet.Id, PatchSetApproval>
+ buildApprovals() {
+ Multimap<PatchSet.Id, PatchSetApproval> result =
+ ArrayListMultimap.create(approvals.keySet().size(), 3);
+ for (Table<?, ?, Optional<PatchSetApproval>> curr
+ : approvals.values()) {
+ for (PatchSetApproval psa : Optional.presentInstances(curr.values())) {
+ result.put(psa.getPatchSetId(), psa);
+ }
+ }
+ for (Collection<PatchSetApproval> v : result.asMap().values()) {
+ Collections.sort((List<PatchSetApproval>) v, ChangeNotes.PSA_BY_TIME);
+ }
+ return ImmutableListMultimap.copyOf(result);
+ }
+
+ ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessages() {
+ for (Collection<ChangeMessage> v : changeMessages.asMap().values()) {
+ Collections.sort((List<ChangeMessage>) v, ChangeNotes.MESSAGE_BY_TIME);
+ }
+ return ImmutableListMultimap.copyOf(changeMessages);
+ }
+
+ private void parse(RevCommit commit) throws ConfigInvalidException, IOException {
+ if (status == null) {
+ status = parseStatus(commit);
+ }
+ PatchSet.Id psId = parsePatchSetId(commit);
+ Account.Id accountId = parseIdent(commit);
+ parseChangeMessage(psId, accountId, commit);
+
+
+ if (submitRecords.isEmpty()) {
+ // Only parse the most recent set of submit records; any older ones are
+ // still there, but not currently used.
+ parseSubmitRecords(commit.getFooterLines(FOOTER_SUBMITTED_WITH));
+ }
+
+ for (String line : commit.getFooterLines(FOOTER_LABEL)) {
+ parseApproval(psId, accountId, commit, line);
+ }
+
+ for (ReviewerState state : ReviewerState.values()) {
+ for (String line : commit.getFooterLines(state.getFooterKey())) {
+ parseReviewer(state, line);
+ }
+ }
+ }
+
+ private Change.Status parseStatus(RevCommit commit)
+ throws ConfigInvalidException {
+ List<String> statusLines = commit.getFooterLines(FOOTER_STATUS);
+ if (statusLines.isEmpty()) {
+ return null;
+ } else if (statusLines.size() > 1) {
+ throw expectedOneFooter(FOOTER_STATUS, statusLines);
+ }
+ Optional<Change.Status> status = Enums.getIfPresent(
+ Change.Status.class, statusLines.get(0).toUpperCase());
+ if (!status.isPresent()) {
+ throw invalidFooter(FOOTER_STATUS, statusLines.get(0));
+ }
+ return status.get();
+ }
+
+ private PatchSet.Id parsePatchSetId(RevCommit commit)
+ throws ConfigInvalidException {
+ List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET);
+ if (psIdLines.size() != 1) {
+ throw expectedOneFooter(FOOTER_PATCH_SET, psIdLines);
+ }
+ Integer psId = Ints.tryParse(psIdLines.get(0));
+ if (psId == null) {
+ throw invalidFooter(FOOTER_PATCH_SET, psIdLines.get(0));
+ }
+ return new PatchSet.Id(changeId, psId);
+ }
+
+ private void parseChangeMessage(PatchSet.Id psId, Account.Id accountId,
+ RevCommit commit) {
+ byte[] raw = commit.getRawBuffer();
+ int size = raw.length;
+ Charset enc = RawParseUtils.parseEncoding(raw);
+
+ int subjectStart = RawParseUtils.commitMessage(raw, 0);
+ if (subjectStart < 0 || subjectStart >= size) {
+ return;
+ }
+
+ int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
+ if (subjectEnd == size) {
+ return;
+ }
+
+ int changeMessageStart;
+
+ if (raw[subjectEnd] == '\n') {
+ changeMessageStart = subjectEnd + 2; //\n\n ends paragraph
+ } else if (raw[subjectEnd] == '\r') {
+ changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
+ } else {
+ return;
+ }
+
+ int ptr = size - 1;
+ int changeMessageEnd = -1;
+ while(ptr > changeMessageStart) {
+ ptr = RawParseUtils.prevLF(raw, ptr, '\r');
+ if (ptr == -1) {
+ break;
+ }
+ if (raw[ptr] == '\n') {
+ changeMessageEnd = ptr - 1;
+ break;
+ } else if (raw[ptr] == '\r') {
+ changeMessageEnd = ptr - 3;
+ break;
+ }
+ }
+
+ if (ptr <= changeMessageStart) {
+ return;
+ }
+
+ String changeMsgString = RawParseUtils.decode(enc, raw,
+ changeMessageStart, changeMessageEnd + 1);
+ ChangeMessage changeMessage = new ChangeMessage(
+ new ChangeMessage.Key(psId.getParentKey(), commit.name()),
+ accountId,
+ new Timestamp(commit.getCommitterIdent().getWhen().getTime()),
+ psId);
+ changeMessage.setMessage(changeMsgString);
+ changeMessages.put(psId, changeMessage);
+ }
+
+ private void parseComments()
+ throws IOException, ConfigInvalidException, ParseException {
+ commentNoteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
+ ChangeNoteUtil.changeRefName(changeId), walk, changeId,
+ commentsForBase, commentsForPs, PatchLineComment.Status.PUBLISHED);
+ }
+
+ private void parseApproval(PatchSet.Id psId, Account.Id accountId,
+ RevCommit commit, String line) throws ConfigInvalidException {
+ Table<Account.Id, String, Optional<PatchSetApproval>> curr =
+ approvals.get(psId);
+ if (curr == null) {
+ curr = Tables.newCustomTable(
+ Maps.<Account.Id, Map<String, Optional<PatchSetApproval>>>
+ newHashMapWithExpectedSize(2),
+ new Supplier<Map<String, Optional<PatchSetApproval>>>() {
+ @Override
+ public Map<String, Optional<PatchSetApproval>> get() {
+ return Maps.newLinkedHashMap();
+ }
+ });
+ approvals.put(psId, curr);
+ }
+
+ if (line.startsWith("-")) {
+ String label = line.substring(1);
+ if (!curr.contains(accountId, label)) {
+ curr.put(accountId, label, Optional.<PatchSetApproval> absent());
+ }
+ } else {
+ LabelVote l;
+ try {
+ l = LabelVote.parseWithEquals(line);
+ } catch (IllegalArgumentException e) {
+ ConfigInvalidException pe =
+ parseException("invalid %s: %s", FOOTER_LABEL, line);
+ pe.initCause(e);
+ throw pe;
+ }
+ if (!curr.contains(accountId, l.getLabel())) {
+ curr.put(accountId, l.getLabel(), Optional.of(new PatchSetApproval(
+ new PatchSetApproval.Key(
+ psId,
+ accountId,
+ new LabelId(l.getLabel())),
+ l.getValue(),
+ new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
+ }
+ }
+ }
+
+ private void parseSubmitRecords(List<String> lines)
+ throws ConfigInvalidException {
+ SubmitRecord rec = null;
+
+ for (String line : lines) {
+ int c = line.indexOf(": ");
+ if (c < 0) {
+ rec = new SubmitRecord();
+ submitRecords.add(rec);
+ int s = line.indexOf(' ');
+ String statusStr = s >= 0 ? line.substring(0, s) : line;
+ Optional<SubmitRecord.Status> status =
+ Enums.getIfPresent(SubmitRecord.Status.class, statusStr);
+ checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
+ rec.status = status.get();
+ if (s >= 0) {
+ rec.errorMessage = line.substring(s);
+ }
+ } else {
+ checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
+ SubmitRecord.Label label = new SubmitRecord.Label();
+ if (rec.labels == null) {
+ rec.labels = Lists.newArrayList();
+ }
+ rec.labels.add(label);
+
+ Optional<SubmitRecord.Label.Status> status = Enums.getIfPresent(
+ SubmitRecord.Label.Status.class, line.substring(0, c));
+ checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
+ label.status = status.get();
+ int c2 = line.indexOf(": ", c + 2);
+ if (c2 >= 0) {
+ label.label = line.substring(c + 2, c2);
+ PersonIdent ident =
+ RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
+ checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
+ label.appliedBy = parseIdent(ident);
+ } else {
+ label.label = line.substring(c + 2);
+ }
+ }
+ }
+ }
+
+ private Account.Id parseIdent(RevCommit commit)
+ throws ConfigInvalidException {
+ return parseIdent(commit.getAuthorIdent());
+ }
+
+ private Account.Id parseIdent(PersonIdent ident)
+ throws ConfigInvalidException {
+ String email = ident.getEmailAddress();
+ int at = email.indexOf('@');
+ if (at >= 0) {
+ String host = email.substring(at + 1, email.length());
+ Integer id = Ints.tryParse(email.substring(0, at));
+ if (id != null && host.equals(GERRIT_PLACEHOLDER_HOST)) {
+ return new Account.Id(id);
+ }
+ }
+ throw parseException("invalid identity, expected <id>@%s: %s",
+ GERRIT_PLACEHOLDER_HOST, email);
+ }
+
+ private void parseReviewer(ReviewerState state, String line)
+ throws ConfigInvalidException {
+ PersonIdent ident = RawParseUtils.parsePersonIdent(line);
+ if (ident == null) {
+ throw invalidFooter(state.getFooterKey(), line);
+ }
+ Account.Id accountId = parseIdent(ident);
+ if (!reviewers.containsKey(accountId)) {
+ reviewers.put(accountId, state);
+ }
+ }
+
+ private void pruneReviewers() {
+ Iterator<Map.Entry<Account.Id, ReviewerState>> rit =
+ reviewers.entrySet().iterator();
+ while (rit.hasNext()) {
+ Map.Entry<Account.Id, ReviewerState> e = rit.next();
+ if (e.getValue() == ReviewerState.REMOVED) {
+ rit.remove();
+ for (Table<Account.Id, ?, ?> curr : approvals.values()) {
+ curr.rowKeySet().remove(e.getKey());
+ }
+ }
+ }
+ }
+
+ private ConfigInvalidException expectedOneFooter(FooterKey footer,
+ List<String> actual) {
+ return parseException("missing or multiple %s: %s",
+ footer.getName(), actual);
+ }
+
+ private ConfigInvalidException invalidFooter(FooterKey footer,
+ String actual) {
+ return parseException("invalid %s: %s", footer.getName(), actual);
+ }
+
+ private void checkFooter(boolean expr, FooterKey footer, String actual)
+ throws ConfigInvalidException {
+ if (!expr) {
+ throw invalidFooter(footer, actual);
+ }
+ }
+
+ private ConfigInvalidException parseException(String fmt, Object... args) {
+ return ChangeNotes.parseException(changeId, fmt, args);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
new file mode 100644
index 0000000..23a55ec
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
@@ -0,0 +1,336 @@
+// Copyright (C) 2014 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.notedb;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+public class ChangeRebuilder {
+ private static final long TS_WINDOW_MS =
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
+
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeControl.GenericFactory controlFactory;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final PatchListCache patchListCache;
+ private final ChangeUpdate.Factory updateFactory;
+ private final ChangeDraftUpdate.Factory draftUpdateFactory;
+
+ @Inject
+ ChangeRebuilder(Provider<ReviewDb> dbProvider,
+ ChangeControl.GenericFactory controlFactory,
+ IdentifiedUser.GenericFactory userFactory,
+ PatchListCache patchListCache,
+ PatchLineCommentsUtil plcUtil,
+ ChangeUpdate.Factory updateFactory,
+ ChangeDraftUpdate.Factory draftUpdateFactory) {
+ this.dbProvider = dbProvider;
+ this.controlFactory = controlFactory;
+ this.userFactory = userFactory;
+ this.patchListCache = patchListCache;
+ this.updateFactory = updateFactory;
+ this.draftUpdateFactory = draftUpdateFactory;
+ }
+
+ public ListenableFuture<?> rebuildAsync(final Change change,
+ ListeningExecutorService executor, final BatchRefUpdate bru,
+ final BatchRefUpdate bruForDrafts, final Repository changeRepo,
+ final Repository allUsersRepo) {
+ return executor.submit(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ rebuild(change, bru, bruForDrafts, changeRepo, allUsersRepo);
+ return null;
+ }
+ });
+ }
+
+ public void rebuild(Change change, BatchRefUpdate bru,
+ BatchRefUpdate bruForDrafts, Repository changeRepo,
+ Repository allUsersRepo) throws NoSuchChangeException, IOException,
+ OrmException {
+ deleteRef(change, changeRepo);
+ ReviewDb db = dbProvider.get();
+ Change.Id changeId = change.getId();
+
+ // We will rebuild all events, except for draft comments, in buckets based
+ // on author and timestamp. However, all draft comments for a given change
+ // and author will be written as one commit in the notedb.
+ List<Event> events = Lists.newArrayList();
+ Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents =
+ ArrayListMultimap.create();
+
+ for (PatchSet ps : db.patchSets().byChange(changeId)) {
+ events.add(new PatchSetEvent(ps));
+ for (PatchLineComment c : db.patchComments().byPatchSet(ps.getId())) {
+ PatchLineCommentEvent e =
+ new PatchLineCommentEvent(c, change, ps, patchListCache);
+ if (c.getStatus() == Status.PUBLISHED) {
+ events.add(e);
+ } else {
+ draftCommentEvents.put(c.getAuthor(), e);
+ }
+ }
+ }
+
+ for (PatchSetApproval psa : db.patchSetApprovals().byChange(changeId)) {
+ events.add(new ApprovalEvent(psa));
+ }
+
+
+ Collections.sort(events);
+ BatchMetaDataUpdate batch = null;
+ ChangeUpdate update = null;
+ for (Event e : events) {
+ if (!sameUpdate(e, update)) {
+ if (update != null) {
+ writeToBatch(batch, update, changeRepo);
+ }
+ IdentifiedUser user = userFactory.create(dbProvider, e.who);
+ update = updateFactory.create(
+ controlFactory.controlFor(change, user), e.when);
+ update.setPatchSetId(e.psId);
+ if (batch == null) {
+ batch = update.openUpdateInBatch(bru);
+ }
+ }
+ e.apply(update);
+ }
+ if (batch != null) {
+ if (update != null) {
+ writeToBatch(batch, update, changeRepo);
+ }
+
+ // Since the BatchMetaDataUpdates generated by all ChangeRebuilders on a
+ // given project are backed by the same BatchRefUpdate, we need to
+ // synchronize on the BatchRefUpdate. Therefore, since commit on a
+ // BatchMetaDataUpdate is the only method that modifies a BatchRefUpdate,
+ // we can just synchronize this call.
+ synchronized (bru) {
+ batch.commit();
+ }
+ }
+
+ for (Account.Id author : draftCommentEvents.keys()) {
+ IdentifiedUser user = userFactory.create(dbProvider, author);
+ ChangeDraftUpdate draftUpdate = null;
+ BatchMetaDataUpdate batchForDrafts = null;
+ for (PatchLineCommentEvent e : draftCommentEvents.get(author)) {
+ if (draftUpdate == null) {
+ draftUpdate = draftUpdateFactory.create(
+ controlFactory.controlFor(change, user), e.when);
+ draftUpdate.setPatchSetId(e.psId);
+ batchForDrafts = draftUpdate.openUpdateInBatch(bruForDrafts);
+ }
+ e.applyDraft(draftUpdate);
+ }
+ writeToBatch(batchForDrafts, draftUpdate, allUsersRepo);
+ synchronized(bruForDrafts) {
+ batchForDrafts.commit();
+ }
+ }
+ }
+
+ private void deleteRef(Change change, Repository changeRepo)
+ throws IOException {
+ String refName = ChangeNoteUtil.changeRefName(change.getId());
+ RefUpdate ru = changeRepo.updateRef(refName, true);
+ ru.setForceUpdate(true);
+ RefUpdate.Result result = ru.delete();
+ switch (result) {
+ case FORCED:
+ case NEW:
+ case NO_CHANGE:
+ break;
+ default:
+ throw new IOException(
+ String.format("Failed to delete ref %s: %s", refName, result));
+ }
+ }
+
+ private void writeToBatch(BatchMetaDataUpdate batch,
+ AbstractChangeUpdate update, Repository repo) throws IOException,
+ OrmException {
+ ObjectInserter inserter = repo.newObjectInserter();
+ try {
+ update.setInserter(inserter);
+ update.writeCommit(batch);
+ } finally {
+ inserter.release();
+ }
+ }
+
+ private static long round(Date when) {
+ return when.getTime() / TS_WINDOW_MS;
+ }
+
+ private static boolean sameUpdate(Event event, ChangeUpdate update) {
+ return update != null
+ && round(event.when) == round(update.getWhen())
+ && event.who.equals(update.getUser().getAccountId())
+ && event.psId.equals(update.getPatchSetId());
+ }
+
+ private static abstract class Event implements Comparable<Event> {
+ final PatchSet.Id psId;
+ final Account.Id who;
+ final Timestamp when;
+
+ protected Event(PatchSet.Id psId, Account.Id who, Timestamp when) {
+ this.psId = psId;
+ this.who = who;
+ this.when = when;
+ }
+
+ protected void checkUpdate(AbstractChangeUpdate update) {
+ checkState(Objects.equal(update.getPatchSetId(), psId),
+ "cannot apply event for %s to update for %s",
+ update.getPatchSetId(), psId);
+ checkState(when.getTime() - update.getWhen().getTime() <= TS_WINDOW_MS,
+ "event at %s outside update window starting at %s",
+ when, update.getWhen());
+ checkState(Objects.equal(update.getUser().getAccountId(), who),
+ "cannot apply event by %s to update by %s",
+ who, update.getUser().getAccountId());
+ }
+
+ abstract void apply(ChangeUpdate update) throws OrmException;
+
+ @Override
+ public int compareTo(Event other) {
+ return ComparisonChain.start()
+ // TODO(dborowitz): Smarter bucketing: pick a bucket start time T and
+ // include all events up to T + TS_WINDOW_MS but no further.
+ // Interleaving different authors complicates things.
+ .compare(round(when), round(other.when))
+ .compare(who.get(), other.who.get())
+ .compare(psId.get(), other.psId.get())
+ .result();
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("psId", psId)
+ .add("who", who)
+ .add("when", when)
+ .toString();
+ }
+ }
+
+ private static class ApprovalEvent extends Event {
+ private PatchSetApproval psa;
+
+ ApprovalEvent(PatchSetApproval psa) {
+ super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted());
+ this.psa = psa;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) {
+ checkUpdate(update);
+ update.putApproval(psa.getLabel(), psa.getValue());
+ }
+ }
+
+ private static class PatchSetEvent extends Event {
+ private final PatchSet ps;
+
+ PatchSetEvent(PatchSet ps) {
+ super(ps.getId(), ps.getUploader(), ps.getCreatedOn());
+ this.ps = ps;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) {
+ checkUpdate(update);
+ if (ps.getPatchSetId() == 1) {
+ update.setSubject("Create change");
+ } else {
+ update.setSubject("Create patch set " + ps.getPatchSetId());
+ }
+ }
+ }
+
+ private static class PatchLineCommentEvent extends Event {
+ public final PatchLineComment c;
+ private final Change change;
+ private final PatchSet ps;
+ private final PatchListCache cache;
+
+ PatchLineCommentEvent(PatchLineComment c, Change change, PatchSet ps,
+ PatchListCache cache) {
+ super(getCommentPsId(c), c.getAuthor(), c.getWrittenOn());
+ this.c = c;
+ this.change = change;
+ this.ps = ps;
+ this.cache = cache;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) throws OrmException {
+ checkUpdate(update);
+ if (c.getRevId() == null) {
+ setCommentRevId(c, cache, change, ps);
+ }
+ update.insertComment(c);
+ }
+
+ void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException {
+ if (c.getRevId() == null) {
+ setCommentRevId(c, cache, change, ps);
+ }
+ draftUpdate.insertComment(c);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index be3a44d..d494a55 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -31,17 +31,21 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -60,12 +64,14 @@
import java.util.Map;
/**
- * A single delta to apply atomically to a change.
+ * A delta to apply to a change.
* <p>
- * This delta becomes a single commit on the notes branch, so there are
- * limitations on the set of modifications that can be handled in a single
- * update. In particular, there is a single author and timestamp for each
- * update.
+ * This delta will become two unique commits: one in the AllUsers repo that will
+ * contain the draft comments on this change and one in the notes branch that
+ * will contain approvals, reviewers, change status, subject, submit records,
+ * the change message, and published comments. There are limitations on the set
+ * of modifications that can be handled in a single update. In particular, there
+ * is a single author and timestamp for each update.
* <p>
* This class is not thread-safe.
*/
@@ -88,34 +94,48 @@
private List<PatchLineComment> commentsForBase;
private List<PatchLineComment> commentsForPs;
private String changeMessage;
+ private ChangeNotes notes;
+
+ private final ChangeDraftUpdate.Factory draftUpdateFactory;
+ private ChangeDraftUpdate draftUpdate;
@AssistedInject
private ChangeUpdate(
@GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
GitRepositoryManager repoManager,
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
+ DraftCommentNotes.Factory draftNotesFactory,
+ Provider<AllUsersName> allUsers,
+ ChangeDraftUpdate.Factory draftUpdateFactory,
ProjectCache projectCache,
IdentifiedUser user,
@Assisted ChangeControl ctl,
CommentsInNotesUtil commentsUtil) {
- this(serverIdent, repoManager, migration, accountCache, updateFactory,
+ this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
+ updateFactory, draftNotesFactory, allUsers, draftUpdateFactory,
projectCache, ctl, serverIdent.getWhen(), commentsUtil);
}
@AssistedInject
private ChangeUpdate(
@GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
GitRepositoryManager repoManager,
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
+ DraftCommentNotes.Factory draftNotesFactory,
+ Provider<AllUsersName> allUsers,
+ ChangeDraftUpdate.Factory draftUpdateFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
@Assisted Date when,
CommentsInNotesUtil commentsUtil) {
- this(serverIdent, repoManager, migration, accountCache, updateFactory, ctl,
+ this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
+ updateFactory, draftNotesFactory, allUsers, draftUpdateFactory, ctl,
when,
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
commentsUtil);
@@ -128,15 +148,21 @@
@AssistedInject
private ChangeUpdate(
@GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
GitRepositoryManager repoManager,
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
+ DraftCommentNotes.Factory draftNotesFactory,
+ Provider<AllUsersName> allUsers,
+ ChangeDraftUpdate.Factory draftUpdateFactory,
@Assisted ChangeControl ctl,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator,
CommentsInNotesUtil commentsUtil) {
- super(migration, repoManager, updateFactory, ctl, serverIdent, when);
+ super(migration, repoManager, updateFactory, ctl, serverIdent,
+ anonymousCowardName, when);
+ this.draftUpdateFactory = draftUpdateFactory;
this.accountCache = accountCache;
this.commentsUtil = commentsUtil;
this.approvals = Maps.newTreeMap(labelNameComparator);
@@ -174,20 +200,148 @@
this.changeMessage = changeMessage;
}
- public void putComment(PatchLineComment comment) {
- checkArgument(psId != null,
- "setPatchSetId must be called before putComment");
- checkArgument(getCommentPsId(comment).equals(psId),
- "Comment on %s doesn't match previous patch set %s",
- getCommentPsId(comment), psId);
- checkArgument(comment.getRevId() != null);
- if (comment.getSide() == 0) {
- commentsForBase.add(comment);
+ public void insertComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ insertDraftComment(comment);
} else {
- commentsForPs.add(comment);
+ insertPublishedComment(comment);
}
}
+ public void upsertComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ upsertDraftComment(comment);
+ } else {
+ deleteDraftCommentIfPresent(comment);
+ upsertPublishedComment(comment);
+ }
+ }
+
+ public void updateComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ updateDraftComment(comment);
+ } else {
+ deleteDraftCommentIfPresent(comment);
+ updatePublishedComment(comment);
+ }
+ }
+
+ public void deleteComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ deleteDraftComment(comment);
+ } else {
+ throw new IllegalArgumentException("Cannot delete a published comment.");
+ }
+ }
+
+ private void insertPublishedComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ if (notes == null) {
+ notes = getChangeNotes().load();
+ }
+ if (migration.readChanges()) {
+ checkArgument(!notes.containsComment(c),
+ "A comment already exists with the same key as the following comment,"
+ + " so we cannot insert this comment: %s", c);
+ }
+ if (c.getSide() == 0) {
+ commentsForBase.add(c);
+ } else {
+ commentsForPs.add(c);
+ }
+ }
+
+ private void insertDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.insertComment(c);
+ }
+
+ private void upsertPublishedComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ if (notes == null) {
+ notes = getChangeNotes().load();
+ }
+ // This could allow callers to update a published comment if migration.write
+ // is on and migration.readComments is off because we will not be able to
+ // verify that the comment didn't already exist as a published comment
+ // since we don't have a ReviewDb.
+ if (migration.readChanges()) {
+ checkArgument(!notes.containsCommentPublished(c),
+ "Cannot update a comment that has already been published and saved");
+ }
+ if (c.getSide() == 0) {
+ commentsForBase.add(c);
+ } else {
+ commentsForPs.add(c);
+ }
+ }
+
+ private void upsertDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.upsertComment(c);
+ }
+
+ private void updatePublishedComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ if (notes == null) {
+ notes = getChangeNotes().load();
+ }
+ // See comment above in upsertPublishedComment() about potential risk with
+ // this check.
+ if (migration.readChanges()) {
+ checkArgument(!notes.containsCommentPublished(c),
+ "Cannot update a comment that has already been published and saved");
+ }
+ if (c.getSide() == 0) {
+ commentsForBase.add(c);
+ } else {
+ commentsForPs.add(c);
+ }
+ }
+
+ private void updateDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.updateComment(c);
+ }
+
+ private void deleteDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.deleteComment(c);
+ }
+
+ private void deleteDraftCommentIfPresent(PatchLineComment c)
+ throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.deleteCommentIfPresent(c);
+ }
+
+ private void createDraftUpdateIfNull(PatchLineComment c) throws OrmException {
+ if (draftUpdate == null) {
+ draftUpdate = draftUpdateFactory.create(ctl, when);
+ if (psId != null) {
+ draftUpdate.setPatchSetId(psId);
+ } else {
+ draftUpdate.setPatchSetId(getCommentPsId(c));
+ }
+ }
+ }
+
+ private void verifyComment(PatchLineComment c) {
+ checkArgument(psId != null,
+ "setPatchSetId must be called first");
+ checkArgument(getCommentPsId(c).equals(psId),
+ "Comment on %s doesn't match previous patch set %s",
+ getCommentPsId(c), psId);
+ checkArgument(c.getRevId() != null);
+ checkArgument(c.getStatus() == Status.PUBLISHED,
+ "Cannot add a draft comment to a ChangeUpdate. Use a ChangeDraftUpdate"
+ + " for draft comments");
+ checkArgument(c.getAuthor().equals(getUser().getAccountId()),
+ "The author for the following comment does not match the author of"
+ + " this ChangeDraftUpdate (%s): %s", getUser().getAccountId(), c);
+
+ }
+
public void putReviewer(Account.Id reviewer, ReviewerState type) {
checkArgument(type != ReviewerState.REMOVED, "invalid ReviewerType");
reviewers.put(reviewer, type);
@@ -235,14 +389,10 @@
public RevCommit commit() throws IOException {
BatchMetaDataUpdate batch = openUpdate();
try {
- CommitBuilder builder = new CommitBuilder();
- if (migration.write()) {
- ObjectId treeId = storeCommentsInNotes();
- if (treeId != null) {
- builder.setTreeId(treeId);
- }
+ writeCommit(batch);
+ if (draftUpdate != null) {
+ draftUpdate.commit();
}
- batch.write(builder);
RevCommit c = batch.commit();
return c;
} catch (OrmException e) {
@@ -253,6 +403,19 @@
}
@Override
+ public void writeCommit(BatchMetaDataUpdate batch) throws OrmException,
+ IOException {
+ CommitBuilder builder = new CommitBuilder();
+ if (migration.writeChanges()) {
+ ObjectId treeId = storeCommentsInNotes();
+ if (treeId != null) {
+ builder.setTreeId(treeId);
+ }
+ }
+ batch.write(this, builder);
+ }
+
+ @Override
protected String getRefName() {
return ChangeNoteUtil.changeRefName(getChange().getId());
}
@@ -338,12 +501,13 @@
private boolean isEmpty() {
return approvals.isEmpty()
- && reviewers.isEmpty()
+ && changeMessage == null
&& commentsForBase.isEmpty()
&& commentsForPs.isEmpty()
+ && reviewers.isEmpty()
&& status == null
- && submitRecords == null
- && changeMessage == null;
+ && subject == null
+ && submitRecords == null;
}
private static StringBuilder addFooter(StringBuilder sb, FooterKey footer) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
index ede979fe..c2e5dfc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
@@ -18,11 +18,12 @@
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.CommentRange;
@@ -33,13 +34,15 @@
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -48,11 +51,11 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.GitDateFormatter;
+import org.eclipse.jgit.util.GitDateFormatter.Format;
import org.eclipse.jgit.util.GitDateParser;
import org.eclipse.jgit.util.MutableInteger;
import org.eclipse.jgit.util.QuotedString;
import org.eclipse.jgit.util.RawParseUtils;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -69,6 +72,7 @@
* Utility functions to parse PatchLineComments out of a note byte array and
* store a list of PatchLineComments in the form of a note (in a byte array).
**/
+@Singleton
public class CommentsInNotesUtil {
private static final String AUTHOR = "Author";
private static final String BASE_PATCH_SET = "Base-for-patch-set";
@@ -79,6 +83,7 @@
private static final String PATCH_SET = "Patch-set";
private static final String REVISION = "Revision";
private static final String UUID = "UUID";
+ private static final int MAX_NOTE_SZ = 25 << 20;
public static NoteMap parseCommentsFromNotes(Repository repo, String refName,
RevWalk walk, Change.Id changeId,
@@ -90,14 +95,16 @@
if (ref == null) {
return null;
}
+
+ ObjectReader reader = walk.getObjectReader();
RevCommit commit = walk.parseCommit(ref.getObjectId());
- NoteMap noteMap = NoteMap.read(walk.getObjectReader(), commit);
+ NoteMap noteMap = NoteMap.read(reader, commit);
for (Note note: noteMap) {
- byte[] bytes = walk.getObjectReader().open(
- note.getData(), Constants.OBJ_BLOB).getBytes();
+ byte[] bytes =
+ reader.open(note.getData(), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
List<PatchLineComment> result = parseNote(bytes, changeId, status);
- if ((result == null) || (result.isEmpty())) {
+ if (result == null || result.isEmpty()) {
continue;
}
PatchSet.Id psId = result.get(0).getKey().getParentKey().getParentKey();
@@ -232,6 +239,7 @@
if (note[ptr.value] == '\n') {
range.setEndLine(startLine);
+ ptr.value += 1;
return range;
} else if (note[ptr.value] == ':') {
range.setStartLine(startLine);
@@ -368,7 +376,7 @@
private PersonIdent newIdent(Account author, Date when) {
return new PersonIdent(
- author.getFullName(),
+ new AccountInfo(author).getName(anonymousCowardName),
author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
when, serverIdent.getTimeZone());
}
@@ -410,13 +418,15 @@
private final AccountCache accountCache;
private final PersonIdent serverIdent;
+ private final String anonymousCowardName;
- @VisibleForTesting
@Inject
public CommentsInNotesUtil(AccountCache accountCache,
- @GerritPersonIdent PersonIdent serverIdent) {
+ @GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName) {
this.accountCache = accountCache;
this.serverIdent = serverIdent;
+ this.anonymousCowardName = anonymousCowardName;
}
/**
@@ -454,11 +464,11 @@
checkArgument(psId.equals(currentPsId),
"All comments being added must all have the same PatchSet.Id. The"
+ "comment below does not have the same PatchSet.Id as the others "
- + "(%d).\n%s", psId.toString(), c.toString());
+ + "(%s).\n%s", psId.toString(), c.toString());
checkArgument(side == c.getSide(),
"All comments being added must all have the same side. The"
+ "comment below does not have the same side as the others "
- + "(%d).\n%s", side, c.toString());
+ + "(%s).\n%s", side, c.toString());
String commentFilename =
QuotedString.GIT_PATH.quote(c.getKey().getParentKey().getFileName());
@@ -520,12 +530,10 @@
throws OrmException, IOException {
checkArgument(!allComments.isEmpty(),
"No comments to write; to delete, use removeNoteFromNoteMap().");
- ObjectId commitOID =
+ ObjectId commit =
ObjectId.fromString(allComments.get(0).getRevId().get());
Collections.sort(allComments, ChangeNotes.PatchLineCommentComparator);
- byte[] note = buildNote(allComments);
- ObjectId noteId = inserter.insert(Constants.OBJ_BLOB, note, 0, note.length);
- noteMap.set(commitOID, noteId);
+ noteMap.set(commit, inserter.insert(OBJ_BLOB, buildNote(allComments)));
}
public void removeNote(NoteMap noteMap, RevId commitId)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
new file mode 100644
index 0000000..2f2c97f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -0,0 +1,165 @@
+// Copyright (C) 2014 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.notedb;
+
+import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Table;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+/**
+ * View of the draft comments for a single {@link Change} based on the log of
+ * its drafts branch.
+ */
+public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
+ @Singleton
+ public static class Factory {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName draftsProject;
+
+ @VisibleForTesting
+ @Inject
+ public Factory(GitRepositoryManager repoManager,
+ AllUsersNameProvider allUsers) {
+ this.repoManager = repoManager;
+ this.draftsProject = allUsers.get();
+ }
+
+ public DraftCommentNotes create(Change.Id changeId, Account.Id accountId) {
+ return new DraftCommentNotes(repoManager, draftsProject, changeId,
+ accountId);
+ }
+ }
+
+ private final AllUsersName draftsProject;
+ private final Account.Id author;
+
+ private final Table<PatchSet.Id, String, PatchLineComment> draftBaseComments;
+ private final Table<PatchSet.Id, String, PatchLineComment> draftPsComments;
+ private NoteMap noteMap;
+
+ DraftCommentNotes(GitRepositoryManager repoManager,
+ AllUsersName draftsProject, Change.Id changeId, Account.Id author) {
+ super(repoManager, changeId);
+ this.draftsProject = draftsProject;
+ this.author = author;
+
+ this.draftBaseComments = HashBasedTable.create();
+ this.draftPsComments = HashBasedTable.create();
+ }
+
+ public NoteMap getNoteMap() {
+ return noteMap;
+ }
+
+ public Account.Id getAuthor() {
+ return author;
+ }
+
+ /**
+ * @return a defensive copy of the table containing all draft comments
+ * on this change with side == 0. The row key is the comment's PatchSet.Id,
+ * the column key is the comment's UUID, and the value is the comment.
+ */
+ public Table<PatchSet.Id, String, PatchLineComment>
+ getDraftBaseComments() {
+ return HashBasedTable.create(draftBaseComments);
+ }
+
+ /**
+ * @return a defensive copy of the table containing all draft comments
+ * on this change with side == 1. The row key is the comment's PatchSet.Id,
+ * the column key is the comment's UUID, and the value is the comment.
+ */
+ public Table<PatchSet.Id, String, PatchLineComment>
+ getDraftPsComments() {
+ return HashBasedTable.create(draftPsComments);
+ }
+
+ public boolean containsComment(PatchLineComment c) {
+ Table<PatchSet.Id, String, PatchLineComment> t =
+ c.getSide() == (short) 0
+ ? getDraftBaseComments()
+ : getDraftPsComments();
+ return t.contains(getCommentPsId(c), c.getKey().get());
+ }
+
+ @Override
+ protected String getRefName() {
+ return RefNames.refsDraftComments(author, getChangeId());
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ ObjectId rev = getRevision();
+ if (rev == null) {
+ return;
+ }
+
+ RevWalk walk = new RevWalk(reader);
+ try (DraftCommentNotesParser parser = new DraftCommentNotesParser(
+ getChangeId(), walk, rev, repoManager, draftsProject, author)) {
+ parser.parseDraftComments();
+
+ buildCommentTable(draftBaseComments, parser.draftBaseComments);
+ buildCommentTable(draftPsComments, parser.draftPsComments);
+ noteMap = parser.noteMap;
+ } finally {
+ walk.release();
+ }
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException {
+ throw new UnsupportedOperationException(
+ getClass().getSimpleName() + " is read-only");
+ }
+
+ @Override
+ protected Project.NameKey getProjectName() {
+ return draftsProject;
+ }
+
+ private void buildCommentTable(
+ Table<PatchSet.Id, String, PatchLineComment> commentTable,
+ Multimap<PatchSet.Id, PatchLineComment> allComments) {
+ for (PatchLineComment c : allComments.values()) {
+ commentTable.put(getCommentPsId(c), c.getKey().get(), c);
+ }
+ }
+
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java
new file mode 100644
index 0000000..4b3fbdf
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2014 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.notedb;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+class DraftCommentNotesParser implements AutoCloseable {
+ final Multimap<PatchSet.Id, PatchLineComment> draftBaseComments;
+ final Multimap<PatchSet.Id, PatchLineComment> draftPsComments;
+ NoteMap noteMap;
+
+ private final Change.Id changeId;
+ private final ObjectId tip;
+ private final RevWalk walk;
+ private final Repository repo;
+ private final Account.Id author;
+
+ DraftCommentNotesParser(Change.Id changeId, RevWalk walk, ObjectId tip,
+ GitRepositoryManager repoManager, AllUsersName draftsProject,
+ Account.Id author) throws RepositoryNotFoundException, IOException {
+ this.changeId = changeId;
+ this.walk = walk;
+ this.tip = tip;
+ this.repo = repoManager.openMetadataRepository(draftsProject);
+ this.author = author;
+
+ draftBaseComments = ArrayListMultimap.create();
+ draftPsComments = ArrayListMultimap.create();
+ }
+
+ @Override
+ public void close() {
+ repo.close();
+ }
+
+ void parseDraftComments() throws IOException, ConfigInvalidException {
+ walk.markStart(walk.parseCommit(tip));
+ noteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
+ RefNames.refsDraftComments(author, changeId),
+ walk, changeId, draftBaseComments,
+ draftPsComments, PatchLineComment.Status.DRAFT);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
index 174997c..4193fe4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
@@ -20,5 +20,6 @@
@Override
public void configure() {
factory(ChangeUpdate.Factory.class);
+ factory(ChangeDraftUpdate.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
index 36f685d..e6d9ff8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -14,61 +14,103 @@
package com.google.gerrit.server.notedb;
-import com.google.common.annotations.VisibleForTesting;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
+import java.util.HashSet;
+import java.util.Set;
+
/**
- * Holds the current state of the notes DB migration.
+ * Holds the current state of the NoteDb migration.
* <p>
- * During a transitional period, different subsets of the former gwtorm DB
- * functionality may be enabled on the site, possibly only for reading or
- * writing.
+ * The migration will proceed one root entity type at a time. A <em>root
+ * entity</em> is an entity stored in ReviewDb whose key's
+ * {@code getParentKey()} method returns null. For an example of the entity
+ * hierarchy rooted at Change, see the diagram in
+ * {@code com.google.gerrit.reviewdb.client.Change}.
+ * <p>
+ * During a transitional period, each root entity group from ReviewDb may be
+ * either <em>written to</em> or <em>both written to and read from</em> NoteDb.
+ * <p>
+ * This class controls the state of the migration according to options in
+ * {@code gerrit.config}. In general, any changes to these options should only
+ * be made by adventurous administrators, who know what they're doing, on
+ * non-production data, for the purposes of testing the NoteDb implementation.
+ * Changing options quite likely requires re-running {@code RebuildNoteDb}. For
+ * these reasons, the options remain undocumented.
*/
@Singleton
public class NotesMigration {
- @VisibleForTesting
- static NotesMigration allEnabled() {
- Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
- cfg.setBoolean("notedb", "changeMessages", "read", true);
- cfg.setBoolean("notedb", "publishedComments", "read", true);
- return new NotesMigration(cfg);
+ private static enum Table {
+ CHANGES;
+
+ private String key() {
+ return name().toLowerCase();
+ }
}
- private final boolean write;
- private final boolean readPatchSetApprovals;
- private final boolean readChangeMessages;
- private final boolean readPublishedComments;
+ private static final String NOTEDB = "notedb";
+ private static final String READ = "read";
+ private static final String WRITE = "write";
+
+ private static void checkConfig(Config cfg) {
+ Set<String> keys = new HashSet<>();
+ for (Table t : Table.values()) {
+ keys.add(t.key());
+ }
+ for (String t : cfg.getSubsections(NOTEDB)) {
+ checkArgument(keys.contains(t.toLowerCase()),
+ "invalid notedb table: %s", t);
+ for (String key : cfg.getNames(NOTEDB, t)) {
+ String lk = key.toLowerCase();
+ checkArgument(lk.equals(WRITE) || lk.equals(READ),
+ "invalid notedb key: %s.%s", t, key);
+ }
+ boolean write = cfg.getBoolean(NOTEDB, t, WRITE, false);
+ boolean read = cfg.getBoolean(NOTEDB, t, READ, false);
+ checkArgument(!(read && !write),
+ "must have write enabled when read enabled: %s", t);
+ }
+ }
+
+ public static NotesMigration allEnabled() {
+ return new NotesMigration(allEnabledConfig());
+ }
+
+ public static Config allEnabledConfig() {
+ Config cfg = new Config();
+ for (Table t : Table.values()) {
+ cfg.setBoolean(NOTEDB, t.key(), WRITE, true);
+ cfg.setBoolean(NOTEDB, t.key(), READ, true);
+ }
+ return cfg;
+ }
+
+ private final boolean writeChanges;
+ private final boolean readChanges;
@Inject
NotesMigration(@GerritServerConfig Config cfg) {
- write = cfg.getBoolean("notedb", null, "write", false);
- readPatchSetApprovals =
- cfg.getBoolean("notedb", "patchSetApprovals", "read", false);
- readChangeMessages =
- cfg.getBoolean("notedb", "changeMessages", "read", false);
- readPublishedComments =
- cfg.getBoolean("notedb", "publishedComments", "read", false);
+ checkConfig(cfg);
+ writeChanges = cfg.getBoolean(NOTEDB, Table.CHANGES.key(), WRITE, false);
+ readChanges = cfg.getBoolean(NOTEDB, Table.CHANGES.key(), READ, false);
}
- public boolean write() {
- return write;
+ public boolean enabled() {
+ return writeChanges()
+ || readChanges();
}
- public boolean readPatchSetApprovals() {
- return readPatchSetApprovals;
+ public boolean writeChanges() {
+ return writeChanges;
}
- public boolean readChangeMessages() {
- return readChangeMessages;
- }
-
- public boolean readPublishedComments() {
- return readPublishedComments;
+ public boolean readChanges() {
+ return readChanges;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index 31f5e96..e7c56be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -24,9 +24,9 @@
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.Patch.PatchType;
+import com.google.gerrit.reviewdb.client.PatchSet;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.lib.Constants;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
index ff03840..9867b11 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -22,8 +22,8 @@
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 4eede00..de36020 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -21,7 +21,9 @@
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
import com.google.inject.Inject;
import org.eclipse.jgit.diff.DiffEntry;
@@ -35,6 +37,7 @@
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
@@ -45,8 +48,8 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeFormatter;
import org.eclipse.jgit.merge.MergeResult;
-import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -72,11 +75,14 @@
private final GitRepositoryManager repoManager;
private final PatchListCache patchListCache;
+ private final ThreeWayMergeStrategy mergeStrategy;
@Inject
- PatchListLoader(GitRepositoryManager mgr, PatchListCache plc) {
+ PatchListLoader(GitRepositoryManager mgr, PatchListCache plc,
+ @GerritServerConfig Config cfg) {
repoManager = mgr;
patchListCache = plc;
+ mergeStrategy = MergeUtil.getMergeStrategy(cfg);
}
@Override
@@ -224,7 +230,7 @@
}
}
- private static RevObject aFor(final PatchListKey key,
+ private RevObject aFor(final PatchListKey key,
final Repository repo, final RevWalk rw, final RevCommit b)
throws IOException {
if (key.getOldId() != null) {
@@ -240,20 +246,20 @@
return r;
}
case 2:
- return automerge(repo, rw, b);
+ return automerge(repo, rw, b, mergeStrategy);
default:
// TODO(sop) handle an octopus merge.
return null;
}
}
- public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b)
- throws IOException {
- return automerge(repo, rw, b, true);
+ public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
+ ThreeWayMergeStrategy mergeStrategy) throws IOException {
+ return automerge(repo, rw, b, mergeStrategy, true);
}
public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
- boolean save) throws IOException {
+ ThreeWayMergeStrategy mergeStrategy, boolean save) throws IOException {
String hash = b.name();
String refName = RefNames.REFS_CACHE_AUTOMERGE
+ hash.substring(0, 2)
@@ -264,8 +270,7 @@
return rw.parseTree(ref.getObjectId());
}
- ObjectId treeId;
- ResolveMerger m = (ResolveMerger) MergeStrategy.RESOLVE.newMerger(repo, true);
+ ResolveMerger m = (ResolveMerger) mergeStrategy.newMerger(repo, true);
final ObjectInserter ins = repo.newObjectInserter();
try {
DirCache dc = DirCache.newInCore();
@@ -297,6 +302,7 @@
return null;
}
+ ObjectId treeId;
if (couldMerge) {
treeId = m.getResultTreeId();
@@ -381,17 +387,18 @@
treeId = dc.writeTree(ins);
}
ins.flush();
+
+ if (save) {
+ RefUpdate update = repo.updateRef(refName);
+ update.setNewObjectId(treeId);
+ update.disableRefLog();
+ update.forceUpdate();
+ }
+
+ return rw.lookupTree(treeId);
} finally {
ins.release();
}
-
- if (save) {
- RefUpdate update = repo.updateRef(refName);
- update.setNewObjectId(treeId);
- update.disableRefLog();
- update.forceUpdate();
- }
- return rw.parseTree(treeId);
}
private static ObjectId emptyTree(final Repository repo) throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 0c98ccf..f3137fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -20,11 +20,11 @@
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index b4337cf..e2aa2c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -19,13 +19,13 @@
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -338,7 +338,8 @@
private void loadDrafts(final Map<Patch.Key, Patch> byKey,
final AccountInfoCacheFactory aic, final Account.Id me, final String file)
throws OrmException {
- for (PatchLineComment c : db.patchComments().draftByChangeFileAuthor(changeId, file, me)) {
+ for (PatchLineComment c :
+ plcUtil.draftByChangeFileAuthor(db, control.getNotes(), file, me)) {
if (comments.include(c)) {
aic.want(me);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
index 4a50c21..727a43c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.plugins;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.Export;
@@ -28,8 +30,6 @@
import java.util.Set;
import java.util.jar.Manifest;
-import static com.google.common.base.Preconditions.checkState;
-
/**
* Base plugin scanner for a set of pre-loaded classes.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index 6f1204b..759700e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -23,12 +23,17 @@
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
@@ -36,12 +41,14 @@
import java.util.Set;
class AutoRegisterModules {
+ private static final Logger log = LoggerFactory.getLogger(AutoRegisterModules.class);
+
private final String pluginName;
private final PluginGuiceEnvironment env;
private final PluginContentScanner scanner;
private final ClassLoader classLoader;
private final ModuleGenerator sshGen;
- private final ModuleGenerator httpGen;
+ private final HttpModuleGenerator httpGen;
private Set<Class<?>> sysSingletons;
private Multimap<TypeLiteral<?>, Class<?>> sysListen;
@@ -117,6 +124,19 @@
for (ExtensionMetaData listener : extensions.get(Listen.class)) {
listen(listener);
}
+ exportInitJs();
+ }
+
+ private void exportInitJs() {
+ try {
+ if (scanner.getEntry(JavaScriptPlugin.STATIC_INIT_JS).isPresent()) {
+ httpGen.export(JavaScriptPlugin.INIT_JS);
+ }
+ } catch (IOException e) {
+ log.warn(String.format("Cannot access %s from plugin %s: "
+ + "JavaScript auto-discovered plugin will not be registered",
+ JavaScriptPlugin.STATIC_INIT_JS, pluginName), e);
+ }
}
private void export(ExtensionMetaData def) throws InvalidPluginException {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
similarity index 63%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
index 73db6f5..44b2434 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 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.
@@ -12,14 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.server.plugins;
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface InstallPlugins {
+public interface HttpModuleGenerator extends ModuleGenerator {
+ void export(String javascript);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index c5611dd..54f05f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -29,11 +29,7 @@
import org.kohsuke.args4j.Option;
-import java.io.BufferedWriter;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -66,27 +62,12 @@
}
@Override
- public Object apply(TopLevelResource resource)
- throws UnsupportedEncodingException {
+ public Object apply(TopLevelResource resource) {
format = OutputFormat.JSON;
return display(null);
}
- public JsonElement display(OutputStream displayOutputStream)
- throws UnsupportedEncodingException {
- PrintWriter stdout = null;
- if (displayOutputStream != null) {
- try {
- stdout = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(displayOutputStream, "UTF-8")));
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("JVM lacks UTF-8 encoding", e);
- }
- } else if (!format.isJson()) {
- throw new IllegalStateException(
- "Text output requires that a display OutputStream is provided.");
- }
-
+ public JsonElement display(PrintWriter stdout) {
Map<String, PluginInfo> output = Maps.newTreeMap();
List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins(all));
Collections.sort(plugins, new Comparator<Plugin>() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index 5b63a215..fbc95c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -83,7 +83,7 @@
private Module httpModule;
private Provider<ModuleGenerator> sshGen;
- private Provider<ModuleGenerator> httpGen;
+ private Provider<HttpModuleGenerator> httpGen;
private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
@@ -187,7 +187,7 @@
public void setHttpInjector(Injector injector) {
httpModule = copy(injector);
- httpGen = injector.getProvider(ModuleGenerator.class);
+ httpGen = injector.getProvider(HttpModuleGenerator.class);
httpItems = dynamicItemsOf(injector);
httpSets = dynamicSetsOf(injector);
httpMaps = dynamicMapsOf(injector);
@@ -204,7 +204,7 @@
return httpModule;
}
- ModuleGenerator newHttpModuleGenerator() {
+ HttpModuleGenerator newHttpModuleGenerator() {
return httpGen.get();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
index fab1ed3..930360a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
@@ -51,8 +51,12 @@
}
}
+ private final com.google.gerrit.server.git.BanCommit banCommit;
+
@Inject
- private com.google.gerrit.server.git.BanCommit banCommit;
+ BanCommit(com.google.gerrit.server.git.BanCommit banCommit) {
+ this.banCommit = banCommit;
+ }
@Override
public BanResultInfo apply(ProjectResource rsrc, Input input)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
index fc9c807..2543818 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
@@ -14,22 +14,29 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.revwalk.RevCommit;
-public class CommitResource extends ProjectResource {
+public class CommitResource implements RestResource {
public static final TypeLiteral<RestView<CommitResource>> COMMIT_KIND =
new TypeLiteral<RestView<CommitResource>>() {};
+ private final ProjectResource project;
private final RevCommit commit;
- public CommitResource(ProjectControl control, RevCommit commit) {
- super(control);
+ public CommitResource(ProjectResource project, RevCommit commit) {
+ this.project = project;
this.commit = commit;
}
+ public Project.NameKey getProject() {
+ return project.getNameKey();
+ }
+
public RevCommit getCommit() {
return commit;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java
index f27dc6e..e5e7bed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java
@@ -19,8 +19,10 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -37,12 +39,15 @@
ChildCollection<ProjectResource, CommitResource> {
private final DynamicMap<RestView<CommitResource>> views;
private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> db;
@Inject
public CommitsCollection(DynamicMap<RestView<CommitResource>> views,
- GitRepositoryManager repoManager) {
+ GitRepositoryManager repoManager,
+ Provider<ReviewDb> db) {
this.views = views;
this.repoManager = repoManager;
+ this.db = db;
}
@Override
@@ -65,13 +70,14 @@
RevWalk rw = new RevWalk(repo);
try {
RevCommit commit = rw.parseCommit(objectId);
- if (!parent.getControl().canReadCommit(rw, commit)) {
+ rw.parseBody(commit);
+ if (!parent.getControl().canReadCommit(db.get(), rw, commit)) {
throw new ResourceNotFoundException(id);
}
for (int i = 0; i < commit.getParentCount(); i++) {
- rw.parseCommit(commit.getParent(i));
+ rw.parseBody(rw.parseCommit(commit.getParent(i)));
}
- return new CommitResource(parent.getControl(), commit);
+ return new CommitResource(parent, commit);
} catch (MissingObjectException | IncorrectObjectTypeException e) {
throw new ResourceNotFoundException(id);
} finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 3dcf9f4..983549d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -25,6 +25,7 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -67,6 +68,7 @@
private final Provider<IdentifiedUser> identifiedUser;
private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> db;
private final GitReferenceUpdated referenceUpdated;
private final ChangeHooks hooks;
private String ref;
@@ -74,10 +76,12 @@
@Inject
CreateBranch(Provider<IdentifiedUser> identifiedUser,
GitRepositoryManager repoManager,
+ Provider<ReviewDb> db,
GitReferenceUpdated referenceUpdated, ChangeHooks hooks,
@Assisted String ref) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
+ this.db = db;
this.referenceUpdated = referenceUpdated;
this.hooks = hooks;
this.ref = ref;
@@ -129,7 +133,8 @@
}
}
- if (!refControl.canCreate(rw, object, true)) {
+ rw.reset();
+ if (!refControl.canCreate(db.get(), rw, object)) {
throw new AuthException("Cannot create \"" + ref + "\"");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
index 2d0af29..f0544fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
@@ -19,7 +19,6 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.server.project.BranchResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
index 4dc4338..f383230 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
@@ -40,7 +40,7 @@
@Override
public FileResource parse(CommitResource parent, IdString id)
throws ResourceNotFoundException {
- return new FileResource(parent.getNameKey(), parent.getCommit().getName(),
+ return new FileResource(parent.getProject(), parent.getCommit().getName(),
id.get());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
index d43ea68..63cee1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
@@ -15,14 +15,12 @@
package com.google.gerrit.server.project;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CommonConverters;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
-import java.sql.Timestamp;
import java.util.ArrayList;
@Singleton
@@ -30,16 +28,14 @@
@Override
public CommitInfo apply(CommitResource rsrc) {
- RevCommit c = rsrc.getCommit();
- CommitInfo info = toCommitInfo(c);
- return info;
+ return toCommitInfo(rsrc.getCommit());
}
private static CommitInfo toCommitInfo(RevCommit commit) {
CommitInfo info = new CommitInfo();
info.commit = commit.getName();
- info.author = toGitPerson(commit.getAuthorIdent());
- info.committer = toGitPerson(commit.getCommitterIdent());
+ info.author = CommonConverters.toGitPerson(commit.getAuthorIdent());
+ info.committer = CommonConverters.toGitPerson(commit.getCommitterIdent());
info.subject = commit.getShortMessage();
info.message = commit.getFullMessage();
info.parents = new ArrayList<>(commit.getParentCount());
@@ -52,13 +48,4 @@
}
return info;
}
-
- private static GitPerson toGitPerson(PersonIdent ident) {
- GitPerson gp = new GitPerson();
- gp.name = ident.getName();
- gp.email = ident.getEmailAddress();
- gp.date = new Timestamp(ident.getWhen().getTime());
- gp.tz = ident.getTimeZoneOffset();
- return gp;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
index 5a9221b..00f25bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
@@ -17,25 +17,25 @@
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.FileContentUtil;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
public class GetContent implements RestReadView<FileResource> {
- private final Provider<com.google.gerrit.server.change.GetContent> getContent;
+ private final FileContentUtil fileContentUtil;
@Inject
- GetContent(Provider<com.google.gerrit.server.change.GetContent> getContent) {
- this.getContent = getContent;
+ GetContent(FileContentUtil fileContentUtil) {
+ this.fileContentUtil = fileContentUtil;
}
@Override
public BinaryResult apply(FileResource rsrc)
throws ResourceNotFoundException, IOException {
- return getContent.get().apply(rsrc.getProject(), rsrc.getRev(),
+ return fileContentUtil.getContent(rsrc.getProject(), rsrc.getRev(),
rsrc.getPath());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
index f05ece4..0530a4c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
@@ -17,8 +17,10 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -34,12 +36,14 @@
@Singleton
public class GetHead implements RestReadView<ProjectResource> {
-
private GitRepositoryManager repoManager;
+ private Provider<ReviewDb> db;
@Inject
- GetHead(GitRepositoryManager repoManager) {
+ GetHead(GitRepositoryManager repoManager,
+ Provider<ReviewDb> db) {
this.repoManager = repoManager;
+ this.db = db;
}
@Override
@@ -61,7 +65,7 @@
RevWalk rw = new RevWalk(repo);
try {
RevCommit commit = rw.parseCommit(head.getObjectId());
- if (rsrc.getControl().canReadCommit(rw, commit)) {
+ if (rsrc.getControl().canReadCommit(db.get(), rw, commit)) {
return head.getObjectId().name();
}
throw new AuthException("not allowed to see HEAD");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
index b6415b5..7e52381 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
@@ -20,12 +20,12 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.args4j.TimestampHandler;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
@@ -75,7 +75,7 @@
public List<ReflogEntryInfo> apply(BranchResource rsrc) throws AuthException,
ResourceNotFoundException, RepositoryNotFoundException, IOException {
if (!rsrc.getControl().isOwner()) {
- throw new AuthException("no project owner");
+ throw new AuthException("not project owner");
}
Repository repo = repoManager.openRepository(rsrc.getNameKey());
@@ -122,14 +122,7 @@
public ReflogEntryInfo(ReflogEntry e) {
oldId = e.getOldId().getName();
newId = e.getNewId().getName();
-
- PersonIdent ident = e.getWho();
- who = new GitPerson();
- who.name = ident.getName();
- who.email = ident.getEmailAddress();
- who.date = new Timestamp(ident.getWhen().getTime());
- who.tz = ident.getTimeZoneOffset();
-
+ who = CommonConverters.toGitPerson(e.getWho());
comment = e.getComment();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
index 1910a5e..8d1e95f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -26,6 +26,7 @@
import org.kohsuke.args4j.Option;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -96,7 +97,13 @@
node.addChild(key);
}
}
- return getChildProjectsRecursively(projects.get(parent));
+
+ ProjectNode n = projects.get(parent);
+ if (n != null) {
+ return getChildProjectsRecursively(n);
+ } else {
+ return Collections.emptyList();
+ }
}
private List<ProjectInfo> getChildProjectsRecursively(ProjectNode p) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
index fbfcc8f..07fd095 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
@@ -112,29 +112,25 @@
throws IOException {
List<DashboardInfo> list = Lists.newArrayList();
TreeWalk tw = new TreeWalk(rw.getObjectReader());
- try {
- tw.addTree(rw.parseTree(ref.getObjectId()));
- tw.setRecursive(true);
- while (tw.next()) {
- if (tw.getFileMode(0) == FileMode.REGULAR_FILE) {
- try {
- list.add(DashboardsCollection.parse(
- definingProject,
- ref.getName().substring(REFS_DASHBOARDS.length()),
- tw.getPathString(),
- new BlobBasedConfig(null, git, tw.getObjectId(0)),
- project,
- setDefault));
- } catch (ConfigInvalidException e) {
- log.warn(String.format(
- "Cannot parse dashboard %s:%s:%s: %s",
- definingProject.getName(), ref.getName(), tw.getPathString(),
- e.getMessage()));
- }
+ tw.addTree(rw.parseTree(ref.getObjectId()));
+ tw.setRecursive(true);
+ while (tw.next()) {
+ if (tw.getFileMode(0) == FileMode.REGULAR_FILE) {
+ try {
+ list.add(DashboardsCollection.parse(
+ definingProject,
+ ref.getName().substring(REFS_DASHBOARDS.length()),
+ tw.getPathString(),
+ new BlobBasedConfig(null, git, tw.getObjectId(0)),
+ project,
+ setDefault));
+ } catch (ConfigInvalidException e) {
+ log.warn(String.format(
+ "Cannot parse dashboard %s:%s:%s: %s",
+ definingProject.getName(), ref.getName(), tw.getPathString(),
+ e.getMessage()));
}
}
- } finally {
- tw.release();
}
return list;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index a28ee40..8b895f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -16,6 +16,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -38,14 +39,12 @@
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
-
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
@@ -449,8 +448,10 @@
private Iterable<Project.NameKey> scan() throws BadRequestException {
if (matchPrefix != null) {
+ checkMatchOptions(matchSubstring == null && matchRegex == null);
return projectCache.byName(matchPrefix);
} else if (matchSubstring != null) {
+ checkMatchOptions(matchPrefix == null && matchRegex == null);
return Iterables.filter(projectCache.all(),
new Predicate<Project.NameKey>() {
public boolean apply(Project.NameKey in) {
@@ -459,32 +460,31 @@
}
});
} else if (matchRegex != null) {
- if (matchRegex.startsWith("^")) {
- matchRegex = matchRegex.substring(1);
- if (matchRegex.endsWith("$") && !matchRegex.endsWith("\\$")) {
- matchRegex = matchRegex.substring(0, matchRegex.length() - 1);
- }
- }
- if (matchRegex.equals(".*")) {
- return projectCache.all();
- }
+ checkMatchOptions(matchPrefix == null && matchSubstring == null);
+ RegexListSearcher<Project.NameKey> searcher;
try {
- final RunAutomaton a =
- new RunAutomaton(new RegExp(matchRegex).toAutomaton());
- return Iterables.filter(projectCache.all(),
- new Predicate<Project.NameKey>() {
- public boolean apply(Project.NameKey in) {
- return a.run(in.get());
- }
- });
+ searcher = new RegexListSearcher<Project.NameKey>(matchRegex) {
+ @Override
+ public String apply(Project.NameKey in) {
+ return in.get();
+ }
+ };
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
+ return searcher.search(ImmutableList.copyOf(projectCache.all()));
} else {
return projectCache.all();
}
}
+ private static void checkMatchOptions(boolean cond)
+ throws BadRequestException {
+ if (!cond) {
+ throw new BadRequestException("specify exactly one of p/m/r");
+ }
+ }
+
private void printProjectTree(final PrintWriter stdout,
final TreeMap<Project.NameKey, ProjectNode> treeMap) {
final SortedSet<ProjectNode> sortedNodes = new TreeSet<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 53b7368..2ae09f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccessSection;
@@ -30,6 +31,7 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
@@ -37,15 +39,16 @@
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
+import com.google.gerrit.server.git.ChangeCache;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -148,6 +151,8 @@
private final ChangeControl.AssistedFactory changeControlFactory;
private final PermissionCollection.Factory permissionFilter;
private final Collection<ContributorAgreement> contributorAgreements;
+ private final TagCache tagCache;
+ private final ChangeCache changeCache;
private List<SectionMatcher> allSections;
private List<SectionMatcher> localSections;
@@ -162,11 +167,15 @@
PermissionCollection.Factory permissionFilter,
GitRepositoryManager repoManager,
ChangeControl.AssistedFactory changeControlFactory,
+ TagCache tagCache,
+ ChangeCache changeCache,
@CanonicalWebUrl @Nullable String canonicalWebUrl,
@Assisted CurrentUser who,
@Assisted ProjectState ps) {
this.repoManager = repoManager;
this.changeControlFactory = changeControlFactory;
+ this.tagCache = tagCache;
+ this.changeCache = changeCache;
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.permissionFilter = permissionFilter;
@@ -263,12 +272,12 @@
/** Can this user see all the refs in this projects? */
public boolean allRefsAreVisible() {
- return allRefsAreVisibleExcept(Collections.<String> emptySet());
+ return allRefsAreVisible(Collections.<String> emptySet());
}
- public boolean allRefsAreVisibleExcept(Set<String> except) {
+ public boolean allRefsAreVisible(Set<String> ignore) {
return user instanceof InternalUser
- || canPerformOnAllRefs(Permission.READ, except);
+ || canPerformOnAllRefs(Permission.READ, ignore);
}
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
@@ -426,7 +435,7 @@
return false;
}
- private boolean canPerformOnAllRefs(String permission, Set<String> except) {
+ private boolean canPerformOnAllRefs(String permission, Set<String> ignore) {
boolean canPerform = false;
Set<String> patterns = allRefPatterns(permission);
if (patterns.contains(AccessSection.ALL)) {
@@ -437,7 +446,7 @@
for (final String pattern : patterns) {
if (controlForRef(pattern).canPerform(permission)) {
canPerform = true;
- } else if (except.contains(pattern)) {
+ } else if (ignore.contains(pattern)) {
continue;
} else {
return false;
@@ -513,40 +522,38 @@
return false;
}
- public boolean canReadCommit(RevWalk rw, RevCommit commit) {
- if (controlForRef("refs/*").canPerform(Permission.READ)) {
- return true;
- }
-
- Project.NameKey projName = state.getProject().getNameKey();
+ public boolean canReadCommit(ReviewDb db, RevWalk rw, RevCommit commit) {
try {
- Repository repo = repoManager.openRepository(projName);
+ Repository repo = openRepository();
try {
- RefDatabase refDb = repo.getRefDatabase();
- List<Ref> allRefs = Lists.newLinkedList();
- allRefs.addAll(refDb.getRefs(Constants.R_HEADS).values());
- allRefs.addAll(refDb.getRefs(Constants.R_TAGS).values());
- List<Ref> canReadRefs = Lists.newLinkedList();
- for (Ref r : allRefs) {
- if (controlForRef(r.getName()).canPerform(Permission.READ)) {
- canReadRefs.add(r);
- }
- }
-
- if (!canReadRefs.isEmpty() && IncludedInResolver.includedInOne(
- repo, rw, commit, canReadRefs)) {
- return true;
- }
+ return isMergedIntoVisibleRef(repo, db, rw, commit,
+ repo.getAllRefs().values());
} finally {
repo.close();
}
} catch (IOException e) {
- String msg =
- String.format(
- "Cannot verify permissions to commit object %s in repository %s",
- commit.name(), projName.get());
+ String msg = String.format(
+ "Cannot verify permissions to commit object %s in repository %s",
+ commit.name(), getProject().getNameKey());
log.error(msg, e);
+ return false;
}
- return false;
+ }
+
+ boolean isMergedIntoVisibleRef(Repository repo, ReviewDb db, RevWalk rw,
+ RevCommit commit, Collection<Ref> unfilteredRefs) throws IOException {
+ VisibleRefFilter filter =
+ new VisibleRefFilter(tagCache, changeCache, repo, this, db, true);
+ Map<String, Ref> m = Maps.newHashMapWithExpectedSize(unfilteredRefs.size());
+ for (Ref r : unfilteredRefs) {
+ m.put(r.getName(), r);
+ }
+ Map<String, Ref> refs = filter.filter(m, true);
+ return !refs.isEmpty()
+ && IncludedInResolver.includedInOne(repo, rw, commit, refs.values());
+ }
+
+ Repository openRepository() throws IOException {
+ return repoManager.openRepository(getProject().getNameKey());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 9fa9b52..e584b2e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -32,11 +32,11 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 00ecee3..9848faa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -24,6 +24,7 @@
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.extensions.api.projects.ProjectState;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
@@ -31,12 +32,16 @@
import dk.brics.automaton.RegExp;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -49,6 +54,8 @@
/** Manages access control for Git references (aka branches, tags). */
public class RefControl {
+ private static final Logger log = LoggerFactory.getLogger(RefControl.class);
+
private final ProjectControl projectControl;
private final String refName;
@@ -231,12 +238,13 @@
/**
* Determines whether the user can create a new Git ref.
*
- * @param rw revision pool {@code object} was parsed in.
+ * @param db db for checking change visibility.
+ * @param rw revision pool {@code object} was parsed in; must be reset before
+ * calling this method.
* @param object the object the user will start the reference with.
- * @param existsOnServer the object exists on server or not.
* @return {@code true} if the user specified can create a new Git ref
*/
- public boolean canCreate(RevWalk rw, RevObject object, boolean existsOnServer) {
+ public boolean canCreate(ReviewDb db, RevWalk rw, RevObject object) {
if (!canWrite()) {
return false;
}
@@ -255,10 +263,24 @@
}
if (object instanceof RevCommit) {
- return admin
- || (owner && !isBlocked(Permission.CREATE))
- || (canPerform(Permission.CREATE) && (!existsOnServer && canUpdate() || projectControl
- .canReadCommit(rw, (RevCommit) object)));
+ if (admin || (owner && !isBlocked(Permission.CREATE))) {
+ // Admin or project owner; bypass visibility check.
+ return true;
+ } else if (!canPerform(Permission.CREATE)) {
+ // No create permissions.
+ return false;
+ } else if (canUpdate()) {
+ // If the user has push permissions, they can create the ref regardless
+ // of whether they are pushing any new objects along with the create.
+ return true;
+ } else if (isMergedIntoBranchOrTag(db, rw, (RevCommit) object)) {
+ // If the user has no push permissions, check whether the object is
+ // merged into a branch or tag readable by this user. If so, they are
+ // not effectively "pushing" more objects, so they can create the ref
+ // even if they don't have push permission.
+ return true;
+ }
+ return false;
} else if (object instanceof RevTag) {
final RevTag tag = (RevTag) object;
try {
@@ -297,6 +319,28 @@
}
}
+ private boolean isMergedIntoBranchOrTag(ReviewDb db, RevWalk rw,
+ RevCommit commit) {
+ try {
+ Repository repo = projectControl.openRepository();
+ try {
+ List<Ref> refs = new ArrayList<>(
+ repo.getRefDatabase().getRefs(Constants.R_HEADS).values());
+ refs.addAll(repo.getRefDatabase().getRefs(Constants.R_TAGS).values());
+ return projectControl.isMergedIntoVisibleRef(
+ repo, db, rw, commit, refs);
+ } finally {
+ repo.close();
+ }
+ } catch (IOException e) {
+ String msg = String.format(
+ "Cannot verify permissions to commit object %s in repository %s",
+ commit.name(), projectControl.getProject().getNameKey());
+ log.error(msg, e);
+ }
+ return false;
+ }
+
/**
* Determines whether the user can delete the Git ref controlled by this
* object.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
index dcf6841..e9db0ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -15,8 +15,11 @@
package com.google.gerrit.server.project;
import static com.google.gerrit.server.project.RefControl.isRE;
+
import com.google.gerrit.common.data.ParameterizedString;
+
import dk.brics.automaton.Automaton;
+
import java.util.Collections;
import java.util.regex.Pattern;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
index 8ce6fa3..37d96ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
@@ -30,7 +30,8 @@
new InvalidProvider<ReviewDb>(), //
new InvalidProvider<ChangeQueryRewriter>(), //
null, null, null, null, null, null, null, //
- null, null, null, null, null, null, null, null, null, null), null);
+ null, null, null, null, null, null, null, null, null, null, null),
+ null);
private static final QueryRewriter.Definition<ChangeData, BasicChangeRewrites> mydef =
new QueryRewriter.Definition<ChangeData, BasicChangeRewrites>(
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 ed64015..662ec3c 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
@@ -35,6 +35,7 @@
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration;
@@ -78,7 +79,7 @@
}
if (!missing.isEmpty()) {
ChangeData first = missing.values().iterator().next();
- if (!first.notesMigration.readPatchSetApprovals()) {
+ if (!first.notesMigration.readChanges()) {
ReviewDb db = missing.values().iterator().next().db;
for (Change change : db.changes().get(missing.keySet())) {
missing.get(change.getId()).change = change;
@@ -119,7 +120,7 @@
throws OrmException {
List<ResultSet<PatchSetApproval>> pending = Lists.newArrayList();
for (ChangeData cd : changes) {
- if (!cd.notesMigration.readPatchSetApprovals()) {
+ if (!cd.notesMigration.readChanges()) {
if (cd.currentApprovals == null) {
pending.add(cd.db.patchSetApprovals()
.byPatchSet(cd.change().currentPatchSetId()));
@@ -154,7 +155,7 @@
*/
static ChangeData createForTest(Change.Id id, int currentPatchSetId) {
ChangeData cd = new ChangeData(null, null, null, null, null,
- null, null, null, null, id);
+ null, null, null, null, null, id);
cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
return cd;
}
@@ -166,6 +167,7 @@
private final ChangeNotes.Factory notesFactory;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
+ private final PatchLineCommentsUtil plcUtil;
private final PatchListCache patchListCache;
private final NotesMigration notesMigration;
private final Change.Id legacyId;
@@ -179,7 +181,7 @@
private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals;
private List<PatchSetApproval> currentApprovals;
private Map<Integer, List<String>> files = new HashMap<>();
- private Collection<PatchLineComment> comments;
+ private Collection<PatchLineComment> publishedComments;
private CurrentUser visibleTo;
private ChangeControl changeControl;
private List<ChangeMessage> messages;
@@ -194,6 +196,7 @@
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
NotesMigration notesMigration,
@Assisted ReviewDb db,
@@ -205,6 +208,7 @@
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.notesMigration = notesMigration;
legacyId = id;
@@ -218,6 +222,7 @@
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
NotesMigration notesMigration,
@Assisted ReviewDb db,
@@ -229,6 +234,7 @@
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.notesMigration = notesMigration;
legacyId = c.getId();
@@ -243,6 +249,7 @@
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
NotesMigration notesMigration,
@Assisted ReviewDb db,
@@ -254,6 +261,7 @@
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.notesMigration = notesMigration;
legacyId = c.getChange().getId();
@@ -519,12 +527,12 @@
return approvalsUtil.getReviewers(notes(), approvals().values());
}
- public Collection<PatchLineComment> comments()
+ public Collection<PatchLineComment> publishedComments()
throws OrmException {
- if (comments == null) {
- comments = db.patchComments().byChange(legacyId).toList();
+ if (publishedComments == null) {
+ publishedComments = plcUtil.publishedByChange(db, notes());
}
- return comments;
+ return publishedComments;
}
public List<ChangeMessage> messages()
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 ac7b9ae..fabe61c 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
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
@@ -146,6 +147,7 @@
final CapabilityControl.Factory capabilityControlFactory;
final ChangeControl.GenericFactory changeControlGenericFactory;
final ChangeData.Factory changeDataFactory;
+ final PatchLineCommentsUtil plcUtil;
final AccountResolver accountResolver;
final GroupBackend groupBackend;
final AllProjectsName allProjectsName;
@@ -168,6 +170,7 @@
CapabilityControl.Factory capabilityControlFactory,
ChangeControl.GenericFactory changeControlGenericFactory,
ChangeData.Factory changeDataFactory,
+ PatchLineCommentsUtil plcUtil,
AccountResolver accountResolver,
GroupBackend groupBackend,
AllProjectsName allProjectsName,
@@ -187,6 +190,7 @@
this.capabilityControlFactory = capabilityControlFactory;
this.changeControlGenericFactory = changeControlGenericFactory;
this.changeDataFactory = changeDataFactory;
+ this.plcUtil = plcUtil;
this.accountResolver = accountResolver;
this.groupBackend = groupBackend;
this.allProjectsName = allProjectsName;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
index 53d2bbd..ebb7389 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -33,7 +33,8 @@
private final Arguments args;
private final Account.Id accountId;
- HasDraftByPredicate(Arguments args, Account.Id accountId) {
+ HasDraftByPredicate(Arguments args,
+ Account.Id accountId) {
super(ChangeQueryBuilder.FIELD_DRAFTBY, accountId.toString());
this.args = args;
this.accountId = accountId;
@@ -41,20 +42,16 @@
@Override
public boolean match(final ChangeData object) throws OrmException {
- for (PatchLineComment c : object.comments()) {
- if (c.getStatus() == PatchLineComment.Status.DRAFT
- && c.getAuthor().equals(accountId)) {
- return true;
- }
- }
- return false;
+ return !args.plcUtil
+ .draftByChangeAuthor(args.db.get(), object.notes(), accountId)
+ .isEmpty();
}
@Override
public ResultSet<ChangeData> read() throws OrmException {
Set<Change.Id> ids = new HashSet<>();
- for (PatchLineComment sc : args.db.get().patchComments()
- .draftByAuthor(accountId)) {
+ for (PatchLineComment sc :
+ args.plcUtil.draftByAuthor(args.db.get(), accountId)) {
ids.add(sc.getKey().getParentKey().getParentKey().getParentKey());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 7927cbb..4d2ae49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -366,7 +366,7 @@
eventFactory.addComments(c, d.messages());
if (includePatchSets) {
for (PatchSetAttribute attribute : c.patchSets) {
- eventFactory.addPatchSetComments(attribute, d.comments());
+ eventFactory.addPatchSetComments(attribute, d.publishedComments());
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index d073002..3d6f8b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -16,76 +16,21 @@
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gwtorm.server.OrmException;
-import dk.brics.automaton.Automaton;
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
-
-import java.util.Collections;
import java.util.List;
class RegexPathPredicate extends RegexPredicate<ChangeData> {
- private final RunAutomaton pattern;
-
- private final String prefixBegin;
- private final String prefixEnd;
- private final int prefixLen;
- private final boolean prefixOnly;
-
RegexPathPredicate(String fieldName, String re) {
super(ChangeField.PATH, re);
-
- if (re.startsWith("^")) {
- re = re.substring(1);
- }
-
- if (re.endsWith("$") && !re.endsWith("\\$")) {
- re = re.substring(0, re.length() - 1);
- }
-
- Automaton automaton = new RegExp(re).toAutomaton();
- prefixBegin = automaton.getCommonPrefix();
- prefixLen = prefixBegin.length();
-
- if (0 < prefixLen) {
- char max = (char) (prefixBegin.charAt(prefixLen - 1) + 1);
- prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
- prefixOnly = re.equals(prefixBegin + ".*");
- } else {
- prefixEnd = "";
- prefixOnly = false;
- }
-
- pattern = prefixOnly ? null : new RunAutomaton(automaton);
}
@Override
public boolean match(ChangeData object) throws OrmException {
List<String> files = object.currentFilePaths();
if (files != null) {
- int begin, end;
-
- if (0 < prefixLen) {
- begin = find(files, prefixBegin);
- end = find(files, prefixEnd);
- } else {
- begin = 0;
- end = files.size();
- }
-
- if (prefixOnly) {
- return begin < end;
- }
-
- while (begin < end) {
- if (pattern.run(files.get(begin++))) {
- return true;
- }
- }
-
- return false;
-
+ return RegexListSearcher.ofStrings(getValue()).hasMatch(files);
} else {
// The ChangeData can't do expensive lookups right now. Bypass
// them and include the result anyway. We might be able to do
@@ -95,11 +40,6 @@
}
}
- private static int find(List<String> files, String p) {
- int r = Collections.binarySearch(files, p);
- return r < 0 ? -(r + 1) : r;
- }
-
@Override
public int getCost() {
return 1;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
index 697f303..c4f8458 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -34,9 +34,9 @@
import com.google.gerrit.extensions.common.InheritableBoolean;
import com.google.gerrit.extensions.common.SubmitType;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.SystemConfig;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
index bec2f3f..b57dcb9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
@@ -24,6 +24,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
new file mode 100644
index 0000000..4b0fd35
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2014 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.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Chars;
+
+import dk.brics.automaton.Automaton;
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+import java.util.Collections;
+import java.util.List;
+
+/** Helper to search sorted lists for elements matching a regex. */
+public abstract class RegexListSearcher<T> implements Function<T, String> {
+ public static RegexListSearcher<String> ofStrings(String re) {
+ return new RegexListSearcher<String>(re) {
+ @Override
+ public String apply(String in) {
+ return in;
+ }
+ };
+ }
+
+ private final RunAutomaton pattern;
+
+ private final String prefixBegin;
+ private final String prefixEnd;
+ private final int prefixLen;
+ private final boolean prefixOnly;
+
+ public RegexListSearcher(String re) {
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ Automaton automaton = new RegExp(re).toAutomaton();
+ prefixBegin = automaton.getCommonPrefix();
+ prefixLen = prefixBegin.length();
+
+ if (0 < prefixLen) {
+ char max = Chars.checkedCast(prefixBegin.charAt(prefixLen - 1) + 1);
+ prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
+ prefixOnly = re.equals(prefixBegin + ".*");
+ } else {
+ prefixEnd = "";
+ prefixOnly = false;
+ }
+
+ pattern = prefixOnly ? null : new RunAutomaton(automaton);
+ }
+
+ public Iterable<T> search(List<T> list) {
+ checkNotNull(list);
+ int begin, end;
+
+ if (0 < prefixLen) {
+ // Assumes many consecutive elements may have the same prefix, so the cost
+ // of two binary searches is less than iterating to find the endpoints.
+ begin = find(list, prefixBegin);
+ end = find(list, prefixEnd);
+ } else {
+ begin = 0;
+ end = list.size();
+ }
+
+ if (prefixOnly) {
+ return begin < end ? list.subList(begin, end) : ImmutableList.<T> of();
+ }
+
+ return Iterables.filter(
+ list.subList(begin, end),
+ new Predicate<T>() {
+ @Override
+ public boolean apply(T in) {
+ return pattern.run(RegexListSearcher.this.apply(in));
+ }
+ });
+ }
+
+ public boolean hasMatch(List<T> list) {
+ return !Iterables.isEmpty(search(list));
+ }
+
+ private int find(List<T> list, String p) {
+ int r = Collections.binarySearch(Lists.transform(list, this), p);
+ return r < 0 ? -(r + 1) : r;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
index 6730e30..2b6b86e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
@@ -17,9 +17,9 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.InternalUser;
+import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
-import com.google.inject.Inject;
/** RequestContext with an InternalUser making the internals visible. */
public class ServerRequestContext implements RequestContext {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java
index 6bc261f..2a68c49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java
@@ -28,9 +28,8 @@
return new Timestamp(nowMs());
}
- public static Timestamp roundTimestampToSecond(Timestamp t) {
- long milliseconds = (t.getTime()/1000) * 1000;
- return new Timestamp(milliseconds);
+ public static Timestamp roundToSecond(Timestamp t) {
+ return new Timestamp((t.getTime() / 1000) * 1000);
}
private TimeUtil() {
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
index daf9948..83878be 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
@@ -16,6 +16,7 @@
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.patch.PatchList;
+
import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.Operation;
import com.googlecode.prolog_cafe.lang.Predicate;
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
index 9eb7d9b..056da87 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -5,7 +5,9 @@
createProject = Create Project
emailReviewers = Email Reviewers
flushCaches = Flush Caches
+generateHttpPassword = Generate HTTP Password
killTask = Kill Task
+modifyAccount = Modify Account
priority = Priority
queryLimit = Query Limit
runAs = Run As
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
index c697400..1c0f8ae 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
@@ -14,6 +14,10 @@
package com.google.gerrit.rules;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.Guice;
import com.google.inject.Module;
@@ -39,10 +43,6 @@
import java.util.Arrays;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
/** Base class for any tests written in Prolog. */
public abstract class PrologTestCase {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
index 0bbec8a..b4d6e96 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
public class StringUtilTest {
/**
* Test the boundary condition that the first character of a string
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index a30fa92..4d19afe 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -37,12 +37,13 @@
import com.google.gerrit.reviewdb.client.CommentRange;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
@@ -50,6 +51,7 @@
import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -63,16 +65,18 @@
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.TimeUtil;
-import com.google.gerrit.testutil.TestChanges;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeAccountCache;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestChanges;
import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Providers;
@@ -101,30 +105,36 @@
@ConfigSuite.Config
public static @GerritServerConfig Config noteDbEnabled() {
- @GerritServerConfig Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "publishedComments", "read", true);
- return cfg;
+ return NotesMigration.allEnabledConfig();
}
private Injector injector;
private Project.NameKey project;
private InMemoryRepositoryManager repoManager;
+ private AllUsersNameProvider allUsers;
private PatchLineCommentsUtil plcUtil;
private RevisionResource revRes1;
private RevisionResource revRes2;
private PatchLineComment plc1;
private PatchLineComment plc2;
private PatchLineComment plc3;
+ private PatchLineComment plc4;
+ private PatchLineComment plc5;
private IdentifiedUser changeOwner;
@Before
public void setUp() throws Exception {
@SuppressWarnings("unchecked")
- final DynamicMap<RestView<CommentResource>> views =
+ final DynamicMap<RestView<CommentResource>> commentViews =
createMock(DynamicMap.class);
- final TypeLiteral<DynamicMap<RestView<CommentResource>>> viewsType =
+ final TypeLiteral<DynamicMap<RestView<CommentResource>>> commentViewsType =
new TypeLiteral<DynamicMap<RestView<CommentResource>>>() {};
+ @SuppressWarnings("unchecked")
+ final DynamicMap<RestView<DraftResource>> draftViews =
+ createMock(DynamicMap.class);
+ final TypeLiteral<DynamicMap<RestView<DraftResource>>> draftViewsType =
+ new TypeLiteral<DynamicMap<RestView<DraftResource>>>() {};
+
final AccountInfo.Loader.Factory alf =
createMock(AccountInfo.Loader.Factory.class);
final ReviewDb db = createMock(ReviewDb.class);
@@ -137,10 +147,23 @@
@SuppressWarnings("unused")
InMemoryRepository repo = repoManager.createRepository(project);
+ Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
+ co.setFullName("Change Owner");
+ co.setPreferredEmail("change@owner.com");
+ accountCache.put(co);
+ final Account.Id ownerId = co.getId();
+
+ Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
+ ou.setFullName("Other Account");
+ ou.setPreferredEmail("other@account.com");
+ accountCache.put(ou);
+ Account.Id otherUserId = ou.getId();
+
AbstractModule mod = new AbstractModule() {
@Override
protected void configure() {
- bind(viewsType).toInstance(views);
+ bind(commentViewsType).toInstance(commentViews);
+ bind(draftViewsType).toInstance(draftViews);
bind(AccountInfo.Loader.Factory.class).toInstance(alf);
bind(ReviewDb.class).toProvider(Providers.<ReviewDb>of(db));
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(config);
@@ -160,25 +183,16 @@
bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
.toInstance(serverIdent);
}
+
+ @Provides
+ @Singleton
+ CurrentUser getCurrentUser(IdentifiedUser.GenericFactory userFactory) {
+ return userFactory.create(ownerId);
+ }
};
injector = Guice.createInjector(mod);
- NotesMigration migration = injector.getInstance(NotesMigration.class);
- plcUtil = new PatchLineCommentsUtil(migration);
-
- Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
- co.setFullName("Change Owner");
- co.setPreferredEmail("change@owner.com");
- accountCache.put(co);
- Account.Id ownerId = co.getId();
-
- Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
- ou.setFullName("Other Account");
- ou.setPreferredEmail("other@account.com");
- accountCache.put(ou);
- Account.Id otherUserId = ou.getId();
-
IdentifiedUser.GenericFactory userFactory =
injector.getInstance(IdentifiedUser.GenericFactory.class);
changeOwner = userFactory.create(ownerId);
@@ -194,6 +208,10 @@
expect(alf.create(true)).andReturn(accountLoader).anyTimes();
replay(accountLoader, alf);
+ allUsers = injector.getInstance(AllUsersNameProvider.class);
+ repoManager.createRepository(allUsers.get());
+ plcUtil = injector.getInstance(PatchLineCommentsUtil.class);
+
PatchLineCommentAccess plca = createMock(PatchLineCommentAccess.class);
expect(db.patchComments()).andReturn(plca).anyTimes();
@@ -203,7 +221,7 @@
PatchSet.Id psId2 = new PatchSet.Id(change.getId(), 2);
PatchSet ps2 = new PatchSet(psId2);
- long timeBase = TimeUtil.nowMs();
+ long timeBase = TimeUtil.roundToSecond(TimeUtil.nowTs()).getTime();
plc1 = newPatchLineComment(psId1, "Comment1", null,
"FileOne.txt", Side.REVISION, 3, ownerId, timeBase,
"First Comment", new CommentRange(1, 2, 3, 4));
@@ -216,32 +234,56 @@
"FileOne.txt", Side.PARENT, 3, ownerId, timeBase + 2000,
"First Parent Comment", new CommentRange(1, 2, 3, 4));
plc3.setRevId(new RevId("CDEFCDEFCDEFCDEFCDEFCDEFCDEFCDEFCDEFCDEF"));
+ plc4 = newPatchLineComment(psId2, "Comment4", null, "FileOne.txt",
+ Side.REVISION, 3, ownerId, timeBase + 3000, "Second Comment",
+ new CommentRange(1, 2, 3, 4), Status.DRAFT);
+ plc4.setRevId(new RevId("BCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDE"));
+ plc5 = newPatchLineComment(psId2, "Comment5", null, "FileOne.txt",
+ Side.REVISION, 5, ownerId, timeBase + 4000, "Third Comment",
+ new CommentRange(3, 4, 5, 6), Status.DRAFT);
+ plc5.setRevId(new RevId("BCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDE"));
List<PatchLineComment> commentsByOwner = Lists.newArrayList();
commentsByOwner.add(plc1);
commentsByOwner.add(plc3);
List<PatchLineComment> commentsByReviewer = Lists.newArrayList();
commentsByReviewer.add(plc2);
+ List<PatchLineComment> drafts = Lists.newArrayList();
+ drafts.add(plc4);
+ drafts.add(plc5);
plca.upsert(commentsByOwner);
expectLastCall().anyTimes();
plca.upsert(commentsByReviewer);
expectLastCall().anyTimes();
+ plca.upsert(drafts);
+ expectLastCall().anyTimes();
expect(plca.publishedByPatchSet(psId1))
.andAnswer(results(plc1, plc2, plc3)).anyTimes();
expect(plca.publishedByPatchSet(psId2))
.andAnswer(results()).anyTimes();
+ expect(plca.draftByPatchSetAuthor(psId1, ownerId))
+ .andAnswer(results()).anyTimes();
+ expect(plca.draftByPatchSetAuthor(psId2, ownerId))
+ .andAnswer(results(plc4, plc5)).anyTimes();
+ expect(plca.byChange(change.getId()))
+ .andAnswer(results(plc1, plc2, plc3, plc4, plc5)).anyTimes();
replay(db, plca);
ChangeUpdate update = newUpdate(change, changeOwner);
update.setPatchSetId(psId1);
- plcUtil.addPublishedComments(db, update, commentsByOwner);
+ plcUtil.upsertComments(db, update, commentsByOwner);
update.commit();
update = newUpdate(change, otherUser);
update.setPatchSetId(psId1);
- plcUtil.addPublishedComments(db, update, commentsByReviewer);
+ plcUtil.upsertComments(db, update, commentsByReviewer);
+ update.commit();
+
+ update = newUpdate(change, changeOwner);
+ update.setPatchSetId(psId2);
+ plcUtil.upsertComments(db, update, drafts);
update.commit();
ChangeControl ctl = stubChangeControl(change);
@@ -250,7 +292,7 @@
}
private ChangeControl stubChangeControl(Change c) throws OrmException {
- return TestChanges.stubChangeControl(repoManager, c, changeOwner);
+ return TestChanges.stubChangeControl(repoManager, c, allUsers, changeOwner);
}
private Change newChange() {
@@ -258,7 +300,7 @@
}
private ChangeUpdate newUpdate(Change c, final IdentifiedUser user) throws Exception {
- return TestChanges.newUpdate(injector, repoManager, c, user);
+ return TestChanges.newUpdate(injector, repoManager, c, allUsers, user);
}
@Test
@@ -281,6 +323,33 @@
assertGetComment(injector, revRes1, null, "BadComment");
}
+ @Test
+ public void testListDrafts() throws Exception {
+ // test ListDrafts for patch set 1
+ assertListDrafts(injector, revRes1,
+ Collections.<String, ArrayList<PatchLineComment>> emptyMap());
+
+ // test ListDrafts for patch set 2
+ assertListDrafts(injector, revRes2, ImmutableMap.of(
+ "FileOne.txt", Lists.newArrayList(plc4, plc5)));
+ }
+
+ @Test
+ public void testPatchLineCommentsUtilByCommentStatus() throws OrmException {
+ List<PatchLineComment> publishedActual = plcUtil.publishedByChange(
+ injector.getInstance(ReviewDb.class), revRes2.getNotes());
+ List<PatchLineComment> draftActual = plcUtil.draftByChange(
+ injector.getInstance(ReviewDb.class), revRes2.getNotes());
+ List<PatchLineComment> publishedExpected =
+ Lists.newArrayList(plc1, plc2, plc3);
+ List<PatchLineComment> draftExpected =
+ Lists.newArrayList(plc4, plc5);
+ assertEquals(publishedExpected.size(), publishedActual.size());
+ assertEquals(draftExpected.size(), draftActual.size());
+ assertEquals(publishedExpected, publishedActual);
+ assertEquals(draftExpected, draftActual);
+ }
+
private static IAnswer<ResultSet<PatchLineComment>> results(
final PatchLineComment... comments) {
return new IAnswer<ResultSet<PatchLineComment>>() {
@@ -300,7 +369,7 @@
fail("Expected no comment");
}
CommentInfo actual = getComment.apply(commentRes);
- assertComment(expected, actual);
+ assertComment(expected, actual, true);
} catch (ResourceNotFoundException e) {
if (expected != null) {
fail("Expected to find comment");
@@ -325,28 +394,54 @@
assertNotNull(actualComments);
assertEquals(expectedComments.size(), actualComments.size());
for (int i = 0; i < expectedComments.size(); i++) {
- assertComment(expectedComments.get(i), actualComments.get(i));
+ assertComment(expectedComments.get(i), actualComments.get(i), true);
}
}
}
- private static void assertComment(PatchLineComment plc, CommentInfo ci) {
+ private static void assertListDrafts(Injector inj, RevisionResource res,
+ Map<String, ArrayList<PatchLineComment>> expected) throws Exception {
+ Drafts drafts = inj.getInstance(Drafts.class);
+ RestReadView<RevisionResource> listView =
+ (RestReadView<RevisionResource>) drafts.list();
+ @SuppressWarnings("unchecked")
+ Map<String, List<CommentInfo>> actual =
+ (Map<String, List<CommentInfo>>) listView.apply(res);
+ assertNotNull(actual);
+ assertEquals(expected.size(), actual.size());
+ assertEquals(expected.keySet(), actual.keySet());
+ for (Map.Entry<String, ArrayList<PatchLineComment>> entry : expected.entrySet()) {
+ List<PatchLineComment> expectedComments = entry.getValue();
+ List<CommentInfo> actualComments = actual.get(entry.getKey());
+ assertNotNull(actualComments);
+ assertEquals(expectedComments.size(), actualComments.size());
+ for (int i = 0; i < expectedComments.size(); i++) {
+ assertComment(expectedComments.get(i), actualComments.get(i), false);
+ }
+ }
+ }
+
+ private static void assertComment(PatchLineComment plc, CommentInfo ci,
+ boolean isPublished) {
assertEquals(plc.getKey().get(), ci.id);
assertEquals(plc.getParentUuid(), ci.inReplyTo);
assertEquals(plc.getMessage(), ci.message);
- assertNotNull(ci.author);
- assertEquals(plc.getAuthor(), ci.author._id);
+ if (isPublished) {
+ assertNotNull(ci.author);
+ assertEquals(plc.getAuthor(), ci.author._id);
+ }
assertEquals(plc.getLine(), (int) ci.line);
assertEquals(plc.getSide() == 0 ? Side.PARENT : Side.REVISION,
Objects.firstNonNull(ci.side, Side.REVISION));
- assertEquals(TimeUtil.roundTimestampToSecond(plc.getWrittenOn()),
- TimeUtil.roundTimestampToSecond(ci.updated));
+ assertEquals(TimeUtil.roundToSecond(plc.getWrittenOn()),
+ TimeUtil.roundToSecond(ci.updated));
assertEquals(plc.getRange(), ci.range);
}
private static PatchLineComment newPatchLineComment(PatchSet.Id psId,
String uuid, String inReplyToUuid, String filename, Side side, int line,
- Account.Id authorId, long millis, String message, CommentRange range) {
+ Account.Id authorId, long millis, String message, CommentRange range,
+ PatchLineComment.Status status) {
Patch.Key p = new Patch.Key(psId, filename);
PatchLineComment.Key id = new PatchLineComment.Key(p, uuid);
PatchLineComment plc =
@@ -354,8 +449,15 @@
plc.setMessage(message);
plc.setRange(range);
plc.setSide(side == Side.PARENT ? (short) 0 : (short) 1);
- plc.setStatus(Status.PUBLISHED);
+ plc.setStatus(status);
plc.setWrittenOn(new Timestamp(millis));
return plc;
}
+
+ private static PatchLineComment newPatchLineComment(PatchSet.Id psId,
+ String uuid, String inReplyToUuid, String filename, Side side, int line,
+ Account.Id authorId, long millis, String message, CommentRange range) {
+ return newPatchLineComment(psId, uuid, inReplyToUuid, filename, side, line,
+ authorId, millis, message, range, Status.PUBLISHED);
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
index cc19811..480efb4 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.config;
-import org.junit.Test;
-
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -23,6 +21,8 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
public class ConfigUtilTest {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
new file mode 100644
index 0000000..de06cc0
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2014 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.edit;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+
+import org.junit.Test;
+
+public class ChangeEditTest {
+ @Test
+ public void changeEditRef() throws Exception {
+ Account.Id accountId = new Account.Id(1000042);
+ Change.Id changeId = new Change.Id(56414);
+ String refName = ChangeEditUtil.editRefName(accountId, changeId);
+ assertEquals("refs/users/42/1000042/edit-56414", refName);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
index 1b6ae4e..9c1dd38 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
@@ -68,11 +68,6 @@
}
@Override
- public void insert(ChangeData cd) {
- throw new UnsupportedOperationException();
- }
-
- @Override
public void replace(ChangeData cd) {
throw new UnsupportedOperationException();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
index 50e5764..7090f0d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
@@ -26,8 +26,8 @@
new FakeQueryBuilder.Definition<>(
FakeQueryBuilder.class),
new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, indexes, null, null,
- null, null),
+ null, null, null, null, null, null, null, null, null, indexes, null,
+ null, null, null),
null);
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
index 622b31e..d9f86bd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.ioutil;
-import org.junit.Test;
-
import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
@@ -25,6 +23,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import org.junit.Test;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
index 02ebf51..d8ff543 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
@@ -14,14 +14,14 @@
package com.google.gerrit.server.mail;
-import org.junit.Test;
-
-import java.io.UnsupportedEncodingException;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import org.junit.Test;
+
+import java.io.UnsupportedEncodingException;
+
public class AddressTest {
@Test
public void testParse_NameEmail1() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
new file mode 100644
index 0000000..3bc2a40
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -0,0 +1,231 @@
+// Copyright (C) 2014 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.notedb;
+
+import static com.google.inject.Scopes.SINGLETON;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.CommentRange;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.AnonymousCowardNameProvider;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitModule;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.testutil.FakeAccountCache;
+import com.google.gerrit.testutil.FakeRealm;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestChanges;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StandardKeyEncoder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.util.Providers;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.After;
+import org.junit.Before;
+
+import java.sql.Timestamp;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class AbstractChangeNotesTest {
+ private static final TimeZone TZ =
+ TimeZone.getTimeZone("America/Los_Angeles");
+
+ protected Account.Id otherUserId;
+ protected FakeAccountCache accountCache;
+ protected IdentifiedUser changeOwner;
+ protected IdentifiedUser.GenericFactory userFactory;
+ protected IdentifiedUser otherUser;
+ protected InMemoryRepository repo;
+ protected InMemoryRepositoryManager repoManager;
+ protected PersonIdent serverIdent;
+ protected Project.NameKey project;
+
+ private AllUsersNameProvider allUsers;
+ private Injector injector;
+ private String systemTimeZone;
+ private volatile long clockStepMs;
+
+ @Before
+ public void setUp() throws Exception {
+ setTimeForTesting();
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+
+ serverIdent = new PersonIdent(
+ "Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
+ project = new Project.NameKey("test-project");
+ repoManager = new InMemoryRepositoryManager();
+ repo = repoManager.createRepository(project);
+ accountCache = new FakeAccountCache();
+ Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
+ co.setFullName("Change Owner");
+ co.setPreferredEmail("change@owner.com");
+ accountCache.put(co);
+ Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
+ ou.setFullName("Other Account");
+ ou.setPreferredEmail("other@account.com");
+ accountCache.put(ou);
+
+ injector = Guice.createInjector(new FactoryModule() {
+ @Override
+ public void configure() {
+ install(new GitModule());
+ bind(NotesMigration.class).toInstance(NotesMigration.allEnabled());
+ bind(GitRepositoryManager.class).toInstance(repoManager);
+ bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
+ bind(CapabilityControl.Factory.class)
+ .toProvider(Providers.<CapabilityControl.Factory> of(null));
+ bind(Config.class).annotatedWith(GerritServerConfig.class)
+ .toInstance(new Config());
+ bind(String.class).annotatedWith(AnonymousCowardName.class)
+ .toProvider(AnonymousCowardNameProvider.class);
+ bind(String.class).annotatedWith(CanonicalWebUrl.class)
+ .toInstance("http://localhost:8080/");
+ bind(Realm.class).to(FakeRealm.class);
+ bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
+ bind(AccountCache.class).toInstance(accountCache);
+ bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
+ .toInstance(serverIdent);
+ bind(GitReferenceUpdated.class)
+ .toInstance(GitReferenceUpdated.DISABLED);
+ }
+ });
+
+ userFactory = injector.getInstance(IdentifiedUser.GenericFactory.class);
+ allUsers = injector.getInstance(AllUsersNameProvider.class);
+ repoManager.createRepository(allUsers.get());
+ changeOwner = userFactory.create(co.getId());
+ otherUser = userFactory.create(ou.getId());
+ otherUserId = otherUser.getAccountId();
+ }
+
+ private void setTimeForTesting() {
+ systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
+ clockStepMs = MILLISECONDS.convert(1, SECONDS);
+ final AtomicLong clockMs = new AtomicLong(
+ new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
+
+ DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
+ @Override
+ public long getMillis() {
+ return clockMs.getAndAdd(clockStepMs);
+ }
+ });
+ }
+
+ @After
+ public void resetTime() {
+ DateTimeUtils.setCurrentMillisSystem();
+ System.setProperty("user.timezone", systemTimeZone);
+ }
+
+ protected Change newChange() {
+ return TestChanges.newChange(project, changeOwner);
+ }
+
+ protected ChangeUpdate newUpdate(Change c, IdentifiedUser user)
+ throws OrmException {
+ return TestChanges.newUpdate(injector, repoManager, c, allUsers, user);
+ }
+
+ protected ChangeNotes newNotes(Change c) throws OrmException {
+ return new ChangeNotes(repoManager, allUsers, c).load();
+ }
+
+ protected static SubmitRecord submitRecord(String status,
+ String errorMessage, SubmitRecord.Label... labels) {
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.valueOf(status);
+ rec.errorMessage = errorMessage;
+ if (labels.length > 0) {
+ rec.labels = ImmutableList.copyOf(labels);
+ }
+ return rec;
+ }
+
+ protected static SubmitRecord.Label submitLabel(String name, String status,
+ Account.Id appliedBy) {
+ SubmitRecord.Label label = new SubmitRecord.Label();
+ label.label = name;
+ label.status = SubmitRecord.Label.Status.valueOf(status);
+ label.appliedBy = appliedBy;
+ return label;
+ }
+
+ protected PatchLineComment newPublishedPatchLineComment(PatchSet.Id psId,
+ String filename, String UUID, CommentRange range, int line,
+ IdentifiedUser commenter, String parentUUID, Timestamp t,
+ String message, short side, String commitSHA1) {
+ return newPatchLineComment(psId, filename, UUID, range, line, commenter,
+ parentUUID, t, message, side, commitSHA1,
+ PatchLineComment.Status.PUBLISHED);
+ }
+
+ protected PatchLineComment newPatchLineComment(PatchSet.Id psId,
+ String filename, String UUID, CommentRange range, int line,
+ IdentifiedUser commenter, String parentUUID, Timestamp t,
+ String message, short side, String commitSHA1,
+ PatchLineComment.Status status) {
+ PatchLineComment comment = new PatchLineComment(
+ new PatchLineComment.Key(
+ new Patch.Key(psId, filename), UUID),
+ line, commenter.getAccountId(), parentUUID, t);
+ comment.setSide(side);
+ comment.setMessage(message);
+ comment.setRange(range);
+ comment.setRevId(new RevId(commitSHA1));
+ comment.setStatus(status);
+ return comment;
+ }
+
+ protected static Timestamp truncate(Timestamp ts) {
+ return new Timestamp((ts.getTime() / 1000) * 1000);
+ }
+
+ protected static Timestamp after(Change c, long millis) {
+ return new Timestamp(c.getCreatedOn().getTime() + millis);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
new file mode 100644
index 0000000..32f455d
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -0,0 +1,218 @@
+// Copyright (C) 2014 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.notedb;
+
+import static org.junit.Assert.fail;
+
+import com.google.gerrit.server.util.TimeUtil;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ChangeNotesParserTest extends AbstractChangeNotesTest {
+ private TestRepository<InMemoryRepository> testRepo;
+ private RevWalk walk;
+
+ @Before
+ public void setUpTestRepo() throws Exception {
+ testRepo = new TestRepository<>(repo);
+ walk = new RevWalk(repo);
+ }
+
+ @After
+ public void tearDownTestRepo() throws Exception {
+ walk.release();
+ }
+
+ @Test
+ public void parseAuthor() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n");
+ assertParseFails(writeCommit("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n",
+ new PersonIdent("Change Owner", "owner@example.com",
+ serverIdent.getWhen(), serverIdent.getTimeZone())));
+ assertParseFails(writeCommit("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n",
+ new PersonIdent("Change Owner", "x@gerrit",
+ serverIdent.getWhen(), serverIdent.getTimeZone())));
+ }
+
+ @Test
+ public void parseStatus() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: NEW\n");
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: new\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: OOPS\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: NEW\n"
+ + "Status: NEW\n");
+ }
+
+ @Test
+ public void parsePatchSetId() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n");
+ assertParseFails("Update change\n"
+ + "\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Patch-Set: 1\n");
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: x\n");
+ }
+
+ @Test
+ public void parseApproval() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1=+1\n"
+ + "Label: Label2=1\n"
+ + "Label: Label3=0\n"
+ + "Label: Label4=-1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1=X\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1 = 1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: X+Y\n");
+ }
+
+ @Test
+ public void parseSubmitRecords() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Code-Review\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Alternative-Code-Review\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: OOPS\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: NEED: X+Y\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: OK: X+Y: Change Owner <1@gerrit>\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: OK: Code-Review: 1@gerrit\n");
+ }
+
+ @Test
+ public void parseReviewer() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Reviewer: Change Owner <1@gerrit>\n"
+ + "CC: Other Account <2@gerrit>\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Reviewer: 1@gerrit\n");
+ }
+
+ private RevCommit writeCommit(String body) throws Exception {
+ return writeCommit(body, ChangeNoteUtil.newIdent(
+ changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent,
+ "Anonymous Coward"));
+ }
+
+ private RevCommit writeCommit(String body, PersonIdent author)
+ throws Exception {
+ ObjectInserter ins = testRepo.getRepository().newObjectInserter();
+ try {
+ CommitBuilder cb = new CommitBuilder();
+ cb.setAuthor(author);
+ cb.setCommitter(new PersonIdent(serverIdent, author.getWhen()));
+ cb.setTreeId(testRepo.tree());
+ cb.setMessage(body);
+ ObjectId id = ins.insert(cb);
+ ins.flush();
+ RevCommit commit = walk.parseCommit(id);
+ walk.parseBody(commit);
+ return commit;
+ } finally {
+ ins.release();
+ }
+ }
+
+ private void assertParseSucceeds(String body) throws Exception {
+ try (ChangeNotesParser parser = newParser(writeCommit(body))) {
+ parser.parseAll();
+ }
+ }
+
+ private void assertParseFails(String body) throws Exception {
+ assertParseFails(writeCommit(body));
+ }
+
+ private void assertParseFails(RevCommit commit) throws Exception {
+ try (ChangeNotesParser parser = newParser(commit)) {
+ parser.parseAll();
+ fail("Expected parse to fail:\n" + commit.getFullMessage());
+ } catch (ConfigInvalidException e) {
+ // Expected.
+ }
+ }
+
+ private ChangeNotesParser newParser(ObjectId tip) throws Exception {
+ return new ChangeNotesParser(newChange(), tip, walk, repoManager);
+ }
+}
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 29d2449..06a48eb 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
@@ -17,14 +17,14 @@
import static com.google.gerrit.server.notedb.ReviewerState.CC;
import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER;
import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
-import static com.google.inject.Scopes.SINGLETON;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
@@ -36,311 +36,31 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.CommentRange;
-import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.AnonymousCowardNameProvider;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitModule;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.notedb.CommentsInNotesUtil;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.TimeUtil;
-import com.google.gerrit.testutil.TestChanges;
-import com.google.gerrit.testutil.FakeAccountCache;
-import com.google.gerrit.testutil.FakeRealm;
-import com.google.gerrit.testutil.InMemoryRepositoryManager;
-import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StandardKeyEncoder;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.util.Providers;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeUtils;
-import org.joda.time.DateTimeUtils.MillisProvider;
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.junit.Test;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicLong;
-public class ChangeNotesTest {
- private static final TimeZone TZ =
- TimeZone.getTimeZone("America/Los_Angeles");
-
- private PersonIdent serverIdent;
- private Project.NameKey project;
- private InMemoryRepositoryManager repoManager;
- private InMemoryRepository repo;
- private FakeAccountCache accountCache;
- private IdentifiedUser changeOwner;
- private IdentifiedUser otherUser;
- private Injector injector;
- private String systemTimeZone;
- private volatile long clockStepMs;
-
- @Before
- public void setUp() throws Exception {
- setTimeForTesting();
- KeyUtil.setEncoderImpl(new StandardKeyEncoder());
-
- serverIdent = new PersonIdent(
- "Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
- project = new Project.NameKey("test-project");
- repoManager = new InMemoryRepositoryManager();
- repo = repoManager.createRepository(project);
- accountCache = new FakeAccountCache();
- Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
- co.setFullName("Change Owner");
- co.setPreferredEmail("change@owner.com");
- accountCache.put(co);
- Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
- ou.setFullName("Other Account");
- ou.setPreferredEmail("other@account.com");
- accountCache.put(ou);
-
- injector = Guice.createInjector(new FactoryModule() {
- @Override
- public void configure() {
- install(new GitModule());
- bind(NotesMigration.class).toInstance(NotesMigration.allEnabled());
- bind(GitRepositoryManager.class).toInstance(repoManager);
- bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
- bind(CapabilityControl.Factory.class)
- .toProvider(Providers.<CapabilityControl.Factory> of(null));
- bind(Config.class).annotatedWith(GerritServerConfig.class)
- .toInstance(new Config());
- bind(String.class).annotatedWith(AnonymousCowardName.class)
- .toProvider(AnonymousCowardNameProvider.class);
- bind(String.class).annotatedWith(CanonicalWebUrl.class)
- .toInstance("http://localhost:8080/");
- bind(Realm.class).to(FakeRealm.class);
- bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
- bind(AccountCache.class).toInstance(accountCache);
- bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
- .toInstance(serverIdent);
- bind(GitReferenceUpdated.class)
- .toInstance(GitReferenceUpdated.DISABLED);
- }
- });
-
- IdentifiedUser.GenericFactory userFactory =
- injector.getInstance(IdentifiedUser.GenericFactory.class);
- changeOwner = userFactory.create(co.getId());
- otherUser = userFactory.create(ou.getId());
- }
-
- private void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- clockStepMs = MILLISECONDS.convert(1, SECONDS);
- final AtomicLong clockMs = new AtomicLong(
- new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
-
- DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
- @Override
- public long getMillis() {
- return clockMs.getAndAdd(clockStepMs);
- }
- });
- }
-
- @After
- public void resetTime() {
- DateTimeUtils.setCurrentMillisSystem();
- System.setProperty("user.timezone", systemTimeZone);
- }
-
- @Test
- public void approvalsCommitFormatSimple() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.putApproval("Verified", (short) 1);
- update.putApproval("Code-Review", (short) -1);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
- update.putReviewer(otherUser.getAccount().getId(), CC);
- update.commit();
- assertEquals("refs/changes/01/1/meta", update.getRefName());
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Reviewer: Change Owner <1@gerrit>\n"
- + "CC: Other Account <2@gerrit>\n"
- + "Label: Code-Review=-1\n"
- + "Label: Verified=+1\n",
- commit.getFullMessage());
-
- PersonIdent author = commit.getAuthorIdent();
- assertEquals("Change Owner", author.getName());
- assertEquals("1@gerrit", author.getEmailAddress());
- assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
- author.getWhen());
- assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
-
- PersonIdent committer = commit.getCommitterIdent();
- assertEquals("Gerrit Server", committer.getName());
- assertEquals("noreply@gerrit.com", committer.getEmailAddress());
- assertEquals(author.getWhen(), committer.getWhen());
- assertEquals(author.getTimeZone(), committer.getTimeZone());
- } finally {
- walk.release();
- }
- }
-
- @Test
- public void changeMessageCommitFormatSimple() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setChangeMessage("Just a little code change.\n"
- + "How about a new line");
- update.commit();
- assertEquals("refs/changes/01/1/meta", update.getRefName());
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Just a little code change.\n"
- + "How about a new line\n"
- + "\n"
- + "Patch-set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.release();
- }
- }
-
- @Test
- public void approvalTombstoneCommitFormat() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.removeApproval("Code-Review");
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Label: -Code-Review\n",
- commit.getFullMessage());
- } finally {
- walk.release();
- }
- }
-
- @Test
- public void submitCommitFormat() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setSubject("Submit patch set 1");
-
- update.submit(ImmutableList.of(
- submitRecord("NOT_READY", null,
- submitLabel("Verified", "OK", changeOwner.getAccountId()),
- submitLabel("Code-Review", "NEED", null)),
- submitRecord("NOT_READY", null,
- submitLabel("Verified", "OK", changeOwner.getAccountId()),
- submitLabel("Alternative-Code-Review", "NEED", null))));
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Submit patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Status: submitted\n"
- + "Submitted-with: NOT_READY\n"
- + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
- + "Submitted-with: NEED: Code-Review\n"
- + "Submitted-with: NOT_READY\n"
- + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
- + "Submitted-with: NEED: Alternative-Code-Review\n",
- commit.getFullMessage());
-
- PersonIdent author = commit.getAuthorIdent();
- assertEquals("Change Owner", author.getName());
- assertEquals("1@gerrit", author.getEmailAddress());
- assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
- author.getWhen());
- assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
-
- PersonIdent committer = commit.getCommitterIdent();
- assertEquals("Gerrit Server", committer.getName());
- assertEquals("noreply@gerrit.com", committer.getEmailAddress());
- assertEquals(author.getWhen(), committer.getWhen());
- assertEquals(author.getTimeZone(), committer.getTimeZone());
- } finally {
- walk.release();
- }
- }
-
- @Test
- public void submitWithErrorMessage() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setSubject("Submit patch set 1");
-
- update.submit(ImmutableList.of(
- submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Submit patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Status: submitted\n"
- + "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.release();
- }
- }
-
+public class ChangeNotesTest extends AbstractChangeNotesTest {
@Test
public void approvalsOnePatchSet() throws Exception {
Change c = newChange();
@@ -612,6 +332,21 @@
}
@Test
+ public void emptyChangeUpdate() throws Exception {
+ ChangeUpdate update = newUpdate(newChange(), changeOwner);
+ update.commit();
+ assertNull(update.getRevision());
+ }
+
+ @Test
+ public void emptyExceptSubject() throws Exception {
+ ChangeUpdate update = newUpdate(newChange(), changeOwner);
+ update.setSubject("Create change");
+ update.commit();
+ assertNotNull(update.getRevision());
+ }
+
+ @Test
public void multipleUpdatesInBatch() throws Exception {
Change c = newChange();
ChangeUpdate update1 = newUpdate(c, changeOwner);
@@ -644,6 +379,114 @@
}
@Test
+ public void multipleUpdatesIncludingComments() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update1 = newUpdate(c, otherUser);
+ String uuid1 = "uuid1";
+ String message1 = "comment 1";
+ CommentRange range1 = new CommentRange(1, 1, 2, 1);
+ Timestamp time1 = TimeUtil.nowTs();
+ PatchSet.Id psId = c.currentPatchSetId();
+ BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ BatchMetaDataUpdate batch = update1.openUpdateInBatch(bru);
+ PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
+ uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
+ (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+ update1.setPatchSetId(psId);
+ update1.upsertComment(comment1);
+ update1.writeCommit(batch);
+ ChangeUpdate update2 = newUpdate(c, otherUser);
+ update2.putApproval("Code-Review", (short) 2);
+ update2.writeCommit(batch);
+
+ RevWalk rw = new RevWalk(repo);
+ try {
+ batch.commit();
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+
+ ChangeNotes notes = newNotes(c);
+ ObjectId tip = notes.getRevision();
+ RevCommit commitWithApprovals = rw.parseCommit(tip);
+ assertNotNull(commitWithApprovals);
+ RevCommit commitWithComments = commitWithApprovals.getParent(0);
+ assertNotNull(commitWithComments);
+
+ ChangeNotesParser notesWithComments =
+ new ChangeNotesParser(c, commitWithComments.copy(), rw, repoManager);
+ notesWithComments.parseAll();
+ ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals1 =
+ notesWithComments.buildApprovals();
+ assertEquals(0, approvals1.size());
+ assertEquals(1, notesWithComments.commentsForBase.size());
+ notesWithComments.close();
+
+ ChangeNotesParser notesWithApprovals =
+ new ChangeNotesParser(c, commitWithApprovals.copy(), rw, repoManager);
+ notesWithApprovals.parseAll();
+ ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals2 =
+ notesWithApprovals.buildApprovals();
+ assertEquals(1, approvals2.size());
+ assertEquals(1, notesWithApprovals.commentsForBase.size());
+ notesWithApprovals.close();
+ } finally {
+ batch.close();
+ rw.release();
+ }
+ }
+
+ @Test
+ public void multipleUpdatesAcrossRefs() throws Exception {
+ Change c1 = newChange();
+ ChangeUpdate update1 = newUpdate(c1, changeOwner);
+ update1.putApproval("Verified", (short) 1);
+
+ Change c2 = newChange();
+ ChangeUpdate update2 = newUpdate(c2, otherUser);
+ update2.putApproval("Code-Review", (short) 2);
+
+ BatchMetaDataUpdate batch1 = null;
+ BatchMetaDataUpdate batch2 = null;
+
+ BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ try {
+ batch1 = update1.openUpdateInBatch(bru);
+ batch1.write(update1, new CommitBuilder());
+ batch1.commit();
+ assertNull(repo.getRef(update1.getRefName()));
+
+ batch2 = update2.openUpdateInBatch(bru);
+ batch2.write(update2, new CommitBuilder());
+ batch2.commit();
+ assertNull(repo.getRef(update2.getRefName()));
+ } finally {
+ if (batch1 != null) {
+ batch1.close();
+ }
+ if (batch2 != null) {
+ batch2.close();
+ }
+ }
+
+ List<ReceiveCommand> cmds = bru.getCommands();
+ assertEquals(2, cmds.size());
+ assertEquals(update1.getRefName(), cmds.get(0).getRefName());
+ assertEquals(update2.getRefName(), cmds.get(1).getRefName());
+
+ RevWalk rw = new RevWalk(repo);
+ try {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ } finally {
+ rw.release();
+ }
+
+ assertEquals(ReceiveCommand.Result.OK, cmds.get(0).getResult());
+ assertEquals(ReceiveCommand.Result.OK, cmds.get(1).getResult());
+
+ assertNotNull(repo.getRef(update1.getRefName()));
+ assertNotNull(repo.getRef(update2.getRefName()));
+ }
+
+ @Test
public void changeMessageOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -666,6 +509,64 @@
}
@Test
+ public void noChangeMessage() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+ notes.getChangeMessages();
+ assertEquals(0, changeMessages.keySet().size());
+ }
+
+ @Test
+ public void changeMessageWithTrailingDoubleNewline() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Testing trailing double newline\n"
+ + "\n");
+ update.commit();
+ PatchSet.Id ps1 = c.currentPatchSetId();
+
+ ChangeNotes notes = newNotes(c);
+ ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+ notes.getChangeMessages();
+ assertEquals(1, changeMessages.keySet().size());
+
+ ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+ assertEquals("Testing trailing double newline\n" + "\n", cm1.getMessage());
+ assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
+ }
+
+ @Test
+ public void changeMessageWithMultipleParagraphs() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Testing paragraph 1\n"
+ + "\n"
+ + "Testing paragraph 2\n"
+ + "\n"
+ + "Testing paragraph 3");
+ update.commit();
+ PatchSet.Id ps1 = c.currentPatchSetId();
+
+ ChangeNotes notes = newNotes(c);
+ ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+ notes.getChangeMessages();
+ assertEquals(1, changeMessages.keySet().size());
+
+ ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+ assertEquals("Testing paragraph 1\n"
+ + "\n"
+ + "Testing paragraph 2\n"
+ + "\n"
+ + "Testing paragraph 3", cm1.getMessage());
+ assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
+ }
+
+ @Test
public void changeMessagesMultiplePatchSets() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -701,112 +602,6 @@
}
@Test
- public void noChangeMessage() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Reviewer: Change Owner <1@gerrit>\n",
- commit.getFullMessage());
- } finally {
- walk.release();
- }
-
- ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
- notes.getChangeMessages();
- assertEquals(0, changeMessages.keySet().size());
- }
-
- @Test
- public void changeMessageWithTrailingDoubleNewline() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setChangeMessage("Testing trailing double newline\n"
- + "\n");
- update.commit();
- PatchSet.Id ps1 = c.currentPatchSetId();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Testing trailing double newline\n"
- + "\n"
- + "\n"
- + "\n"
- + "Patch-set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.release();
- }
-
- ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
- notes.getChangeMessages();
- assertEquals(1, changeMessages.keySet().size());
-
- ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
- assertEquals("Testing trailing double newline\n" + "\n", cm1.getMessage());
- assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
-
- }
-
- @Test
- public void changeMessageWithMultipleParagraphs() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setChangeMessage("Testing paragraph 1\n"
- + "\n"
- + "Testing paragraph 2\n"
- + "\n"
- + "Testing paragraph 3");
- update.commit();
- PatchSet.Id ps1 = c.currentPatchSetId();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Testing paragraph 1\n"
- + "\n"
- + "Testing paragraph 2\n"
- + "\n"
- + "Testing paragraph 3\n"
- + "\n"
- + "Patch-set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.release();
- }
-
- ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
- notes.getChangeMessages();
- assertEquals(1, changeMessages.keySet().size());
-
- ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
- assertEquals("Testing paragraph 1\n"
- + "\n"
- + "Testing paragraph 2\n"
- + "\n"
- + "Testing paragraph 3", cm1.getMessage());
- assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
- }
-
- @Test
public void changeMessageMultipleInOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -844,7 +639,9 @@
public void patchLineCommentNotesFormatSide1() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
+ String uuid3 = "uuid3";
String message1 = "comment 1";
String message2 = "comment 2";
String message3 = "comment 3";
@@ -855,28 +652,28 @@
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
- uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
+ uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
- uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
+ uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range3 = new CommentRange(3, 1, 4, 1);
PatchLineComment comment3 = newPublishedPatchLineComment(psId, "file2",
- uuid, range3, range3.getEndLine(), otherUser, null, time3, message3,
+ uuid3, range3, range3.getEndLine(), otherUser, null, time3, message3,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment3);
+ update.upsertComment(comment3);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -897,14 +694,14 @@
+ "1:1-2:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
+ "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
+ + "UUID: uuid1\n"
+ "Bytes: 9\n"
+ "comment 1\n"
+ "\n"
+ "2:1-3:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
+ "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
+ + "UUID: uuid2\n"
+ "Bytes: 9\n"
+ "comment 2\n"
+ "\n"
@@ -913,7 +710,7 @@
+ "3:1-4:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time3) + "\n"
+ "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
+ + "UUID: uuid3\n"
+ "Bytes: 9\n"
+ "comment 3\n"
+ "\n",
@@ -924,7 +721,8 @@
public void patchLineCommentNotesFormatSide0() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
String message1 = "comment 1";
String message2 = "comment 2";
CommentRange range1 = new CommentRange(1, 1, 2, 1);
@@ -933,19 +731,19 @@
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
- uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
+ uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
- uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
+ uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -966,14 +764,14 @@
+ "1:1-2:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
+ "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
+ + "UUID: uuid1\n"
+ "Bytes: 9\n"
+ "comment 1\n"
+ "\n"
+ "2:1-3:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
+ "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
+ + "UUID: uuid2\n"
+ "Bytes: 9\n"
+ "comment 2\n"
+ "\n",
@@ -986,7 +784,8 @@
throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
String messageForBase = "comment for base";
String messageForPS = "comment for ps";
CommentRange range = new CommentRange(1, 1, 2, 1);
@@ -994,20 +793,20 @@
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment commentForBase =
- newPublishedPatchLineComment(psId, "filename", uuid,
+ newPublishedPatchLineComment(psId, "filename", uuid1,
range, range.getEndLine(), otherUser, null, now, messageForBase,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(commentForBase);
+ update.upsertComment(commentForBase);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment commentForPS =
- newPublishedPatchLineComment(psId, "filename", uuid,
+ newPublishedPatchLineComment(psId, "filename", uuid2,
range, range.getEndLine(), otherUser, null, now, messageForPS,
(short) 1, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(psId);
- update.putComment(commentForPS);
+ update.upsertComment(commentForPS);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1027,7 +826,8 @@
@Test
public void patchLineCommentMultipleOnePatchsetOneFile() throws Exception {
Change c = newChange();
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename = "filename";
@@ -1037,18 +837,18 @@
Timestamp timeForComment1 = TimeUtil.nowTs();
Timestamp timeForComment2 = TimeUtil.nowTs();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, filename,
- uuid, range, range.getEndLine(), otherUser, null, timeForComment1,
+ uuid1, range, range.getEndLine(), otherUser, null, timeForComment1,
"comment 1", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, filename,
- uuid, range, range.getEndLine(), otherUser, null, timeForComment2,
+ uuid2, range, range.getEndLine(), otherUser, null, timeForComment2,
"comment 2", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1086,7 +886,7 @@
uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -1094,7 +894,7 @@
uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1130,7 +930,7 @@
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(ps1);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
incrementPatchSet(c);
@@ -1142,7 +942,7 @@
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
side, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(ps2);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1165,68 +965,185 @@
assertEquals(comment2, commentFromPs2);
}
- private Change newChange() {
- return TestChanges.newChange(project, changeOwner);
+ @Test
+ public void patchLineCommentSingleDraftToPublished() throws Exception {
+ Change c = newChange();
+ String uuid = "uuid";
+ CommentRange range = new CommentRange(1, 1, 2, 1);
+ PatchSet.Id ps1 = c.currentPatchSetId();
+ String filename = "filename1";
+ short side = (short) 1;
+
+ ChangeUpdate update = newUpdate(c, otherUser);
+ Timestamp now = TimeUtil.nowTs();
+ PatchLineComment comment1 = newPatchLineComment(ps1, filename, uuid,
+ range, range.getEndLine(), otherUser, null, now, "comment on ps1", side,
+ "abcd4567abcd4567abcd4567abcd4567abcd4567", Status.DRAFT);
+ update.setPatchSetId(ps1);
+ update.insertComment(comment1);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ assertEquals(1, notes.getDraftPsComments(otherUserId).values().size());
+ assertEquals(0, notes.getDraftBaseComments(otherUserId).values().size());
+
+ comment1.setStatus(Status.PUBLISHED);
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(ps1);
+ update.updateComment(comment1);
+ update.commit();
+
+ notes = newNotes(c);
+
+ assertTrue(notes.getDraftPsComments(otherUserId).values().isEmpty());
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
+
+ assertTrue(notes.getBaseComments().values().isEmpty());
+ PatchLineComment commentFromNotes =
+ Iterables.getOnlyElement(notes.getPatchSetComments().values());
+ assertEquals(comment1, commentFromNotes);
}
- private PatchLineComment newPublishedPatchLineComment(PatchSet.Id psId,
- String filename, String UUID, CommentRange range, int line,
- IdentifiedUser commenter, String parentUUID, Timestamp t,
- String message, short side, String commitSHA1) {
- return newPatchLineComment(psId, filename, UUID, range, line, commenter,
- parentUUID, t, message, side, commitSHA1, Status.PUBLISHED);
+ @Test
+ public void patchLineCommentMultipleDraftsSameSidePublishOne()
+ throws OrmException, IOException {
+ Change c = newChange();
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
+ CommentRange range1 = new CommentRange(1, 1, 2, 2);
+ CommentRange range2 = new CommentRange(2, 2, 3, 3);
+ String filename = "filename1";
+ short side = (short) 1;
+ Timestamp now = TimeUtil.nowTs();
+ String commitSHA1 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ PatchSet.Id psId = c.currentPatchSetId();
+
+ // Write two drafts on the same side of one patch set.
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+ PatchLineComment comment1 = newPatchLineComment(psId, filename, uuid1,
+ range1, range1.getEndLine(), otherUser, null, now, "comment on ps1",
+ side, commitSHA1, Status.DRAFT);
+ PatchLineComment comment2 = newPatchLineComment(psId, filename, uuid2,
+ range2, range2.getEndLine(), otherUser, null, now, "other on ps1",
+ side, commitSHA1, Status.DRAFT);
+ update.insertComment(comment1);
+ update.insertComment(comment2);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
+ assertEquals(2, notes.getDraftPsComments(otherUserId).values().size());
+
+ assertTrue(notes.getDraftPsComments(otherUserId).containsValue(comment1));
+ assertTrue(notes.getDraftPsComments(otherUserId).containsValue(comment2));
+
+ // Publish first draft.
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+ comment1.setStatus(Status.PUBLISHED);
+ update.updateComment(comment1);
+ update.commit();
+
+ notes = newNotes(c);
+ assertEquals(comment1,
+ Iterables.getOnlyElement(notes.getPatchSetComments().get(psId)));
+ assertEquals(comment2,
+ Iterables.getOnlyElement(
+ notes.getDraftPsComments(otherUserId).values()));
+
+ assertTrue(notes.getBaseComments().values().isEmpty());
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
}
- private PatchLineComment newPatchLineComment(PatchSet.Id psId,
- String filename, String UUID, CommentRange range, int line,
- IdentifiedUser commenter, String parentUUID, Timestamp t,
- String message, short side, String commitSHA1, Status status) {
- PatchLineComment comment = new PatchLineComment(
- new PatchLineComment.Key(
- new Patch.Key(psId, filename), UUID),
- line, commenter.getAccountId(), parentUUID, t);
- comment.setSide(side);
- comment.setMessage(message);
- comment.setRange(range);
- comment.setRevId(new RevId(commitSHA1));
- comment.setStatus(status);
- return comment;
+ @Test
+ public void patchLineCommentsMultipleDraftsBothSidesPublishAll()
+ throws OrmException, IOException {
+ Change c = newChange();
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
+ CommentRange range1 = new CommentRange(1, 1, 2, 2);
+ CommentRange range2 = new CommentRange(2, 2, 3, 3);
+ String filename = "filename1";
+ Timestamp now = TimeUtil.nowTs();
+ String commitSHA1 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ String baseSHA1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ PatchSet.Id psId = c.currentPatchSetId();
+
+ // Write two drafts, one on each side of the patchset.
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+ PatchLineComment baseComment = newPatchLineComment(psId, filename, uuid1,
+ range1, range1.getEndLine(), otherUser, null, now, "comment on base",
+ (short) 0, baseSHA1, Status.DRAFT);
+ PatchLineComment psComment = newPatchLineComment(psId, filename, uuid2,
+ range2, range2.getEndLine(), otherUser, null, now, "comment on ps",
+ (short) 1, commitSHA1, Status.DRAFT);
+
+ update.insertComment(baseComment);
+ update.insertComment(psComment);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ PatchLineComment baseDraftCommentFromNotes =
+ Iterables.getOnlyElement(
+ notes.getDraftBaseComments(otherUserId).values());
+ PatchLineComment psDraftCommentFromNotes =
+ Iterables.getOnlyElement(
+ notes.getDraftPsComments(otherUserId).values());
+
+ assertEquals(baseComment, baseDraftCommentFromNotes);
+ assertEquals(psComment, psDraftCommentFromNotes);
+
+ // Publish both comments.
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+
+ baseComment.setStatus(Status.PUBLISHED);
+ psComment.setStatus(Status.PUBLISHED);
+ update.updateComment(baseComment);
+ update.updateComment(psComment);
+ update.commit();
+
+ notes = newNotes(c);
+
+ PatchLineComment baseCommentFromNotes =
+ Iterables.getOnlyElement(notes.getBaseComments().values());
+ PatchLineComment psCommentFromNotes =
+ Iterables.getOnlyElement(notes.getPatchSetComments().values());
+
+ assertEquals(baseComment, baseCommentFromNotes);
+ assertEquals(psComment, psCommentFromNotes);
+
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
+ assertTrue(notes.getDraftPsComments(otherUserId).values().isEmpty());
}
- private ChangeUpdate newUpdate(Change c, IdentifiedUser user)
- throws Exception {
- return TestChanges.newUpdate(injector, repoManager, c, user);
- }
+ @Test
+ public void patchLineCommentNoRange() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+ String uuid = "uuid";
+ String messageForBase = "comment for base";
+ Timestamp now = TimeUtil.nowTs();
+ PatchSet.Id psId = c.currentPatchSetId();
- private ChangeNotes newNotes(Change c) throws OrmException {
- return new ChangeNotes(repoManager, c).load();
- }
+ PatchLineComment commentForBase =
+ newPublishedPatchLineComment(psId, "filename", uuid,
+ null, 1, otherUser, null, now, messageForBase,
+ (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+ update.setPatchSetId(psId);
+ update.upsertComment(commentForBase);
+ update.commit();
- private static Timestamp truncate(Timestamp ts) {
- return new Timestamp((ts.getTime() / 1000) * 1000);
- }
+ ChangeNotes notes = newNotes(c);
+ Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
+ notes.getBaseComments();
+ Multimap<PatchSet.Id, PatchLineComment> commentsForPs =
+ notes.getPatchSetComments();
- private static Timestamp after(Change c, long millis) {
- return new Timestamp(c.getCreatedOn().getTime() + millis);
- }
-
- private static SubmitRecord submitRecord(String status,
- String errorMessage, SubmitRecord.Label... labels) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.valueOf(status);
- rec.errorMessage = errorMessage;
- if (labels.length > 0) {
- rec.labels = ImmutableList.copyOf(labels);
- }
- return rec;
- }
-
- private static SubmitRecord.Label submitLabel(String name, String status,
- Account.Id appliedBy) {
- SubmitRecord.Label label = new SubmitRecord.Label();
- label.label = name;
- label.status = SubmitRecord.Label.Status.valueOf(status);
- label.appliedBy = appliedBy;
- return label;
+ assertTrue(commentsForPs.isEmpty());
+ assertEquals(commentForBase,
+ Iterables.getOnlyElement(commentsForBase.get(psId)));
}
}
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
new file mode 100644
index 0000000..900084f
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -0,0 +1,260 @@
+// Copyright (C) 2014 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.notedb;
+
+import static com.google.gerrit.server.notedb.ReviewerState.CC;
+import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.testutil.TestChanges;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Test;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+public class CommitMessageOutputTest extends AbstractChangeNotesTest {
+ @Test
+ public void approvalsCommitFormatSimple() throws Exception {
+ Change c = TestChanges.newChange(project, changeOwner, 1);
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.putApproval("Verified", (short) 1);
+ update.putApproval("Code-Review", (short) -1);
+ update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(otherUser.getAccount().getId(), CC);
+ update.commit();
+ assertEquals("refs/changes/01/1/meta", update.getRefName());
+
+ RevCommit commit = parseCommit(update.getRevision());
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Reviewer: Change Owner <1@gerrit>\n"
+ + "CC: Other Account <2@gerrit>\n"
+ + "Label: Code-Review=-1\n"
+ + "Label: Verified=+1\n",
+ commit);
+
+ PersonIdent author = commit.getAuthorIdent();
+ assertEquals("Change Owner", author.getName());
+ assertEquals("1@gerrit", author.getEmailAddress());
+ assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
+ author.getWhen());
+ assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
+
+ PersonIdent committer = commit.getCommitterIdent();
+ assertEquals("Gerrit Server", committer.getName());
+ assertEquals("noreply@gerrit.com", committer.getEmailAddress());
+ assertEquals(author.getWhen(), committer.getWhen());
+ assertEquals(author.getTimeZone(), committer.getTimeZone());
+ }
+
+ @Test
+ public void changeMessageCommitFormatSimple() throws Exception {
+ Change c = TestChanges.newChange(project, changeOwner, 1);
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Just a little code change.\n"
+ + "How about a new line");
+ update.commit();
+ assertEquals("refs/changes/01/1/meta", update.getRefName());
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Just a little code change.\n"
+ + "How about a new line\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void approvalTombstoneCommitFormat() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.removeApproval("Code-Review");
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Label: -Code-Review\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void submitCommitFormat() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setSubject("Submit patch set 1");
+
+ update.submit(ImmutableList.of(
+ submitRecord("NOT_READY", null,
+ submitLabel("Verified", "OK", changeOwner.getAccountId()),
+ submitLabel("Code-Review", "NEED", null)),
+ submitRecord("NOT_READY", null,
+ submitLabel("Verified", "OK", changeOwner.getAccountId()),
+ submitLabel("Alternative-Code-Review", "NEED", null))));
+ update.commit();
+
+ RevCommit commit = parseCommit(update.getRevision());
+ assertBodyEquals("Submit patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Status: submitted\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Code-Review\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Alternative-Code-Review\n",
+ commit);
+
+ PersonIdent author = commit.getAuthorIdent();
+ assertEquals("Change Owner", author.getName());
+ assertEquals("1@gerrit", author.getEmailAddress());
+ assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
+ author.getWhen());
+ assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
+
+ PersonIdent committer = commit.getCommitterIdent();
+ assertEquals("Gerrit Server", committer.getName());
+ assertEquals("noreply@gerrit.com", committer.getEmailAddress());
+ assertEquals(author.getWhen(), committer.getWhen());
+ assertEquals(author.getTimeZone(), committer.getTimeZone());
+ }
+
+ @Test
+ public void anonymousUser() throws Exception {
+ Account anon = new Account(new Account.Id(3), TimeUtil.nowTs());
+ accountCache.put(anon);
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, userFactory.create(anon.getId()));
+ update.setChangeMessage("Comment on the change.");
+ update.commit();
+
+ RevCommit commit = parseCommit(update.getRevision());
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Comment on the change.\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ commit);
+
+ PersonIdent author = commit.getAuthorIdent();
+ assertEquals("Anonymous Coward (3)", author.getName());
+ assertEquals("3@gerrit", author.getEmailAddress());
+ }
+
+ @Test
+ public void submitWithErrorMessage() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setSubject("Submit patch set 1");
+
+ update.submit(ImmutableList.of(
+ submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
+ update.commit();
+
+ assertBodyEquals("Submit patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Status: submitted\n"
+ + "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void noChangeMessage() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Reviewer: Change Owner <1@gerrit>\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void changeMessageWithTrailingDoubleNewline() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Testing trailing double newline\n"
+ + "\n");
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Testing trailing double newline\n"
+ + "\n"
+ + "\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void changeMessageWithMultipleParagraphs() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Testing paragraph 1\n"
+ + "\n"
+ + "Testing paragraph 2\n"
+ + "\n"
+ + "Testing paragraph 3");
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Testing paragraph 1\n"
+ + "\n"
+ + "Testing paragraph 2\n"
+ + "\n"
+ + "Testing paragraph 3\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ update.getRevision());
+ }
+
+ private RevCommit parseCommit(ObjectId id) throws Exception {
+ if (id instanceof RevCommit) {
+ return (RevCommit) id;
+ }
+ RevWalk walk = new RevWalk(repo);
+ try {
+ RevCommit commit = walk.parseCommit(id);
+ walk.parseBody(commit);
+ return commit;
+ } finally {
+ walk.release();
+ }
+ }
+
+ private void assertBodyEquals(String expected, ObjectId commitId)
+ throws Exception {
+ RevCommit commit = parseCommit(commitId);
+ assertEquals(expected, commit.getFullMessage());
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
index af60ea8..bff557c 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
@@ -14,14 +14,15 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.reviewdb.client.Patch;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import com.google.gerrit.reviewdb.client.Patch;
+
+import org.junit.Test;
+
public class PatchListEntryTest {
@Test
public void testEmpty1() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
new file mode 100644
index 0000000..c3fbccd
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
@@ -0,0 +1,176 @@
+// Copyright (C) 2014 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;
+
+import static com.google.gerrit.common.data.Permission.READ;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.Util.allow;
+import static com.google.gerrit.server.project.Util.deny;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.schema.SchemaCreator;
+import com.google.gerrit.testutil.InMemoryDatabase;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Unit tests for {@link ProjectControl}. */
+public class ProjectControlTest {
+ @Inject private AccountManager accountManager;
+ @Inject private IdentifiedUser.RequestFactory userFactory;
+ @Inject private InMemoryDatabase schemaFactory;
+ @Inject private InMemoryRepositoryManager repoManager;
+ @Inject private ProjectControl.GenericFactory projectControlFactory;
+ @Inject private SchemaCreator schemaCreator;
+
+ private LifecycleManager lifecycle;
+ private ReviewDb db;
+ private TestRepository<InMemoryRepository> repo;
+ private ProjectConfig project;
+ private IdentifiedUser user;
+
+ @Before
+ public void setUp() throws Exception {
+ Injector injector = Guice.createInjector(new InMemoryModule());
+ injector.injectMembers(this);
+ lifecycle = new LifecycleManager();
+ lifecycle.add(injector);
+ lifecycle.start();
+
+ db = schemaFactory.open();
+ schemaCreator.create(db);
+ Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user"))
+ .getAccountId();
+ user = userFactory.create(userId);
+
+ Project.NameKey name = new Project.NameKey("project");
+ InMemoryRepository inMemoryRepo = repoManager.createRepository(name);
+ project = new ProjectConfig(name);
+ project.load(inMemoryRepo);
+ repo = new TestRepository<InMemoryRepository>(inMemoryRepo);
+ }
+
+ @After
+ public void tearDown() {
+ if (repo != null) {
+ repo.getRepository().close();
+ }
+ if (lifecycle != null) {
+ lifecycle.stop();
+ }
+ if (db != null) {
+ db.close();
+ }
+ InMemoryDatabase.drop(schemaFactory);
+ }
+
+ @Test
+ public void canReadCommitWhenAllRefsVisible() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/*");
+ ObjectId id = repo.branch("master").commit().create();
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id)));
+ }
+
+ @Test
+ public void canReadCommitIfRefVisible() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+ deny(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+
+ ObjectId id1 = repo.branch("branch1").commit().create();
+ ObjectId id2 = repo.branch("branch2").commit().create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(id2)));
+ }
+
+ @Test
+ public void canReadCommitIfReachableFromVisibleRef() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+ deny(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+
+ RevCommit parent1 = repo.commit().create();
+ repo.branch("branch1").commit().parent(parent1).create();
+
+ RevCommit parent2 = repo.commit().create();
+ repo.branch("branch2").commit().parent(parent2).create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(parent2)));
+ }
+
+ @Test
+ public void cannotReadAfterRollbackWithRestrictedRead() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+
+ RevCommit parent1 = repo.commit().create();
+ ObjectId id1 = repo.branch("branch1").commit().parent(parent1).create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+
+ repo.branch("branch1").update(parent1);
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+ }
+
+ @Test
+ public void canReadAfterRollbackWithAllRefsVisible() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/*");
+
+ RevCommit parent1 = repo.commit().create();
+ ObjectId id1 = repo.branch("branch1").commit().parent(parent1).create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+
+ repo.branch("branch1").update(parent1);
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+ }
+
+ private ProjectControl newProjectControl() throws Exception {
+ return projectControlFactory.controlFor(project.getName(), user);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 7963ce4..664ab4e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -55,6 +55,8 @@
private final AccountGroup.UUID fixers = new AccountGroup.UUID("test.fixers");
private Project.NameKey localKey = new Project.NameKey("local");
private ProjectConfig local;
+ private Project.NameKey parentKey = new Project.NameKey("parent");
+ private ProjectConfig parent;
private final Util util;
public RefControlTest() {
@@ -63,9 +65,14 @@
@Before
public void setUp() throws Exception {
+ parent = new ProjectConfig(parentKey);
+ parent.load(newRepository(parentKey));
+ util.add(parent);
+
local = new ProjectConfig(localKey);
local.load(newRepository(localKey));
util.add(local);
+ local.getProject().setParentName(parentKey);
}
@Test
@@ -127,8 +134,8 @@
@Test
public void testInheritRead_SingleBranchDeniesUpload() {
- allow(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
- allow(util.getParentConfig(), PUSH, REGISTERED_USERS, "refs/for/refs/*");
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
allow(local, READ, REGISTERED_USERS, "refs/heads/foobar");
doNotInherit(local, READ, "refs/heads/foobar");
doNotInherit(local, PUSH, "refs/for/refs/heads/foobar");
@@ -145,8 +152,8 @@
@Test
public void testBlockPushDrafts() {
- allow(util.getParentConfig(), PUSH, REGISTERED_USERS, "refs/for/refs/*");
- block(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/drafts/*");
+ allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
ProjectControl u = util.user(local);
assertTrue("can upload refs/heads/master",
@@ -157,8 +164,8 @@
@Test
public void testBlockPushDraftsUnblockAdmin() {
- block(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/drafts/*");
- allow(util.getParentConfig(), PUSH, ADMIN, "refs/drafts/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
+ allow(parent, PUSH, ADMIN, "refs/drafts/*");
assertTrue("push is blocked for anonymous to refs/drafts/master",
util.user(local).controlForRef("refs/drafts/refs/heads/master")
@@ -170,8 +177,8 @@
@Test
public void testInheritRead_SingleBranchDoesNotOverrideInherited() {
- allow(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
- allow(util.getParentConfig(), PUSH, REGISTERED_USERS, "refs/for/refs/*");
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
allow(local, READ, REGISTERED_USERS, "refs/heads/foobar");
ProjectControl u = util.user(local);
@@ -186,20 +193,20 @@
@Test
public void testInheritDuplicateSections() throws Exception {
- allow(util.getParentConfig(), READ, ADMIN, "refs/*");
+ allow(parent, READ, ADMIN, "refs/*");
allow(local, READ, DEVS, "refs/heads/*");
- local.getProject().setParentName(util.getParentConfig().getProject().getName());
assertTrue("a can read", util.user(local, "a", ADMIN).isVisible());
- local = new ProjectConfig(new Project.NameKey("local"));
+ local = new ProjectConfig(localKey);
local.load(newRepository(localKey));
+ local.getProject().setParentName(parentKey);
allow(local, READ, DEVS, "refs/*");
assertTrue("d can read", util.user(local, "d", DEVS).isVisible());
}
@Test
public void testInheritRead_OverrideWithDeny() {
- allow(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
deny(local, READ, REGISTERED_USERS, "refs/*");
ProjectControl u = util.user(local);
@@ -208,7 +215,7 @@
@Test
public void testInheritRead_AppendWithDenyOfRef() {
- allow(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
deny(local, READ, REGISTERED_USERS, "refs/heads/*");
ProjectControl u = util.user(local);
@@ -220,7 +227,7 @@
@Test
public void testInheritRead_OverridesAndDeniesOfRef() {
- allow(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
deny(local, READ, REGISTERED_USERS, "refs/*");
allow(local, READ, REGISTERED_USERS, "refs/heads/*");
@@ -233,7 +240,7 @@
@Test
public void testInheritSubmit_OverridesAndDeniesOfRef() {
- allow(util.getParentConfig(), SUBMIT, REGISTERED_USERS, "refs/*");
+ allow(parent, SUBMIT, REGISTERED_USERS, "refs/*");
deny(local, SUBMIT, REGISTERED_USERS, "refs/*");
allow(local, SUBMIT, REGISTERED_USERS, "refs/heads/*");
@@ -245,7 +252,7 @@
@Test
public void testCannotUploadToAnyRef() {
- allow(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
allow(local, READ, DEVS, "refs/heads/*");
allow(local, PUSH, DEVS, "refs/for/refs/heads/*");
@@ -288,7 +295,7 @@
@Test
public void testSortWithRegex() {
allow(local, READ, DEVS, "^refs/heads/.*");
- allow(util.getParentConfig(), READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");
+ allow(parent, READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");
ProjectControl u = util.user(local, DEVS), d = util.user(local, DEVS);
assertTrue("u can read", u.controlForRef("refs/heads/foo-QA-bar").isVisible());
@@ -298,7 +305,7 @@
@Test
public void testBlockRule_ParentBlocksChild() {
allow(local, PUSH, DEVS, "refs/tags/*");
- block(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/tags/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/tags/*");
ProjectControl u = util.user(local, DEVS);
assertFalse("u can't update tag", u.controlForRef("refs/tags/V10").canUpdate());
}
@@ -307,7 +314,7 @@
public void testBlockRule_ParentBlocksChildEvenIfAlreadyBlockedInChild() {
allow(local, PUSH, DEVS, "refs/tags/*");
block(local, PUSH, ANONYMOUS_USERS, "refs/tags/*");
- block(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/tags/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/tags/*");
ProjectControl u = util.user(local, DEVS);
assertFalse("u can't update tag", u.controlForRef("refs/tags/V10").canUpdate());
@@ -316,7 +323,7 @@
@Test
public void testBlockLabelRange_ParentBlocksChild() {
allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
- block(util.getParentConfig(), LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+ block(parent, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
ProjectControl u = util.user(local, DEVS);
@@ -331,7 +338,7 @@
public void testBlockLabelRange_ParentBlocksChildEvenIfAlreadyBlockedInChild() {
allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
block(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
- block(util.getParentConfig(), LABEL + "Code-Review", -2, +2, DEVS,
+ block(parent, LABEL + "Code-Review", -2, +2, DEVS,
"refs/heads/*");
ProjectControl u = util.user(local, DEVS);
@@ -393,7 +400,7 @@
@Test
public void testUnblockInLocal_Fails() {
- block(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
allow(local, PUSH, fixers, "refs/heads/*");
ProjectControl f = util.user(local, fixers);
@@ -402,8 +409,8 @@
@Test
public void testUnblockInParentBlockInLocal() {
- block(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(util.getParentConfig(), PUSH, DEVS, "refs/heads/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(parent, PUSH, DEVS, "refs/heads/*");
block(local, PUSH, DEVS, "refs/heads/*");
ProjectControl d = util.user(local, DEVS);
@@ -411,7 +418,7 @@
}
@Test
- public void testUnblockVisibilityByREGISTEREDUsers() {
+ public void testUnblockVisibilityByRegisteredUsers() {
block(local, READ, ANONYMOUS_USERS, "refs/heads/*");
allow(local, READ, REGISTERED_USERS, "refs/heads/*");
@@ -421,7 +428,7 @@
@Test
public void testUnblockInLocalVisibilityByRegisteredUsers_Fails() {
- block(util.getParentConfig(), READ, ANONYMOUS_USERS, "refs/heads/*");
+ block(parent, READ, ANONYMOUS_USERS, "refs/heads/*");
allow(local, READ, REGISTERED_USERS, "refs/heads/*");
ProjectControl u = util.user(local, REGISTERED_USERS);
@@ -440,7 +447,7 @@
@Test
public void testUnblockInLocalForceEditTopicName_Fails() {
- block(util.getParentConfig(), EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*");
+ block(parent, EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*");
allow(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
ProjectControl u = util.user(local, REGISTERED_USERS);
@@ -483,7 +490,7 @@
@Test
public void testUnblockInLocalRange_Fails() {
- block(util.getParentConfig(), LABEL + "Code-Review", -1, 1, ANONYMOUS_USERS,
+ block(parent, LABEL + "Code-Review", -1, 1, ANONYMOUS_USERS,
"refs/heads/*");
allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
index c0f35ba..ba292d6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -61,6 +61,7 @@
import com.google.inject.util.Providers;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -178,19 +179,21 @@
private final CapabilityControl.Factory capabilityControlFactory;
private final ChangeControl.AssistedFactory changeControlFactory;
private final PermissionCollection.Factory sectionSorter;
- private final GitRepositoryManager repoManager;
+ private final InMemoryRepositoryManager repoManager;
- private final AllProjectsName allProjectsName = new AllProjectsName("parent");
- private final ProjectConfig parent = new ProjectConfig(allProjectsName);
+ private final AllProjectsName allProjectsName =
+ new AllProjectsName("All-Projects");
+ private final ProjectConfig allProjects;
public Util() {
all = new HashMap<>();
repoManager = new InMemoryRepositoryManager();
try {
Repository repo = repoManager.createRepository(allProjectsName);
- parent.load(repo);
- parent.getLabelSections().put(CR.getName(), CR);
- add(parent);
+ allProjects = new ProjectConfig(new Project.NameKey(allProjectsName.get()));
+ allProjects.load(repo);
+ allProjects.getLabelSections().put(CR.getName(), CR);
+ add(allProjects);
} catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e);
}
@@ -281,25 +284,26 @@
injector.getInstance(ChangeControl.AssistedFactory.class);
}
- public ProjectConfig getParentConfig() {
- return this.parent;
- }
-
- public void add(ProjectConfig pc) {
+ public InMemoryRepository add(ProjectConfig pc) {
PrologEnvironment.Factory envFactory = null;
ProjectControl.AssistedFactory projectControlFactory = null;
RulesCache rulesCache = null;
SitePaths sitePaths = null;
List<CommentLinkInfo> commentLinks = null;
+ InMemoryRepository repo;
try {
- repoManager.createRepository(pc.getProject().getNameKey());
- } catch (IOException e) {
+ repo = repoManager.createRepository(pc.getName());
+ if (pc.getProject() == null) {
+ pc.load(repo);
+ }
+ } catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e);
}
- all.put(pc.getProject().getNameKey(), new ProjectState(sitePaths,
+ all.put(pc.getName(), new ProjectState(sitePaths,
projectCache, allProjectsName, projectControlFactory, envFactory,
repoManager, rulesCache, commentLinks, pc));
+ return repo;
}
public ProjectControl user(ProjectConfig local, AccountGroup.UUID... memberOf) {
@@ -312,8 +316,8 @@
return new ProjectControl(Collections.<AccountGroup.UUID> emptySet(),
Collections.<AccountGroup.UUID> emptySet(), projectCache,
- sectionSorter, repoManager, changeControlFactory, canonicalWebUrl,
- new MockUser(name, memberOf), newProjectState(local));
+ sectionSorter, repoManager, changeControlFactory, null, null,
+ canonicalWebUrl, new MockUser(name, memberOf), newProjectState(local));
}
private ProjectState newProjectState(ProjectConfig local) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
index 0eca069..e349273 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
@@ -13,11 +13,11 @@
// limitations under the License.
package com.google.gerrit.server.query;
+import static org.junit.Assert.assertEquals;
+
import org.antlr.runtime.tree.Tree;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-
public class QueryParserTest {
@Test
public void testProjectBare() throws QueryParseException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index d8b6048..868a0e3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.schema;
+import static org.junit.Assert.assertEquals;
+
import com.google.gerrit.reviewdb.client.SystemConfig;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@@ -47,8 +49,6 @@
import java.util.List;
import java.util.UUID;
-import static org.junit.Assert.assertEquals;
-
public class SchemaUpdaterTest {
private InMemoryDatabase db;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
index a2228d8..3be4f8a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.util;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import java.util.HashSet;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
public class IdGeneratorTest {
@Test
public void test1234() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java
new file mode 100644
index 0000000..8f73005
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2014 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class RegexListSearcherTest {
+ private static final List<String> EMPTY = ImmutableList.of();
+
+ @Test
+ public void emptyList() {
+ assertSearchReturns(EMPTY, "pat", EMPTY);
+ }
+
+ @Test
+ public void hasMatch() {
+ List<String> list = ImmutableList.of("bar", "foo", "quux");
+ assertTrue(RegexListSearcher.ofStrings("foo").hasMatch(list));
+ assertFalse(RegexListSearcher.ofStrings("xyz").hasMatch(list));
+ }
+
+ @Test
+ public void anchors() {
+ List<String> list = ImmutableList.of("foo");
+ assertSearchReturns(list, "^f.*", list);
+ assertSearchReturns(list, "^f.*o$", list);
+ assertSearchReturns(list, "f.*o$", list);
+ assertSearchReturns(list, "f.*o$", list);
+ assertSearchReturns(EMPTY, "^.*\\$", list);
+ }
+
+ @Test
+ public void noCommonPrefix() {
+ List<String> list = ImmutableList.of("bar", "foo", "quux");
+ assertSearchReturns(ImmutableList.of("foo"), "f.*", list);
+ assertSearchReturns(ImmutableList.of("foo"), ".*o.*", list);
+ assertSearchReturns(ImmutableList.of("bar", "foo", "quux"), ".*[aou].*",
+ list);
+ }
+
+ @Test
+ public void commonPrefix() {
+ List<String> list = ImmutableList.of(
+ "bar",
+ "baz",
+ "foo1",
+ "foo2",
+ "foo3",
+ "quux");
+ assertSearchReturns(ImmutableList.of("bar", "baz"), "b.*", list);
+ assertSearchReturns(ImmutableList.of("foo1", "foo2"), "foo[12]", list);
+ assertSearchReturns(ImmutableList.of("foo1", "foo2", "foo3"), "foo.*",
+ list);
+ assertSearchReturns(ImmutableList.of("quux"), "q.*", list);
+ }
+
+ private void assertSearchReturns(List<?> expected, String re,
+ List<String> inputs) {
+ assertTrue(Ordering.natural().isOrdered(inputs));
+ assertEquals(expected,
+ ImmutableList.copyOf(RegexListSearcher.ofStrings(re).search(inputs)));
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
index 328df75..0635464 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
@@ -80,6 +80,12 @@
}
@Override
+ public InMemoryRepository openMetadataRepository(Project.NameKey name)
+ throws RepositoryNotFoundException {
+ return openRepository(name);
+ }
+
+ @Override
public SortedSet<Project.NameKey> list() {
SortedSet<Project.NameKey> names = Sets.newTreeSet();
for (DfsRepository repo : repos.values()) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
index 1a0ccaf..9c1b8ed 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
@@ -23,8 +23,11 @@
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.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeDraftUpdate;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
@@ -34,13 +37,22 @@
import org.easymock.EasyMock;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Utility functions to create and manipulate Change, ChangeUpdate, and
* ChangeControl objects for testing.
*/
public class TestChanges {
+ private static final AtomicInteger nextChangeId = new AtomicInteger(1);
+
public static Change newChange(Project.NameKey project, IdentifiedUser user) {
- Change.Id changeId = new Change.Id(1);
+ return newChange(project, user, nextChangeId.getAndIncrement());
+ }
+
+ public static Change newChange(Project.NameKey project, IdentifiedUser user,
+ int id) {
+ Change.Id changeId = new Change.Id(id);
Change c = new Change(
new Change.Key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
changeId,
@@ -52,26 +64,29 @@
}
public static ChangeUpdate newUpdate(Injector injector,
- GitRepositoryManager repoManager, Change c, final IdentifiedUser user)
+ GitRepositoryManager repoManager, Change c,
+ final AllUsersNameProvider allUsers, final IdentifiedUser user)
throws OrmException {
return injector.createChildInjector(new FactoryModule() {
@Override
public void configure() {
factory(ChangeUpdate.Factory.class);
+ factory(ChangeDraftUpdate.Factory.class);
bind(IdentifiedUser.class).toInstance(user);
+ bind(AllUsersName.class).toProvider(allUsers);
}
}).getInstance(ChangeUpdate.Factory.class).create(
- stubChangeControl(repoManager, c, user), TimeUtil.nowTs(),
+ stubChangeControl(repoManager, c, allUsers, user), TimeUtil.nowTs(),
Ordering.<String> natural());
}
public static ChangeControl stubChangeControl(
- GitRepositoryManager repoManager, Change c, IdentifiedUser user)
- throws OrmException {
+ GitRepositoryManager repoManager, Change c, AllUsersNameProvider allUsers,
+ IdentifiedUser user) throws OrmException {
ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class);
expect(ctl.getChange()).andStubReturn(c);
expect(ctl.getCurrentUser()).andStubReturn(user);
- ChangeNotes notes = new ChangeNotes(repoManager, c);
+ ChangeNotes notes = new ChangeNotes(repoManager, allUsers, c);
notes = notes.load();
expect(ctl.getNotes()).andStubReturn(notes);
EasyMock.replay(ctl);
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
index 8c8b007..96ba032 100644
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
@@ -147,25 +147,6 @@
}
@Override
- public void insert(ChangeData cd) throws IOException {
- String id = cd.getId().toString();
- SolrInputDocument doc = toDocument(cd);
- try {
- if (cd.change().getStatus().isOpen()) {
- closedIndex.deleteById(id);
- openIndex.add(doc);
- } else {
- openIndex.deleteById(id);
- closedIndex.add(doc);
- }
- } catch (OrmException | SolrServerException e) {
- throw new IOException(e);
- }
- commit(openIndex);
- commit(closedIndex);
- }
-
- @Override
public void replace(ChangeData cd) throws IOException {
String id = cd.getId().toString();
SolrInputDocument doc = toDocument(cd);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
index 62efaa0..d4bb353 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
@@ -21,10 +21,10 @@
import com.google.gerrit.sshd.SshScope.Context;
import org.apache.commons.codec.binary.Base64;
-import org.apache.sshd.common.future.CloseFuture;
-import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Constants;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
index fea16cd..59892a3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
@@ -21,10 +21,10 @@
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.FieldSetter;
import org.kohsuke.args4j.spi.OneArgumentOptionHandler;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Setter;
-import org.kohsuke.args4j.spi.FieldSetter;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index a1099dc..c533f4f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -24,16 +24,13 @@
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +38,7 @@
@RequiresCapability(GlobalCapability.RUN_GC)
@CommandMetaData(name = "gc", description = "Run Git garbage collection",
runsAt = MASTER_OR_SLAVE)
-public class GarbageCollectionCommand extends BaseCommand {
+public class GarbageCollectionCommand extends SshCommand {
@Option(name = "--all", usage = "runs the Git garbage collection for all projects")
private boolean all;
@@ -59,23 +56,10 @@
@Inject
private GarbageCollection.Factory garbageCollectionFactory;
- private PrintWriter stdout;
-
@Override
- public void start(Environment env) throws IOException {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- stdout = toPrintWriter(out);
- try {
- parseCommandLine();
- verifyCommandLine();
- runGC();
- } finally {
- stdout.flush();
- }
- }
- });
+ public void run() throws Exception {
+ verifyCommandLine();
+ runGC();
}
private void verifyCommandLine() throws UnloggedFailure {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index f0169a8..e53edc6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -28,43 +28,36 @@
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gerrit.server.group.ListGroups;
import com.google.gerrit.server.ioutil.ColumnFormatter;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Option;
import java.io.PrintWriter;
@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller",
runsAt = MASTER_OR_SLAVE)
-public class ListGroupsCommand extends BaseCommand {
+public class ListGroupsCommand extends SshCommand {
@Inject
private MyListGroups impl;
@Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
- }
- final PrintWriter stdout = toPrintWriter(out);
- try {
- impl.display(stdout);
- } finally {
- stdout.flush();
- }
- }
- });
+ public void run() throws Exception {
+ if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
+ throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
+ }
+ impl.display(stdout);
}
- private static class MyListGroups extends ListGroups {
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
+ }
+
+ private static class MyListGroups extends ListGroups {
@Option(name = "--verbose", aliases = {"-v"},
usage = "verbose output format with tab-separated columns for the " +
"group name, UUID, description, owner group name, " +
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index f8cf8dd..4e5bfdf 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -26,11 +26,10 @@
import com.google.gerrit.server.account.GroupDetailFactory.Factory;
import com.google.gerrit.server.group.ListMembers;
import com.google.gerrit.server.ioutil.ColumnFormatter;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import java.io.PrintWriter;
@@ -43,24 +42,18 @@
*/
@CommandMetaData(name = "ls-members", description = "List the members of a given group",
runsAt = MASTER_OR_SLAVE)
-public class ListMembersCommand extends BaseCommand {
+public class ListMembersCommand extends SshCommand {
@Inject
ListMembersCommandImpl impl;
@Override
- public void start(Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- final PrintWriter stdout = toPrintWriter(out);
- try {
- impl.display(stdout);
- } finally {
- stdout.flush();
- }
- }
- });
+ public void run() throws Exception {
+ impl.display(stdout);
+ }
+
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
private static class ListMembersCommandImpl extends ListMembers {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index 78034fc..134a719 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -17,37 +17,34 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.server.project.ListProjects;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
-
import java.util.List;
@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller",
runsAt = MASTER_OR_SLAVE)
-final class ListProjectsCommand extends BaseCommand {
+final class ListProjectsCommand extends SshCommand {
@Inject
private ListProjects impl;
@Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- if (!impl.getFormat().isJson()) {
- List<String> showBranch = impl.getShowBranch();
- if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
- }
- if (impl.isShowTree() && impl.isShowDescription()) {
- throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
- }
- }
- impl.display(out);
+ public void run() throws Exception {
+ if (!impl.getFormat().isJson()) {
+ List<String> showBranch = impl.getShowBranch();
+ if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {
+ throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
}
- });
+ if (impl.isShowTree() && impl.isShowDescription()) {
+ throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
+ }
+ }
+ impl.display(out);
+ }
+
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 9f6bb50..dc8abf2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -19,29 +19,23 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.ListPlugins;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
-
-import java.io.IOException;
-
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "ls", description = "List the installed plugins",
runsAt = MASTER_OR_SLAVE)
-final class PluginLsCommand extends BaseCommand {
+final class PluginLsCommand extends SshCommand {
@Inject
private ListPlugins impl;
@Override
- public void start(Environment env) throws IOException {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- impl.display(out);
- }
- });
+ public void run() throws Exception {
+ impl.display(stdout);
+ }
+
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index f39cd36..f85b1b3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -16,6 +16,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
+import com.google.common.io.CharStreams;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.extensions.api.GerritApi;
@@ -30,8 +31,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@@ -39,6 +40,7 @@
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.util.cli.CmdLineParser;
+import com.google.gson.JsonSyntaxException;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
@@ -50,6 +52,8 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -110,6 +114,9 @@
@Option(name = "--delete", usage = "delete the specified draft patch set(s)")
private boolean deleteDraftPatchSet;
+ @Option(name = "--json", aliases = "-j", usage = "read review input json from stdin")
+ private boolean json;
+
@Option(name = "--label", aliases = "-l", usage = "custom label(s) to assign", metaVar = "LABEL=VALUE")
void addLabel(final String token) {
LabelVote v = LabelVote.parseWithEquals(token);
@@ -159,17 +166,44 @@
throw error("publish and delete actions are mutually exclusive");
}
}
- if (deleteDraftPatchSet) {
- if (submitChange) {
- throw error("delete and submit actions are mutually exclusive");
+ if (json) {
+ if (restoreChange) {
+ throw error("json and restore actions are mutually exclusive");
}
+ if (submitChange) {
+ throw error("json and submit actions are mutually exclusive");
+ }
+ if (deleteDraftPatchSet) {
+ throw error("json and delete actions are mutually exclusive");
+ }
+ if (publishPatchSet) {
+ throw error("json and publish actions are mutually exclusive");
+ }
+ if (abandonChange) {
+ throw error("json and abandon actions are mutually exclusive");
+ }
+ if (changeComment != null) {
+ throw error("json and message are mutually exclusive");
+ }
+ }
+ if (deleteDraftPatchSet && submitChange) {
+ throw error("delete and submit actions are mutually exclusive");
}
boolean ok = true;
+ ReviewInput input = null;
+ if (json) {
+ input = reviewFromJson();
+ }
+
for (final PatchSet patchSet : patchSets) {
try {
- reviewPatchSet(patchSet);
- } catch (UnloggedFailure e) {
+ if (input != null) {
+ applyReview(patchSet, input);
+ } else {
+ reviewPatchSet(patchSet);
+ }
+ } catch (RestApiException | UnloggedFailure e) {
ok = false;
writeError("error: " + e.getMessage() + "\n");
} catch (NoSuchChangeException e) {
@@ -184,21 +218,30 @@
}
if (!ok) {
- throw new UnloggedFailure(1, "one or more reviews failed;"
- + " review output above");
+ throw error("one or more reviews failed; review output above");
}
}
private void applyReview(PatchSet patchSet,
- final ReviewInput review) throws Exception {
+ final ReviewInput review) throws RestApiException {
gApi.get().changes()
.id(patchSet.getId().getParentKey().get())
.revision(patchSet.getRevision().get())
.review(review);
}
- private void reviewPatchSet(final PatchSet patchSet) throws Exception {
+ private ReviewInput reviewFromJson() throws UnloggedFailure {
+ try (InputStreamReader r =
+ new InputStreamReader(in, StandardCharsets.UTF_8)) {
+ return OutputFormat.JSON.newGson().
+ fromJson(CharStreams.toString(r), ReviewInput.class);
+ } catch (IOException | JsonSyntaxException e) {
+ writeError(e.getMessage() + '\n');
+ throw error("internal error while reading review input");
+ }
+ }
+ private void reviewPatchSet(final PatchSet patchSet) throws Exception {
if (changeComment == null) {
changeComment = "";
}
@@ -251,8 +294,7 @@
} else if (deleteDraftPatchSet) {
revisionApi(patchSet).delete();
}
- } catch (IllegalStateException | InvalidChangeOperationException
- | RestApiException e) {
+ } catch (IllegalStateException | RestApiException e) {
throw error(e.getMessage());
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 5088424..b56c439 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -14,7 +14,10 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -33,14 +36,13 @@
import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
import com.google.gerrit.server.account.PutActive;
import com.google.gerrit.server.account.PutHttpPassword;
+import com.google.gerrit.server.account.PutPreferred;
import com.google.gerrit.server.account.PutName;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -56,7 +58,8 @@
/** Set a user's account settings. **/
@CommandMetaData(name = "set-account", description = "Change an account's settings")
-final class SetAccountCommand extends BaseCommand {
+@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
+final class SetAccountCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id")
private Account.Id id;
@@ -76,6 +79,9 @@
@Option(name = "--delete-email", metaVar = "EMAIL", usage = "email addresses to delete from the account")
private List<String> deleteEmails = new ArrayList<>();
+ @Option(name = "--preferred-email", metaVar = "EMAIL", usage = "a registered email address from the account")
+ private String preferredEmail;
+
@Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account")
private List<String> addSshKeys = new ArrayList<>();
@@ -85,8 +91,8 @@
@Option(name = "--http-password", metaVar = "PASSWORD", usage = "password for HTTP authentication for the account")
private String httpPassword;
- @Inject
- private IdentifiedUser currentUser;
+ @Option(name = "--clear-http-password", usage = "clear HTTP password for the account")
+ private boolean clearHttpPassword;
@Inject
private IdentifiedUser.GenericFactory genericUserFactory;
@@ -95,52 +101,42 @@
private CreateEmail.Factory createEmailFactory;
@Inject
- private Provider<GetEmails> getEmailsProvider;
+ private GetEmails getEmails;
@Inject
- private Provider<DeleteEmail> deleteEmailProvider;
+ private DeleteEmail deleteEmail;
@Inject
- private Provider<PutName> putNameProvider;
+ private PutPreferred putPreferred;
@Inject
- private Provider<PutHttpPassword> putHttpPasswordProvider;
+ private PutName putName;
@Inject
- private Provider<PutActive> putActiveProvider;
+ private PutHttpPassword putHttpPassword;
@Inject
- private Provider<DeleteActive> deleteActiveProvider;
+ private PutActive putActive;
@Inject
- private Provider<AddSshKey> addSshKeyProvider;
+ private DeleteActive deleteActive;
@Inject
- private Provider<GetSshKeys> getSshKeysProvider;
+ private AddSshKey addSshKey;
@Inject
- private Provider<DeleteSshKey> deleteSshKeyProvider;
+ private GetSshKeys getSshKeys;
+
+ @Inject
+ private DeleteSshKey deleteSshKey;
private IdentifiedUser user;
private AccountResource rsrc;
@Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- if (!currentUser.getCapabilities().canAdministrateServer()) {
- String msg =
- String.format(
- "fatal: %s does not have \"Administrator\" capability.",
- currentUser.getUserName());
- throw new UnloggedFailure(1, msg);
- }
- parseCommandLine();
- validate();
- setAccount();
- }
- });
+ public void run() throws Exception {
+ validate();
+ setAccount();
}
private void validate() throws UnloggedFailure {
@@ -148,6 +144,11 @@
throw new UnloggedFailure(1,
"--active and --inactive options are mutually exclusive.");
}
+ if (clearHttpPassword && !Strings.isNullOrEmpty(httpPassword)) {
+ throw new UnloggedFailure(1,
+ "--http-password and --clear-http-password options are mutually " +
+ "exclusive.");
+ }
if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) {
throw new UnloggedFailure(1, "Only one option may use the stdin");
}
@@ -157,6 +158,11 @@
if (deleteEmails.contains("ALL")) {
deleteEmails = Collections.singletonList("ALL");
}
+ if (deleteEmails.contains(preferredEmail)) {
+ throw new UnloggedFailure(1,
+ "--preferred-email and --delete-email options are mutually " +
+ "exclusive for the same email address.");
+ }
}
private void setAccount() throws OrmException, IOException, UnloggedFailure {
@@ -171,23 +177,27 @@
deleteEmail(email);
}
+ if (preferredEmail != null) {
+ putPreferred(preferredEmail);
+ }
+
if (fullName != null) {
PutName.Input in = new PutName.Input();
in.name = fullName;
- putNameProvider.get().apply(rsrc, in);
+ putName.apply(rsrc, in);
}
- if (httpPassword != null) {
+ if (httpPassword != null || clearHttpPassword) {
PutHttpPassword.Input in = new PutHttpPassword.Input();
in.httpPassword = httpPassword;
- putHttpPasswordProvider.get().apply(rsrc, in);
+ putHttpPassword.apply(rsrc, in);
}
if (active) {
- putActiveProvider.get().apply(rsrc, null);
+ putActive.apply(rsrc, null);
} else if (inactive) {
try {
- deleteActiveProvider.get().apply(rsrc, null);
+ deleteActive.apply(rsrc, null);
} catch (ResourceNotFoundException e) {
// user is already inactive
}
@@ -227,13 +237,13 @@
return sshKey.length();
}
};
- addSshKeyProvider.get().apply(rsrc, in);
+ addSshKey.apply(rsrc, in);
}
}
private void deleteSshKeys(List<String> sshKeys) throws RestApiException,
OrmException {
- List<SshKeyInfo> infos = getSshKeysProvider.get().apply(rsrc);
+ List<SshKeyInfo> infos = getSshKeys.apply(rsrc);
if (sshKeys.contains("ALL")) {
for (SshKeyInfo i : infos) {
deleteSshKey(i);
@@ -253,7 +263,7 @@
private void deleteSshKey(SshKeyInfo i) throws OrmException {
AccountSshKey sshKey = new AccountSshKey(
new AccountSshKey.Id(user.getAccountId(), i.seq), i.sshPublicKey);
- deleteSshKeyProvider.get().apply(
+ deleteSshKey.apply(
new AccountResource.SshKey(user, sshKey), null);
}
@@ -272,18 +282,28 @@
private void deleteEmail(String email) throws UnloggedFailure,
RestApiException, OrmException {
if (email.equals("ALL")) {
- List<EmailInfo> emails = getEmailsProvider.get().apply(rsrc);
- DeleteEmail deleteEmail = deleteEmailProvider.get();
+ List<EmailInfo> emails = getEmails.apply(rsrc);
for (EmailInfo e : emails) {
deleteEmail.apply(new AccountResource.Email(user, e.email),
new DeleteEmail.Input());
}
} else {
- deleteEmailProvider.get().apply(new AccountResource.Email(user, email),
+ deleteEmail.apply(new AccountResource.Email(user, email),
new DeleteEmail.Input());
}
}
+ private void putPreferred(String email) throws RestApiException,
+ OrmException {
+ for (EmailInfo e : getEmails.apply(rsrc)) {
+ if (e.email.equals(email)) {
+ putPreferred.apply(new AccountResource.Email(user, email), null);
+ return;
+ }
+ }
+ stderr.println("preferred email not found: " + email);
+ }
+
private List<String> readSshKey(final List<String> sshKeys)
throws UnsupportedEncodingException, IOException {
if (!sshKeys.isEmpty()) {
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
index e330834..7771d7f 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -50,9 +50,9 @@
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.BooleanOptionHandler;
import org.kohsuke.args4j.spi.EnumOptionHandler;
+import org.kohsuke.args4j.spi.FieldSetter;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Setter;
-import org.kohsuke.args4j.spi.FieldSetter;
import java.io.StringWriter;
import java.io.Writer;
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index fc73973..2a04402 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -9,8 +9,9 @@
'//gerrit-httpd:httpd',
'//gerrit-lucene:lucene',
'//gerrit-openid:openid',
+ '//gerrit-pgm:init',
'//gerrit-pgm:init-api',
- '//gerrit-pgm:init-base',
+ '//gerrit-pgm:util',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-server/src/main/prolog:common',
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 5525eef..81d8498 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-war</artifactId>
- <version>2.10-rc0</version>
+ <version>2.11-SNAPSHOT</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
index 9665cdd..37f8d25 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd;
-import com.google.gerrit.pgm.BaseInit;
+import com.google.gerrit.pgm.init.BaseInit;
import com.google.gerrit.pgm.init.PluginsDistribution;
import org.slf4j.Logger;
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index b1d5933..66a12c1 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -15,6 +15,7 @@
':jruby',
'//lib:args4j',
'//lib:guava',
+ '//lib/log:api',
],
visibility = ['//tools/eclipse:classpath'],
)
@@ -42,8 +43,8 @@
maven_jar(
name = 'asciidoctor',
- id = 'org.asciidoctor:asciidoctor-java-integration:0.1.4',
- sha1 = '3596c7142fd30d7b65a0e64ba294f3d9d4bd538f',
+ id = 'org.asciidoctor:asciidoctorj:1.5.0',
+ sha1 = '192df5660f72a0fb76966dcc64193b94fba65f99',
license = 'Apache2.0',
visibility = [],
attach_source = False,
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index 9e48641..c7562df 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -53,6 +53,9 @@
@Option(name = "--out-ext", usage = "extension for output files")
private String outExt = ".html";
+ @Option(name = "--base-dir", usage = "base directory")
+ private File basedir;
+
@Option(name = "--tmp", usage = "temporary output path")
private File tmpdir;
@@ -82,7 +85,7 @@
OptionsBuilder optionsBuilder = OptionsBuilder.options();
optionsBuilder.backend(backend).docType(DOCTYPE).eruby(ERUBY)
- .safe(SafeMode.UNSAFE);
+ .safe(SafeMode.UNSAFE).baseDir(basedir);
// XXX(fishywang): ideally we should just output to a string and add the
// content into zip. But asciidoctor will actually ignore all attributes if
// not output to a file. So we *have* to output to a file then read the
diff --git a/lib/bouncycastle/BUCK b/lib/bouncycastle/BUCK
index 99f960e..038de5d 100644
--- a/lib/bouncycastle/BUCK
+++ b/lib/bouncycastle/BUCK
@@ -1,7 +1,7 @@
include_defs('//lib/maven.defs')
# This version must match the version that also appears in
-# gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
+# gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
VERSION = '1.49'
maven_jar(
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 8d2b718..3bf514d 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -18,12 +18,33 @@
deps = [
':javax-validation',
':javax-validation_src',
+ ':json',
],
attach_source = False,
exclude = ['org/eclipse/jetty/*'],
)
maven_jar(
+ name = 'codeserver',
+ id = 'com.google.gwt:gwt-codeserver:' + VERSION,
+ sha1 = '940edc715cc31b1957e18f617f75a068f251346a',
+ license = 'Apache2.0',
+ deps = [
+ ':dev',
+ ':legacy-jetty-servlet-aggregate',
+ ],
+ attach_source = False,
+)
+
+maven_jar(
+ name = 'json',
+ id = 'org.json:json:20140107',
+ sha1 = 'd1ffca6e2482b002702c6a576166fd685e3370e3',
+ license = 'DO_NOT_DISTRIBUTE',
+ attach_source = False,
+)
+
+maven_jar(
name = 'javax-validation',
id = 'javax.validation:validation-api:1.0.0.GA',
bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e',
@@ -52,3 +73,21 @@
visibility = [],
)
+maven_jar(
+ name = 'legacy-jetty-servlet-aggregate',
+ id = 'org.eclipse.jetty.aggregate:jetty-servlet:8.1.12.v20130726',
+ sha1 = '4d0f0cb6e5a54de01be46717a7ab48d0b45dcadd',
+ license = 'Apache2.0',
+ attach_source = False,
+ deps = [':legacy-jetty-servlets'],
+ visibility = [],
+)
+
+maven_jar(
+ name = 'legacy-jetty-servlets',
+ id = 'org.eclipse.jetty:jetty-servlets:8.1.12.v20130726',
+ sha1 = '4ebc6894b899fee0c3597697d11f255ce9214bbf',
+ license = 'Apache2.0',
+ attach_source = False,
+ visibility = [],
+)
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index 9161a7b..0c35900 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -1,12 +1,14 @@
include_defs('//lib/maven.defs')
-VERS = '3.4.0.201406110918-r'
+VERS = '3.4.1.201406201815-r.112-g94c4d7e'
+REPO = GERRIT
maven_jar(
name = 'jgit',
id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
- bin_sha1 = '60e74a29895be82ec7bd1fbb6304975e92b955a5',
- src_sha1 = '69adaa263e2b5c21a84a25105c0c93761bdd8a80',
+ repository = REPO,
+ bin_sha1 = '71a742daa0b0c00ed4cfcc92a5c1118ab88dbcff',
+ src_sha1 = '6862fad24075de2f7cbdd8193f6ce533154a8352',
license = 'jgit',
unsign = True,
deps = [':ewah'],
@@ -20,7 +22,8 @@
maven_jar(
name = 'jgit-servlet',
id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
- sha1 = '5c778052a90520a970041f2f51e89cac9cf2cb3f',
+ repository = REPO,
+ sha1 = '8954853d1cb126fe2f6ac8a28eb3d3b58d720fa0',
license = 'jgit',
deps = [':jgit'],
unsign = True,
@@ -33,7 +36,8 @@
maven_jar(
name = 'jgit-archive',
id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
- sha1 = '8644e0dde6127c2d1b5a4fa902bf9b534764e969',
+ repository = REPO,
+ sha1 = 'ec54f685eb312f5d70170d496e5b18587f3cc1b7',
license = 'jgit',
deps = [':jgit',
'//lib/commons:compress',
@@ -49,7 +53,8 @@
maven_jar(
name = 'junit',
id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
- sha1 = '4d1232e8b2412521d707d741ca14db9fc6806db2',
+ repository = REPO,
+ sha1 = '539361def7007b26a3ca87b6179295fbf74e5a94',
license = 'DO_NOT_DISTRIBUTE',
unsign = True,
deps = [':jgit'],
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index a93b856..c882e58 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit a93b85656a68b6a71fe7cf0bb0cc4ed8143657bf
+Subproject commit c882e583226d712053ad02fdee4afcfd1e4df915
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index 6582905..27df553 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit 6582905669d0ccdd009f839936f8209010ae9d6f
+Subproject commit 27df5534c152cf1892e2ede53ee1a55cca4316ee
diff --git a/plugins/download-commands b/plugins/download-commands
index 6287d6a..4e978f9 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 6287d6a8941f68ba8a3a8c27f2a979c02ede489a
+Subproject commit 4e978f916d429ab22b0ea28d25ac87755513cc56
diff --git a/plugins/replication b/plugins/replication
index 677c250..52e48e3 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 677c250ce56e8631751e2a6a58276a0138b20b08
+Subproject commit 52e48e3ce1eea6f24926dd6a2928bb0bb080e3f3
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 6170241..de5eccc 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 61702414c046dd6b811c9137b765f9db422f83db
+Subproject commit de5eccc9c5477a3c504c523f39dfede35426ec8c
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 73c2381..3c59757 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 73c2381c5768f216b3a4abb1c623f8d0134a9600
+Subproject commit 3c5975763148a6ea2bfa867a96ba5498ae5767f2
diff --git a/tools/default.defs b/tools/default.defs
index 27efa11..4ebb4e4 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -38,6 +38,11 @@
kw['resources'] += [gwt_xml]
if 'srcs' in kw:
kw['resources'] += kw['srcs']
+
+ # Buck does not accept duplicate resources. Callers may have
+ # included gwt_xml or srcs as part of resources, so de-dupe.
+ kw['resources'] = list(set(kw['resources']))
+
java_library(**kw)
def gerrit_extension(
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index 4e76b029..2cc66a0 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -11,6 +11,7 @@
'//gerrit-main:main_lib',
'//gerrit-patch-jgit:jgit_patch_tests',
'//gerrit-plugin-gwtui:gwtui-api-lib',
+ '//gerrit-reviewdb:client_tests',
'//gerrit-server:server',
'//gerrit-server:server_tests',
'//lib/asciidoctor:asciidoc_lib',
diff --git a/tools/eclipse/gerrit_gwt_codeserver.launch b/tools/eclipse/gerrit_gwt_codeserver.launch
new file mode 100644
index 0000000..e7f5206
--- /dev/null
+++ b/tools/eclipse/gerrit_gwt_codeserver.launch
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" javaProject="gerrit" path="1" type="4"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gerrit/buck-out/gen/lib/gwt/legacy-jetty-servlet-aggregate/jetty-servlet-8.1.12.v20130726.jar" path="3" type="2"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gerrit/buck-out/gen/lib/gwt/codeserver/gwt-codeserver-2.6.1.jar" path="3" type="2"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gerrit/buck-out/gen/lib/gwt/legacy-jetty-servlets/jetty-servlets-8.1.12.v20130726.jar" path="3" type="2"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="gerrit"/> </runtimeClasspathEntry> "/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.codeserver.CodeServer"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noprecompile -src ${resource_loc:/gerrit} -workDir ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui com.google.gerrit.GerritGwtUI"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1024M -XX:MaxPermSize=256M"/>
+</launchConfiguration>